🔥알림🔥
① 테디노트 유튜브 - 구경하러 가기!
② LangChain 한국어 튜토리얼 바로가기 👀
③ 랭체인 노트 무료 전자책(wikidocs) 바로가기 🙌

6 분 소요

이번 포스팅에서는 랭체인(LangChain) 을 활용하여 웹사이트 본문을 스크래핑한 뒤, 형식(schema) 에 맞게 정보 추출 하는 방법에 대해 알아보겠습니다.

이번 튜토리얼에서는 langchain 의 웹사이트가 다소 복잡한 구조를 가지더라도 쉽게 크롤링해주는 Chromium 기반의 AsyncChromiumLoader() 의 사용법에 대해 다룹니다.

또한, 스크래핑된 웹 정보를 파싱해주는 도구인 BeautifulSoup 기반의 BeautifulSoupTransformer() 을 활용하여 스크래핑 된 HTML 문서에서 원하는 태그 정보만 추출하는 방법을 알아보겠습니다.

끝으로, 형식(schema) 기반 문서내용을 추출해 주는 create_extraction_chain() 을 활용하여 추출된 내용을 형식에 맞게 정리하는 방법도 알아보겠습니다.


✔️ (이전글) LangChain 튜토리얼


🌱 환경설정

# 필요한 라이브러리 설치
# !pip install -q openai langchain playwright beautifulsoup4 nest-asyncio
# !playwright install
# OPENAI_API
# import os

# os.environ['OPENAI_API_KEY'] = 'OPENAI API KEY 입력'
# 토큰 정보로드를 위한 라이브러리
# 설치: pip install python-dotenv
from dotenv import load_dotenv

# 토큰 정보로드
load_dotenv()
True
# Async 로 Html 받아올 때 발생하는 에러 방지용 임시 코드
import nest_asyncio

nest_asyncio.apply()

🔥 웹스크래핑

AsyncChromiumLoader()

  • Chromium은 브라우저 자동화를 제어하는 데 사용되는 라이브러리인 플레이라이트(Playwright) 에서 지원하는 브라우저 중 하나입니다. 여기서 Head-less 인스턴스의 Chromium을 실행합니다. Head-less 모드는 그래픽 사용자 인터페이스 없이 브라우저가 실행되고 있음을 의미합니다.

  • AsyncChromiumLoader 는 페이지를 로드하고, 이후에 Html2TextTransformer 를 사용하여 텍스트로 변환합니다.

from langchain.document_loaders import AsyncChromiumLoader
from langchain.document_transformers import BeautifulSoupTransformer

# Load HTML
loader = AsyncChromiumLoader(["https://news.naver.com/main/list.naver?mode=LS2D&mid=shm&sid1=101&sid2=259"])
html = loader.load()
html[0].page_content[:300]
'<!DOCTYPE html><html lang="ko" data-useragent="Mozilla/5.0 (X11; Linux aarch64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/117.0.5938.62 Safari/537.36"><head>\n<meta charset="euc-kr">\n<meta http-equiv="X-UA-Compatible" content="IE=edge">\n<meta name="referrer" contents="always">\n<meta http-'

HTML 콘텐츠에서 <p>, <li>, <div>, <a> 태그와 같은 텍스트 콘텐츠 태그를 스크레이핑합니다.

(참고) HTML 태그 목록

  • <p>: 단락 태그입니다. HTML에서 단락을 정의하며, 관련된 문장 및/또는 구를 함께 그룹화하는 데 사용됩니다.

  • <li>: 목록 항목 태그입니다. 정렬된 (<ol>) 및 정렬되지 않은 (<ul>) 목록 내에서 개별 항목을 정의하는 데 사용됩니다.

  • <div>: 구획 태그입니다. 다른 인라인 또는 블록 레벨 요소를 그룹화하는 데 사용되는 블록 레벨 요소입니다.

  • <a>: 앵커 태그입니다. 하이퍼링크를 정의하는 데 사용됩니다.

  • <span>: 텍스트의 일부 또는 문서의 일부를 마크업하는 데 사용되는 인라인 컨테이너입니다.

많은 뉴스 웹사이트(예: 네이버 뉴스, 다음 등)에서는 제목과 요약이 모두 태그 내에 있습니다.

② BeautifulSoupTransformer()

BeautifulSoupTransformer()BeautifulSoup 을 사용하여 HTML/XML 문서를 파싱 하고 특정 태그와 콘텐츠만 추출할 수 있게 해줍니다.

