OpenClawで社内Wikiを自動巡回させてみた(実例紹介)

はじめに

「あのプロジェクトの設計思想、どのドキュメントに書いてあったっけ?」 「新メンバー向けのオンボーディング手順、最新版はWiki?それともGoogle Docs?」 「先月の定例会の議事録、誰が持ってるんだっけ…」

エンジニアの皆さんなら、こんな経験に一度は心当たりがあるのではないでしょうか。

私たちのチームでも、情報がConfluence、Notion、Google Drive、GitHub Wikiなど、様々な場所に散在していました。いわゆる「情報のサイロ化」です。この結果、必要な情報を見つけるために多大な時間を費やし、時には見つけられずに同じ質問がSlackで繰り返される、という非効率が常態化していました。

この問題を解決すべく、私たちは社内に散らばった情報を自動で収集・要約し、一元的に検索できるボットの開発に取り組みました。そして、その中核技術として採用したのが、Webクローラーフレームワーク「Scrapy」とLLM(大規模言語モデル)を統合した「OpenClaw」です。

この記事では、OpenClawを使って実際に社内Wiki(今回はConfluenceを例にします)を自動巡回し、ページの情報を収集・要約するボットを構築した実例を、具体的なコードと共に詳しくご紹介します。社内のナレッジマネジメントに課題を感じているエンジニアの方々にとって、一つの実践的な解決策となれば幸いです。

なぜこの技術・話題が重要なのか(背景と課題)

現代の開発現場では、スピードとコラボレーションが強く求められます。その基盤となるのが、円滑な情報共有です。しかし、組織が成長し、プロジェクトが複雑化するにつれて、情報は意図せず分散し、サイロ化していく傾向にあります。

情報サイロ化が引き起こす深刻な問題

情報のサイロ化は、単なる「探し物が面倒」というレベルの問題にとどまりません。

  1. 生産性の低下: 開発者は1日のうち、情報の検索に平均で20%以上の時間を費やしているという調査結果もあります。これは週に1日分を情報検索に浪費している計算になり、無視できないコストです。
  2. オンボーディングの困難化: 新しくチームに参加したメンバーが、必要な情報を自力でキャッチアップするのが非常に困難になります。教育担当者の負担が増え、新メンバーの立ち上がりも遅れてしまいます。
  3. ナレッジの陳腐化と属人化: ドキュメントが更新されずに古い情報が参照されたり、特定の人物しか知らない「暗黙知」が増えたりします。結果として、誤った意思決定や、担当者不在時の業務停滞リスクが高まります。
  4. 機会損失: 過去のプロジェクトで得られた知見やノウハウが埋もれてしまい、再利用されません。車輪の再発明が繰り返され、組織全体の学習効率が低下します。

LLM時代における新たなアプローチ

こうした課題に対し、これまでも全文検索エンジンの導入など、様々な対策が取られてきました。しかし、キーワード検索だけでは、大量の検索結果の中から本当に求めている情報(コンテキスト)を見つけ出すのは依然として困難でした。

ここで大きな変革をもたらしたのが、LLMの登場です。LLMは、自然言語で書かれた膨大なテキストを理解し、要約したり、質問に答えたりする能力を持っています。

このLLMの能力と、Web上の情報を体系的に収集するWebクローリング技術を組み合わせることで、「散在する情報を自動で収集し、文脈を理解した上で要約・整理し、自然言語で対話的に検索できる」という、次世代のナレッジマネジメントが実現可能になります。

OpenClawは、まさにこの「クローリング」と「LLMによるデータ処理」をシームレスに繋ぐために設計されたフレームワークであり、この課題に対する強力なソリューションとなり得るのです。

具体的な解決策や詳細な解説

それでは、実際にOpenClawを使って社内Confluenceを巡回する情報収集ボットを構築するプロセスを、ステップ・バイ・ステップで解説していきます。

システム全体のアーキテクチャ

今回構築するシステムの全体像は以下のようになります。

