AI

AI 기반 웹 스크래핑: llm-scraper 활용

llm-scraper를 활용한 AI 기반 웹 스크래퍼 구축 방법, 구조화된 데이터 추출, LLM과 TypeScript를 이용한 스크래핑 코드 생성을 알아보세요.
4 분 읽기
Web Scraping With llm-scraper

이 가이드에서 배우게 될 내용:

  • llm-scraper란 무엇인가
  • 단계별 사용 방법
  • 코드 생성에 활용하는 방법
  • LLM 기반 스크래핑의 주요 대안
  • 주요 한계점과 극복 방법

자, 시작해 보겠습니다!

llm-scraper란 무엇인가요?

llm-scraper는 LLM을 활용하여 모든 웹페이지에서 구조화된 데이터를 추출하는 TypeScript 라이브러리입니다.
각 사이트마다 맞춤형 파싱 로직을 작성하는 대신, 단순히 스키마를 정의하면 패키지가 LLM을 활용하여 페이지 콘텐츠를 분석하여 이를 지능적으로 채워 넣습니다.

이 라이브러리는 2024년 중반에 처음 출시되어 아직 상당히 새롭습니다. 그러나 이미 GitHub에서 4,000개 이상의 스타를 획득하며 빠르게 인기를 얻고 있음을 보여줍니다:

The GitHub star evolution of llm-scraper

기존 스크래퍼가 자주 오류가 발생하는 동적 또는 불규칙한 웹사이트(예: 전자상거래)에 이상적입니다. 이러한 시나리오에서 LLM 스크래핑은 탁월한 성능을 발휘합니다.

llm-scraper가 지원하는 주요 기능은 다음과 같습니다:

  • 다중 LLM 공급자 통합: 로컬 모델(Ollama, GGUF 등) 및 클라우드 공급자(OpenAI, Vercel AI SDK 등)와 연동됩니다.
  • 스키마 기반 데이터 추출: 강력한 구조와 유효성 검증을 위해 Zod 스키마를 사용하여 추출할 내용을 정의합니다.
  • 완전한 타입 안전성: TypeScript를 위해 설계되어 컴파일 시점에 완벽한 타입 검사를 제공합니다.
  • Playwright 기반: 브라우저 제어 및 페이지 콘텐츠 수집을 위해 Playwright를 내부적으로 사용합니다.
  • 스트리밍 지원: 전체 추출 완료를 기다리지 않고 스크래핑 중 객체를 스트리밍할 수 있습니다.
  • 코드 생성: 스키마와 대상에 따라 스크래핑 코드를 동적으로 생성할 수 있습니다.
  • 다양한 페이지 콘텐츠 포맷팅 옵션: LLM에 페이지 콘텐츠를 전송하는 방식을 선택할 수 있습니다.

웹 스크래핑을 위한 llm-scraper 사용 방법

이 튜토리얼 섹션에서는 llm-scraper 라이브러리를 사용하여 AI 기반 스크레이퍼를 구축하는 방법을 배웁니다. 대상 사이트는 ToScrape 웹사이트의 전자상거래 제품 페이지입니다:

The target site

이 예시는 전자상거래 사이트 스크래핑이 까다로울 수 있다는 점에서 훌륭한 사례입니다. 해당 사이트의 페이지 구조는 자주 변경되며, 제품마다 레이아웃과 정보가 다를 수 있습니다. 이로 인해 정적 파싱 로직을 작성하는 것은 어렵고 효과적이지 않습니다. 따라서 AI가 확실히 도움이 될 수 있습니다.

아래 단계를 따라 LLM 기반 스크레이퍼를 구축해 보세요!

필수 조건

이 튜토리얼을 따라하려면 다음이 필요합니다:

  • 컴퓨터에 설치된 Node.js
  • OpenAI API 키 (또는 Groq 같은 유사 제공업체의 키)
  • TypeScript 및 비동기 프로그래밍에 대한 기본적인 이해

1단계: 프로젝트 설정