# html 문서를 파싱
bs_transformer = BeautifulSoupTransformer()
docs_transformed = bs_transformer.transform_documents(html, tags_to_extract=['span'])

# 파싱결과의 일부 출력
print(docs_transformed[0].page_content[:500])
메인 메뉴로 바로가기 본문으로 바로가기 뉴스검색 전체삭제 자동저장 끄기켜기 도움말 전체삭제 자동저장 끄기켜기 끄기 켜기 도움말 | | 검색 NAVER 뉴스     언론사별 정치 경제 선택됨 사회 생활/문화 IT/과학 세계 랭킹 신문보기 오피니언 TV 팩트체크 알고리즘 안내 정정보도 모음 10.03(화) (화) 선택됨 2016년 6월 이후 7년 만에 셔틀 회의 재개 예정 한국과 일본이 금융당국 간 셔틀 회의를 7년 만에 재개하기로 했다. 금융위 … 더팩트 35분전 태국 중앙은행이 한국 인터넷은행의 성장성에 주목하며 토스뱅크를 찾았다. 토스뱅크는 태국 중앙은행의 로나돌 놈논다(Ronadol  … 뉴시스 1시간전	 가계대출 증가 억제 위해 금융당국 DSR 개편 추진 중장년층 미래소득 적으면 DSR 산정때 만기 축소 검토 강제성 부족했던 규제 … 매일경제 2시간전	 추석 상여금 등으로 여유자금이 생겨 투자처를 고민하는 사람들이 많다. 전문가들은 경제 불확실성이 여전히 큰 상황에서 안전 자산

③ 문서를 Chunk 단위로 쪼개기

문서의 내용이 LLM 의 허용토큰의 최대 길이를 넘어가는 경우 에러가 발생합니다. 따라서, 문서를 특정 기준으로 쪼개는 작업을 선행합니다.

여기서는 RecursiveCharacterTextSplitter 모듈을 사용하여 문서를 3000개 chunk size 단위로 쪼개도록 하겠습니다.

from langchain.text_splitter import RecursiveCharacterTextSplitter

# 웹스크래핑 내용의 3000 글자 기준으로 내용 스플릿, 오버랩 없음.
splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(chunk_size=3000, 
                                                                chunk_overlap=0)
splits = splitter.split_documents(docs_transformed)
# 첫번째 스플릿 결과 출력
print(splits[0].page_content)
메인 메뉴로 바로가기 본문으로 바로가기 뉴스검색 전체삭제 자동저장 끄기켜기 도움말 전체삭제 자동저장 끄기켜기 끄기 켜기 도움말 | | 검색 NAVER 뉴스     언론사별 정치 경제 선택됨 사회 생활/문화 IT/과학 세계 랭킹 신문보기 오피니언 TV 팩트체크 알고리즘 안내 정정보도 모음 10.03(화) (화) 선택됨 2016년 6월 이후 7년 만에 셔틀 회의 재개 예정 한국과 일본이 금융당국 간 셔틀 회의를 7년 만에 재개하기로 했다. 금융위 … 더팩트 35분전 태국 중앙은행이 한국 인터넷은행의 성장성에 주목하며 토스뱅크를 찾았다. 토스뱅크는 태국 중앙은행의 로나돌 놈논다(Ronadol  … 뉴시스 1시간전	 가계대출 증가 억제 위해 금융당국 DSR 개편 추진 중장년층 미래소득 적으면 DSR 산정때 만기 축소 검토 강제성 부족했던 규제 … 매일경제 2시간전	 추석 상여금 등으로 여유자금이 생겨 투자처를 고민하는 사람들이 많다. 전문가들은 경제 불확실성이 여전히 큰 상황에서 안전 자산으 … 디지털타임스 신문10면  2시간전	 '킹 달러' 공포 속에 엔·달러 환율이 연중 최저치를 경신했다. 3일 도쿄 외환시장에서 엔·달러 환율은 장중 한때 149.91엔 … 디지털타임스 신문16면  2시간전	 자영업자들이 은행·저축은행·상호금융 등 금융권에서 빌린 돈이 최근 2년 새 100조원 이상 불어난 것으로 나타났다. 경기침체가  … 아시아경제 2시간전	 3개사 수익·실적 동반 악화 속 성장기반 확장 등 긍정적 평가 연말 인사철, 유임여부 촉각 임기 만료를 앞둔 카드사 CEO(대표 … 디지털타임스 신문10면  TOP 3시간전	 블록체인 전문 투자사 해시드가 웹3 빌더 커뮤니티 '프로토콜 캠프(Protocol Camp)' 5기 활동을 시작했다고 3일 밝혔 … 뉴시스 3시간전	 금융감독원은 업무혁신 로드맵(FSS) 도입 이후 지난 1년간 분쟁처리 건수가 30% 넘게 늘어나고 비조치 의견서 처리기간이 절반 … 파이낸셜뉴스 신문9면  3시간전	 자산 2조원 이상 금융회사 74개사 중 30개사는 여성 등기이사가 한 명도 선임되지 않은 것으로 나타났다. 여성 등기이사 비율도 … 파이낸셜뉴스 신문9면  3시간전	 은행연합회가 일본 은행협회와 함께 디지털화, ESG확산 등 글로벌 금융환경 변화에 대한 공동대응을 강화한다. 김광수 은행연합회  … 파이낸셜뉴스 신문9면  3시간전	 "반월·시화BIZ프라임센터에는 다양한 분야에서 오랫동안 경험을 쌓은 RM들이 많습니다. 고객들이 우리은행의 문을 두드린 순간,  … 파이낸셜뉴스 신문9면  3시간전	 최근 '돈맥경화'에 시달리는 부동산 프로젝트파이낸싱(PF) 사업장에 대해 정부와 금융권이 21조원 이상의 신규 자금 공급이란 ' … 파이낸셜뉴스 신문9면  3시간전	 KDB산업은행의 신입 행원 채용 일정(국제신문 지난달 7일 자 5면 보도)이 전면 연기됐다. 서류심사 평가위원에 부적격 인사가  … 국제신문 3시간전	 보험업계의 숙원인 '실손보험 청구 간소화법'과 '보험사기 방지 특별법'이 여야 간 극한대립으로 표류하고 있다. 실손보험 청구 간 …

④ 스키마 정의 & 내용 추출

스키마는 문서에서 내용 추출시 원하는 형식이나 내용을 정의할 수 있도록 도와줍니다.

아래의 예시처럼 스키마를 정의하면, 웹스크래핑 문서에서 제목, 카테고리, 키워드 를 알아서 추출하고, 형식에 맞게 결과를 json 형식으로 반환합니다.

이때 create_extraction_chain() 모듈을 사용하여 쉽게 적용할 수 있습니다.

import pprint
from langchain.chains import create_extraction_chain
from langchain.chat_models import ChatOpenAI

# 스키마 정의
schema = {
    "properties": {
        "뉴스기사_제목": {"type": "string"},
        "뉴스기사_카테고리": {"type": "string"},
        "뉴스기사_키워드": {"type": "string"},
    },
    "required": ["뉴스기사_제목", "뉴스기사_카테고리", "뉴스기사_키워드"],
}

# LLM 정의
llm = ChatOpenAI(model_name="gpt-3.5-turbo-16k", 
                 temperature=0)

# 문서내용 추출 체인객체 생성
ext_chain = create_extraction_chain(schema=schema, llm=llm)

# 첫번째 스플릿으로 실행한 결과
pprint.pprint(ext_chain.run(splits[0].page_content))
[{'뉴스기사_제목': '한국과 일본이 금융당국 간 셔틀 회의를 7년 만에 재개하기로 했다.',
  '뉴스기사_카테고리': '금융',
  '뉴스기사_키워드': '한국, 일본, 금융당국, 셔틀 회의'},
 {'뉴스기사_제목': '태국 중앙은행이 한국 인터넷은행의 성장성에 주목하며 토스뱅크를 찾았다.',
  '뉴스기사_카테고리': '금융',
  '뉴스기사_키워드': '태국, 중앙은행, 한국 인터넷은행, 성장성, 토스뱅크'},
 {'뉴스기사_제목': '가계대출 증가 억제 위해 금융당국 DSR 개편 추진',
  '뉴스기사_카테고리': '금융',
  '뉴스기사_키워드': '가계대출, 금융당국, DSR 개편'},
 {'뉴스기사_제목': '추석 상여금 등으로 여유자금이 생겨 투자처를 고민하는 사람들이 많다.',
  '뉴스기사_카테고리': '금융',
  '뉴스기사_키워드': '추석, 상여금, 여유자금, 투자처'},
 {'뉴스기사_제목': "'킹 달러' 공포 속에 엔·달러 환율이 연중 최저치를 경신했다.",
  '뉴스기사_카테고리': '금융',
  '뉴스기사_키워드': '킹 달러, 엔·달러 환율, 최저치'},
 {'뉴스기사_제목': '자영업자들이 은행·저축은행·상호금융 등 금융권에서 빌린 돈이 최근 2년 새 100조원 이상 불어난 것으로 나타났다.',
  '뉴스기사_카테고리': '금융',
  '뉴스기사_키워드': '자영업자, 은행, 저축은행, 상호금융, 금융권, 빌린 돈'},
 {'뉴스기사_제목': '3개사 수익·실적 동반 악화 속 성장기반 확장 등 긍정적 평가',
  '뉴스기사_카테고리': '금융',
  '뉴스기사_키워드': '수익, 실적, 성장기반'},
 {'뉴스기사_제목': '은행연합회가 일본 은행협회와 함께 디지털화, ESG확산 등 글로벌 금융환경 변화에 대한 공동대응을 강화한다.',
  '뉴스기사_카테고리': '금융',
  '뉴스기사_키워드': '은행연합회, 일본 은행협회, 디지털화, ESG확산, 글로벌 금융환경'},
 {'뉴스기사_제목': "최근 '돈맥경화'에 시달리는 부동산 프로젝트파이낸싱(PF) 사업장에 대해 정부와 금융권이 21조원 이상의 신규 자금 "
             "공급이란 ' …",
  '뉴스기사_카테고리': '금융',
  '뉴스기사_키워드': '돈맥경화, 부동산, 프로젝트파이낸싱, 정부, 금융권, 신규 자금'},
 {'뉴스기사_제목': 'KDB산업은행의 신입 행원 채용 일정이 전면 연기됐다.',
  '뉴스기사_카테고리': '금융',
  '뉴스기사_키워드': 'KDB산업은행, 신입 행원, 채용 일정'},
 {'뉴스기사_제목': "보험업계의 숙원인 '실손보험 청구 간소화법'과 '보험사기 방지 특별법'이 여야 간 극한대립으로 표류하고 있다.",
  '뉴스기사_카테고리': '금융',
  '뉴스기사_키워드': '보험업계, 실손보험 청구 간소화법, 보험사기 방지 특별법, 여야 간 극한대립'}]

🔥 전체코드

import pprint
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.document_loaders import AsyncChromiumLoader
from langchain.document_transformers import BeautifulSoupTransformer
from langchain.chains import create_extraction_chain
from langchain.chat_models import ChatOpenAI


def scrape_with_playwright(urls, extraction_chain):
    # URL 로부터 본문 내용 웹스크래핑
    loader = AsyncChromiumLoader(urls)
    # 웹스크래핑 내용 로드
    docs = loader.load()
    # HTML 로더로 컨텐츠 로드
    bs_transformer = BeautifulSoupTransformer()
    # 특정 태그에 대한 내용만 추출 ('span')
    docs_transformed = bs_transformer.transform_documents(docs, 
                                                          tags_to_extract=["span"])
    
    # 웹스크래핑 내용의 3000 글자 기준으로 내용 스플릿, 오버랩 없음.
    splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(chunk_size=3000, 
                                                                    chunk_overlap=0)
    splits = splitter.split_documents(docs_transformed)
    
    # 반환할 결과를 담을 리스트
    extracted_contents = []
    
    # 스플릿을 순회하며 schema 에 따라 내용 추출
    for split in splits:
        # 각 스플릿에 대하여 스키마 기반 내용 추출
        extracted_content = extraction_chain.run(split.page_content)
        extracted_contents.extend(extracted_content)
        
    return extracted_contents

# 스키마 정의
schema = {
    "properties": {
        "뉴스기사_제목": {"type": "string"},
        "뉴스기사_카테고리": {"type": "string"},
        "뉴스기사_키워드": {"type": "string"},
    },
    "required": ["뉴스기사_제목", "뉴스기사_카테고리", "뉴스기사_키워드"],
}

# LLM 정의
llm = ChatOpenAI(model_name="gpt-3.5-turbo-16k", 
                 temperature=0)

# 문서내용 추출 체인객체 생성
ext_chain = create_extraction_chain(schema=schema, llm=llm)

# 웹스크래핑 URL 정의
urls = ["https://news.naver.com/main/list.naver?mode=LS2D&mid=shm&sid1=101&sid2=259"]

# 웹스크래핑 및 스키마 기반 내용 추출
extracted_content = scrape_with_playwright(urls, ext_chain)

# 결과 출력
pprint.pprint(extracted_content)

댓글남기기