マネー報道 MoneyReport

マネーにまつわる報道を取り上げ、自分の切り口で切り取り、噛み砕いてお伝えします。

記事紹介ジェネレーターでTitleが取得できないバグ発生(>_<)

スポンサーリンク
あとで読む

公開したWebサービス

先週の土曜日に思いつきで作ったWebサービスですが、アクセス解析で見ているとボチボチ人が来て、試してくれていっているみたいです♪

しかしブコメとかでは

id:zakiyamatoさん
なんですか、これ?

と書かれていたりして、確かにいきなりあの画面を見ても何を出来る物なのかがピンとこないのかな、と(-_-;
下記画像の様な、「リンク先のスクリーンショット、リンク先のタイトル、リンク先のはてなブックマーク数」を表示出来るHTMLコード(ブログ記事などに貼りつけて使う)を出力するサービスになります(^-^)v
f:id:MoneyReport:20160521094407j:plain

ただ名前も良くなかったのかもしれません。
記事紹介ジェネレーター
と聞いても

「何?ジェネレーターってなに?」

という感じで、言い得て妙になっていないのかな、と。
どなたかキャッチコピーを考えたりするのが得意な方がいらっしゃれば、名前を聞くだけで何が出来るWebサービスなのか分かる様に、Webサービス名を考えて頂けると嬉しいです(^-^;

【マネー報道 Webサービス】
記事紹介ジェネレーター | マネー報道 Webサービス

バグ発見

昨日の事なのですが、いざ自分自身でWebサービスを使って記事紹介リンクを取ろうとしました。
そしたらば空振りして返ってきてしまいました(>_<)
記事紹介リンクが作られていないんですよ。

ちょっとそのURLとかを生では見せられないので「はてなブックマーク」のURLである「http://b.hatena.ne.jp/」で代用させて頂きますが、URLを入力して実行してみたら・・・
f:id:MoneyReport:20160520224756j:plain
HTMLコードが出力されていない(>_<)

「もしや!?」

と思いつつ、私が今回使いたかったカラーミーショップの商品URL(関係ない店舗の)を貼ってみると・・・
f:id:MoneyReport:20160520224949j:plain
カラーミーショップのURLもダメだ~(T_T)

「これじゃあ使い物にならないorz」

という事で修正参考ソース探しの旅に出ることに・・・(;_;)

怪しい部分

今回問題となっているのはTitleタグを返す部分の関数がダメっぽいです(-_-;
Webサービス自体はPHPで作っています。

「ページタイトルを取得する関数」を作って利用しているのですが、この関数がTitleを返してきていません。

PHPコードは以下のとおり。

<?php
/**
 * ページタイトルを取得する関数
 */
function GetPageTitle($url)
{
    $html = file_get_contents($url);
    
    $html = mb_convert_encoding($html, mb_internal_encoding(), "UTF-8,SJIS,EUC-JP,ASCII,JIS");
    
    if (preg_match("/<title>(.*?)<\/title>/i", $html, $matches))
    {
        return $matches[1];
    }
    else
    {
        return false;
    }
}
?>

原因究明のために変数「$html」を出力してみたら、なんと中身は空っぽ(>_<)
関数内1行目の「file_get_contents()」関数がそもそも値を返してきていないようです。
きちんと「file_get_contents()」でも取れるURLもあるのですが、今回の事象が起きたURLでは値が取得できないようです。

cURLを使ってみる!

file_get_contents関数が駄目なことが分かったので、file_get_contents関数の代わりになる物を探します!
幾つか検索してみると、下記のcURLという関数で代用できるようです。

Q.PHP Webページのタイトルを取得したい
検索したものに少し手を加えて、以下の関数を使って、URLを元にウェブページのタイトルを取得しています。
 function getPageTitle( $url ) {
  $html = file_get_contents($url);
  $enc_format = "JIS, eucjp-win, sjis-win, UTF-8";
  $html = mb_convert_encoding($html, "UTF-8", $enc_format);
  preg_match("/<title>(.*)<\/title>/is", $html, $retArr);
  return $retArr[1];
 }
しかし、ページによってはタイトルを取得するまでの時間が遅いです。おそらく、HTMLをすべて読み取るまで、終了しないため、遅くなるのだと思います。今回はタイトルを取りたいだけなので、もっと早く処理ができてほしいです。何か方法はございますか?


A.質問者が選んだベストアンサー
まずは
$html = file_get_contents($url);


$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$html = curl_exec($ch);
に変えるところからどうぞ。これだけでかなり速くなると思います。これで速度が足りなければ fopen で1行ずつ読み込んでいくことも検討しましょう。


PHP Webページのタイトルを取得したい - PHP | 【OKWAVE】

ふむふむ。
「curl_init()」、「curl_setopt()」、「curl_exec()」の3点セットで使うことでcURLで取得出来そうです(^-^)v
以前TwitterAPIを使う時にもcURLを使った事がありました。
普通にはURLからファイルを読み込めない時に使う関数になるんでしょうかね?

上記を参考に修正したソースが下記になります。

<?php
/**
 * ページタイトルを取得する関数(cURL版)
 */
function GetPageTitle($url)
{
    $regex = '@<title>([^<]++)</title>@i';
    $order = 'ASCII,JIS,UTF-8,CP51932,SJIS-win';

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_URL, $url);

    $html = mb_convert_encoding(curl_exec($ch), 'UTF-8', $order);

    return preg_match($regex, $html, $m) ? $m[1] : '';
}
?>

修正した関数で再びWebサービスを実行してみましょう。

お!はてなブックマークはちょっと何か出てきました!
f:id:MoneyReport:20160520230403j:plain
ん?
503 Service Temporarily Unavailable
というエラーコードみたいなタイトルを表示していますね(-_-;
503エラー返してますね、コレ・・・。


カラーミーショップはどうかというと・・・
f:id:MoneyReport:20160520230630j:plain
タイトル文字列を何も返してくれていません(>_<)
残念!

という訳で、一部反応が変わりましたが、cURLを単純に適用しただけでは取得できないURLがあるようです。

ヘッダー偽装

もう少し探してみましょう!
惜しい線までは来ているようなので。

・・・(検索中)・・・

こちらはどうかな?

直接の原因はおそらくhon.jpさん側のサーバーの設定変更です。
PHPでデータ取得する場合に「file_get_contents」できない設定に変えたのだと思います。
試しに「curl」に変更したら出来ちゃいました。


おまけ:
【PHP: file_get_contentsができない場合の対処法】
まずは上記のようにcurl関数に変更してしまいます。
それでもダメな場合は、とりあえずヘッダ情報を書き換えて、サーバーからのアクセスではなくて、PCからのアクセスのように偽装してしまいます。


file_get_contentsができない場合の対処法 – マイヤーの開発ブログ

ふむふむ。
サーバーの設定によって、PHPでデータ取得する場合に「file_get_contents」できなくする事も出来るんですね!
ちょっとサーバー管理者とかじゃないので分からないですが、アプリの様なDDOS攻撃とかから身を守るために、関係ないアクセス(本来のブラウザからじゃないアクセス等)の場合にはHTMLを返さないような設定になっているのかもしれません。

ただこの場合にも、ヘッダー情報を偽装してサーバーからのアクセスではなく、クライアントからのアクセスだとする事で対応できるようです。

という訳で、cURLに変更しつつPCからのアクセスのように偽装するというのをやってみましょう(^-^)/

<?php
/**
 * ページタイトルを取得する関数(curl&ヘッダ情報偽装版)
 */
function GetPageTitle($url)
{
    // html取得
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, FALSE);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);

    // Windows IE11に偽装
    curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko');

    $html = curl_exec($ch);
    curl_close($ch);

    // 文字化け対策
    mb_language('Japanese');
    $html = mb_convert_encoding($html, 'utf8', 'auto');

    if (preg_match("/<title>(.*?)<\/title>/i", $html, $matches))
    {
        return $matches[1];
    }
    else
    {
        return false;
    }
}
?>

この修正でどうでしょうか?
Webサービスに組み込んで動かしてみましょう!

「はてなブックマーク」ページのタイトルが正しく取得出来ました!!!
f:id:MoneyReport:20160520231002j:plain

カラーミーショップの方も大成功!
f:id:MoneyReport:20160520231035j:plain

と言うわけで、無事にバグを修正する事が出来ました!

これまでにWebサービス「記事紹介ジェネレーター」を使った方で

「空振りして駄目じゃん」

と残念な思いをされた方がいらっしゃるかもしれませんが、今後は大丈夫になったので、ぜひまた一度使ってみて頂けると幸いです(^-^;

【マネー報道 Webサービス】
記事紹介ジェネレーター | マネー報道 Webサービス

追記(2016/05/24バグ修正)

どうも最終的に採用したソースの正規表現がイマイチだった様で、正規表現の文字列を修正しました。

//修正前
"/<title>(.*?)<\/title>/i"

//修正後
"@<title>([^<]++)</title>@i"

これで上手く取得できなかったURLも一部取得可能になったかと思います(^-^)v

それでもまだ

「空振りして返ってくるよ~(T_T)」

という対象URLがあれば教えて下さい。
早急に修正したいと思いますm(_ _)m

追記(2016/05/24 本日2度目のバグ修正)

正規表現で取得出来ないパターンを特定出来ました!

titleタグが加工されているような場合にうまく取得出来ていませんでした。
具体的には

<title data-default="hoge">hoge</title>

の様にきれいに「<title>」だけになっていないパターンです!
なの上記のパターンも取れるようにと思うと、</title>の1つ前の「>」までの間を取得できれば良いだけとなります。
なので修正すると・・・

//修正前
"@<title>([^<]++)</title>@i"

//修正後
"@>([^<]++)</title>@i"

という感じにすれば取得出来ました!!!