LLM Scraper v2.0の使い方|構造化データ抽出と旧版からの移行

LLM

Webページから商品名、価格、著者、URLなどを取り出したいとき、従来のスクレイピングではCSSセレクタを細かく指定します。しかし、ページごとにHTML構造が違ったり、同じサイトでもレイアウトが変わったりすると、抽出コードを何度も直さなければなりません。

LLM Scraperは、Playwrightで開いたWebページと「欲しいデータの形」をLLMへ渡し、ページ内容を構造化データへ変換するTypeScriptライブラリです。ZodまたはJSON Schemaで出力形式を定義できるため、単なる文章回答ではなく、アプリから扱いやすいオブジェクトとして結果を受け取れます。

2024年の初期版からAPIが大きく変わり、現在のv2.0ではVercel AI SDK 6を使用します。当サイトの2024年版「LLM Scraperとは?」を含む古い記事にあるnew LLMScraper(browser, llm)、URL配列、mode: 'html'をそのまま使うと動きません。

この記事では、LLM Scraper v2.0の仕組み、インストール、最小コード、6つのformat、runstreamgenerateの違い、旧版からの移行、安全な運用方法まで整理します。

この記事の結論

  • LLM Scraper v2は、PlaywrightのPageからLLMで意味を抽出し、Zodなどのスキーマに沿ったデータを返す
  • v2ではVercel AI SDK 6のproviderとOutput.object({ schema })を使う
  • 固定セレクタで取れる値までLLMへ任せず、曖昧な意味抽出へ絞ると安定性と費用を改善できる
  • スキーマを通った結果も事実とは限らないため、件数・URL・原文との照合が必要
  • robots.txt、利用規約、アクセス頻度、個人情報、APIキーをスクレイピング設計と分けて管理する

LLM Scraper v2の処理フロー

図1:WebページをPlaywrightで開き、formatを整え、LLMで抽出し、OutputとZodで検証して構造化データへ変換する。

スポンサーリンク

LLM Scraperとは?

LLM Scraperは、任意のWebページからLLMを使って構造化データを抽出するオープンソースのTypeScriptライブラリです。公式リポジトリはMish Ushakov氏が公開しており、MIT Licenseで利用できます。

処理の中心は次の3要素です。

  1. PlaywrightがWebページを開き、JavaScript実行後のDOMを取得する
  2. LLMがページ内の意味を読み取り、欲しい項目を見つける
  3. ZodまたはJSON Schemaが、戻り値の項目名と型を検証する

例えばニュース一覧では、サイトごとにタイトルのclass名が違っても、「タイトル」「得点」「投稿者」「コメントURL」という意味は共通しています。欲しい形をスキーマとして定義し、LLMへ解釈させるのがLLM Scraperの考え方です。

ただし、LLM Scraperはアクセス制限を突破するサービスでも、取得結果の正しさを保証する仕組みでもありません。ブラウザ操作はPlaywright、意味抽出はLLM、型検証はスキーマが担当します。対象サイトの許可、取得頻度、認証、保存後の品質確認は利用者側の責任です。

2024年版記事との関係

当サイトには、LLM Scraperの初期版を紹介した2024年の記事があります。旧記事は当時のAPIと利用イメージを確認するアーカイブとして残します。本記事は旧記事を上書きするのではなく、v2.0の実装方法と移行差分を扱う最新版です。

初期版の概要を知りたい方は旧記事、v2.0を実際に導入・移行したい方は本記事を参照してください。

スポンサーリンク

v2.0で何が変わった?

公式GitHubとnpm Registryでは、2026年8月16日時点の最新バージョンは2.0.0です。v2はVercel AI SDK 6へ対応し、初期版とは初期化方法とrunの引数が変わりました。

項目 旧版の例 v2.0
LLM初期化 OpenAI SDKのクライアントを直接渡す @ai-sdk/openaiなどのproviderを使う
scraper生成 new LLMScraper(browser, llm) new LLMScraper(llm)
ページ入力 URL配列をrunへ渡す PlaywrightのPageを渡す
出力定義 options内のschema Output.object({ schema })
入力形式 mode: 'html' format: 'html'
結果 ページのasync iterable { data }
コメント JavaScriptでは// #は構文エラーになる

旧コードを直すときに、パッケージ番号だけ更新しても動かない理由はここにあります。特にコンストラクタ、Output.objectformatの3点をまとめて移行してください。

また、公式READMEにはOpenAIだけでなく、Anthropic、Google、Groq互換、Ollamaなどの例があります。LLM Scraper自体がモデルAPIを一つに固定するのではなく、AI SDKのproviderを介してモデルを差し替える設計です。

LLM Scraperが向いているケース

