curlでは取得できない公式XMLをどう自動取得したか?Headless Chromium+CDPで東京電力の停電情報取得に成功するまで

ブラウザでは見えるのに、サーバーからは取れない

Pinpoint Information Hubでは、各電力会社が公開している停電情報を取得し、 共通形式へ変換して表示する仕組みを開発しています。

その中でも、最後まで取得方法に悩まされたのが 東京電力パワーグリッドの停電情報でした。

東京電力では、次のようなXML形式の停電情報が公開されています。

https://teideninfo.tepco.co.jp/flash/xml/00000000000.xml

普通のChromeでこのURLを開くと、XMLは問題なく表示されます。

ところがWordPressやサーバー上のcurlから取得すると、 同じURLなのにトップページへリダイレクトされたり、 期待したXMLではなくHTMLが返されたりしました。

ここから長い検証が始まりました。

最初は単純なHTTP取得を試した

まず試したのは一般的な方法です。

  • WordPress HTTP API
  • PHP cURL
  • Windowsのcurl.exe
  • User-AgentやRefererの指定
  • Cookieを維持したセッション

しかし結果は安定しませんでした。

代表的な応答は次のようなものでした。

HTTP 302
Location: /
Server: AkamaiGHost

XMLのURLを要求しているにもかかわらず、 トップページへ戻されます。

さらに、リダイレクトを自動追跡すると一見取得に成功したように見えても、 実際に保存されていたのはXMLではなくトップページのHTMLでした。

この経験から、 HTTP 200だけでは成功判定にしてはいけない という重要な教訓も得ました。

「普通のChromeなら取得できる」が大きなヒントになった

同じPC、同じインターネット回線でも結果が違いました。

通常のChrome         → XML表示成功
curl.exe              → リダイレクト
Headless自動取得       → 失敗
単純なfetch()          → 失敗

つまり単純にIPアドレスだけで判定されているわけではなさそうです。

ブラウザのCookie、セッション、JavaScriptの実行状態、 HTTP通信の特徴など、複数の要素によって結果が変わっている可能性があります。

ここで発想を変えました。

「HTTPクライアントでChromeを真似する」のではなく、 本物のChrome自身に取得させればいいのではないか?

Chrome DevTools Protocolを試す

そこで登場したのが Chrome DevTools Protocol(CDP)です。

CDPを使うと、Chromeをプログラムから操作できます。

  • ページを開く
  • 新しいタブを作る
  • ページを移動する
  • JavaScriptを実行する
  • ネットワーク通信を確認する
  • ページ内容を取得する

最初は、東京電力トップページを開いた後にJavaScriptの fetch() でXMLを取得する方法を試しました。

しかし、これも失敗しました。

突破口は「別タブでXMLをページとして開く」だった

検証していて、非常に面白い違いに気付きました。

人間がChromeを操作した場合は、 東京電力トップページを開いたあと、 XMLのURLを別のページとして開けば正常に表示されます。

そこでCDPでも同じ操作順序を再現しました。

タブ1
東京電力トップページを開く

    ↓ 数秒待つ

タブ2
停電情報XMLをトップレベルページとして開く

すると結果が変わりました。

TAB1_NAVIGATE
https://teideninfo.tepco.co.jp/

TAB2_NAVIGATE
https://teideninfo.tepco.co.jp/flash/xml/00000000000.xml

HTTP 200

RESULT OK

Windows上のHeadless Chromeで、 ついに完全自動取得に成功しました。

fetchで取得するのと、 ブラウザのページ遷移として取得するのでは挙動が違った のです。

「これ、レンタルサーバーでもできないか?」

ここでさらに欲が出ました。

Windows PCで取得してFTPでサーバーへ送る方法でも運用できます。

しかし理想は、 WordPressが動いているレンタルサーバー内ですべて完結すること です。

そこでSSHでサーバー環境を調査しました。

CPU:x86_64
PHP:8.4系
exec:利用可能
proc_open:利用可能
shell_exec:利用可能
curl:利用可能
unzip:利用可能

外部プログラムをPHPから起動できる環境でした。

ところがChromeもChromiumもインストールされていません。

最新Chromeはglibcの壁で起動できなかった

Chrome for TestingのHeadless Shellをユーザー領域へ配置してみました。

しかし、サーバーのglibcは2.17。 現行Chromeが必要とするライブラリより古く、起動時にエラーとなりました。

GLIBC_2.18 not found
GLIBC_2.25 not found

root権限のない共用レンタルサーバーなので、 OS側のglibcを更新するわけにもいきません。

ここで最新Chromeを使う方法は断念しました。

旧Chromium 94が動いた

次に、glibc 2.17で動作可能な世代のChromiumを探しました。

Chromium公式のSnapshotから、 2021年のChromium 94系をユーザー領域へ配置して試します。

Chromium 94.0.4606.0

結果は成功。

No missing libraries detected

さらにHeadlessモードで起動し、 ローカルのCDPポートへ接続しました。

Browser:
HeadlessChrome/94.0.4606.0

Protocol-Version:
1.3

これで、 共用レンタルサーバー上でもHeadless Chromium+CDPが動作する ことが確認できました。

レンタルサーバーでも複数タブ方式を再現

Windowsで成功した方法をそのままサーバーへ移植します。

Headless Chromium起動

↓

タブ1
https://teideninfo.tepco.co.jp/

