午前2時、AIパイプラインが停止した
技術系ドキュメントページから情報を集約するRAG(Retrieval-Augmented Generation)ツールを構築していた。翌朝にはデモのデッドラインが迫っていた。ローカルではサンプルデータで問題なく動いていた——しかし本番サイトから実データのクロールを始めると、システムがゴミデータを大量に返し始めた。
ログにはこんな内容が溢れていた:
[ERROR] Parsed content: "Please enable JavaScript to view this page"
[ERROR] Parsed content: "Verifying you are human. This may take a few seconds."
[ERROR] Empty markdown extracted from https://docs.example.com/api-reference
BeautifulSoupはHTMLをちゃんと取得していた——でもそれはローディング画面のHTMLであって、実際のコンテンツではなかった。AIにこんなゴミを食わせれば、出力もひどいものになる。
原因:現代のWebは従来のスクレイパーに優しくない
深夜30分ほどデバッグして、ようやく問題の全体像が見えてきた。現代のドキュメントサイトやWebページは、通常のスクレイパーに対して3種類の問題を引き起こす:
- JavaScriptレンダリング:React/Vue/Next.jsでコンテンツが読み込まれる——BeautifulSoupはJS実行前のHTMLしか見えない。
- アンチボット保護:Cloudflare、CAPTCHA、User-Agent検知が自動リクエストをブロックする。
- 動的コンテンツ:無限スクロール、遅延読み込み、ユーザー操作に依存するコンテンツ。
Seleniumに切り替えてみた。動くことは動いた——が、とにかく遅い。50ページのクロールに20分かかり、数時間連続稼働するとメモリリークも発生する。数千ページを処理する必要があるパイプラインには、現実的な選択肢ではなかった。
# BeautifulSoupを使った古いアプローチ - JSレンダリングページで失敗する
import requests
from bs4 import BeautifulSoup
def scrape_old_way(url):
response = requests.get(url, headers={"User-Agent": "Mozilla/5.0..."})
soup = BeautifulSoup(response.text, "html.parser")
# 受け取った結果: "Please enable JavaScript" 😭
return soup.get_text()
Firecrawlとは何か、そしてなぜ違うのか
午前3時の絶望的なGoogle検索の末、20分でFirecrawlにたどり着いた。これはAPIサービス(セルフホストオプションもあり)で、汎用スクレイパーではない——AIパイプラインにクリーンなデータを提供するという、ただ一つのことのために生まれたツールだ。他のツールと比較すると:
- コンテンツ抽出前にJavaScriptを完全にレンダリング
- HTMLをクリーンなMarkdownに自動変換——LLMが最も消化しやすい形式
- アンチボット対策、レート制限、リトライを完全自動で処理
- 単一ページだけでなく、深さに応じてウェブサイト全体をクロール
Firecrawl Python SDKのインストール
pip install firecrawl-py
firecrawl.devでAPIキーを取得——テスト用の無料枠がある。その後、環境変数を設定:
export FIRECRAWL_API_KEY="fc-your-api-key-here"
Firecrawlの実践的な使い方
1. 単一ページのスクレイプ
最もシンプルな使い方:URLを渡してクリーンなMarkdownを受け取る。
import os
from firecrawl import FirecrawlApp
app = FirecrawlApp(api_key=os.environ["FIRECRAWL_API_KEY"])
# 1ページをスクレイプし、Markdownを取得
result = app.scrape_url(
"https://docs.python.org/3/library/asyncio.html",
formats=["markdown"]
)
print(result["markdown"][:500])
# 出力: クリーンなコンテンツ、LLMへの入力準備完了
返ってくるのは純粋なMarkdown——余計なHTMLタグなし、ナビゲーションメニューのゴミなし、広告フッターなし。手動でコンテンツ抽出の正規表現を書いた経験があれば、これがどれだけの手間を省いてくれるか、すぐわかるはずだ。
2. 深さに応じてウェブサイト全体をクロール
RAGパイプライン用にdocsサイト全体をインデックスしたい場合——1ページずつではなく:
import os
from firecrawl import FirecrawlApp
app = FirecrawlApp(api_key=os.environ["FIRECRAWL_API_KEY"])
# docsサイト全体をクロール、最大50ページ
crawl_result = app.crawl_url(
"https://docs.example.com",
limit=50,
scrape_options={"formats": ["markdown"]}
)
for page in crawl_result["data"]:
print(f"URL: {page['metadata']['sourceURL']}")
print(f"Content length: {len(page['markdown'])} chars")
print("---")
3. LLMによる構造化データの抽出
最もよく使う機能——あらかじめ定義したスキーマに従って情報を抽出し、Firecrawlが自動でAIを使って埋めてくれる:
import os
from firecrawl import FirecrawlApp
app = FirecrawlApp(api_key=os.environ["FIRECRAWL_API_KEY"])
# 商品ページから構造化データを抽出
result = app.extract(
["https://example.com/product/123"],
schema={
"type": "object",
"properties": {
"product_name": {"type": "string"},
"price": {"type": "number"},
"features": {
"type": "array",
"items": {"type": "string"}
},
"availability": {"type": "boolean"}
},
"required": ["product_name", "price"]
}
)
print(result["data"])
# Output: {"product_name": "...", "price": 29.99, "features": [...], ...}
最善の組み合わせ:FirecrawlとLLMの連携
これは深夜2時のパイプライン修復後、現在本番で動かしているパターンだ。FirecrawlでクロールしてClaudeでコンテンツを処理する:
import os
import anthropic
from firecrawl import FirecrawlApp
firecrawl = FirecrawlApp(api_key=os.environ["FIRECRAWL_API_KEY"])
claude = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
def research_topic(url: str, question: str) -> str:
"""
URLをクロールし、そのコンテンツからClaudeが質問に答える。
"""
# Step 1: Webからクリーンなコンテンツを取得
scrape_result = firecrawl.scrape_url(url, formats=["markdown"])
content = scrape_result.get("markdown", "")
if not content:
return "このURLからコンテンツを取得できません。"
# Step 2: クロールしたコンテンツからClaudeで回答
response = claude.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[
{
"role": "user",
"content": f"""以下のWebコンテンツを基に、質問に答えてください。
コンテンツ:
{content[:8000]}
質問: {question}"""
}
]
)
return response.content[0].text
# 使用例
answer = research_topic(
url="https://docs.python.org/3/library/asyncio-task.html",
question="asyncio.create_task()とasyncio.ensure_future()の違いは何ですか?"
)
print(answer)
シンプルなRAGインデクサーの構築
データを保存して繰り返しクエリしたい場合はこれ。最小限のインデクサーのスケルトン:
import os
from firecrawl import FirecrawlApp
from typing import List, Dict
firecrawl = FirecrawlApp(api_key=os.environ["FIRECRAWL_API_KEY"])
class SimpleRAGIndexer:
def __init__(self):
self.documents: List[Dict] = []
def index_website(self, base_url: str, max_pages: int = 20):
"""ウェブサイト全体をクロールしてインデックスする。"""
print(f"{base_url}をクロール中...")
result = firecrawl.crawl_url(
base_url,
limit=max_pages,
scrape_options={"formats": ["markdown"]}
)
for page in result.get("data", []):
if page.get("markdown"):
self.documents.append({
"url": page["metadata"]["sourceURL"],
"content": page["markdown"],
"title": page["metadata"].get("title", "")
})
print(f"{len(self.documents)}ページをインデックスしました。")
return self.documents
def search(self, query: str, top_k: int = 3) -> List[Dict]:
"""シンプルなキーワード検索 — 実際はベクターDBを使うべき。"""
query_lower = query.lower()
results = [
{**doc, "score": doc["content"].lower().count(query_lower)}
for doc in self.documents
if doc["content"].lower().count(query_lower) > 0
]
return sorted(results, key=lambda x: x["score"], reverse=True)[:top_k]
# 使用方法
indexer = SimpleRAGIndexer()
indexer.index_website("https://docs.python.org/3/library/", max_pages=30)
relevant_docs = indexer.search("async await coroutine")
for doc in relevant_docs:
print(f"URL: {doc['url']} | Score: {doc['score']}")
実践から学んだ注意点
本番で約3ヶ月使ってきた。全体的には問題ない——ただし、事前に知っておくべきことがいくつかある:
- レート制限:Firecrawlの無料枠には月間リクエスト制限がある。プランを選ぶ前に必要量を見積もっておくこと——本番データのクロール中に止まってしまうのは最悪だ。
- セルフホストオプション:FirecrawlはオープンソースでGitHubの
mendableai/firecrawlにある。データプライバシーが必要な場合や長期的なコストを管理したい場合は、VPSに自分でデプロイできる。 - 結果のキャッシュ:同じURLを何度もクロールするとクォータを大量消費する。適切なTTLでキャッシュしよう——ドキュメントは24時間、ニュースフィードは1時間が目安。
- robots.txt:Firecrawlはデフォルトでrobots.txtを尊重する。上書きするには明示的な設定が必要——そして、そのサイトをクロールする権限があるか必ず確認すること。
各ツールの簡単な比較
ツール | JSレンダリング | クリーン出力 | 使いやすさ | コスト
-----------------|---------------|-------------|-----------|--------------------
BeautifulSoup | ❌ | ❌ | ✅ | 無料
Selenium | ✅ | ❌ | ❌ | 無料(遅い)
Playwright | ✅ | ❌ | 中程度 | 無料(インフラ必要)
Firecrawl API | ✅ | ✅ | ✅ | 有料 / セルフホスト
Firecrawl (self) | ✅ | ✅ | 中程度 | インフラのみ
クリーンで一貫したデータが必要なAIパイプラインにおいて、Firecrawlはエッジケース処理のための500行のコードを書かずとも、まさにその課題を解決してくれる。あらゆる面で「最高」というわけではない——静的サイトにはBeautifulSoupで十分だ。自分のRAGパイプラインは現在1日200〜300ページをクロールしており、エラー率は2%以下。そして何より重要なのは——もうスクレイパーのデバッグで夜明かしすることがなくなったことだ。

