🤖

AIスクレインピングエージェントの構築(LangGraph, Firecrawl)

2024/12/27に公開

はじめに

この記事では、LangGraphとFirecrawlを使用して、企業のWebサイトから特定の情報を抽出するWebスクレインピングエージェントを構築する方法を紹介します。具体的には、企業のホームページから社長の名前を取得する方法を解説します。

参考にさせていただいた動画

https://www.youtube.com/watch?v=vSz5-KeRyHs
この動画では特定のキーワードをホームページから取得していますが、今回は正規表現では取得できないより抽象的な情報を取得することに挑戦しました。

使用技術

LangGraph

LangGraphは、AIエージェントを作るためのツールで、複雑な処理をいい感じに管理してくれる便利なフレームワークです。状態遷移グラフを使って、どの処理をどの順番でやるかを分かりやすく設計できます。

  • 特徴:

    • 処理の流れを図にして確認できる(Mermaid図対応)
    • 条件分岐や並列処理ができて柔軟
    • タスクの状態を一括管理
  • 本プロジェクトでの役割:
    LangGraphはこのプロジェクトの「司令塔」みたいな存在です。サイトマップの取得やURLの優先順位づけ、社長名を探す処理の流れをしっかり管理してくれます。

https://www.langchain.com/langgraph


Firecrawlとは

Firecrawlは、Webページから情報を集めるスクレイピングツールで、AIとの相性が抜群です。普通のHTMLだけでなく、Markdown形式でもデータを取れるので、そのままAIに渡して解析させるのに便利です。(初回無料クレジットがあるので手軽に試せます)

  • 特徴:

    • ページの内容をMarkdown形式で取得できる
    • サイト全体のURLを一気に集められる
    • AI解析にそのまま使えるデータを準備
  • 本プロジェクトでの役割:
    Firecrawlは「情報収集係」です。WebサイトからURLリストを集めたり、ページ内容をMarkdown形式で取得して、AIが処理できるように準備を整えます。

https://www.firecrawl.dev/


この2つのツールを組み合わせることで、ただのスクレイピングでは難しいような、抽象的で高レベルな情報の収集ができるようになります。

コードの全体像

このプロジェクトは、以下のステップで動作します。

  1. 初期設定
    FirecrawlとOpenAIのAPIキーを設定して、必要なライブラリを準備します。

  2. エージェントの定義
    LangGraphを使用して、処理の流れを管理するエージェントを定義します。

  3. サイトマップの取得
    Firecrawlを使って、指定したWebサイトのすべてのURLを取得します。

  4. 優先URLの選定
    OpenAIのAPIを使用し、取得したサイトマップから社長の名前が記載されている可能性が高い5つのURLを選定します。

  5. 情報の抽出
    各URLにアクセスしてページの内容をMarkdown形式で取得し、OpenAIのAPIを使って社長の名前を抽出します。

  6. 結果の保存
    抽出した社長名と関連情報をCSV形式で保存します。

フロー図を可視化

display(Image(graph.get_graph().draw_mermaid_png()))
Langgraphの関数を使ってフロー図を可視化すると下記のようになります。

元プロジェクトとの変更点と具体的なコード

1. エージェントのクラスにceo_nameを追加

エージェントが解析結果(社長名)を状態として保持できるよう、状態管理クラスにceo_nameフィールドを追加しました。

# エージェントの状態管理クラス
class OverallState(TypedDict):
    urls: List[str]  # スクレイピング対象のURLリスト
    current_url_index: int  # 現在のスクレイピング対象のインデックス
    total_urls: int  # 全URLの数
    urls_to_scrape: List[str]  # これからスクレイピングするURLリスト
    ceo_name: Optional[str]  # 社長名を保持

2. サイトマップを取得後、LLMで優先URLを選定

取得したサイトマップから、OpenAIのLLMを利用して社長の名前が含まれている可能性の高いURLを自動で上位5つに絞り込みます。(プロンプトも頑張って書いたので参考になるかもしれません。)

