ウェブサイトをスクレイピングしてチャットボットのナレッジベースを構築する: クロール、チャンク、引用

サポートボットの性能は与えられる情報に依存します。自社のサイト、ドキュメント、FAQ、ヘルプセンターをクロールしてクリーンなMarkdownに変換し、検索可能なチャンクに分割します。これが、回答を根拠に基づかせ、引用を確保するパイプラインです。

サポートチャットボットの性能は与えられる情報に依存し、最適な情報源は通常自社のウェブサイトです — 既に管理しているドキュメント、FAQ、ヘルプセンターの記事がそれに当たります。ただし、ボットは人間のようにウェブサイトを読むことはできません。クリーンでチャンク化され、検索可能なテキストにし、各部分にソースを付ける必要があります。このガイドは完全なパイプラインです: サイトをクロールしてMarkdownに変換し、チャンク化し、ベクトルストアに埋め込み、クエリ時に引用されたコンテキストを取得する — サイトが変わるたびに正確さを保つ根拠のあるナレッジベースです。

なぜRAGであってファインチューニングではないのか

コンテンツに基づいてモデルをファインチューニングすることもできますが、サポートボットには不適切です: 再トレーニングは遅く、高価で、ドキュメントを編集した瞬間に古くなります。リトリーバル・オーグメンテッド・ジェネレーション(RAG)は知識をモデルの外に保持します — コンテンツをチャンク化し、埋め込み、質問時に最も関連性の高い部分を取得し、モデルにコンテキストとして渡します。ドキュメントを更新し、再クロールすると、ボットの知識も更新されます。需要は本物です: チャットボット市場は2030年までに23.3%のCAGRで成長すると予測され、KPMGの調査では69%の人々がすでにチャットボットを使用しており、スタンフォード大学とMITの調査では5,179人のサポートエージェントの生産性が生成AI支援により平均14%向上し、新人エージェントでは35%向上しました。

ステップ1: サイトをクロールしてクリーンなMarkdownに変換する

生のHTMLをRAGパイプラインに投入すると、ナビゲーション、クッキーバナー、スクリプトタグで汚染されます。代わりにMarkdownに直接クロールしてください — 見出し、リスト、テーブルを保持しながら、プレゼンテーションのノイズを排除します。これがLLMが最もよく取り込むものです。Scraper APIのクロールモードを使用すると、サイトを巡回し、各ページをクリーンなMarkdownとして返します。

import requests

resp = requests.post(
    "https://api.quantumproxies.io/crawl",
    json={"url": "https://docs.example.com", "limit": 300, "format": "markdown"},
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    timeout=120,
)
pages = resp.json()["pages"]  # each: {"url": ..., "markdown": ...}

自社サイトをクロールするのは簡単ですが、地理的制限やレート制限のあるドキュメントをクロールする際には、適切なIPを経由することが重要です。そうしないと、クロールが途中で停止してしまいます。Markdownファーストの原則は、LLMに新鮮なウェブデータを供給するのと同じです。

ステップ2: 見出しごとにチャンク化し、ソースを保持する

ページ全体を埋め込まないでください — 検索はフォーカスされたチャンクで最も効果的です。各ページを見出しごとに分割し、各チャンクが一貫したトピックになるようにし、後で引用できるようにソースURLを保持します。これは良いソースドキュメントが役立つところでもあります: 明確な見出しを持ち、各アイデアが一つずつ、質問がテキストに再記述されている記事は、プローズの壁よりもはるかに優れたチャンクになります(ボットは人間が推測するコンテキストを推測できません)。

def chunk_markdown(md, source_url):
    chunks, cur = [], {"heading": "", "text": ""}
    for line in md.splitlines():
        if line.startswith("#"):
            if cur["text"].strip():
                chunks.append({**cur, "source": source_url})
            cur = {"heading": line.lstrip("# ").strip(), "text": ""}
        else:
            cur["text"] += line + "\n"
    if cur["text"].strip():
        chunks.append({**cur, "source": source_url})
    return chunks

all_chunks = [c for p in pages for c in chunk_markdown(p["markdown"], p["url"])]
RAGパイプラインでウェブサイトをサポートボットに変える: Markdownにクロールし、見出しごとにチャンク化し、ベクトルデータベースに埋め込み、取得して引用する
サイト全体をプロンプトに詰め込むのではなく、クエリ時に引用されたチャンクを取得する — 根拠があり、検証可能です。

ステップ3: 埋め込み、保存し、引用付きで取得する

各チャンクをベクトルに埋め込み、ソースURLをメタデータとしてベクトルデータベースにアップサートします。クエリ時には、ユーザーの質問を埋め込み、上位の一致を引き出し、そのテキストをモデルにコンテキストとして渡します — そしてソースリンクを表示することで、回答が検証可能でブラックボックスではないことを示します。