↓

数秒待機

↓

タブ2
東京電力の停電情報XML

↓

XML内容取得

さらに検証を進める中で、 サーバー版では view-source: を使ってXMLをブラウザ自身に表示させる方法 が安定して利用できることが分かりました。

CDPのRuntime機能からChromeが表示している内容を取得すると、 ついに次の結果になりました。

BYTES=7973
CANONICAL_ROOT=YES
ExitCode=0

取得した内容の中に、 東京電力公式XMLのルート要素が存在することを確認できました。

レンタルサーバー内だけでの取得成功です。

Pinpoint Information Hubへ本番実装

検証だけで終わらせず、 この取得方法をPinpoint Information Hubへ組み込みました。

本番版では東京電力の取得を次の順番で試します。

WordPress HTTP API
        ↓
PHP cURL
        ↓
Server Chromium CDP
        ↓
公式HTML等の既存フォールバック

通常のHTTP取得が成功すれば、 わざわざChromiumは起動しません。

従来方法が失敗したときだけ、 サーバー上のChromiumを一時的に起動してCDP方式へ切り替えます。

取得後はChromiumを終了し、 一時プロファイルも削除します。

そして、本番画面で成功を確認

Information Hubの管理画面から東京電力のテスト解析を実行しました。

結果は成功。

fetch_transport:
server_cdp_multitab_view_source

Server Chromium CDP:
HTTP 200

Content-Type:
application/xml; charset=UTF-8

Browser:
HeadlessChrome/94.0.4606.0

CDP Protocol:
1.3

さらに公式XMLの解析にも成功し、 停電件数、地域情報、公式案内、更新日時まで既存のInformation Hubへ正常に渡りました。

取得した公式XMLの原文も管理画面から確認できます。

Windows PCもFTP中継も不要。

東京電力の取得から解析まで、 レンタルサーバー内だけで完結しました。

今回分かった重要なこと

今回の検証では、 単純なHTTP取得が失敗したからといって 「その情報は自動取得できない」とは限らないことが分かりました。

Webサイトによっては、 HTTPクライアントから直接取得する場合と、 実際のブラウザでページとしてアクセスする場合で 結果が異なる場合があります。

その場合、 本物のブラウザエンジンを利用した取得方法が選択肢になります。

ただしこれは、 アクセス制御を無理に回避するという意味ではありません。

公開情報を取得する場合でも、 提供元の利用条件やアクセス頻度に配慮し、 必要最小限の取得にすることが重要です。

もう一つの教訓。「200 OK」を信用しすぎない

今回、HTTP 200が返っていても、 実際には期待したXMLではなくHTMLだったケースがありました。

そのため現在のInformation Hubでは、 単にHTTPステータスだけを見るのではなく、 取得データそのものを確認します。

HTTPステータス
Content-Type
最終URL
XMLルート
データ構造
公式更新日時
公式停電件数

これらを組み合わせて、 初めて「正常取得」と判定します。

これは災害・停電情報のような、 誤表示をできるだけ避けたいデータでは特に重要です。

失敗を積み重ねたから見つかった方法

今回の方法にたどり着くまでには、 かなり多くの失敗がありました。

WordPress HTTP API   → 失敗
PHP cURL             → 失敗
Windows curl.exe     → 失敗
クラウド環境          → 失敗
単純Headless          → 失敗
CDP + fetch           → 失敗
CDP + 複数タブ         → 成功
サーバーChromium       → 成功
Information Hub実装    → 成功

途中では、 「これはローカルPCからFTPで送るしかないかもしれない」 というところまで行きました。

しかし、 人間がChromeで操作すると成功する手順を細かく分解してみることで、 自動化でも同じ条件を再現できました。

動かない理由を一つずつ切り分ける。

今回一番役に立ったのは、 結局この基本的なデバッグ方法だったと思います。

我々は、完全に勝利した

最後にInformation Hubの画面へ表示されたのは、 東京電力公式XMLを取得・解析した正常な結果でした。

長く続いた東京電力XML取得問題は、 レンタルサーバー上の Headless Chromium+Chrome DevTools Protocol という形で決着しました。

単に「取得できた」だけではありません。

今回作ったブラウザ取得方式は、 今後Information Hubで同じように 「通常HTTPでは取得しにくい公式情報」が出てきたときの 新しい取得手段としても応用できます。

失敗した方法も含めて、 全部が次の仕組みの材料になりました。

我々は、完全に勝利した!

この仕組みを実際に運用しているサイト

今回紹介した東京電力の停電情報取得や、 災害・障害情報を収集・解析する仕組みは、 Pinpoint(ピンポイント)で実際に開発・運用しています。

Pinpointでは、 「ピンポイントをキーワードに、新しい世界をつくる。」 をテーマに、AIやWeb技術を活用しながら、 生活、災害情報、AIツール、投資など、 さまざまな情報を扱っています。

今回の記事で登場した Pinpoint Information Hubも、 公式情報をできるだけ正確に取得し、 人にもAIにも扱いやすい形へ整理することを目的に開発している仕組みの一つです。

今回のような開発・検証についても、 成功した方法だけでなく、 失敗した方法や途中で分かったことも含めて記録していきます。

決済はStripeで安全に処理されます。
Amazonで「取得・xml」を検索
Amazonで探す

この記事の感想・お問い合わせをこっそり教えてください(非公開)