# Concept
- 크롤링(`웹 페이지에서 데이터를 추출하는 작업`) 위해 사용하는 `Package`를 말한다.
- `Python`에서 크롤링을 하는 방법은 여러가지가 있지만 여기선 `Playwright`와 `sitemapLoader`을 소개한다.
# Site Loader Types
### Playwright & Async Chromium
- **json이 많은 웹 사이트**는 동적이기 때문에 바로 불러올 수 없다. 그래서 `Playwright`와 `Async Chromium`을 사용한다.
- 하지만 이는 직접 내 컴퓨터로 브라우저를 열고 데이터를 얻는 것이기 때문에 많은 양의 url을 넣어 실행하면 작업 시간이 오래 걸릴 수 있다.
#### Playwright
- `Playwright`는 브라우저 컨트롤을 할 수 있는 Package로 [Selenium](https://www.selenium.dev/)하고 비슷하다.
- Microsoft에서 개발하였고 브라우저 테스트 및 웹 스크래핑을 위한 **오픈 소스 자동화 라이브러리**이다.
- `from langchain.document_loaders import AsyncChromiumLoader`로 불러올 수 있다.
#### Async Chromium
- `Chromium`은 브라우저 자동화를 제어하는 데 사용되는 라이브러리인 `Playwright`에서 지원하는 브라우저 중 하나이다.
#### NotImplementedError
- window에서 Async Chromium 사용 시, `NotImplementedError`가 나오는 데 이를 `asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())` 사용하여 해결 할 수 있다.
```python
import sys
if "win32" in sys.platform:
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
cmds = [['C:/Windows/system32/HOSTNAME.EXE']]
else:
cmds = [
['du', '-sh', '/Users/fredrik/Desktop'],
['du', '-sh', '/Users/fredrik'],
['du', '-sh', '/Users/fredrik/Pictures']
]
```
#### Use Playwright & Async Chromium
- `AsyncChromiumLoader([{url}])`를 통해 loader를 만들 수 있다. URL를 `list`형태로 전달해야 함에 주의해야 한다.
- 또한 `AsyncChromiumLoader`의 `.load()` function을 통해 load 한 값을 문서화 시킬 수 있다.
- load한 데이터를 `text`로 변환하기 위해서는 `Html2TextTransformer`를 사용해야 하는데 이는 `from langchain.document_transformers import Html2TextTransformer`로 불러 올 수 있다.
- `Html2TextTransformer`에서 `transform_documents({docs})` function을 사용해 `load`한 문서를 `text`로 변환한다.
```python
import asyncio
import sys
from langchain_community.document_loaders import AsyncChromiumLoader
from langchain_community.document_transformers import Html2TextTransformer
import streamlit as st
html2text_transformer = Html2TextTransformer()
if "win32" in sys.platform:
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
cmds = [['C:/Windows/system32/HOSTNAME.EXE']]
else:
cmds = [
['du', '-sh', '/Users/fredrik/Desktop'],
['du', '-sh', '/Users/fredrik'],
['du', '-sh', '/Users/fredrik/Pictures']
]
with st.sidebar:
url = st.text_input(
"Write down a URL",
placeholder="https://example.com",
)
if url:
loader = AsyncChromiumLoader([url])
docs = loader.load()
trsanformde = html2text_transformer.transform_documents(docs)
st.write(docs)
```
### SitemapLoader
- 웹 사이트가 동적인 것이 아닌 **단순 text가 많은 정적인 사이트**라면 `SitemapLoader`을 이용할 수 있음
- `.xml` 형식의 사이트를 `크롤링`할 수 있다. (**html 사이트는 안된다.**)
#### Check if it is xml site
- `SitemapLoader`에서 지원이 되는 사이트인지 체크해보려면 아래와 같이 request 했을 때 xml 형식을 주는지 html 형식을 주는지 보면 된다. `xml 형식`을 print하면 지원이 되는 것이다.
```python
import requests
response = requests.get("https://openai.com/sitemap.xml")
print(response.text)
```
#### Use SitemapLoader
##### URL Loader
- SitemapLoader는 `from langchain.document_loaders import SitemapLoader`로 불러 올 수 있다.
- `SitemapLoader({url})`로 loader 만들 수 있고 `.load()` function을 통해 해당 URL를 크롤링 할 수 있다.
- `Sitemap`이 되는 site의 URL은 웹 사이트가 `Google` 등 다른 크롤러의 스크랩을 허용해 놓은 거지만 너무 빠르면 정책의 위배될 수 있다.
- `.requests_per_second = {num}`를 통해 해당 사이트의 데이터를 얼머나 빠르게 가져올 지를 정할 수 있으나 너무 빠르게 설정하면 해당 site에서 block 할 수 있다.
```python
from langchain_community.document_loaders.sitemap import SitemapLoader
import streamlit as st
@st.cache_data(show_spinner="Loading website....")
def load_website(url):
loader = SitemapLoader(url)
loader.requests_per_second = 5
docs = loader.load()
return docs
```
##### Filter URLS
- `Sitemap`에서 스크립트 대상을 URL의 하위 페이지만 포함하거나 제외하기 위해선, `SitemapLoader`에 filter_urls parameter를 넘겨주면 된다.
- `r"^(.*\/research\/).*"` : url의 하위 페이지(research)만 포함하여 스크립트 하기
- `r"^(?!.*\/research\/).*"` : url의 하위 페이지(research)를 제외하고 스크립트 하기
```python
loader = SitemapLoader(
url,
filter_urls=[
r"^(?!.*\/research\/).*"
],
)
```
##### Beautiful soup
- 스크립트 받은 `html` 값에는 `header`나 `footer`, `'\n` 같은 필요 없는 정보들도 들어가 있다. 따라서 이러한 값들을 수정하기 위해선 `beautiful soup` 사용한다.
- `SitemapLoader`에 `parsing_function`을 지정할 수 있는데 이를 지정하면 자동으로 `soup`를 Parameter로 넘겨준다.
- [beautiful soup](https://en.wikipedia.org/wiki/Beautiful_Soup_(HTML_parser))이란 인터넷 문서의 구조에서 명확한 데이터를 추출하고 처리하는 가장 쉬운 라이브러리이다.
- `soup.find("header")`를 통해 해당 html tag가 달린 내용을 찾을 수 있고
- `header.decompose()`로 해당 내용을 제거할 수 있다.
- 나머지 context 중에 수정할 내용은 `.get_text()`로 string 형태로 값을 가져온 다음 `replace()`을 통해 수정하면 된다.
```python
def parse_page(soup):
header = soup.find("header")
footer = soup.find("footer")
if header:
header.decompose()
if footer:
footer.decompose()
return str(soup.get_text()).replace("\n", " ").replace("\xa0", " ")
```
##### RecursiveCharacterTextSplitter
- 전처리를 통해 필요한 데이터만을 얻었다면, 이를 LLM에 넘겨주기 위해 `splitter`로 splite을 해주어야 한다.**(model의 `Context Window`를 만족시키거나 [[Map Reduce LCEL Chain]]나 [[Map Re-rank LCEL Chain]]을 수행하기 위해)**
- `RecursiveCharacterTextSplitter`는 일반 텍스트에 권장되는 분할 기법이다.
- 이 spliter는 문자 목록에 `매개변수화`(기본 목록 : ["\n\n", "\n", " ", ""])되어 있는데, 이 목록에 순서대로 분할을 시도하여 청크가 충분히 작아질 때까지 계속 splite 한다.
- 이 spliter는 의미론적으로 관련된 텍스트의 가장 강력한 조각으로 보이는 문단 (그리고 그 다음 문장, 그리고 단어)을 최대한 유지하려고 시도한다.
```python
@st.cache_data(show_spinner="Loading website....")
def load_website(url):
splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
chunk_size=1000,
chunk_overlap=200,
)
loader = SitemapLoader(
url, filter_urls=[r"^(.*\/research\/).*"], parsing_function=parse_page
)
loader.requests_per_second = 5
docs = loader.load_and_split(text_splitter=splitter)
return docs
```