PHPでCookieを保持しつつWebスクレイピングする例
PHPを使ったWebスクレイピングといえば5年ほど前に投稿した「PHPでHTMLを簡単に解析できるDOMDocument」でほとんどコト足りていましたし、これでは取得できないページでも「VBScriptのIE操作をPuppeteerのChrome操作へ移行する例」のようにPuppeteerを使ってブラウザを自動操作※することで解決していました。
しかし、最近Puppeteerの調子がよろしくない。よくわからないエラーで止まってしまうことがあり、せっかく自動化していても手動でチェックする手間が増えていました。
コード上のエラーなら修正するのですが、ほとんどの原因はサーバー側の一時的なキャパオーバーでタイムアウトが発生しているだけ。取得しているのは単なるレポートページといえど、最近のHPは管理ページですらデザインも凝っているため、あちこちべたべたと画像が貼られていたり、jQuery等の外部JavaScriptが原因で重くなっているだけだったりします。
うーん、それならPuppeteerじゃなくて普通にPHPのfile_get_contentsとかcurlでテキストデータだけ取得したほうが相手にも負荷かけないし楽ちんじゃないか。と思うのですが、今度はセキュリティ上の問題でCookieを引き継がないとログインすらさせてもらえない管理ページがけっこうあります。
ということで、今回、ログインページを開く→クッキー(cookie)を引き継ぎつつログインする、という流れをPHPだけで実装したので、備忘録がてらサンプルコードをアップしておきます。
PHPでCookieを取得&送信しつつWebスクレイピングする例
$url_login = '[ログイン先のURL]';
$url_report = '[解析するページのURL]';
$url_logout = '[ログアウト用URL]';
//POSTするデータ(もちろんWebページによって異なる)
$postdata['user_id'] = '[ユーザーID]';
$postdata['user_pw'] = '[パスワード]';
$postdata['mode'] = 'login';
//ブラウザ情報がセットされていないとログインさせてくれないことがあるため、それっぽいのを書いておく
$cfg['USER_AGENT'] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36 Edg/117.0.2045.47";
$ch = curl_init();
//初回ページアクセス
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => [ 'User-Agent: '.$cfg['USER_AGENT'] ],
CURLOPT_URL => $url_login,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 20,
CURLOPT_AUTOREFERER => true,
CURLOPT_HEADER => true,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_ENCODING => ''
]);
$result = curl_exec($ch);
list($headers, $content) = explode("\r\n\r\n", $result, 2);
//初回アクセス時に送られてくるクッキーを保存
$cookies = [];
foreach (explode("\r\n", $headers) as $header) {
if (strtolower(substr($header, 0, 11)) == 'set-cookie:') {
if (($pos = strpos($header, ';')) !== false) {
$cookies[] = trim(substr($header, 11, $pos - 11));
}
}
}
//送られてきたクッキーをセットして再度リクエスト
$headers = [ 'User-Agent: '.$cfg['USER_AGENT'], 'Cookie: '.implode(';', $cookies) ];
//ログイン処理(POST)
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => $headers,
CURLOPT_URL => $url_login,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $postdata,
CURLOPT_FOLLOWLOCATION => true,
]);
$result = curl_exec($ch);
//後は好きにページを解析する
curl_setopt($ch, CURLOPT_URL, $url_report);
curl_setopt($ch, CURLOPT_POST, false);
$result = curl_exec($ch);
list($headers, $content) = explode("\r\n\r\n", $result, 2);
//DOMDocumentでの解析例
$content = mb_convert_encoding($content, "HTML-ENTITIES", 'auto');
$dom = new DOMDocument;
@$dom->loadHTML($html);
$xpath = new DOMXPath($dom);
$tableNode = $xpath->query('//table[@class="report"]');
$table = $tableNode->item(0);
$TRs = $table->getElementsByTagName('tr');
foreach ($TRs as $TR) {
//あれやこれや
}
//ログアウト
curl_setopt($ch, CURLOPT_URL, $url_logout);
$result = curl_exec($ch);
まとめ
file_get_contentsで書いても良かったのですが、$http_response_headerというグローバル変数でヘッダーを読み取る必要があったり、慣れていない人には逆にわかりづらいのでは?という気がしたため、オプションも豊富なcurl関数を使ってみました。CURLOPT_ENCODINGに空文字を渡すことでHTML圧縮にも自動で対応してくれて帯域幅の節約にもなりますし。
やってること自体は単純で、
- ログインページを開く
- 送られてきたクッキーを変数へ保存
- クッキーをヘッダーにセットしつつ、ログイン処理(POST)を実行
- あとはいつもどおり
以上です。
実例は控えますが、ぼくが使っているWebサービスのいくつかで、こういったクッキーを引き継ぎながらのPOSTをしないとログインできないページがあったので、うちはこの処理で今のところうまく動作しています。
ほかにもCSRF対策していたり、なんだったらreCAPTCHA入れてるところもあるだろうから、そういった場合の対応はちょっと難しいですが…。
ともあれ、クッキーを引き継ぎながらのGET/POST処理がわからなくて困っている方のお役に立てば幸いです。