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

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

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

目次

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

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

サンプル・プログラム

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

<img src=~> の表現形式

<img> タグの src 部分の URL 指定は、前々回で紹介した「URL で使える文字」の通りだが、相対指定――'http://' ではじまらない――があることに留意する必要がある。
また、拡張子は画像限定なので、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)' に対応する大文字の拡張子にもマッチする。

解説:正規表現

0039: //URLパターン
0040: define('REG_URL', '/(https?\:\/\/[\-_\.\!\~\*\'\(\)a-zA-Z0-9\;\/\?\:\@\&\=\+\$\,\%\#]+)/i');
0041: 
0042: //画像URLパターン
0043: 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抽出

0204: /**
0205:  * 指定したURLに含まれる画像URLを取り出す
0206:  * @param   string $url     処理対象URL
0207:  * @param   array  $images  画像URLを格納する配列
0208:  * @param   string $errmsg  エラーメッセージを格納
0209:  * @return  int マッチしたURL数 / FALSE(抽出に失敗)
0210: */
0211: function parse_image_url($url, &$images, &$errmsg) {
0212:     //コンテンツURLチェック
0213:     if (preg_match(REG_URL$url) == 0) {
0214:         $errmsg = '"' . $url .'" はURLではない';
0215:         return FALSE;
0216:     }
0217:     //コンテンツ読み込み
0218:     $str = @file_get_contents($url);
0219:     if ($str == FALSE) {
0220:         $errmsg = '"' . $url .'" が見つかりません';
0221:         return FALSE;
0222:     }
0223: 
0224:     //マッチするすべての部分文字列を取り出す
0225:     if (($n = preg_match_all(REG_IMAGE$str$arrPREG_SET_ORDER)) == FALSE) {
0226:         $errmsg = '画像URLが見つかりません';
0227:         return FALSE;
0228:     }
0229: 
0230:     //配列にコピーする
0231:     $arr2 = array();
0232:     foreach ($arr as $key=>$val) {
0233:         $arr2[$key] = reg_urls($arr[$key][2]$url);
0234:     }
0235: 
0236:     //画像URLを一意にする
0237:     $images = array_unique($arr2);
0238: 
0239:     //並び替え
0240:     sort($images);
0241: 
0242:     return $n;
0243: }

コンテンツ 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 では、相対指定である('http://' ではじまらない)場合がある。このままでも構わないのだが、最初にコンテンツの URL を入力しているので、これから絶対アドレスを計算することができる。URL を絶対パスに正規化するために、「PHP でリンク切れを調べる」で作ったユーザー関数 reg_urls を流用した。

質疑応答

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