graph TD
    subgraph "開発環境"
        A[開発者] -- 1. 実行コマンド --> B(OpenClaw実行環境);
    end

    subgraph "OpenClawボット"
        B -- 2. ログイン要求 --> C(社内Wiki: Confluence);
        C -- 3. ログイン成功/Cookie発行 --> B;
        B -- 4. ページ巡回要求 --> C;
        C -- 5. HTMLを返す --> B;
        B -- 6. HTMLから本文抽出 --> D(LLM Pipeline);
        D -- 7. テキスト要約要求 --> E[LLM API (例: GPT-4o)];
        E -- 8. 要約結果を返す --> D;
        D -- 9. 処理済みデータを保存 --> F[データストア (JSONファイル)];
    end

    A -- 10. 結果を確認 --> F;
  1. 開発者がローカル環境でOpenClawのクローラーを実行します。
  2. クローラーはまずConfluenceにログインし、セッションを確立します。
  3. 指定されたスペースのページを順番に巡回し、各ページのHTMLコンテンツを取得します。
  4. 取得したHTMLから、ヘッダーやサイドバーなどの不要な部分を取り除き、本文テキストのみを抽出します。
  5. 抽出したテキストを、OpenClawのパイプライン機能を使ってLLM API(今回はOpenAIのGPT-4oを想定)に送信します。
  6. LLMがテキストを要約し、その結果を返します。
  7. ページのタイトル、URL、そしてLLMによる要約をセットにして、JSONファイルとして保存します。

Step 1: 環境構築

まず、開発環境を準備します。Python 3.8以上と、パッケージ管理ツールであるpipが必要です。仮想環境を作成することを強く推奨します。

1
2
3
4
5
6
7
# 仮想環境の作成とアクティベート
python -m venv .venv
source .venv/bin/activate

# OpenClawと必要なライブラリのインストール
pip install openclaw
pip install python-dotenv # .envファイルでAPIキーを管理するため

Step 2: プロジェクトの作成

OpenClawには、プロジェクトの雛形を生成する便利なコマンドが用意されています。

1
2
claw new confluence_crawler
cd confluence_crawler

これを実行すると、以下のようなディレクトリ構造が生成されます。

confluence_crawler/
├── confluence_crawler/
│   ├── __init__.py
│   ├── items.py         # 収集するデータの構造を定義
│   ├── middlewares.py
│   ├── pipelines.py     # データ処理のパイプラインを定義
│   ├── settings.py      # プロジェクト全体の設定
│   └── spiders/         # クローラー本体を配置
│       └── __init__.py
└── claw.cfg

Scrapyを使ったことがある方なら、非常によく似た構造であることに気づくでしょう。OpenClawはScrapyをベースに構築されているため、Scrapyの知識を活かすことができます。

Step 3: Itemの定義

まず、収集したいデータの構造をitems.pyで定義します。今回はページのタイトル、URL、本文、そしてLLMによる要約を保存することにします。

confluence_crawler/items.py

1
2
3
4
5
6
7
8
import scrapy

class ConfluencePageItem(scrapy.Item):
    # 収集するデータのフィールドを定義
    url = scrapy.Field()
    title = scrapy.Field()
    content = scrapy.Field()
    summary = scrapy.Field() # LLMによる要約を格納するフィールド

Step 4: クローラー(Spider)の実装

次に、ボットの心臓部であるクローラーをspidersディレクトリに作成します。今回は、Confluenceにログインし、特定のスペース内のページを巡回するクローラーを実装します。

confluence_crawler/spiders/confluence_spider.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import scrapy
from confluence_crawler.items import ConfluencePageItem

class ConfluenceSpider(scrapy.Spider):
    name = "confluence"
    
    # .envファイルなどから設定を読み込むことを推奨
    # ここでは例として直接記述
    CONFLUENCE_BASE_URL = "https://your-company.atlassian.net"
    LOGIN_URL = f"{CONFLUENCE_BASE_URL}/login"
    START_URL = f"{CONFLUENCE_BASE_URL}/wiki/spaces/YOUR_SPACE_KEY/pages"
    USERNAME = "[email protected]"
    PASSWORD = "your-password-or-api-token" # APIトークンの使用を推奨

    def start_requests(self):
        # 最初にログインページにアクセス
        yield scrapy.Request(
            url=self.LOGIN_URL,
            callback=self.login,
            dont_filter=True
        )

    def login(self, response):
        # ログインフォームに認証情報をPOST
        # CSRFトークンなどが必要な場合は、responseから抽出して含める
        # Confluenceのログイン仕様に合わせて調整が必要
        return scrapy.FormRequest.from_response(
            response,
            formdata={
                "username": self.USERNAME,
                "password": self.PASSWORD
            },
            callback=self.after_login
        )

    def after_login(self, response):
        # ログインが成功したかを確認
        # ログイン後のリダイレクト先やページの内容で判断
        if "dashboard" in response.url:
            self.logger.info("Login successful!")
            # ログイン成功後、目的のページの巡回を開始
            yield scrapy.Request(url=self.START_URL, callback=self.parse_space)
        else:
            self.logger.error("Login failed!")

    def parse_space(self, response):
        # スペース内のページ一覧から各ページへのリンクをたどる
        # CSSセレクタは実際のConfluenceのHTML構造に合わせて調整
        page_links = response.css('a.content-list-item-title::attr(href)').getall()
        for link in page_links:
            yield response.follow(link, callback=self.parse_page)

        # 「次へ」のリンクがあればたどる(ページネーション対応)
        next_page = response.css('a.pagination-next::attr(href)').get()
        if next_page:
            yield response.follow(next_page, callback=self.parse_space)

    def parse_page(self, response):
        # ページから情報を抽出
        item = ConfluencePageItem()
        item['url'] = response.url
        
        # タイトルを抽出
        item['title'] = response.css('h1#title-text a::text').get() or \
                        response.css('h1#title-text::text').get()

        # 本文を抽出(メインコンテンツエリアを指定)
        # このセレクタが最も重要で、サイトの構造に合わせて調整が必要
        content_html = response.css('div#main-content').get()
        
        # HTMLタグを除去してプレーンテキストにする
        from w3lib.html import remove_tags
        item['content'] = remove_tags(content_html).strip()

        # Itemをパイプラインに渡す
        yield item