LLMを使う価値が高いのは、HTML上の場所より「意味」で探したい場面です。

  • 複数サイトから、表記の違う商品名・価格・特徴を共通スキーマへ揃える
  • ニュースやブログから、タイトル・著者・要点・関連URLを抽出する
  • 求人ページから、職種・勤務地・必須条件・歓迎条件を整理する
  • 規約や仕様ページから、対象条件・制限・変更点を取り出す
  • ページ構造が少し変わっても、意味が同じ項目を見つけたい

反対に、常に同じサイトのdata-price属性を読むだけなら、通常のPlaywrightやCheerioの方が速く、安く、再現性があります。LLM Scraperは「どこにあるかは一定しないが、何を意味するかは説明できる」情報に向いています。

インストール

Node.jsのES Modulesを使うプロジェクトを前提にします。公式READMEの基本パッケージは次の通りです。

npm install llm-scraper playwright zod ai @ai-sdk/openai
npx playwright install chromium

playwrightをnpmへ追加しただけでは、環境によってブラウザ本体が入っていないことがあります。Chromiumを使うならnpx playwright install chromiumも実行します。CIやLinuxでは追加のシステム依存関係が必要になる場合があるため、Playwright公式のBrowsersドキュメントを確認してください。

APIキーはソースコードへ直接書かず、環境変数へ設定します。

export OPENAI_API_KEY="your-api-key"
export OPENAI_MODEL="gpt-4o"

.envを使う場合もGitへコミットしません。ログへリクエストヘッダーやキーを出さないようにし、用途ごとに権限と利用上限を分ける方が安全です。

v2.0の最小コード

次の例はHacker Newsの上位5件を、タイトル、得点、投稿者、コメントURLへ変換します。モデル名は環境変数で差し替えられるようにしています。

import { chromium } from 'playwright'
import { z } from 'zod'
import { Output } from 'ai'
import { openai } from '@ai-sdk/openai'
import LLMScraper from 'llm-scraper'

const schema = z.object({
  top: z
    .array(
      z.object({
        title: z.string(),
        points: z.number(),
        by: z.string(),
        commentsURL: z.string().url(),
      }),
    )
    .length(5),
})

const browser = await chromium.launch()

try {
  const page = await browser.newPage()
  await page.goto('https://news.ycombinator.com', {
    waitUntil: 'domcontentloaded',
  })

  const model = openai(process.env.OPENAI_MODEL ?? 'gpt-4o')
  const scraper = new LLMScraper(model)
  const { data } = await scraper.run(page, Output.object({ schema }), {
    format: 'html',
  })

  console.log(data.top)
} finally {
  await browser.close()
}

処理のポイントを順に見ていきます。

1. Playwrightでページを開く

v2ではURL文字列をLLM Scraperへ直接渡すのではなく、PlaywrightでPageを作成してから渡します。これにより、Cookie同意、ボタン操作、スクロール、待機、ログイン前後の制御をLLM抽出の前に実装できます。

ただし、ログイン済みページを扱うときは個人情報や機密情報がモデル提供者へ送られる可能性を確認してください。認証できることと、LLMへ内容を渡してよいことは別です。

2. Zodで欲しい形を定義する

スキーマはLLMへの説明と戻り値の検証に使われます。文字列、数値、URL、配列の長さなどを具体的にするほど、後工程で扱いやすくなります。

一方、現実のページに存在しない項目まで必須にすると失敗が増えます。欠ける可能性がある値はnullable()optional()を検討し、「存在しない場合は推測しない」と項目説明へ明記します。

3. Output.objectで構造化出力を指定する

AI SDK 6ではOutput.object({ schema })が型付きオブジェクトの出力仕様になります。JSONとして読めるだけでよければOutput.json()もありますが、項目と型を保証したい抽出処理ではOutput.object()が適しています。

4. formatを選ぶ

上の例ではhtmlを使っています。旧版のmodeではありません。ページが大きい場合や装飾が多い場合は、textmarkdownで入力を絞るとトークン量を下げられることがあります。

5. ブラウザを必ず閉じる

例ではfinallybrowser.close()を呼びます。抽出やモデルAPIが失敗してもブラウザプロセスを残さないためです。複数ページを処理する場合は、毎回ブラウザを起動せず、BrowserContextやPageの寿命を設計してください。

6つのformatの選び方

LLM Scraper v2は6種類の入力形式を用意しています。