시작하기 전에 로컬에 최신 LTS 버전의 Node.js가 설치되어 있는지 확인하세요. 설치되어 있지 않다면 공식 사이트에서 다운로드하여 설치하세요.

이제 스크레이퍼 프로젝트를 생성하고 터미널에서 해당 디렉터리로 이동하세요:

mkdir llm-scraper-project
cd llm-scraper-project

프로젝트 내에서 빈 Node.js 프로젝트를 초기화하려면 다음 명령어를 실행하세요:

npm init -y

위 명령어로 생성된 package.json 파일을 열고 다음 내용이 포함되어 있는지 확인하세요:

"type": "module"

Visual Studio Code와 같은 선호하는 TypeScript IDE에서 프로젝트 폴더 llm-scraper-project를 로드하세요.

다음으로 TypeScript를 개발 의존성으로 설치합니다:

npm install typescript --save-dev

TypeScript 설치 후 다음 명령어로 프로젝트를 초기화하세요:

npx tsc --init

프로젝트 폴더에 tsconfig.json 파일이 생성됩니다. 해당 파일을 열고 다음 내용으로 대체하세요:

{
  "compilerOptions": {
    "module": "ESNext",
    "target": "ESNext",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "skipLibCheck": true
  }
}

이제 프로젝트에 scraper.ts 파일을 추가하세요:

The llm-scraper project file structure

이 파일에는 곧 llm-scraper 데이터 추출 로직이 포함될 것입니다. 스크립트가 비동기 TypeScript 로직을 사용할 것이므로, 내부에서 async 함수를 초기화하세요:

async function llmScraping() {
  // LLM 스크래핑 로직...
}

llmScraping()

훌륭합니다! 이제 AI 기반 스크레이퍼 구축을 시작할 준비가 완전히 되었습니다.

2단계: Scraped 라이브러리 설치

llm-scraper가 작동하려면 다음 두 개의 추가 라이브러리가 필요합니다:

  • Zod: TypeScript 중심의 스키마 선언 및 검증 라이브러리.
  • Playwright: 단일 API로 Chromium, Firefox, WebKit 브라우저를 자동화하는 라이브러리입니다.

다음 명령어로 llm-scraper와 함께 설치하세요:

npm install zod playwright llm-scraper

Playwright는 일부 추가 종속성(예: 브라우저 바이너리)이 필요합니다. 다음 명령어로 설치하세요:

npx playwright install

이제 scraper.ts에서 Zod와 Playwright를 임포트하세요:

import { z } from "zod"
import { chromium } from "playwright"
import LLMScraper from "llm-scraper"

좋습니다! 이제 TypeScript로 LLM 웹 스크래핑을 시작하는 데 필요한 모든 라이브러리를 갖추셨습니다.

3단계: OpenAI 설정

llm-scraper는 OpenAI, Groq, Ollama, GGUF 등 여러 LLM 제공자를 지원합니다. 이번에는 OpenAI를 사용할 예정입니다. 아직 OpenAI API 키를 발급받지 않았다면 반드시 발급받으세요.

먼저 OpenAI JavaScript 클라이언트를 설치하세요:

npm install @ai-sdk/openai

그런 다음 코드에 임포트하고 llmScraping() 함수 내에서 LLM 모델을 초기화하는 데 사용하세요:

// 기타 임포트...
import { openai } from "@ai-sdk/openai"

// ...

const llm = openai.chat("gpt-4o")

