1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AIに「記事を読ませる」ために — MarketLens AI の情報収集エンジンを作り直した話

1
Last updated at Posted at 2026-06-08

MarketLens AI とは

個人・フリーランス・中小企業向けの市場調査補助ツールです。

「競合他社が最近何をしているか調べたい」「業界ニュースを毎日チェックしたい」という需要は多いですが、専門の調査会社に依頼するコストを払えない層は多くあります。MarketLens AI はその層向けに、ニュースサイトや企業プレスリリースなどから情報を自動で収集し、カテゴリ分類・要約・感情分析をかけて画面上で読みやすく整理して表示するツールです。「全自動で完璧なレポートが出てくる」というより、毎日の情報収集・整理の手間を減らすことを目指しています。

技術スタックは Python(FastAPI + Celery + Redis)+ React + Docker 構成です。


今回のアップデートで解決した問題

「AI が見出ししか読めていなかった」

このツールは収集した記事を AI に渡して分類・要約を行う設計にしています。ところが実装当初、情報収集の処理がニュースサイトの一覧ページからメタ情報を取得するだけで、記事の中身はほとんど取れていませんでした。

実際の取得内容はこんな状態でした:

情報源 取得内容の例 文字数
あるプレスリリースサイト "9分前\n会社名" 16文字
ニュースサイトA "[カテゴリ] タイトル" 32文字
ニュースサイトB タイトルのみ 36〜88文字

16文字の「9分前 会社名」を AI に渡しても、分析できる内容はゼロです。ツールとして成立していませんでした。


改修内容(v11)

一覧ページ → 記事詳細ページへの変更

各情報源の収集処理を 「一覧ページのメタ情報を取得」から「記事詳細ページの本文を取得」 する方式に全面改修しました。

# 改修前:一覧ページから title, url のみ取得
def parse(self, response):
    for item in response.css(".article-list li"):
        yield {"title": item.css("a::text").get(), "url": item.css("a::attr(href)").get()}

# 改修後:記事URLをたどって本文まで取得
def parse(self, response):
    for item in response.css(".article-list li"):
        url = item.css("a::attr(href)").get()
        yield response.follow(url, callback=self.parse_detail)

def parse_detail(self, response):
    yield {"title": ..., "content": response.css("article.main-content").get()}

結果として取得できる内容が大きく変わりました:

情報源 改修前 改修後
プレスリリースサイト 16文字 6,510文字
ニュースサイトA 88文字 1,568文字
ニュースサイトB 36文字 1,178文字
ECサイト商品ページ 116文字 1,151文字

取得済みデータの再収集対応

改修前に取り込んだレコードは内容が薄いまま残っています。save_article のロジックを変更し、同じ記事でもコンテンツが 100 文字以上増加した場合は上書きして再分析対象(status='pending')に戻すようにしました。

-- 改修前
INSERT INTO articles (...) ON CONFLICT (url) DO NOTHING;

-- 改修後
INSERT INTO articles (...) ON CONFLICT (url) DO UPDATE
  SET content = EXCLUDED.content,
      status = 'pending',
      scraped_at = NOW()
  WHERE length(EXCLUDED.content) - length(articles.content) > 100;

情報源の死活監視機能を追加("ドクターイエロー")

22ある情報源がいつの間にか壊れていても、ログを掘らないと気づきません。そこで 全情報源を 12 時間ごとに自動チェックする監視機能 を追加しました。プロジェクト内では新幹線の線路点検車にならって "ドクターイエロー" と呼んでいます。

HEALTH_STATUS = Literal["healthy", "degraded", "stale", "down"]

@app.task
def check_scraper_health():
    for target in scrape_targets.all():
        recent = articles.filter(target=target, scraped_at__gte=12h_ago)
        if not recent.exists():
            status = "down"
        elif recent.avg_content_len() < target.min_content:
            status = "degraded"
        else:
            status = "healthy"
        notify_if_changed(target, status)
  • healthy → degraded/down に変化したタイミングで Webhook・メール通知
  • GET /api/v1/system/scraper-health で現在の状態を一覧取得できる

テスト:API 338 / DB 68 / 定期実行 51 = 457/457 PASS


GUI の改善(v12 — 36項目)

情報収集が動くようになってきたので、管理画面(React SPA)の使い勝手も整えました。主な変更点:

ローディング中の表示(Skeleton)

// 改修前:データ取得中は何も表示されない
{isLoading ? null : <KpiCard data={data} />}

// 改修後:枠線アニメーションで「読み込み中」を表現
{isLoading ? <Skeleton variant="rectangular" height={120} /> : <KpiCard data={data} />}

空状態の統一表示

データが 0 件のとき空のテーブルが表示される問題を解消。全 6 ページに <EmptyState message="まだデータがありません" /> を統一適用しました。

相対時間表示

// "2026-06-07T09:00:00+09:00" → "昨日" or ホバーで正確な日時
<Tooltip title={formatDateTime(date)}>{formatRelative(date)}</Tooltip>

詰まっていた情報源の修復(v14)

v11 の改修後も 2 つの情報源が取得件数ゼロのままでした。

パターン①:レスポンス形式の読み違い

# APIが [{...}] のリスト形式で返すのに、オブジェクトとして処理していた
data = response.json()          # [{...}]
items = data.get("ResultList")  # None → 全件スキップ

# 修正後
data = response.json()
items = data[0].get("ResultList", [])

パターン②:User-Agent による弾き返し

# 修正前:独自UAでアクセス → CDN の bot-protection にブロックされる
custom_settings = {"USER_AGENT": "MarketLensBot/1.0"}

# 修正後:通常ブラウザと同じ UA を使用
custom_settings = {"USER_AGENT": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ..."}

2つ合わせて 108件/回 の取得が可能になり、全 22 情報源が healthy 状態になりました。


現在の状態

項目 状態
全22情報源 healthy
テスト 457/457 PASS(API 338 / DB 68 / 定期実行 51)
取得本文量 16〜116文字 → 521〜6,510文字

情報収集の基盤がようやく使い物になってきました。次は収集した記事を AI がどう分析・整理するかの精度向上に取り組む予定です。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?