format LLMへ渡す内容 向いている用途 注意点
html 前処理したHTML 見出し、リンク、表、属性を含む一般的な抽出 ページが大きいと入力が増える
raw_html 生のHTML scriptや属性を含む原文構造が必要 ノイズとトークンが増えやすい
markdown Markdown化した内容 記事、ドキュメント、読み物 元のDOM位置や一部属性は失われる
text Readability系で抽出したテキスト 本文中心の記事、要点抽出 ナビゲーションや表構造が落ちる場合がある
image ページのスクリーンショット 見た目、配置、画像内文字を含むページ マルチモーダル対応モデルが必要
custom 独自関数の戻り値 必要部分だけ前処理して渡す 前処理の保守が必要

迷った場合はhtmlから始め、入力が大きすぎるならtextまたはmarkdownを試します。価格表の列関係やカードの配置が重要ならHTML、視覚配置そのものが重要ならimageというように、抽出対象が残る形式を選びます。

raw_htmlを常用すると、非表示要素、スクリプト、計測タグなども入力へ混ざります。情報量が多いほど精度が上がるとは限りません。対象DOMをPlaywright側で絞ったり、customで必要部分だけ渡したりする方法も検討してください。

run・stream・generateの違い

run・stream・generateの使い分け

図2:通常の一括抽出はrun、途中表示はstream、同じ構造を繰り返し処理するならgenerateを検討する。

run:完成したデータを1回で受け取る

runは最も基本的な方法です。ページと出力スキーマを渡し、検証済みのdataを受け取ります。少数ページの抽出、管理画面の補助、バッチ処理の試作では、まずrunから始めると分かりやすいでしょう。

stream:部分オブジェクトを順次受け取る

streamは部分的な結果を順に受け取ります。大きなオブジェクトを生成するときや、画面上で処理中の項目を表示したいときに向いています。

途中値は完成品ではありません。必須項目がまだない、配列が増える、値が後から変わる可能性があります。保存や外部送信は最終結果の検証後に行います。

generate:再利用できるPlaywrightコードを作る

generateは、スキーマに沿って内容を取得するPlaywrightコードを生成します。公式例では生成コードをpage.evaluate(code)で実行し、最後にschema.parse(result)で検証します。

同じ構造のページを繰り返し取得する場合、毎回LLMへ全文を送るより、生成したコードをレビューして固定化した方が速く、安くなる可能性があります。ただし、生成コードは信頼せず、人間のコードレビュー、対象ドメイン制限、実行時間制限、テストを通してから使ってください。

精度を上げるスキーマ設計

LLM抽出の品質はモデルだけでなく、スキーマの書き方に大きく影響されます。

項目名を具体的にする

valuedataより、priceIncludingTaxpublishedAtsourceURLのように意味が分かる名前を使います。単位がある値は、数値と単位を分けるか、文字列として保持するかを先に決めます。

.describe()で判断基準を書く

同じ「日付」でも、公開日、更新日、開催日があります。曖昧な項目には説明を付け、「ページにない場合はnull」「推測しない」「表示されている税込価格」のように条件を固定します。

URLや件数へ制約を付ける

URLならz.string().url()、固定件数なら.length(5)、上限があるなら.max()を使います。ただし、ページ側の件数が変わるのに固定長へすると、妥当なデータでも失敗します。仕様として必要な制約だけに絞ります。

推定値と原文を分ける

要約や分類と、ページに明記された事実を同じフィールドへ入れない方が安全です。例えばdescriptionTextcategoryInferredを分け、推定項目にはその旨を名前で示します。

従来スクレイピングと組み合わせる

LLM Scraperだけですべて取得するより、通常のPlaywrightと役割分担した方が実務では安定します。

情報 推奨方法
固定ID、data属性、明確な価格欄 Playwright locator、CSSセレクタ
ページURL、取得時刻、HTTP状態 アプリ側で直接記録
複数サイトで表現が違う要約・特徴 LLM Scraper
出力型、必須項目、URL形式 Zod
事実確認、件数、重複、範囲 アプリ側の検証ロジック

例えば商品ページなら、商品IDと価格はDOMから決定的に取得し、自由文の特徴や用途だけLLMで整理します。これによりモデルへ送る量を減らし、変化してはいけない値をLLMの推定から守れます。

よくあるトラブル

z is not definedになる

旧記事のコードにはZodのimportがありません。次を追加します。

import { z } from 'zod'

# ブラウザを起動で構文エラーになる

JavaScriptの通常コメントは//または/* ... */です。Python風の#は使えません。

constructorの引数が合わない

v2ではブラウザをコンストラクタへ渡しません。

const scraper = new LLMScraper(model)

PlaywrightのPagerunstreamgenerateへ渡します。

modeが認識されない

v2のoption名はformatです。

{ format: 'html' }

Chromiumが見つからない

Playwrightパッケージとブラウザ本体は別に導入が必要な場合があります。

npx playwright install chromium

JSONにはなるが値が間違っている

