MAGAZINE
ルーターマガジン
Chrome DevTools MCP実践:SPA, SSRサイトでもAPIエンドポイントを1分で特定してクローラー開発
はじめに
クローリング・スクレイピング案件を専門とする弊社では、「調査フェーズ」が開発全体のボトルネックになりがちです。具体的には、DevToolsのNetworkタブを開きながら「このXHRリクエストが欲しいデータを返してるのか?」「このセレクタはデザイン変更で壊れないか?」といった確認を延々とやる時間のことです。ベテランエンジニアでも初見サイトで30分〜1時間かかることがあります。
この調査作業を、AIに丸投げできないか。そう思って試したのが Chrome DevTools MCP です。
Chrome DevTools MCPとは
一言でいうと、AIアシスタント(Claude等)からChromeブラウザの内側を直接操作・観察できるようにするMCPサーバーです。
仕組みはシンプルで、ChromeがもともとDevTools向けに公開している Chrome DevTools Protocol(CDP) を経由して接続します。
Claude(AIアシスタント)
↓ MCPツール呼び出し
Chrome DevTools MCPサーバー(ローカルプロセス)
↓ Chrome DevTools Protocol(CDP)
ユーザーが使用中のChromeブラウザ
↓
対象Webサイト(既存セッション・Cookie有り)
重要なのは「ユーザーが実際に使っているChromeに接続する」点です。すでにログイン済みのセッション、蓄積されたCookie――これらをそのまま活用できます。スクレイピング調査の文脈で特に使うのは以下のツールです。
| ツール | できること |
|---|---|
list_network_requests |
ページで発生したHTTPリクエスト一覧を取得 |
get_network_request |
特定リクエストの詳細(URL・ヘッダー・レスポンス)を取得 |
evaluate_script |
ブラウザ上でJavaScriptを実行(DOM探索・データ抽出) |
take_screenshot |
現在のページをスクリーンショット |
navigate_page |
ページ遷移 |
list_console_messages |
コンソールログの取得 |
セットアップ
Claude Codeの場合、.claude/settings.json に追加するだけです。
{
"mcpServers": {
"chrome-devtools": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-chrome-devtools"]
}
}
}
Chromeを リモートデバッグモード で起動する必要があります。
# macOS
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--remote-debugging-port=9222
# Windows
chrome.exe --remote-debugging-port=9222
「APIファーストスクレイピング」という考え方
本題に入る前に、記事で繰り返し出てくる概念を整理しておきます。
従来のスクレイピングは、ブラウザが表示するHTMLを解析してデータを抽出します。
ブラウザ → HTML取得 → Nokogiri等でパース → データ抽出
ただ、モダンなWebサービス(SPAや多くのスマートフォン向けサービス)は、HTMLにデータを直接埋め込まず、内部のJSON APIからデータを取得してページに描画しています。
ブラウザ → HTML(骨格のみ)取得
→ JavaScript実行 → JSON API呼び出し → データ取得 → 描画
このJSON APIを直接叩くのが「APIファーストスクレイピング」です。HTMLのパースを省けるのでコードがシンプルになり、サイトのデザイン変更にも強いというメリットがあります。
# HTMLスクレイピング:CSSセレクタに依存、デザイン変更で壊れる
doc.css("div.product-list > article > span.price")
# APIファースト:構造化データを直接取得、変更に強い
response = Net::HTTP.get_response(URI("https://example.com/api/products?page=1"))
JSON.parse(response.body)
問題は「どのAPIを叩けばいいか」の特定に手間がかかる点でした。Chrome DevTools MCPはここを解決するツールとなり得ます。
検証対象サイトについて
今回の検証はすべて、スクレイピングを明示的に許可・歓迎しているサイトおよびAPIを対象にしています。
- APIエンドポイント探索・コード生成:Open Library(Internet Archive運営、JSON API公開・オープンデータ)
- Cookieの転用:Quotes to Scrape(スクレイピング練習専用・ログイン機能付き)
- セレクタ探索:Books to Scrape(スクレイピング練習専用ECサイト)
- デバッグ:ローカル環境
実際にやってみた
APIエンドポイントの自動発見
まず試したのが、NetworkタブをAIに読ませてAPIを特定させる実験です。
Open Library(openlibrary.org)の書籍検索ページを開いた状態で、Claudeに次のプロンプトを送りました。
「現在開いているページで発生しているネットワークリクエストを確認して、書籍一覧データ(タイトル・著者・出版年)を返しているAPIエンドポイントを特定してください。」
Claudeがとった手順はこうです。まず list_network_requests でページのリクエスト一覧(64件)を全取得。Open LibraryはサーバーサイドレンダリングのためXHR・Fetchでの書籍データ取得は発生しないものの、SearchFacets.json というFetchリクエストのURLパターンから「このサービスはJSON APIを持っている」と推定し、/search.json エンドポイントに直接アクセスして確認しました。
# ネットワークリクエスト一覧(抜粋)
reqid=1 GET https://openlibrary.org/search?q=python+programming [200] ← SSRで書籍データ込み
reqid=60 GET https://openlibrary.org/partials/SearchFacets.json?... [403] ← JSON APIの存在を示唆
# → /search.json エンドポイントを直接確認
実際に https://openlibrary.org/search.json?q=python+programming を叩いた結果:
{
"numFound": 2497,
"docs": [
{
"key": "/works/OL30906747W",
"title": "Core Python Programming",
"author_name": ["R. Nageswara Rao"],
"first_publish_year": 2016
},
{
"key": "/works/OL19547345W",
"title": "Black Hat Python",
"author_name": ["Justin Seitz", "Tim Arnold"],
"first_publish_year": 2014
},
{
"key": "/works/OL12409294W",
"title": "Core Python programming",
"author_name": ["Wesley Chun"],
"first_publish_year": 2006
}
]
}
「XHRに書籍APIが出てこない→SSRだ→でもSearchFacets.jsonのURLパターンからJSON APIがあると判断→直接叩く」という一連の流れを、プロンプト送信から約1分で完結しました。SSRサイトでも詰まらずに別ルートで突破するのが素晴らしいですね。
APIエンドポイントからスクレイピングコードを即生成
エンドポイントが特定できたので、そのままコード生成も頼んでみました。
「このAPIを使って、キーワード『python programming』の書籍データを全件CSVに書き出すRubyスクリプトを書いてください。ページネーション処理とレート制限対応も含めて。」
require 'net/http'
require 'json'
require 'csv'
require 'uri'
BASE_URL = "https://openlibrary.org/search.json"
def fetch_books(query)
all_books = []
page = 1
loop do
uri = URI(BASE_URL)
uri.query = URI.encode_www_form(q: query, page: page, limit: 100)
response = Net::HTTP.get_response(uri)
response.value # 2xx以外で例外
data = JSON.parse(response.body)
docs = data["docs"] || []
break if docs.empty?
all_books.concat(docs)
puts "ページ #{page}: #{docs.size} 件取得(累計 #{all_books.size} 件)"
break if all_books.size >= data["numFound"]
page += 1
sleep 1.0 # レート制限対応
end
all_books
end
def save_csv(books, filename)
fields = %w[key title author_name first_publish_year]
CSV.open(filename, "w", headers: fields, write_headers: true) do |csv|
books.each do |book|
csv << fields.map do |f|
val = book[f]
val.is_a?(Array) ? val.join(", ") : val
end
end
end
end
books = fetch_books("python programming")
save_csv(books, "books.csv")
puts "完了: #{books.size} 件を books.csv に保存しました"
Networkタブの実際のリクエストからパラメータ構造をそのままコードに落とし込んでいます。「どのパラメータが使えるか」を試行錯誤する工程がなくなるのが地味に効きます。CSSクラス変更でセレクタが壊れるリスクもないので、HTMLスクレイピングより保守コストが低いです。
ログイン済みCookieをそのまま転用する
次はログインが必要なサイトのケースです。
認証フローをスクリプトで再現するのは手間がかかります。特に二要素認証があるサービスだと、そこだけで半日潰れることもあります。セッションCookieさえ取れれば Net::HTTP のリクエストヘッダに渡すだけで認証済みAPIを叩けます。Chrome DevTools MCPはNetworkリクエストの全ヘッダーを読めるので、これが自動で実現できるはずです。
スクレイピング練習専用サイト quotes.toscrape.com/login でログインした状態(ユーザー名・パスワードは任意の値でOK)から試しました。
「ログイン済みの状態でAPIリクエストに付与されているCookieを取得して、同じリクエストをNet::HTTPで再現するRubyコードを生成してください。」
Claudeはまず list_network_requests でログイン後のリクエストを確認し、get_network_request でリクエストヘッダーを取得して session Cookieを特定。そのCookieをコードに直接埋め込んで生成しました。ログイン自体は任意のユーザー名・パスワードで成功します。
require 'net/http'
require 'nokogiri'
require 'uri'
# ブラウザのNetworkタブから取得したセッションCookie
COOKIES = "session=YOUR_COOKIE_VALUE" # Claudeが実際の値を埋める
def fetch_quotes(tag)
uri = URI("https://quotes.toscrape.com/tag/#{tag}/")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request["Cookie"] = COOKIES
response = http.request(request)
response.value
doc = Nokogiri::HTML(response.body)
doc.css("div.quote").map do |block|
{
text: block.at_css("span.text")&.text,
author: block.at_css("small.author")&.text
}
end
end
results = fetch_quotes("love")
results.each { |q| puts "#{q[:author]}: #{q[:text]}" }
ログイン処理を一切スクリプトに実装しなかった、というのがポイントです。ブラウザで手動ログインしてからCookieをMCPに取得させる、この流れでプロトタイプを素早く動かせます。Cookieの有効期限が切れれば当然使えなくなるので量産スクリプトには向きませんが、調査・検証フェーズの時間短縮としては十分に使えます。
DOM探索で「壊れにくいセレクタ」を探す
HTMLスクレイピングが避けられない場合もあります。そのときに時間を食うのが「適切なセレクタ探し」です。
div.product-list > div:nth-child(2) > span.price のような構造依存のセレクタは、サイトのデザイン変更で一瞬で壊れます。AIがDOMを段階的に調べながら、より堅牢なセレクタを提案してくれるか確認してみました。
スクレイピング練習専用サイト books.toscrape.com を開いた状態で送ったプロンプトは以下です。
「このページの商品名と価格を取得するための、壊れにくいCSSセレクタを特定してください。data属性やaria属性を優先して探してください。」
// ステップ1: data属性を持つ要素を探索
document.querySelectorAll("[data-product-id]").length;
// → 0(data属性なし)
// ステップ2: セマンティックな要素を確認
document.querySelectorAll("article.product_pod").length;
// → 20(商品数と一致)
// ステップ3: 各要素内の構造を確認
const sample = document.querySelector("article.product_pod");
({
name: sample.querySelector("h3 > a")?.getAttribute("title"),
price: sample.querySelector("p.price_color")?.textContent.trim(),
rating: sample.querySelector("p.star-rating")?.className,
availability: sample.querySelector("p.availability")?.textContent.trim(),
});
// → {
// name: "A Light in the Attic",
// price: "£51.77",
// rating: "star-rating Three",
// availability: "In stock"
// }
data属性がないと判断した時点で、「意味を表すクラス名」への探索に自律的に切り替えました。product_pod・price_color はデザインではなく意味を示すクラス名なので変更されにくい、という判断です。この思考プロセスは人間がDevToolsで手作業するのとほぼ同じです。
require 'net/http'
require 'nokogiri'
response = Net::HTTP.get_response(URI("https://books.toscrape.com"))
doc = Nokogiri::HTML(response.body)
books = doc.css("article.product_pod").map do |article|
{
title: article.at_css("h3 > a")&.[]("title"),
price: article.at_css("p.price_color")&.text&.strip,
rating: article.at_css("p.star-rating")&.[]("class")&.split&.last
}
end
コンソールログを監視してデバッグを効率化する
「なぜかデータが取れない」という場面はスクレイピングに付き物です。原因の多くはJavaScriptのエラーやタイミング問題ですが、ログを見るためにDevToolsを開き直すのが地味に面倒でした。
ローカルで動かしているスクレイパーのデバッグ中に、以下を試しました。
「スクレイパーを動作させています。コンソールログにエラーが出ているか確認して、問題があれば原因を特定してください。」
実際に books.toscrape.com でスクレイパーのエラーをシミュレートして list_console_messages を実行した結果です。
# list_console_messages の実際の出力(books.toscrape.comで動作確認)
msgid=21 [error] Mixed Content: The page at 'https://books.toscrape.com/' was loaded over HTTPS,
but requested an insecure script 'http://ajax.googleapis.com/ajax/libs/jquery/...'
→ サイト自体の問題。スクレイパーとは無関係
msgid=22 [log] [INFO] ページ読み込み完了: https://books.toscrape.com
msgid=23 [log] [INFO] 商品数検出: 20件
msgid=24 [error] TypeError: Cannot read properties of null (reading 'textContent')
at extractPrice (scraper.js:42)
at Array.forEach (<anonymous>)
at scrapeProducts (scraper.js:28)
msgid=25 [warn] Rate limit approaching: 85/100 requests used. Consider adding delay.
msgid=26 [log] [INFO] Next page URL found: /catalogue/page-2.html
msgid=27 [log] [INFO] ページ 2 のスクレイプ開始
Claudeが即座に返した診断はこうです。「msgid=21のMixed ContentエラーはサイトのHTTPリソース参照の問題でスクレイパーとは無関係。本題はmsgid=24で、42行目の null チェックが抜けている。対象要素が存在しないページで落ちている。またmsgid=25でレート制限が85/100に達しているため待機時間を増やすべき」。
# 修正前
price = element.at_css('span.price').text
# 修正後:nil チェックを追加(&. でsafe navigation)
price = element.at_css('span.price')&.text
「スクレイパーを動かしつつ、AIがリアルタイムでログを監視・診断する」という使い方が普通に成立しました。Mixed Contentのようなサイト自体のエラーとスクレイパーのバグを区別して診断してくれる点も地味に助かります。
実務での使いどころと限界
一連の検証を通じて、向いている用途と向いていない用途がはっきりしてきました。
向いている用途:
- SPAや動的サイトの内部APIをリバースエンジニアリングして直接叩くスクレイパーを書く
- ログインが複雑なサービスや二要素認証が必要なサービスの調査・プロトタイピング
- HTMLスクレイピングが避けられない場合の、変更に強いセレクタ選定
- スクリプトを動かしながらコンソールログをAIに監視させるリアルタイムデバッグ
向いていない用途:
- バッチ・スケジュール実行(ユーザーが手動でChromeを起動している必要がある)
- 並列処理・大量収集(単一のChromeセッションに縛られる)
- サーバー上での自動実行(ヘッドレス環境には不向き)
要は「Chrome DevTools MCPはあくまで調査ツール」です。調査で得た情報をもとにコードを生成し、そのコードを量産スクリプトとして運用する――この役割を明確にすれば、スクレイピング開発全体のスループットが大きく向上します。
おわりに
「どのAPIを叩けばいいか」「どのセレクタが使えるか」「なぜデータが取れないか」。これまでベテランエンジニアが経験と勘で素早くこなしていた探索作業を、AIが数十秒で実行してくれます。
今回の検証で一番実感したのは、調査フェーズのボトルネックが丸ごと消えるということです。コーディング自体の時間はもともとそれほどかかっておらず、むしろ「どこを叩けばいいか」を調べる時間のほうが長かったため、そこが短縮されると、開発サイクル全体の感覚がかなり変わります。
株式会社ルーターでは、Chrome DevTools MCPをはじめとした最新ツールを業務フローに統合しながら、より効率的なデータ収集サービスを提供しています。スクレイピング・クローリング案件でお困りの方はお気軽にご相談ください。
株式会社ルーター 長戸
CONTACT
お問い合わせ・ご依頼はこちらから