【注意点】

  • 認証: 上記は基本的なフォーム認証の例です。実際のConfluenceのバージョンや設定(SSOなど)によっては、より複雑な処理(APIトークンの利用、Cookieの手動設定など)が必要になります。
  • CSSセレクタ: ConfluenceのHTML構造はバージョンによって変わる可能性があります。ブラウザの開発者ツールを使い、実際のページの構造を確認してセレクタを調整してください。これがクローリングの肝となります。

Step 5: LLM要約パイプラインの実装

ここがOpenClawの真骨頂です。Spiderから渡されたItemを受け取り、LLMを使って要約処理を行うパイプラインをpipelines.pyに記述します。

まず、OpenAIのライブラリをインストールします。

1
pip install openai

次に、プロジェクトのルートに.envファイルを作成し、APIキーを記述します。

.env

OPENAI_API_KEY="sk-..."

そして、パイプラインを実装します。

confluence_crawler/pipelines.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import os
from dotenv import load_dotenv
from openai import OpenAI
from itemadapter import ItemAdapter

# .envファイルから環境変数を読み込む
load_dotenv()

class LlmSummaryPipeline:
    def open_spider(self, spider):
        # Spider開始時に呼び出される
        api_key = os.getenv("OPENAI_API_KEY")
        if not api_key:
            raise ValueError("OPENAI_API_KEY is not set in .env file")
        self.client = OpenAI(api_key=api_key)
        spider.logger.info("LLM Summary Pipeline is enabled.")

    def close_spider(self, spider):
        # Spider終了時に呼び出される
        spider.logger.info("LLM Summary Pipeline is finished.")

    def process_item(self, item, spider):
        # 各Itemがパイプラインを通過するたびに呼び出される
        adapter = ItemAdapter(item)
        content = adapter.get('content')

        if not content:
            adapter['summary'] = "Content is empty."
            return item

        # 長すぎるコンテンツはLLMに渡す前に切り詰める
        max_tokens_for_content = 8000  # モデルのコンテキスト長に応じて調整
        truncated_content = content[:max_tokens_for_content]

        try:
            # LLMに要約を依頼するプロンプト
            prompt = f"""
            以下の社内Wikiのページ内容を、3つの箇条書きで簡潔に要約してください。
            このページが「誰の」「どんな課題を解決する」ためのドキュメントなのかが明確にわかるようにしてください。

            ---
            {truncated_content}
            ---
            """
            
            response = self.client.chat.completions.create(
                model="gpt-4o",  # または "gpt-3.5-turbo" など
                messages=[
                    {"role": "system", "content": "あなたは優秀なアシスタントです。渡された文章を的確に要約します。"},
                    {"role": "user", "content": prompt}
                ],
                temperature=0.3,
                max_tokens=500
            )
            
            summary = response.choices[0].message.content.strip()
            adapter['summary'] = summary
            spider.logger.info(f"Successfully summarized: {adapter.get('title')}")

        except Exception as e:
            adapter['summary'] = f"Failed to generate summary: {e}"
            spider.logger.error(f"Error during summarization for {adapter.get('url')}: {e}")

        # 次のパイプライン(または出力)にItemを渡す
        return item

Step 6: 設定の有効化

