正規表現で画像ファイルのURLを取り出す

(1/1)
前々回、テキストからURLを取り出す正規表現を紹介したが、今回は、ネット上のコンテンツから <img> タグで指定された画像URLを取り出すプログラムを作ってみることにする。正規表現を少し変更するだけで、音声や動画ファイルのURLを取り出すこともできる。

(2021年8月1日)PHP8対応,リファラ・チェック追加,重複URL削除.

目次

サンプル・プログラムの実行例

正規表現で画像ファイルのURLを取り出す

サンプル・プログラム

圧縮ファイルの内容
geturl.phpサンプル・プログラム本体

<img src=~> の表現形式

<img> タグの src 部分の URL 指定は、前々回で紹介した「URL で使える文字」の通りだが、相対指定――'https://' ではじまらない――があることに留意する必要がある。
また、拡張子は画像限定なので、jpg, jpeg, gif, png, bmp の5種類を対象とすることにする。これを正規表現で表すと以下のようになる。
/\<img(.*)src=\"?([\-_\.\!\~\*\'\(\)a-z0-9\;\/\?\:@&=\+\$\,\%\#]+(jpg|jpeg|gif|png|bmp))/i
拡張子指定を変更することで、音声や動画ファイルのURLを取り出すこともできる。こうしたカスタマイズのしやすさも、正規表現の強みである。
また、詳しいことはプログラムの説明で述べるが、今回は preg 系の正規表現関数を使うため、パターン全体をスラッシュ '/' で囲む必要がある。

今回使った正規表現

(...)
サブパターン。 マッチングだけであれば不要だが、置換を行うために、マッチした部分文字列に番号を付けてやる必要がある。その場合にサブパターンを用いる。
-
文字の範囲指定。 a-z はアルファベット小文字すべてに、0-9は数字すべてにマッチする。
{a,b}
直前文字の a 回以上 b 回以下の繰り返し。bは省略できる。 年は4桁以下、月と日は2桁以下の数字になるので、これを用いた。
?
直前1文字の0または1回の繰り返し。
(a|b)
aまたはbにマッチする。
i
修飾子。パターンの中の文字は大文字にも小文字にもマッチする。
ここでは、文字クラス '[a-z]' に対応するアルファベット大文字や、サブクラス '(jpg|jpeg|gif|png|bmp)' に対応する大文字の拡張子にもマッチする。

解説:正規表現

  39: //URLパターン
  40: define('REG_URL', '/(https?\:\/\/[\-_\.\!\~\*\'\(\)a-zA-Z0-9\;\/\?\:\@\&\=\+\$\,\%\#]+)/i');
  41: 
  42: //画像URLパターン
  43: define('REG_IMAGE', '/\<img([^\>]*)src=\"?([\-_\.\!\~\*\'\(\)a-z0-9\;\/\?\:@&=\+\$\,\%\#]+(jpg|jpeg|gif|png|bmp))/i');

正規表現を2種類用意する。
REG_URL は、コンテンツURLが正しいURLかどうかを判定するもの。「URLで使える文字 - 正規表現でURLをリンクに変換する」で紹介したものだ。
REG_IMAGELは、上述の画像URLを検出するための正規表現。この値を変更することで、コンテンツの中からさまざまな部分文字列を取り出すことができる。

解説:画像URL抽出

 204: /**
 205:  * 指定したURLに含まれる画像URLを取り出す
 206:  * @param   string $url     処理対象URL
 207:  * @param   array  $images  画像URLを格納する配列
 208:  * @param   string $errmsg  エラーメッセージを格納
 209:  * @return  int マッチしたURL数 / FALSE(抽出に失敗)
 210: */
 211: function parse_image_url($url, &$images, &$errmsg) {
 212:     //コンテンツURLチェック
 213:     if (preg_match(REG_URL, $url) == 0) {
 214:         $errmsg = '"' . $url .'" はURLではない';
 215:         return FALSE;
 216:     }
 217:     //コンテンツ読み込み
 218:     $str = @file_get_contents($url);
 219:     if ($str == FALSE) {
 220:         $errmsg = '"' . $url .'" が見つかりません';
 221:         return FALSE;
 222:     }
 223: 
 224:     //マッチするすべての部分文字列を取り出す
 225:     if (($n = preg_match_all(REG_IMAGE, $str, $arr, PREG_SET_ORDER)) == FALSE) {
 226:         $errmsg = '画像URLが見つかりません';
 227:         return FALSE;
 228:     }
 229: 
 230:     //配列にコピーする
 231:     $arr2 = array();
 232:     foreach ($arr as $key=>$val) {
 233:         $arr2[$key] = reg_urls($arr[$key][2], $url);
 234:     }
 235: 
 236:     //画像URLを一意にする
 237:     $images = array_unique($arr2);
 238: 
 239:     //並び替え
 240:     sort($images);
 241: 
 242:     return $n;
 243: }

コンテンツURLから画像URLを取り出すのは、ユーザー定義関数 parse_image_url である。

まず、与えられたコンテンツURLが正しいURLかどうかをチェックする。
続いて、 preg_match_all  関数を使い、REG_IMAGE にマッチする部分文字列を取り出す。
ereg系関数がPOSIX互換の正規表現であったのに対し、preg系関数はPerl互換の正規表現を指定する。
今回は、入力テキストに複数のURLが含まれている場合があるが、ereg系関数では複数の部分文字列を取り出せるような関数が用意されていない。もちろん、ユーザー定義すればできるが、大文字・小文字の場合分けなどが必要で処理が複雑になるため、一気に複数の取り出しができる  preg_match_all  関数を使うことにした。

 preg_match_all  関数の戻り値は2次元配列になる。REG_IMAGE では2番目のサブパターンに画像ファイルを含むURLがマッチするようにつくってあるので、1次元配列 $arr2 にコピーするようにした。
'<img src=~>' で指定されるURLでは、相対指定である('https://' ではじまらない)場合がある。このままでも構わないのだが、最初にコンテンツのURLを入力しているので、これから絶対アドレスを計算することができる。URLを絶対パスに正規化するために、「PHPでリンク切れを調べる」で作ったユーザー関数 reg_urls を流用した。

質疑応答

【質問】
「正規表現で画像ファイルのURLを取り出す」を今作成しているPHPプログラム(普通のリンクを取り出すプログラムです)の参考にしたいので、動作確認のため、プログラムを自分のPCにダウンロードして実行しましたが、正常に動作しませんでした。
しばらくレスポンスが帰ってこない状態が続いた後、ブラウザが空白になってしまうのです。何のエラー表示もありません。原因に心当たりがあったら、教えて頂けないでしょうか。
PHPのバージョンは5.3.0です。よろしくお願いします。
【回答】
PHP 5.3 は、従来のPHP 5.xシリーズから大幅な仕様変更が行われ、本プログラムで使っていた関数 split が非推奨となりました。このための不具合と思われます。
そこで、関数  preg_split  で代替したバージョン1.3を作ってみました。新しいプログラムをダウンロードしてお試しください。
(この項おわり)
header