# 優先URLを選定する関数
def prioritize_urls(urls):
    client = OpenAI(api_key=OPENAI_API_KEY)
    prompt = f"""
    以下のURLリストから、社長の名前が記載されている可能性が高い順に並び替えてください。以下の判断基準を使用してください。
    ### 判断基準:
    1. URLに含まれるキーワード:
      - "about"(会社概要などのページは優先度が高い)
      - "contact" または "offer"(問い合わせページは中程度の優先度)
      - "recruit" または "career"(採用関連ページは中程度の優先度)
      - "showcase" または "product"(ショーケースやプロダクトページは低優先度)
      - その他の一般ページ(最も低い優先度)
    2. 各URLの内容に基づく可能性:
      - 会社概要ページには、社長の名前が記載されている可能性が最も高い。
      - 問い合わせページや採用ページにも、代表者情報が含まれている可能性がある。
      - ショーケースや製品ページには、製品の詳細が記載されることが多く、社長の名前が記載される可能性は低い。
    3. ルートURL(例: "http://example.com")は必ず結果に含めてください。
    4. 並び替え結果は上位5つのURLを出力してください。ただし、入力URLリストの要素を削除したり短縮したりせず、順番を変えるだけで出力してください。
    5. 出力形式はJSON形式にしてください。必ず次の形式で出力してください。
    ### 入力URLリスト:
    {map_result}
    ### 出力形式:
    ```json
    {{
"sorted_urls": ["http://example.com", "http://example.com/about", "http://example.com/contact", "http://example.com/recruit", "http://example.com/showcase"]
    }}
    """
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": prompt}]
    )
    sorted_urls = json.loads(response["choices"][0]["message"]["content"]).get("sorted_urls", [])
    return sorted_urls[:5]

3. 取得したマークダウンから不要なURLを削除

マークダウンデータ内の不要なURLやリンクを削除し、LLM解析の精度を向上させました。
LLMに解析させる前処理は精度向上にも、無駄なトークンを消費しないためにも大切です。

import re

# 不要なURLを削除する関数
def clean_markdown(markdown):
    cleaned_markdown = re.sub(r'https?://\S+', '', markdown)  # URLを削除
    return cleaned_markdown

4. マークダウンからLLMで社長の名前を抽出

LLMを利用して、取得したマークダウンデータから社長名を抽出する処理を追加しました。

# 社長名を抽出する関数
def extract_ceo_name(markdown):
    client = OpenAI(api_key=OPENAI_API_KEY)
    prompt = f"""
    あなたは高度なテキスト解析を行うAIアシスタントです。以下のタスクを正確に実行してください:

    1. 指定された役職(CEO、代表取締役社長、代表取締役会長)に該当する名前を抽出します。
    2. 該当する役職が複数ある場合、最初に出現する役職の名前のみを抽出します。
    3. 名前以外の情報(役職名や企業名、住所など)は含めません。
    4. 出力形式は、JSON形式で「executive_name」というキーに値を設定します。該当する名前がない場合は `null` を設定してください。
    5. 入力はマークダウン形式の文章であり、リンクや画像、HTMLタグなどの不要な情報を無視してください。

    出力例:
    {"executive_name": "山田太郎"}
    または
    {"executive_name": null}
    """

    user_prompt = f"""
    以下の文章から、指定された役職の名前を抽出してください:

    ### 入力文
    {markdown}
    """
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": prompt}]
    )
    return response["choices"][0]["message"]["content"]

5. CSVファイルへの出力機能を追加

解析結果をCSVファイルに保存する機能を実装しました。

コードをコピーする
import csv

# CSVファイルにデータを書き込む関数
def save_to_csv(data, output_file):
    with open(output_file, mode="w", encoding="utf-8") as file:
        writer = csv.writer(file)
        writer.writerow(["会社名", "URL", "社長名"])  # ヘッダーを記載
        writer.writerows(data)  # データを書き込み

デモ

メガベンのHPから社長名を取得してくるデモを行ってみました。
ちょっと時間はかかってしまいますが、上手く取得できている事が分かります。
https://youtu.be/Ubga2nAZhH8

終わりに

LangGraphとFirecrawlを組み合わせることで、従来のスクレイピングでは取得しづらかった情報を取得できるようになりました。
海外ではAIエージェントがめちゃくちゃ流行っています。(たぶん)
海外のYoutubeを調べると、無料で有益な情報を発信している方が沢山いるのでオススメです!
僕もAIエージェントの波に乗り遅れないように頑張ります🔥

Discussion