# index each chunk with its source as metadata
for c in all_chunks:
    vec = embed(c["heading"] + "\n" + c["text"])
    index.upsert(id=uid(c), values=vec,
                 metadata={"source": c["source"], "text": c["text"]})

# at query time: retrieve, ground, and cite
hits = index.query(embed(user_question), top_k=4)
context = "\n\n".join(h.metadata["text"] for h in hits)
citations = list({h.metadata["source"] for h in hits})
answer = llm(f"Answer using only this context:\n{context}", question=user_question)

モデルを取得したコンテキストに基づかせ、そのコンテキストからのみ回答するように指示することが、サポートボットが自信を持ってポリシーを創作するのを防ぎます。引用は二重の役割を果たします: ユーザーが検証できるようにし、ボットが誤ったドキュメントを参照しているときにそれを発見できるようにします。より深いメカニズムについては、古くならないRAGパイプラインに関するガイドで、チャンク化と取得についてさらに詳しく説明しています。

ステップ4: 更新しないと腐る

ナレッジベースは生きているものです。ドキュメントは書き直され、価格は変わり、新しい記事が登場します — 先月のクロールから回答するボットは、完全な自信を持って誤った回答をします。再クロールをスケジュールし、各ページを最後のバージョンと比較し、変更された部分のみを再埋め込みして更新を安価に保ちます。各チャンクに取得日を保持し、回答のソースがどれだけ新しいか常に把握できるようにします。自分でクロールと更新を実行するのではなく、管理された常に新鮮なフィードを消費したい場合は、LLM用に構築されたウェブデータが収集を担当します。

ナレッジベースチャットボットの背後にある統計: 23.3%の市場CAGR、69%の人々がチャットボットを使用し、14-35%のサポート生産性向上
サポートボットへのシフトは進行中です; 根拠があり、引用されたナレッジベースが信頼できるものにします。

ボットのためにどんなサイトでもクリーンなMarkdownにクロールする

よくある質問

ウェブサイトからチャットボットのナレッジベースをどのように構築しますか?

サイトをクロールしてクリーンなMarkdownに変換し、各ページを見出しベースのチャンクに分割し、ソースURLを添付し、チャンクをベクトルデータベースに埋め込み、クエリ時に最も関連性の高いチャンクを取得してモデルの回答を根拠づけます。最新の状態を保つために再クロールをスケジュールします。このRAGアプローチにより、ドキュメントを編集するとボットの知識が再トレーニングなしで更新されます。

コンテンツに基づいてモデルをトレーニングする必要がありますか?

いいえ — サポートボットにはそうすべきではありません。ファインチューニングは遅く、高価で、コンテンツが変わるたびに古くなります。リトリーバル・オーグメンテッド・ジェネレーションは知識をモデルの外にあるベクトルストアに保持するため、ボットは常に最新のクロールから回答します。コンテンツが変わるたびに再クロールと再埋め込みを行うだけで、再トレーニングよりもはるかに安価です。

なぜHTMLではなくMarkdownにクロールするのですか?

生のHTMLはナビゲーション、広告、クッキーバナー、スクリプトを含み、ノイズを増やし、埋め込み予算を浪費します。Markdownは意味のある構造 — 見出し、リスト、テーブル — を保持しながら、プレゼンテーション層を排除します。これがLLMが最もクリーンに取り込むものです。クリーンな入力はより関連性の高い取得と混乱の少ない回答を意味します。ボットは画像やビデオを読むこともできないので、重要な情報はテキストに保持してください。

ナレッジベースを最新の状態に保つにはどうすればよいですか?

定期的な再クロールをスケジュールし、変更されたページを検出し、それらのみを再埋め込みします — 完全な再クロールは帯域幅と計算資源を浪費します。各チャンクに取得日を保存し、回答のソースがどれだけ新しいかを把握し、インデックスをバージョン管理して、悪いクロールが取得を劣化させた場合にロールバックできるようにします。インクリメンタルな更新は、実際の変化に比例したコストを維持します。

ここでの製品はパイプラインです: Markdownにクロールし、見出しごとにチャンク化し、ソース付きで埋め込み、取得して引用し、スケジュールに従って更新します。これを行えば、サポートボットは実際のコンテンツから回答し、ソースをリンクし、サイトの進化に伴って最新の状態を保ちます — 人々が信頼するボットと、回避することを学ぶボットの違いです。AIエージェントに標準インターフェースを通じて同じデータへのライブアクセスを提供するには、QuantumProxies MCPサーバーに関するガイドをご覧ください。

常に新鮮なウェブデータをボットに供給する