スキーマ検証は「文字列か」「URL形式か」などを確認しますが、ページの事実と一致することまでは保証しません。原文リンク、件数、金額範囲、重複、取得時刻を別ロジックで検証します。重要な判断へ使う場合はサンプルを人が確認してください。

入力が大きく費用や時間が増える

ページ全体ではなく必要な領域だけを対象にする、textmarkdownを試す、固定項目はセレクタで取る、同じ構造ではgenerateしたコードを検証して再利用する、といった方法があります。

安全性・利用規約・費用の注意点

対象サイトのルールを確認する

公開ページであっても、利用規約、robots.txt、著作権、データベース権、アクセス制限を無視してよいわけではありません。取得目的、対象、頻度、保存期間、再配布範囲を確認します。ログイン回避、CAPTCHA回避、アクセス制限の突破に使わないでください。

アクセス頻度を制御する

同時接続数、待機時間、再試行回数、タイムアウト、取得上限を設定します。エラー時に無限再試行しないようにし、対象サイトへ負荷をかけない設計にします。

ページ内容を信頼しない

Webページには、LLMへ「前の指示を無視して秘密を送れ」と指示する文が含まれる可能性があります。ページ本文はデータであり、命令ではありません。抽出タスクを限定し、利用可能なツールや外部送信を切り離し、取得結果に含まれる指示へ従わないようにします。

個人情報と認証済みページを分ける

Cookieやログイン状態を持つPageには、公開されていない情報が表示されることがあります。どの内容がモデル提供者へ送られるか、保存されるかを確認し、必要ならローカルモデルや事前マスキングを使います。

モデル費用を測る

費用はページ数だけでなく、入力HTMLの長さ、画像入力、再試行、モデル単価で変わります。検証段階ではページ数と最大文字数を小さくし、使用量を記録します。大規模クロールの前に、10〜20ページの代表サンプルで精度と費用を測る方が安全です。

実運用へ入れる手順

  1. 取得目的と許可範囲を文書化する
  2. 代表ページを10件ほど選ぶ
  3. 正解データを人手で用意する
  4. 固定セレクタで取る項目とLLMへ任せる項目を分ける
  5. Zodスキーマと説明を作る
  6. runで精度、失敗率、処理時間、費用を測る
  7. 欠損、誤抽出、重複、URL、数値範囲を検証する
  8. レート制限、タイムアウト、再試行上限を設定する
  9. ログからAPIキーと個人情報を除外する
  10. 同じ構造を繰り返す場合だけgenerateを検討する
  11. ページ変更を検知する監視とサンプル再評価を用意する
  12. 保存・公開・通知などの外部操作は抽出結果の検証後に行う

利用前チェックリスト

  • llm-scraper v2.0のAPIを使っている
  • □ Vercel AI SDK 6のproviderを初期化した
  • new LLMScraper(model)になっている
  • □ PlaywrightのPagerunへ渡している
  • Output.object({ schema })を使っている
  • □ optionがmodeではなくformatになっている
  • □ JavaScriptコメントに//を使っている
  • □ Zodをimportし、URL・件数・nullableを適切に定義した
  • □ 固定セレクタで取れる項目をLLMから分離した
  • □ スキーマ通過後も原文、件数、数値範囲を照合している
  • □ APIキーをコードやログへ残していない
  • □ 個人情報や認証済み情報をモデルへ送る許可を確認した
  • □ robots.txt、利用規約、取得頻度、保存範囲を確認した
  • □ 失敗時の再試行回数と最大ページ数を制限した
  • □ 小規模サンプルで精度と費用を測った

まとめ

LLM Scraper v2は、Playwrightで開いたWebページから、LLMとスキーマを使って構造化データを抽出するTypeScriptライブラリです。HTML構造が異なる複数サイトを共通スキーマへ揃えたり、自由文から意味のある項目を取り出したりする場面で役立ちます。

現在のv2.0では、Vercel AI SDK 6のprovider、new LLMScraper(model)、PlaywrightのPageOutput.object({ schema })format optionを使います。2024年頃のURL配列やmodeを使う例とは互換性がないため、古いコードはまとめて移行してください。

重要なのは、LLMを万能なHTML解析器として扱わないことです。固定値は通常のセレクタで取得し、曖昧な意味抽出だけLLMへ任せ、Zodとアプリ側の検証で結果を確認します。許可、個人情報、アクセス頻度、費用まで含めて設計すれば、LLM Scraperを実用的な抽出パイプラインへ組み込めます。

関連記事

公式資料

公式情報の確認日:2026年8月16日。パッケージ、AI SDK、モデル名、利用条件は変更される可能性があります。実装前に公式READMEとnpm latestを再確認してください。

コメント

タイトルとURLをコピーしました