다른 통합 방법은 공식 llm-scraper` 문서를 참조하세요.

코드에 OpenAI 키를 하드코딩하지 않으려면 dotenv를 설치하세요:

npm install dotenv

scraper.ts 파일에 dotenv를 임포트하고 dotenv.config() 를 호출하여 환경 변수를 로드하세요:

// 기타 임포트...
import * as dotenv from "dotenv"

// ...

dotenv.config()

이를 통해 .env 파일에서 OpenAI API 키와 같은 환경 변수를 불러올 수 있습니다. 따라서 프로젝트에 .env 파일을 추가하세요:

Adding the .env file to your project

파일을 열고 다음과 같이 OpenAI API 키를 추가하세요:

OPENAI_API_KEY="<YOUR_OPENAI_KEY>"

<YOUR_OPENAI_KEY> 를 자신의 OpenAI 키 값으로 대체하세요

코드를 수동으로 변수를 읽을 필요가 없습니다. @ai-sdk/openai가 자동으로 OPENAI_KEY 환경 변수를 읽으려 시도하기 때문입니다.

완벽합니다! LLM 통합이 완료되었습니다.

4단계: 대상 페이지 연결

llm-scraper는 웹 페이지의 HTML 콘텐츠를 추출하기 위해 브라우저 자동화 엔진으로 Playwright를 사용합니다. 시작하려면 llmScraping() 내부에서 다음 코드 줄을 추가하세요:

  1. Chromium 브라우저 초기화
  2. 새 페이지 열기
  3. Playwright가 대상 페이지를 방문하도록 지시

이를 달성하려면:

const browser = await chromium.launch()
const page = await browser.newPage()

await page.goto("https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html")

마지막으로 브라우저를 닫고 자원을 해제하는 것을 잊지 마세요:

await page.close()
await browser.close()

이 과정이 익숙하지 않다면 Playwright 웹 스크래핑 가이드를 참고하세요.

5단계: 데이터 스키마 정의

이제 llm-scraper는 Playwright를 통해 페이지에서 추출한 콘텐츠를 기반으로 프롬프트를 LLM 모델에 입력하여 특정 데이터 모델에 정의된 구조화된 데이터를 추출하는 방식으로 작동합니다.

이때 Zod가 등장하여 TypeScript로 해당 데이터 모델을 지정하는 데 도움을 줍니다. 스크래핑된 데이터의 스키마가 어떻게 구성되어야 하는지 이해하려면, 브라우저에서 대상 사이트를 열고 페이지 최상위 수준을 분석하는 것으로 시작하세요:

The top level of the target page

여기서 다음 데이터를 추출하는 데 집중해야 합니다:

  • 제목
  • 가격
  • 재고 상태
  • 수량
  • 설명

다음으로 페이지의 마지막 섹션으로 이동하세요:

The last section of the target page

여기서 확인하실 내용은:

  • UPC (유니버설 제품 코드)
  • 제품 유형
  • 세금
  • 리뷰 수

이 모든 것을 합치면 다음과 같은 제품 스키마를 얻을 수 있습니다:

const productSchema = z.object({
  title: z.string().describe("제품의 이름"),
  price: z.string().describe("제품의 가격, 일반적으로 '£19.99'와 같은 문자열 형식"),
  stock: z.string().describe("제품의 재고 상태, 예: '재고 있음' 또는 '품절'"),
  quantity: z.string().describe("재고로 보유 중인 제품의 구체적인 수량"),
  description: z.string().describe("제품의 상세 설명, 특징 및 사양 포함"),
  upc: z.string().describe("제품을 고유하게 식별하는 범용 제품 코드(UPC)"),
  productType: z.string().describe("제품의 카테고리 또는 유형, 예: '도서', '의류' 등"),
  tax: z.string().describe("제품에 적용되는 세금 금액 정보"),
  reviews: z.number().describe("제품이 받은 리뷰 수"),
})

: 스키마를 설명하는 것을 잊지 마세요. 이는 LLM 모델이 데이터를 어떻게 처리해야 하는지 이해하는 데 도움이 됩니다.

훌륭합니다! 이제 llm-scraper에서 AI 기반 스크래핑 작업을 실행할 준비가 되었습니다.

6단계: 스크래핑 작업 실행

3단계에서 정의한 LLM 통합을 사용하여 LLMScraper 객체를 생성하세요:

const scraper = new LLMScraper(llm)

이 객체는 llm-scraper 라이브러리가 제공하는 주요 객체로, AI 기반 스크래핑 작업을 수행하는 역할을 합니다.

그런 다음 다음과 같이 스크레이퍼를 실행하세요:

const { data } = await scraper.run(page, productSchema, {
  format: "markdown",
})

format 매개변수는 페이지 콘텐츠가 LLM에 전달되는 방식을 정의합니다. 가능한 값은 다음과 같습니다:

  • "html": 페이지의 원시 HTML.
  • "text": 페이지에서 추출된 모든 텍스트 콘텐츠.
  • "markdown": HTML 콘텐츠를 마크다운으로 변환한 형태.
  • "cleanup": 페이지에서 추출한 텍스트를 정리한 버전.
  • "image": 페이지의 스크린샷.

참고: 필요한 경우 콘텐츠 서식을 제어하기 위해 사용자 정의 함수를 제공할 수도 있습니다.

“새로운 AI 에이전트가 HTML보다 마크다운을 선택하는 이유”가이드에서 설명한 바와 같이, 마크다운 형식을 사용하면 토큰을 절약하고 스크래핑 프로세스의 속도를 높일 수 있으므로 현명한 선택입니다.

마지막으로, scraper.run() 함수는 예상한 Zod 스키마와 일치하는 객체를 반환합니다.

완벽합니다! AI 기반 스크래핑 작업이 완료되었습니다.

7단계: 스크랩된 데이터 내보내기

현재 스크랩된 데이터는 자바스크립트 객체에 저장됩니다. 데이터 접근, 분석 및 공유를 용이하게 하려면 아래와 같이 JSON 파일로 내보내세요:

const jsonData = JSON.stringify(data, null, 4)
await fs.writeFile("product.json", jsonData, "utf8")

이를 위해 외부 라이브러리가 필요하지 않습니다. 스크레이퍼.ts 파일 상단에 다음 임포트 문만 추가하세요:

import { promises as fs } from "fs"

8단계: 모든 것을 통합하기

이제scraper.ts 파일은 다음과 같이 구성됩니다:

import { z } from "zod"
import { chromium } from "playwright"
import LLMScraper from "llm-scraper"
import { openai } from "@ai-sdk/openai"
import * as dotenv from "dotenv"
import { promises as fs } from "fs"

// 로컬 .env 파일에서 환경 변수 불러오기
dotenv.config()

async function llmScraping() {
  // LLM 엔진 초기화
  const llm = openai.chat("gpt-4o")

  // 브라우저 인스턴스 실행 및 새 페이지 열기
  const browser = await chromium.launch()
  const page = await browser.newPage()

  // 대상 전자상거래 제품 페이지로 이동
  await page.goto("https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html")

  // 제품 스키마 정의
  const productSchema = z.object({
    title: z.string().describe("제품명"),
    price: z.string().describe("제품 가격, 일반적으로 '£19.99' 형식의 문자열"),
    stock: z.string().describe("제품의 재고 상태, 예: '재고 있음' 또는 '품절'"),
    quantity: z.string().describe("재고로 보유 중인 제품의 구체적인 수량"),
    description: z.string().describe("제품의 상세 설명, 기능 및 사양 포함"),
    upc: z.string().describe("제품을 고유하게 식별하는 범용 제품 코드(UPC)"),
    productType: z.string().describe("제품의 카테고리 또는 유형, 예: '도서', '의류' 등"),
    tax: z.string().describe("제품에 적용되는 세금 금액 정보"),
    reviews: z.number().describe("제품이 받은 리뷰 수"),
  })

  // 새로운 LLMScraper 인스턴스 생성
  const scraper = new LLMScraper(llm)

  // LLM 스크레이퍼 실행
  const { data } = await scraper.run(page, productSchema, {
    format: "markdown", // 또는 "html", "text" 등
  })

  // 스크랩된 데이터를 JSON 문자열로 변환
  const jsonData = JSON.stringify(data, null, 4)
  // JSON 문자열로 출력 파일 생성
  await fs.writeFile("product.json", jsonData, "utf8")

  // 페이지와 브라우저를 닫고 리소스를 해제
  await page.close()
  await browser.close()
}

llmScraping()

보시다시피, llm-scraper를 사용하면 몇 줄의 코드로 JavaScript 기반 스크래핑 스크립트를 정의할 수 있습니다.

다음 명령어로 TypeScript 스크립트를 JavaScript로 컴파일하세요:

npx tsc

프로젝트 폴더에 scraper.js 파일이 생성됩니다. 다음 명령어로 실행하세요:

node scraper.js

스크립트 실행이 완료되면 프로젝트 폴더에 product.json 파일이 생성됩니다.

해당 파일을 열면 다음과 같은 내용을 확인할 수 있습니다:

{
  "title": "다락방의 빛",
  "price": "£51.77",
  "stock": "재고 있음",
  "quantity": "22",
  "description": "다락방의 빛이 없는 세상을 상상하기 어렵습니다. 셸 실버스타인의 시와 그림으로 구성된 이 고전적 작품집은 특별판으로 20주년을 기념합니다. 실버스타인의 유머러스하고 창의적인 시는 가장 지루한 독자도 즐겁게 합니다. 시큰둥한 어른과 안절부절 못하는 아이들도 가만히 앉아 이 리드미컬한 시를 읽고 웃고 미소 지으며 실버스타인을 사랑하게 됩니다. 그의 천재성을 증명할 필요가 있나요? 자장자장 아기야, 나무 꼭대기에서 자장자장 자장자장 아기야, 나무 꼭대기가 안전한 자장자장 장소가 아니란 걸 모르니? 그리고 누가 너를 거기에 올려놓았지, 그리고 네 요람도? 아기야, 여기 아래 누군가가 너를 노리고 있는 것 같아. 셸, 네가 이렇게 멋지게 들린 적은 없었어.",
  "upc": "a897fe39b1053632",
  "productType": "Books",
  "tax": "£0.00",
  "reviews": 0
}

이 파일은 타겟팅한 상품 페이지에 표시된 정보를 정확히 포함합니다. 보시다시피, LLM의 힘 덕분에 별도의 파싱 로직 없이도 데이터가 추출되었습니다. 잘하셨습니다!

추가: llm-scraper를 활용한 코드 생성

llm-scraper는 스키마를 기반으로 Playwright 데이터 파싱 로직을 생성하는 기능도 제공합니다. 이는 generate() 함수를 통해 가능합니다.

아래 스니펫에서 예시를 확인하세요:

const { code } = await scraper.generate(page, productSchema)

보시다시피, Playwright 페이지 객체와 Zod 스키마를 입력으로 받아 생성된 Playwright 코드가 포함된 문자열을 반환합니다. 이 경우 출력은 다음과 같습니다:

(function() {
function extractData() {
const title = document.querySelector('h1').innerText;
const price = document.querySelector('.price_color').innerText;
const stockText = document.querySelector('.instock.availability').innerText.trim();
const stock = stockText.includes('In stock') ? 'In Stock' : 'Out of Stock';
const quantityMatch = stockText.match(/d+/);
const quantity = quantityMatch ? quantityMatch[0] : '0';
const description = document.querySelector('#product_description ~ p').innerText;
const upc = document.querySelector('th:contains("UPC") + td').innerText;
const productType = document.querySelector('th:contains("Product Type") + td').innerText;
const tax = document.querySelector('th:contains("Tax") + td').innerText;
const reviews = parseInt(document.querySelector('th:contains("Number of reviews") + td').innerText, 10);
    return {
        title,
        price,
        stock,
        quantity,
        description,
        upc,
        productType,
        tax,
        reviews
    };
}

const data = extractData();
console.log(data);
})()

생성된 자바스크립트 코드를 프로그래밍 방식으로 실행하고 결과를 파싱하려면 다음을 사용하세요:

const result = await page.evaluate(code)
const data = schema.parse(result)

데이터 객체는 이전 장의 6단계에서 생성된 데이터와 동일한 결과를 포함합니다.

LLM 스크래핑을 위한llm-scraper 대안

LLM 기반 스크래핑을 위한 라이브러리는llm-scraper만이 아닙니다. 주목할 만한 다른 대안으로는 다음과 같습니다:

  • Crawl4AI: 초고속 AI 지원 웹 크롤링 에이전트 및 데이터 파이프라인 구축을 위한 Python 라이브러리입니다. 개발자가 신속하고 정확하게 배포할 수 있도록 최적화되었으며 유연성이 뛰어납니다. Crawl4AI 스크래핑 튜토리얼에서 실제 사용 사례를 확인할 수 있습니다.
  • ScrapeGraphAI: LLM과 직접 그래프 논리를 결합하여 웹사이트 및 로컬 문서(XML, HTML, JSON, 마크다운 등)용 스크래핑 파이프라인을 구축하는 Python 웹 스크래핑 라이브러리입니다. ScrapeGraphAI를 활용한 스크래핑 가이드에서 확인해 보세요.

이 웹 스크래핑 접근법의 한계

본문에서 사용한 대상 사이트 ToScrape는 이름에서 알 수 있듯 스크래핑 스크립트를 허용하는 샌드박스 환경입니다. 안타깝게도 실제 웹사이트에 llm-scraper를 적용할 경우 훨씬 더 복잡한 문제가 발생할 수 있습니다…

왜일까요? 전자상거래 기업과 온라인 비즈니스는 자신들의 데이터가 얼마나 가치 있는지 잘 알고 있으며, 이를 보호하기 위해 상당한 노력을 기울이기 때문입니다. 해당 데이터가 제품 페이지에 공개적으로 게시되어 있더라도 마찬가지입니다.

결과적으로 대부분의 전자상거래 플랫폼은 자동화된 크롤러를 차단하기 위해 봇 방지 및 스크래핑 방지 조치를 구현합니다. 이러한 기술은 llm-scraper처럼 Playwright 같은 브라우저 자동화 도구를 기반으로 한 스크레이퍼조차 막을 수 있습니다.

대부분의 봇을 차단하기에 충분한 악명 높은 아마존 CAPTCHA 같은 방어 수단을 말하는 것입니다:

A CAPTCHA verification screen from Amazon asking users to enter characters displayed in a distorted text image. It includes instructions and options to continue or try a different image.

이제 Playwright로 CAPTCHA를 우회하는 데 성공하더라도, 지나친 자동 요청으로 인한 IP 차단 같은 다른 문제들이 스크래핑 작업을 중단시킬 수 있습니다.

이 시점에서 해결책은 스크립트를 끝없이 복잡하게 수정하는 것이 아닙니다. 핵심은 올바른 도구를 활용하는 것입니다.

Playwright를 웹 스크래핑 전용으로 설계된 브라우저(예: Scraping Browser)와 통합하면 모든 것이 훨씬 쉬워집니다. 이 솔루션은 스크래핑에 최적화된 클라우드 기반 브라우저입니다. 인프라 관리를 직접 할 필요 없이 IP 로테이션, 자동 재시도, 고급 봇 우회 메커니즘, 심지어 내장된 CAPTCHA 해결 기능까지 처리합니다.

문서에서 설명한 대로 llm-scraper 에서 Scraping Browser를 다른 브라우저와 동일하게 Playwright에 통합하세요.

결론

이 블로그 글에서는 llm-scraper가 제공하는 기능과 이를 활용해 TypeScript로 AI 기반 스크래핑 스크립트를 구축하는 방법을 알아보았습니다. LLM과의 연동 덕분에 복잡하거나 동적인 페이지 구조를 가진 사이트도 스크래핑할 수 있습니다.

앞서 논의한 바와 같이, 차단되는 것을 피하는 가장 효과적인 방법은 내장된 CAPTCHA 해결 기능과 다양한 봇 방지 우회 기능을 갖춘 Bright Data의 Scraping Browser와 함께 사용하는 것입니다.

해당 솔루션을 기반으로 직접 AI 에이전트를 구축하고 싶다면 Agent Browser를 확인해 보세요. 이 솔루션은 차단되지 않는 원격 브라우저에서 에이전트 주도형 워크플로를 실행합니다. 무한한 확장성을 제공하며 세계에서 가장 신뢰할 수 있는 프록시 네트워크로 구동됩니다.

지금 바로 무료 Bright Data 계정을 생성하고, AI 여정을 가속화할 데이터 및 스크래핑 솔루션을 탐색해 보세요!