最後に、作成したパイプラインと、結果をJSONファイルに出力する設定をsettings.pyで有効にします。

confluence_crawler/settings.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# ... (他の設定) ...

# 作成したパイプラインを有効にする
# 数値が小さいほど先に実行される
ITEM_PIPELINES = {
   'confluence_crawler.pipelines.LlmSummaryPipeline': 300,
}

# 結果をJSONファイルに出力する設定
FEEDS = {
    'output.json': {
        'format': 'json',
        'encoding': 'utf8',
        'store_empty': False,
        'fields': ['title', 'url', 'summary'], # 出力するフィールドを指定
        'indent': 4,
    },
}

# クローリングの挙動に関する設定
ROBOTSTXT_OBEY = False # 社内Wikiなのでrobots.txtは無視
DOWNLOAD_DELAY = 1 # サーバーに負荷をかけないように1秒待つ
# ... (他の設定) ...

Step 7: 実行と結果の確認

すべての準備が整いました。プロジェクトのルートディレクトリで以下のコマンドを実行します。

1
claw crawl confluence

クローラーが動き出し、ログイン、ページ巡回、情報抽出、LLMによる要約、ファイルへの保存が順次実行されます。ログに進行状況が表示されるので、エラーが出ていないか確認しましょう。

処理が完了すると、output.jsonというファイルが生成されているはずです。中身を見てみましょう。

output.json(出力例)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[
    {
        "title": "【設計書】〇〇機能のAPI仕様",
        "url": "https://your-company.atlassian.net/wiki/spaces/DEV/pages/12345/API",
        "summary": "・このドキュメントは、〇〇機能のバックエンドAPIを利用するクライアント開発者を対象としています。\n・APIのエンドポイント一覧、リクエスト/レスポンスのフォーマット、認証方法について詳細に解説しています。\n・クライアント実装時に発生しがちなエラーコードとその対処法も網羅しており、開発効率の向上を目的としています。"
    },
    {
        "title": "新メンバー向けオンボーディングガイド",
        "url": "https://your-company.atlassian.net/wiki/spaces/DEV/pages/67890/",
        "summary": "・このガイドは、新しく開発チームに参加したメンバーがスムーズに業務を開始できるようにするためのものです。\n・開発環境のセットアップ手順、主要なリポジトリへのアクセス方法、チームのコミュニケーションルールについて説明しています。\n・最初の1週間で取り組むべきタスクリストが提示されており、早期の戦力化をサポートします。"
    }
]

このように、社内Wikiの情報が構造化され、かつLLMによって要点がまとめられたデータセットを自動で作成することができました。

メリットとデメリット(あるいは他ツールとの比較)

OpenClawを使ったこのアプローチには、いくつかのメリットとデメリットがあります。

メリット

  • 柔軟性と拡張性の高さ: Scrapyベースであるため、複雑なログイン処理、JavaScriptで動的に生成されるコンテンツへの対応(Playwright連携など)、巡回ルールの細かなカスタマイズなど、非常に柔軟なクローリングが可能です。
  • LLM連携の容易さ: パイプライン機構により、クローリング処理とLLMによるデータ処理をきれいに分離して実装できます。要約だけでなく、感情分析、エンティティ抽出、翻訳など、様々なLLMタスクを簡単に追加できます。
  • 非構造化データに強い: Webページという典型的な非構造化データをインプットとし、LLMの力で構造化データ(JSONなど)に変換できる点が最大の強みです。
  • 既存の資産を活用可能: 豊富なScrapyのドキュメントやコミュニティの知見をそのまま活用できます。

デメリット

  • 学習コスト: ScrapyやWebクローリングの基本的な知識(HTTP、HTML、CSSセレクタなど)が必要です。全くの初心者には少しハードルが高いかもしれません。
  • LLMのAPIコスト: 処理するページ数やテキスト量に比例して、LLMのAPI利用料が発生します。大規模なWikiを対象にする場合は、コストの見積もりが重要になります。
  • メンテナンスコスト: クローリング対象のWebサイト(社内Wiki)のUIが変更されると、CSSセレクタなどを修正する必要があります。定期的なメンテナンスが不可欠です。

他ツールとの比較

