GPT-4oを活用したWebスクレイピング: 強力だが、コストがかかる
(blancas.io)- OpenAI APIの structured outputs を使って、HTMLテーブルを構造化データとして取り出すAI支援のWebスクレイパーを試作したところ、GPT-4oは一部の複雑なテーブルで期待以上の抽出品質を示した
- Weather.comの10日間予報のような 表示構造が複雑なテーブル では、昼/夜の予報を区別し、画面には見えない値もHTMLソースから見つけ出した
- WikipediaのHuman Development Indexのように、繰り返し値が 結合行 として入っている表では、列ごとの値の数がずれてテーブルの再構成が難しく、プロンプト修正だけでは解決しなかった
- API呼び出しコストを抑えるため XPath生成 も試したが、誤っていたり空結果を返したりするXPathが出力され、先にデータを抽出してからその値を基準にXPathを求める方式のほうが安定していた
- 2日間の実験で $24 を使っており、HTML属性を減らすと文字数は半分になって性能低下も見られなかったが、コストのため追加検証は限定された
structured outputsでHTMLテーブル抽出
- OpenAI APIの structured outputs 機能で、AI支援の Webスクレイパー を実装した
- 最初の実験は、HTML文字列をGPT-4oに渡し、テーブルデータを構造化された形で抽出するよう求める方式だった
- 使用した Pydantic モデルは、テーブル名と列一覧を持つ
ParsedTable、列名と値一覧を持つParsedColumnという構造になっている - システムプロンプトでは、HTMLテーブルから 構造化データ を抽出する専門Webスクレイパーという役割を与えた
- GPT-4o miniも試したが結果はかなり悪く、その後の実験は GPT-4o で進めた
複雑なテーブルで見せた強み
- 単純なテーブルの後、Weather.com の10日間 天気予報 テーブルを入力した
- このテーブルは最上部に大きな行が1つあり、残りの9日分はより小さな行で構成されている
- GPT-4oは残り9日分の昼/夜予報の構造を認識し、
Day/Night列 を追加した - 最初は抽出された
Condition列がハルシネーションのように見えたが、HTMLソースを確認すると画面に見えないタグとして実際に存在していた
結合行で発生した失敗
- Wikipediaの Human Development Index テーブルは見た目には単純だが、繰り返し値のある行が 結合 されており、GPT-4oはうまく処理できなかった
- モデルは指示どおり個別の列を抽出したが、列ごとに値の数が異なり、結果をテーブルとして表現しにくかった
- 「結合行は複数のJSON値として抽出し、すべての列で行数を同じにせよ」という形でシステムプロンプトを変更したが、うまく機能しなかった
- 列単位ではなく 行単位の抽出 を指示するプロンプトはまだ試していない
XPath生成方式の限界
- 毎回OpenAI APIを呼び出すとコストが大きくなりうるため、抽出データの代わりに XPath を返すよう試した
- 目的は、同じページで更新されたデータを再取得するとき、モデル呼び出しなしでXPathによってスクレイピングすることだった
- プロンプトはHTML内容と列名を受け取り、Seleniumの
driver.find_elements(By.XPATH, xpath)で評価可能なXPath文字列を返すよう構成した - この方式は安定しなかった
- GPT-4oが 誤ったXPath を返す場合があった
- Seleniumに言及する文で一部は緩和されたが、誤ったデータや空結果を返すXPathも出てきた
データ抽出とXPath生成の組み合わせ
- 次の試みは、まずGPT-4oがデータを抽出し、その結果を 参照値 としてXPathを求める方式だった
- この組み合わせは、最初からXPathだけを要求する方式よりもはるかにうまく動いた
- 生成されたXPathが結果を返さない場合があるため、結果がなければ再試行する単純な リトライロジック を追加した
- テストしたテーブルでは、この再試行方式は効果があった
- 新たな問題も残っている
- 第1段階のデータ抽出で、画像がテキストに変換される場合があった
- たとえば上向き矢印の画像が
arrow-upwardsのようなテキストとして抽出されることがある - 第2段階では実際のHTMLに存在しないテキストを探すことになり、失敗する
- この問題は別途修正していない
コストとHTMLの整理
- GPT-4oでWebスクレイピングを行うと、小さなHTMLテーブルでも多くの文字を含むため コスト が大きくなりうる
- 2日間の実験ですでに $24 を費やした
- コストを下げるため、モデルに渡す前のHTML文字列から不要なデータを除去する整理ロジックを追加した
- 生成されたXPathが主に使っていた
class、id、data-testidを除くすべての属性を削除すると、テーブルの文字数は半分になった - この整理後も性能低下は見られず、抽出品質が実際に改善する可能性もあると考えている
- 現在のXPath生成段階では、テーブルの各列ごとにモデル呼び出しを1回ずつ行っている
- 一度に複数のXPathを生成する改善も可能だが、まだ試しておらず性能評価もしていない
デモと追加実験のアイデア
- GPT-4oの抽出品質は印象的だったが、OpenAIに支払う必要のあるコストもかなり大きく感じられた
- Streamlitで作った簡単なデモは https://orange-resonance-9766.ploomberapp.io で確認できる
- ソースコードは GitHub で公開されており、完成度の高いツールを期待しないでほしいという断り書きが付いている
- もっと多くのテーブルをテストしたかったが、OpenAIのコスト増加のため少数のテーブルしか試せなかった
- 時間がもっとあれば、次を試す予定だ
- 現在のデモは、ユーザーがURLと初期XPathを入力する一回限りのプロセスであり、ユーザーが抽出したいテーブルをクリックしてサンプル行を提供する方式のほうがより良い ユーザー体験 になるかもしれない
- 複雑なテーブルでは、単一のXPathだけで列全体を抽出するのが難しいことがあり、LLMにPythonのような プログラム を返すよう求める方式を試したい
- GPT-4oの利用コストは高く、不要なデータも多く渡していると感じるため、HTML整理の方法をさらに試す必要がある
1件のコメント
Hacker News のコメント
HTMLをそのままLLMに入れるより、まず Markdownのような単純な形式に変換したときが最もうまくいった
使ってみたツールには Extractus https://github.com/extractus と dom-to-semantic-markdown https://github.com/romansky/dom-to-semantic-markdown がある
クラウドで動く Magic Loops https://magicloops.dev/ の内部では Apify https://apify.com/ と Firecrawl https://www.firecrawl.dev/ を使い、Chrome Extension では dom-to-semantic-markdown を使っている
今は特定サイト向けのXPathをユーザー補助フローで生成したうえで、LLM呼び出し前に必要な要素だけを抜き出す方式を実験中で、問題を単純化すると GPT-4o mini でもかなり良好だった
ヘッドレスブラウザでChrome Extensionを実行できるので、データを外に取り出す前にブラウザ内でセマンティックMarkdownへの変換ができる
ブラウザのiFrame可能なリアルタイム画面もあり、ユーザーが生成中のXPathに対するフィードバックをすぐに受け取れる: https://docs.browserbase.com/features/session-live-view#give...
私の経験では性能はMarkdownとかなり近く、変換はより簡単で例外も少なかった
見つけたページが「Alpha Draft」なので、Semantic Markdownコンテンツのコーパスは多くなさそうで、学習データ不足のせいでLLMの理解を妨げる可能性もある
それでも十分読みやすい形式なので、LLMが構造化メタデータを括弧書きの説明のように扱えば、かなりうまく処理できそうだ
OpenAIが最近 Batch APIを発表し、プロンプトをすべて用意してから一括実行でき、費用も50%に下がる: https://platform.openai.com/docs/guides/batch
以前GPT-4o miniでかなり使っており、3,000項目を5分以内に処理できた
リアルタイム性が不要なアプリケーションにはかなり良いかもしれない
vLLMは同じ形式の「offline batch mode」サポートを一部追加したが、まだOpenAIエンドポイントの実装までは至っていない
あまりにも自然な機能だ
構造化コンテンツ、たとえば項目リストや単純な表には、あえて LLMは必要ない
最近、どんなWebサイトでも自動で動作するWebスクレイパー https://easyscraper.com を作り、初期バージョンはAIで作ったが、結局は要素の属性と位置に基づくヒューリスティックのほうが速く、安く、正確だった
ほとんどのWebサイトでは非AIアプローチが非常にうまく機能するので、データが非構造化である場合や、ページデータをもとに出力形式を導き出す必要がある場合のように、本当にAIが必要かをまず確認したほうがよい
筆者のようにLLMにXPathを生成させ、その後は普段そのXPathで通常のスクレイピングを行い、壊れたらLLMでXPathを更新させることができる
それでもデータが再び流れなかったり、パイプラインの後段で予期しない形式のために壊れたりしたら、そのとき人間に通知すればよい
なぜこういうツールがなかったのか、いつも不思議に思っていた
「HTML reducer」のようなものがすでにあるのか気になる
ページソースをそのまま見ると、ランダムなJavaScript、広告、不要な属性、レンダリング用の過剰なネストのせいで、トークンの90%がゴミになる
DOMパーサーで巡回しながら、テキストのあるノード、HTML構造、必要なタグ属性(class/id程度)だけを残せば、コストを大きく下げられそうだし、XPath方式もよりうまく機能しそう
Readabilityも使うがDOM構造が失われるし、JavaScriptの多いWebサイトや「続きを読む」のようにテキストを展開するページでは品質が落ちる
この用途で標準に近いツールが何なのか気になる
興味があればオープンソースとして公開できるし、リデューサーを通した後はLLMの出力がずっと良くなるのに驚いた
任意のURLの前に https://r.jina.ai/ を付けると、そのページの主要コンテンツをLLMに入れやすいMarkdown版で返してくれる
例は https://r.jina.ai/https://simonwillison.net/2024/Sep/2/anato... で、元ページは https://simonwillison.net/2024/Sep/2/anatomy-of-a-textual-us...
コードはオープンソースなので自分で実行できる: https://github.com/jina-ai/reader
TypeScriptで書かれており、Puppeteerと https://github.com/mozilla/readability を使っている
私はMarkdown変換なしでReadabilityだけを使ってページタイトルと本文を抽出しており、Playwrightとshot-scraperで実行するレシピもある: https://shot-scraper.datasette.io/en/stable/javascript.html#...
vimiumから持ってきて修正した方式で、HTMLを重要な部分だけ残すように蒸留しながら、さまざまな例外ケースも処理する
筆者が作業と執筆の99%を済ませておきながら、残り1%であるollamaやllama.cppベースのエンジンをダウンロードして、まともなローカルLLMを試していないのは驚き
このユースケースでは7Bや30Bモデルでも十分うまくできるだろうし、実行コストも十分低いのでGPT-4oは不要かもしれない
いまLLMを触り始めたところで、ローカルホスティングモデルも見てみる予定
Kadoa https://kadoa.com でAIによる自動Webスクレイピングをやってきて、初期実験はこの記事と似ていた
高価で遅いGPT-3しかなかった時代から始めたので、大規模にコスト効率のよい解決策が必要だった
最終的には抽出段階で毎回LLMを使う代わりに、コード生成でCSSセレクタやXPathの抽出コードを作り、その後Webサイトの変更に合わせてスクレイパーコードを調整させるようにした
クレンジングと変換には小さなファインチューニング済みLLMを使い、検証には逆検索のような従来手法とあわせてLLM-as-a-judgeでデータ品質を評価する
いくつかの単純なデータソースに適用するのと、数千のWebサイトで安定的・スケーラブル・コスト効率よく動かすのはまったく別の問題で、従来型のETLエンジニアリングと小さくよく評価されたLLMステップを組み合わせる方法が正解だった
スクレイピングしたいHTML例を渡してBeautifulSoupのコード片を求めると、かなりうまくいった
たいてい取得したい構造はそのまま維持されるが、それをパースする厄介な文字列を自分で書く作業は退屈だ
実際のパースをLLMに任せるのはやり過ぎなうえ、ハルシネーションで結果が汚染されるリスクもある
HTMLを先に前処理すると、より安く、より良い結果が得られる
まだ言及されていないようだが、個人的にはtrafilatura https://trafilatura.readthedocs.io/en/latest/ で良い結果を得ている
LLMにはテキストだけを送ればよいので、コストを大幅に削減できる
最近のプロジェクト https://github.com/philippe2803/contentmap でも使った。ドメインのXMLサイトマップを出発点に、任意のWebサイト向けのベクトルストアを作るシンプルなPythonライブラリだ
ドメインごとにHTML構造が異なるため、実コンテンツだけを抽出し、HTMLタグなどを取り除く必要があったが、Trafilaturaは数行のコードでほぼすべてのURLに対してそれをやってくれる
オレンジ色のタグがこれほど多く付いた投稿は初めて見るレベル
NewsCatcher では GPT-4o で多くのテストを行っており、10万以上のニュース Web サイトをクロールしたうえでニュースコンテンツをパースする必要がある
どの記事からでもデータを抽出するルールベースのモデルはかなりうまく機能していて、GPT でさらに改善する方法は見つけられなかった
より興味深いのはクロールのほうで、ニュース記事が掲載され得るすべての場所を把握する必要があり、ときには50以上のサブセクションがある
Web サイトごとの構造は頻繁には変わらないので、多くのプロジェクトでは抽出コードを生成する方式で十分かもしれないと思う
そのため、LLM で HTML パースコードを生成する方向を見ており、興味があれば artem [at] newscatcherapi.com まで連絡してほしい
セルフサインアップがあるとよい
Web スクレイピングは、共同創業者と私が今の openpipe.ai を作ることになった実際のきっかけだった
GPT-4 はこの作業が本当に得意だが、高すぎる
ただ、特定の種類のサイトをスクレイピングする能力は、ずっと安いファインチューニング済みモデルに蒸留するのがかなり簡単で、その種類のサイトでは安定してうまく取得してくれる
私たちもかなり長くこの問題に取り組んできており、どこまで来たか見せるために連絡する