PHPのcurl_multiによるHTTP並行リクエストを使い、「ディレクトリ型検索エンジンdtn」のディレクトリ登録サイトのリンク切れチェッカーを制作してみました。

この記事では、curl_multiのリクエスト処理でURLに対して接続をかけ、取得したサイトのステータスデータを配列に取り込み保存する流れについて説明したいと思います。curl_multiによるリクエスト処理と、get_headersによるHTTPレスポンス取得も比較しています。

カテゴリ登録サイトのリンク切れチェック

これをお読みの方の中で、ディレクトリ型検索エンジンを運営しているといった方は一人もいないと思いますが、ディレクトリ型サイトを運営していて困ることの一つに登録サイトのリンク切れといったものがあります。

サイトが閉鎖となったり、ドメイン変更やURLが移転されたりと、リンク切れに至る理由は皆さん色々あると思いますが、中にはYahooジオシティーズのように無料スペースを使ってサイトを頑張っていたのに、スパッと閉鎖されてしまったパターンも見られます。

申請いただいたサイトを一つ一つチェックして頑張ってディレクトリを作っていっても、カテゴリにリンク切れが出てしまうと印象も悪く、リンク切ればかりだとSEO的にも悪影響とは思うので、このリンク切れチェックも必要になってきます。

とはいえ、登録申請すら10,000サイト以上たまっているようなこの運用状況では、流石にそこまで手が回るはずもなく、カテゴリを日に頑張って3つチェックしたとしても、900以上のカテゴリを見るには年近い時間がかかってしまう感じで、正直なところ日に1カテゴリもチェックすらできておりませんでした。

そこで、18年目に突入ということもありまして、かつてのYahooカテゴリでも登録サイト向けに頻繁に走っていたクローラーのようなLINKステータスチェッカーをPHPで作ってみようと思い立ちました。

PHPでリンクチェッカー

ステータスをチェックするにはURLからデータを取得しないと話になりませんので、PHPで外部データの取得からスタートします。

get_headers()でHTMLを取得

昔のイメージではfile_get_contents()でという感じでしょうが、今ではget_headers()cURLを使ってという方が一般的のようですので、まずは get_headers() でお試ししてみました。

$URL = 'https://www.dtn.jp/';
$headers = @get_headers($URL);
$headers[0] //ここにステータスが入ってます

カテゴリに登録されたサイトのURLをSQLで一つ引っ張りだし、 get_headers() に投げてステータスを取ってみましたが、全く問題なくステータスを取ることができました。$headersには、 $headers[0] => HTTP/1.1 200 OK’ のような感じでしっかりステータスが入っています。ということで、早速これを100サイト程度ループで回してみましたが、途端にPHPが「504 Gateway Time-outを吐き出してしまいました。

php.inimax_execution_timeをいくらか増やしてみましたが、それでも全くダメという感じです。外部に順次接続しているので、やはり実行時間でコケてしまうようです。

curl_multiでHTMLを取得

ということで、本命のcurl_multiによる並列処理でやってみることにしました。http://にちなんだcURLのロゴがなんだか微妙と感じますが、気にせずやっていきます。

http://をイメージしたcURLライブラリのロゴ
URLを使用してデータ転送するcURLライブラリ

先程同様にPDOでSQLから取ってきた登録URL情報を配列に入れます。

while($rows = $prepare -> fetch()){
  $REG_URLs[] = $rows["url"];
}

あとはcurl_multi_execのお作法に沿い、先程の登録サイトURL配列を元に設定を作っていきます。

curl_setoptの設定項目

cURLのオプションを入れるcurl_setoptは設定項目が多数あるので適当に必要そうなものを追加しておきました。ヘッダを取るのでCURLOPT_HEADERをTRUEとして、本文は不要なのでCURLOPT_NOBODYもTRUEにする等々で良さそうです。

//マルチハンドル初期化
$mh = curl_multi_init();
//URLのセット
foreach($REG_URLs as $URL) {
	$ch = curl_init();
	curl_setopt_array($ch, array(
    		CURLOPT_URL               => $URL,
    		CURLOPT_HEADER            => TRUE,
    		CURLOPT_NOBODY            => TRUE,
    		CURLOPT_FOLLOWLOCATION    => FALSE,
    		CURLOPT_USERAGENT         => "*******",
    		CURLOPT_CONNECTTIMEOUT    => 100,
    		CURLOPT_RETURNTRANSFER    => TRUE,
		CURLOPT_SSL_VERIFYPEER    => FALSE,
		CURLOPT_TIMEOUT           => 100
	));
	curl_multi_add_handle($mh, $ch);
}

ここまできたら、あとはcurl_multiのリクエスト処理で接続をかけ、サイトのステータスデータを配列に取り込むといった感じです。速度は全く問題ありません。

//HEADER情報取得
foreach($ch_array as $ch){
	↓この配列にステータスを取り込み
	$linkstatus[] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
	curl_multi_remove_handle($mh, $ch);
	curl_close($ch);
}

curl_multiについては、phpのマニュアルよりもこちらのサイト様の方がとても分かりやすかったです。

おわりに

curl_multiで取得したHTMLステータスが入った$linkstatus[] のデータと、先に取った$REG_URLs[]のデータを並べて表示させれば、これで簡易リンクチェッカーの出来上がりとなりました。

PHPでリンク先のHTTPステータスコードを取得するリンク切れチェッカー
リンク先のステータスをcURLを使って取得

HTTPのステータスはたまに0が入っている時もありましたが、2xx、3xx、4xxと概ねキッチリ取ることができていました。できたリンクチェッカーを見ると、やはり403が山ほどあり、1,000URL分一覧出力をしたら大変なことになったので、日に100URLずつのチェックという感じにしておきました。