본문으로 바로가기

LangChain으로 크롤링을 해보자.

category Language/Python 2023. 10. 17. 16:15
728x90
반응형

목차

     

    개요 

    최근 사이드 프로젝트에서 크롤링을 맡아 작업하게 됐다. 크롤링에 관한 글을 읽던 중 AI를 이용해 크롤링을 할 수 있다는 정보들이 눈에 띄었다. 과거에 크롤링을 할 때는 HTML Tag를 하나하나 분석해서 데이터를 얻는 노가다성 작업이 이제는 AI를 통해 간편하게 이뤄지는 건가라는 생각과 함께 간단히 튜토리얼을 맛보기로 했다.

     

    튜토리얼로써는 네이버의 뉴스 기사를 "제목", "서론", "본론", "결론"에 따라 요약해 주는 크롤러를 만들어보는 것을 목표로 했다.

     

     

    1. LangChain 설치와 초기화

    Python에서 AI를 이용해 크롤링을 하기 위해서 다운받은 라이브러리는 langchain이다.

    pip install langchain
    pip install tiktoken

    langchain은 " 노출하여 대규모 언어 모델과 애플리케이션의 통합을 간소화하는 SDK" 라 소개되어있다. langchain을 사용하기 전에 'openai'도 사용해 봤지만 LangChain 쪽이 제공되는 도구가 다양하기에 이를 선택했다. tiktoken은 langchain을 돌리다가 error가 나서 다운로드하였는데 조사해 본 결과 langchain이 데이터를 가공처리할 때 필요한 라이브러리 정도로 이해했다.
     
    다음 작업으로 LangChain에서 사용할 Model을 선택하자.

    from langchain.chat_models import ChatOpenAI
    
    # LLM 객체 생성
    llm = ChatOpenAI(
        temperature=0,
        model_name='gpt-3.5-turbo-16k-0613',
        openai_api_key=''
    )

     

     

    2. WebBaseLoader를 이용해 HTML 불러오기

    대개 크롤링이라고 하면 URL을 통해 웹 페이지의 태그를 불러와야 하기에 requests나 urllib를 사용했지만 langchain은 "WebBaseLoader"라는 객체가 이 역할을 해준다. 사용법은 다음과 같다.

    loader = WebBaseLoader("https://n.news.naver.com/article/119/0002758949")

    이제 load()라는 Method를 사용하면 Document 객체를 돌려준다.

    loader = WebBaseLoader("https://n.news.naver.com/article/119/0002758949")
    
    loader.load()
    
    # OutPut
    [Document(page_content='\n\n\n ... 'ko'})]

     

    3. 방대한 HTML Source를 분리하자.

    Document 객체는 langchain 문서에 따르면 "비정형 데이터와 메타데이터를 담고 있는 객체"라고 가이드하고 있다. 그런데 "굳이 그럴 거면 객체로 담는 이유가 있나?"라는 의문이 생겼다.

     

    사용해 본 후 느낀 점은 Document 객체는 "비정형 데이터와 메타 데이터 분리하기 용도"라는 점이다. 다음은 주관적인 사고방식으로서 정리한 내용이다.

    웹 페이지의 HTML Tag는 굉장히 많은 양의 텍스트로 이루어져있다. 이를 한 번에 AI에 입력한다면 필시 입력 제한에 걸릴 것이다. 그러니 적절히 분리하여 AI에게 입력한 다음 합치는 것이 필요하다. 이 단위를 Document라는 객체로서 관리하는 것


    그렇다면 WebBaseLoader를 통해 Document를 적절히 분리한다는 건 어떻게 하는 걸까?

     

    이는 langcahin의 text_splitter에서 제공되는 객체와 WebBaseLoader의 메서드인 load_and_split()을 이용해 가능했다. 

    text_splitter = CharacterTextSplitter(
        separator="\n",
        chunk_size=1500,  # 쪼개는 글자수
        chunk_overlap=500,  # 오버랩 글자수
        length_function=len,
        is_separator_regex=False,
    )
    
    loader = WebBaseLoader("https://n.news.naver.com/article/119/0002758949").load_and_split()

    예제만 보고 따라했기에 chunk_size와 chunk_overlap을 어떤 기준으로 정하는지는 잘 모르겠지만 위 코드의 결과 여러 Document로 분리된다.

    [Document(page_content='경찰 ...'),
     Document(page_content='이전 ...'),
     Document(page_content='구독 ...'),
     Document(page_content='구독 ...'),
     Document(page_content='디저트 ...'),
     Document(page_content="스몰 ...')]

     

    3. Prompt 와 결과 뽑아내기

    ChatGPT를 이용하면서 경험해 봤듯이 AI에게 작업을 요청해야 된다. 이는 Prompt라는 객체를 통해서 사용이 가능하다.

    from langchain.prompts import PromptTemplate

    그런데 지금까지의 작업을 통해 고려할 점이 존재한다. Document가 쪼개졌기에 각 Document에 대한 "요약"과 이를 "합치는" Prompt를 두 개를 작성해야 한다는 점이다. 만약 Document가 한 개였다면 요청을 한 번에 했지만 앞서 언급했듯 HTML Tag를 AI에게 입력하기 위해 분리했다는 점에서 이러한 처리를 해줘야 한다.

    summary_prompt = PromptTemplate(
        template="""다음 내용을 요약해줘
        {text}
        """,
        input_variables=['text']
    )
    combine_prompt = PromptTemplate(
        template="""다음 내용을 제목,서론,본론,결론에 따라 요약해줘
        {text}
        """,
        input_variables=['text']
    )

    Prompt Engineering이라는 말이 화두로 떠오르는 추세던데 관련하여 책을 읽거나 정보를 접하진 않아 일단 요청할 작업만 작성했다. prompt까지 코드를 작성했으면 load_summarize_chain 함수를 통해 원하는 결과를 받아올 수 있다.

    from langchain.chains.summarize import load_summarize_chain
    
    # 요약을 도와주는 load_summarize_chain
    chain = load_summarize_chain(llm,
                                 map_prompt=summary_prompt,
                                 combine_prompt=combine_prompt,
                                 chain_type='map_reduce',
                                 verbose=False)
    chain.run(docs)

     결과는 다음과 같다.

    제목: 경기남부경찰청, 수원 전세사기 의혹에 대한 강제수사 착수
    서론: 경기남부경찰청 반부패·경제범죄수사대가 수원 전세사기 의혹을 수사 중인 임대인 일가에 대한 강제수사에 착수했다고 밝혔다.
    본론: 경찰은 이들의 법인 사무실 등에 대해 압수수색을 진행하고, 확보한 자료를 분석한 뒤 피의자 신분으로 소환해 조사할 예정이다. 이 사건과 관련해 경찰은 지난달 5일부터 134건의 고소장을 접수했으며, 피해 금액은 190억원에 이른다.
    결론: 경기남부경찰청은 수원 전세사기 의혹에 대한 강제수사를 진행하고 있으며, 이에 관련된 법인 사무실 등을 압수수색하고 조사할 예정이다. 이 사건은 지난달부터 134건의 고소장이 접수되었으며, 피해 금액은 190억원에 이른다.

     

     

    4. 삽질


    4.1  LangChain의 UserWarning 

    langchain을 처음 설치하고 나서 다음과 같은 경고가 뜬다.

    뭔가 cache 관련하여 문제가 있는 듯한데 왜 이런 경고가 발생하는지는 찾지 못했다. 그러나 상단에 다음과 같은 코드로 해결할 수 있었다

    from langchain.cache import InMemoryCache
    from langchain.globals import set_llm_cache
    
    set_llm_cache(InMemoryCache())

     

    4.2 LangChaing은 SQLALchemy 2.0을 사용한다.

    하나의 실험으로써 FastAPI에다 LangChain 코드만 가져다 사용하려고 시도했지만 계속 실패했다. SQLALchemy 1.4를 사용하고 있었는데 Langchain이 내부적으로 사용하는 라이브러리를 보니 그중에 SQLALchemy 2.0을 사용하고 있었다.

     

    FastAPI에서 SQLAlchemy 2.0을 사용하는 것으로 코드를 모두 대체하여 해결했지만 LangChain을 사용하게 된다면 아예 분리시켜 사용하는 것이 좋을 듯싶다.


    4.3 여전히 수작업은 필요하다.

    이 튜토리얼은 웹 페이지 전체를 대상으로 입력한 것을 가정하고 있는데 현실적인 부분을  고려한다면 이는 사실 불명확한 일이다.

     

    특정 영역의 데이터를 뽑아서 AI에게 입력한다고 해도 그 "특정 영역"이 입력제한을 넘어버리는 경우가 발생한다. 즉, HTML Tag를 분석하고 원하는 결과를 빠르게 추출해내려면 어느정도 가공하는 단계는 여전히 수작업을 필요로 한다는 것이다.

     

     

    5. 마치며

    LangChain을 사용하면서 확실히 코드를 작성하는 방식에 특이점이 오고 있음을 체감했다. 그러나 입력부터 결과를 뽑기까지 시간이 걸리는 탓에 중간중간 답답한 맛도 있다.

     

     

    크롤링 외에도 다양한 분야에서 사용하고 있는 추세던데 다음에는 다른 튜토리얼을 맛보는 것으로 하고 이만 글을 마친다.

    728x90
    반응형