ツール/アプローチ メリット デメリット
OpenClaw (本記事) 柔軟なクローリングとLLM連携のバランスが良い。拡張性が高い。 Scrapyの学習コスト。APIコスト。メンテナンスコスト。
自前で実装 (Requests+BS4+LangChain) 自由度が最も高い。 非同期処理、エラーハンドリング、パイプライン等をすべて自作する必要があり、開発・保守コストが高い。
RAG特化フレームワーク (LlamaIndex等) データソースコネクタが豊富。RAG構築に特化していて手軽。 クローリングのカスタマイズ性が限定的。複雑な認証やサイト構造に対応しきれない場合がある。
ノーコード/ローコードツール 実装が非常に簡単で迅速。 細かい制御が難しい。対応しているデータソースや処理が限られる。ブラックボックス化しやすい。

私たちのケースでは、社内WikiがSSO認証を導入しており、クローリングの際に特殊なヘッダー情報を付与する必要があったため、柔軟にリクエストをカスタマイズできるOpenClawが最適でした。

現場で使える実践的なTips

基本的な実装に加えて、より実用性を高めるためのTipsをいくつかご紹介します。

1. 差分クローリングで効率化とコスト削減

毎回すべてのページをクロールするのは非効率で、LLMのコストもかさみます。ページの最終更新日時を取得し、前回クロールした日時以降に更新されたページのみを処理対象とすることで、大幅な効率化が可能です。

  • Confluence APIなどを利用して最終更新日時を取得する。
  • Spiderのparse_pageメソッド内で、更新日時をチェックし、古ければyieldしないロジックを追加する。
  • 前回の実行日時をファイルやデータベースに保存しておく。

2. プロンプトエンジニアリングで要約の質を向上

LLMによる要約の質はプロンプトに大きく依存します。目的に合わせてプロンプトを工夫しましょう。

悪い例: 「以下の文章を要約して。」

良い例:

あなたは〇〇チームのテックリードです。
以下のドキュメントは、新しくチームに参加したメンバーが読むことを想定しています。
このドキュメントの【目的】、【主要な技術要素】、【注意すべき点】の3つの観点で、箇条書きで分かりやすく要約してください。
---
{本文テキスト}
---

役割(Role)、対象読者、出力形式などを具体的に指定することで、より意図に沿った質の高い要約が得られます。

3. 収集した情報の活用法:Slackボット連携

収集してJSONにまとめたデータは、そのままでは宝の持ち腐れです。これを活用する最も効果的な方法の一つが、Slackボットとの連携です。

  1. データのインデックス化: 生成されたoutput.jsonを、Elasticsearchやベクトルデータベース(Chroma, Pineconeなど)に登録します。
  2. Slackボットの作成: SlackのBoltフレームワークなどを使って、ユーザーからの質問を受け付けるボットを作成します。
  3. 検索と回答: ユーザーがSlackで「〇〇機能の仕様について教えて」と質問すると、ボットはそのキーワードでデータベースを検索します。
  4. 結果の提示: 検索にヒットしたページの要約とURLをSlackに投稿します。これにより、チームメンバーはSlackを離れることなく、必要な情報に迅速にアクセスできるようになります。

4. 定期実行による情報の鮮度維持

作成したクローラーは、GitHub ActionsやJenkins、あるいはサーバーのcronなどを使って定期的に(例えば毎晩)実行するようにしましょう。これにより、社内ナレッジベースを常に最新の状態に保つことができます。

まとめ

本記事では、情報のサイロ化という多くの組織が抱える課題に対し、OpenClawを用いて社内Wikiを自動巡回し、LLMで情報を要約・構造化するボットを構築した実例をご紹介しました。

このアプローチにより、私たちは以下の成果を得ることができました。

  • 情報検索時間の大幅な短縮: 複数のツールを横断して探す手間がなくなり、Slackボットを通じて数秒で関連情報にたどり着けるようになった。
  • ナレッジの可視化と再利用: 埋もれていた過去のドキュメントが掘り起こされ、チーム全体の知識資産として活用されるようになった。
  • 新メンバーのオンボーディング効率化: 新メンバーが自律的に情報をキャッチアップしやすくなった。

OpenClawは、柔軟なWebクローリング能力と強力なLLMによるデータ処理能力を兼ね備えた、非常にポテンシャルの高いフレームワークです。今回ご紹介した社内Wikiの巡回だけでなく、競合他社のWebサイト分析、業界ニュースの自動収集と要約、採用サイトからの情報抽出など、その応用範囲は無限大です。

情報過多の時代において、必要な情報を効率的に収集・整理・活用する能力は、個人にとっても組織にとっても重要な競争力となります。この記事が、皆さんの組織のナレッジマネジメントを一段階進化させるための一助となれば、これほど嬉しいことはありません。ぜひ、お使いの社内ツールで試してみてはいかがでしょうか。