0
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?

スクレイパーの「精度が低い」は自作バグか仕様か — 手作業再現で切り分ける実践手順

0
Posted at

検証環境

  • Chrome 126(Manifest V3 / Content Scripts)
  • TypeScript 5.4 + Vite 5(拡張ビルド)
  • Playwright 1.44(再現・突き合わせ用)
  • Node.js 20.12
  • 対象: とある求人・企業検索サイトの一覧ページ(DOM は data-testid 付き)

きっかけ: 「別業種が混ざる」という一通の連絡

Chrome拡張で、検索結果ページのDOMから企業一覧を抽出してCSVに吐く機能を作っていました。あるユーザーから「IT業種で検索したのに、建設・物流の企業が混ざる」と報告が来ました。

ここで最初に判断すべきは、この混入が自作の抽出ロジックのバグなのか、検索エンジン側がそう返しているのかです。切り分けずに「仕様です」と返して、実は自作バグだった場合、信用を失います。逆に「バグです」と謝って、実はプラットフォーム側の仕様だった場合も、無駄な修正コミットを積むことになります。

切り分けは「手作業の1回再現」から入る

結論から書くと、やったことはシンプルです。

  1. 拡張を無効化した状態のChromeで、同じキーワード・同じフィルタ条件・同じページ番号を開く
  2. 標準の検索画面に表示されている業種名を、目視で10〜20件テキストに書き出す
  3. 拡張が出力したCSVの業種列と突き合わせる

標準検索が別業種を返しているなら、由来はプラットフォーム側と確定できます。この時点で「仕様です」と返してよい根拠が手元に揃います。逆に標準検索が返していない業種がCSVにだけ存在するなら、抽出コードのスコープが広すぎる疑いが濃厚です。

併せて確認する3点

  • 抽出コードが結果コンテナ(結果を並べる枠)に限定されているか
  • おすすめ枠・広告枠・サイドバーまで走査していないか
  • 遅延読み込み(無限スクロール)で、DOM上の件数と表示上の件数がずれていないか

実際に今回の案件では、標準検索でも建設業の企業が混ざっていました。つまりプラットフォーム側の関連業種サジェストでした。ただし突き合わせの過程で、こちらのコードにも別の問題(おすすめ枠の混入)が見つかりました。片方だけ直して終わりにしないのがポイントです。

実装: コンテナに限定した抽出

スコープ漏れを防ぐには、document.querySelectorAll を直接使わず、必ず結果コンテナを起点にします。

const RESULT_SELECTOR = '[data-testid="search-results"]';
const CARD_SELECTOR = '[data-testid="result-card"]';

type Item = { title: string; industry: string; isSponsored: boolean };

function extractItems(root: ParentNode): Item[] {
  const container = root.querySelector<HTMLElement>(RESULT_SELECTOR);
  if (!container) return [];

  // :scope で結果コンテナ配下に限定。おすすめ枠・サイドバーを巻き込まない
  return Array.from(
    container.querySelectorAll<HTMLElement>(`:scope ${CARD_SELECTOR}`)
  ).map((el) => ({
    title: el.querySelector('[data-testid="title"]')?.textContent?.trim() ?? '',
    industry: el.querySelector('[data-testid="industry"]')?.textContent?.trim() ?? '',
    // 広告・おすすめは closest で祖先まで見て判定する
    isSponsored: el.closest('[data-testid="sponsored"]') !== null,
  }));
}

isSponsored を持たせておくと、後段で「混入は広告枠由来か、検索結果本体由来か」を切り分けられます。抽出時点でメタ情報を付けるのが大事です。

突き合わせを自動化する

手作業の再現は1回で十分ですが、リグレッション検出のために自動化しておきます。正規化した上で集合差分とJaccard係数を見ます。

export function diffIndustries(manual: string[], ext: string[]) {
  const norm = (xs: string[]) =>
    new Set(xs.map((s) => s.normalize('NFKC').replace(/\s+/g, '').trim()));
  const m = norm(manual);
  const e = norm(ext);
  const onlyInExt = [...e].filter((x) => !m.has(x));
  const onlyInManual = [...m].filter((x) => !e.has(x));
  const union = new Set([...m, ...e]);
  const jaccard = union.size === 0 ? 1 : [...m].filter((x) => e.has(x)).length / union.size;
  return { onlyInExt, onlyInManual, jaccard };
}

onlyInExt が空なら「拡張は余計なものを拾っていない」、onlyInManual が空なら「取りこぼしなし」と読めます。全角半角や異体字を normalize('NFKC') で潰しておかないと、偽陽性だらけになります。

再現スクリプトはPlaywrightで回します。

npm i -D playwright@1.44.0
npx playwright install chromium
npx tsx scripts/verify-search.ts --keyword "Webエンジニア" --out artifacts/2024-06-01

--out に DOMスナップショットとスクリーンショットを保存しておくと、後から「その時プラットフォームが何を返していたか」を証跡として出せます。問い合わせ対応では、この証跡の有無がそのまま信頼になります。

ハマりポイント

  • 仮想スクロール: 表示中の数十件しかDOMに存在しない。scrollTo + MutationObserver で全件ロードを待つ必要がある
  • iframe / Shadow DOM: querySelector は境界を越えない。shadowRoot を明示的に降りる実装が別途必要
  • A/Bテスト: 同一URLでもユーザーセグメントでDOM構造が変わる。data-testid が無い場合は構造セレクタが即死する
  • おすすめ枠の位置: 結果リストの「中」に挿入されることがあり、コンテナ限定だけでは防げない。isSponsored のような明示フラグで弾く

症状別の切り分け表

症状 疑う場所 切り分け方法
別カテゴリが混ざる プラットフォームの関連サジェスト / おすすめ枠 標準検索で手作業再現
件数が表示より少ない 仮想スクロール / 遅延読み込み DOM件数とUI件数を比較
特定ユーザーだけ再現 A/Bテスト / ログイン状態 シークレットウィンドウで再現
稀にセレクタが空 描画タイミング waitForSelector の導入
表記ゆれで差分が出る 正規化漏れ NFKC + 空白除去

FAQ

Q. 手作業再現は毎回やるべき?
A. 原因を断定して返信する前には必ず1回。自動化済みならそのログで代替できます。

Q. プラットフォーム側の仕様だった場合、修正は不要?
A. 仕様でも、ユーザーが不要と感じるならフィルタ機能として吸収する価値があります。今回は業種の完全一致フィルタを追加しました。

Q. スクレイピングの利用規約は?
A. 対象サイトの規約とrobots.txtを必ず確認してください。抽出範囲を結果コンテナに限定するのは、負荷と誤抽出の両方を抑える意味でも有効です。

まとめ

「精度が低い」という報告に対して、いきなりコードを読み始めるのは順序が逆です。同じ条件を手作業で1回再現する。それだけで、プラットフォーム由来か自作バグかが確定し、返すべき言葉が変わります。切り分けは手作業の再現から入る派です。


この記事を書いた人

BENTEN Web Works — 業務自動化・システム開発のフリーランスエンジニアです。

GAS / Python / RPA を使った業務自動化や、Web制作・システム開発のご相談を承っています。
「こんなこと自動化できる?」というご質問だけでもお気軽にどうぞ。

👉 業務自動化サービス — 詳細・お問い合わせはこちら
🐦 X(旧Twitter) — 日々の知見を発信中

0
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
0
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?