プログラミングとSEOと暇つぶし

駆け出しエンジニアdallPのブログです。元SEOコンサルタントです。プログラミング、SEO、アフィリエイト、お金などについて役に立つかもしれない情報をやりたいように書きます。

【PHP】Goutteでスクレイピングプログラム(クローラー)を作ってみたので過程とともに晒す

お久しぶりです、dallPです。

ブログのネタは沢山メモっているのですが、最初に投稿するのはやっぱりPHPに関することじゃないと。
と思っていたら更新が遅くなりました。

タイトル通りなのですが、今回は初心者なりにPHPクローラーを書いてみたので、コードとか考えたこととか諸々を晒します。

利用したもの

今回は、個人的なタスクを進める上で必要な情報を取得したかったので、1からクローラーを書くようなことはしていません。
(というか基本しないと思いますが)

利用したのは"Goutte"というPHPのライブラリです。

github.com

上記からダウンロードできます。
超絶初心者なので、このライブラリの中で何やってるのか、とか正直全然分かってません。

だけど、これを使えばPHP歴一ヶ月未満の僕でも2時間でスクレイピングが出来た、というのは事実です。

ちなみにこの記事はMac向けです。

参考にさせて頂いたサイト群

PHP と Goutte ではじめる超絶簡単クローラー入門 - Qiita
Goutteをcomposerでインストール - どんどこすすむの日記
たった数行のコードでひたすらアイドル水着画像をあつめるのをGoutteで書いてみた - iakioの日記
「Goutte」で対象ページ中の全リンクURLを取得する - やったこと
【PHP】特定の文字列を含むかのチェック - Qiita

色々参考にさせていただきましたが、一番上のmojibakeoさんのQiitaの記事が一番簡潔で、コードの基本を真似させて頂きました。
PHP クローラー」でもSEOで一位ですね。素晴らしい。
こんな分かりやすい記事を書けるように、プログラマとして精進します。

前提

今回の僕の目的は、地球の歩き方から各国の主要な観光都市名を抽出することでした。

最初は、ビジネス職にありがちな感じで「気合で頑張ろう」と思ってたのですが。。。
2カ国目をやり始めた時点で思いました。

「辛。」

地球の歩き方のサイトには実に160の国・地域の情報が載っています。
さすがに200近いリンクをポチポチやるのは割に合わないですよね。

Macのエクセル遅いし。

まずやったこと: composerとGoutteのインストール

よくわからないですが、composerなるものが便利らしいです。
PHPのライブラリやパッケージを管理してくれるツールらしいですが、僕はまだ良くわかってませんw

定義を気にする方は下記の記事をどうぞ。
qiita.com

Homebrewが入っている方なら、ターミナルで下記コマンドを打つだけでインストールできます。

brew install homebrew/php/composer

ちなみに、

Homebrew(ホームブルー)とは、Mac OS X オペレーティングシステム上でソフトウェアの導入を単純化するパッケージ管理システムのひとつです。

パッケージ管理システム Homebrew

らしいです

ちなみにHomebrewのインストール方法はこちらが詳しそうです。

qiita.com



話が逸れましたが、composerがインストール出来たら次はGoutteです。

composer require fabpot/goutte

これでインストール完了。

早速クローラー書いてみた

いきなりですが、はじめて書いたコードがこちら。

<?php

require_once __DIR__ . '/vendor/autoload.php';

$cli = new Goutte\Client();
$url = 'http://www.arukikata.co.jp/country/europe/GB/';
$crawler = $cli->request('GET',$url);

$crawler->filter('#cities_link .photo_list p')->each(function($name) {
echo $name->text() . "\n";
});

$urlにスクレイピングしたいURLを代入して、filter()の中には抽出したい部分のCSSセレクタをスペース区切りで入れる。
これだけで、抜ける。

すごい。

第二弾: 複数ページをクロールしてみた

では次。クロールしたいのは1ページだけではないので、まずは最終的に欲しいデータが載ってるページのURLを抽出する必要がある。

そこで書いたコードがこちら。

<?php

require_once __DIR__ . '/vendor/autoload.php';

$cli = new Goutte\Client();
$url = 'http://www.arukikata.co.jp/country/US/';
$crawler = $cli->request('GET',$url);

$urls = $crawler->filter('.country_textlink .top_map .city_list a')->extract('href');
print_r($urls);

ここではじめて出てくるのがextract('href')。
詳細はよく分からないが(←)、これ入れずにprint_rしてもURLが返ってこない。
デフォルトではそのリンクに設定されてる文字の方を拾ってきてしまうようなので、「URLを取ってきてね」的な意味なのかなと思っている(想像)。

さて、これで目的のURLは手に入った。
しかし、ビジネス職の職業病が発動する。

ここで僕はなぜかExcelを起動。
手に入ったURLリストをおもむろにExcelにコピペし、次の瞬間下記のコードが出来上がっていた。

<?php

require_once __DIR__ . '/vendor/autoload.php';

$cli = new Goutte\Client();
$urlArray = array("http://www.arukikata.co.jp/country/europe/GB/","http://www.arukikata.co.jp/country/europe/IT/","http://www.arukikata.co.jp/country/europe/ES/","http://www.arukikata.co.jp/country/europe/DE/","http://www.arukikata.co.jp/country/europe/FR/","http://www.arukikata.co.jp/country/asia/KR/","http://www.arukikata.co.jp/country/asia/SG/","http://www.arukikata.co.jp/country/asia/TH/","http://www.arukikata.co.jp/country/asia/TW/","http://www.arukikata.co.jp/country/asia/CN/","http://www.arukikata.co.jp/country/asia/VN/","http://www.arukikata.co.jp/country/namerica/US/","http://www.arukikata.co.jp/country/namerica/CA/","http://www.arukikata.co.jp/country/samerica/AR/","http://www.arukikata.co.jp/country/samerica/BR/","http://www.arukikata.co.jp/country/samerica/PE/","http://www.arukikata.co.jp/country/samerica/BO/","http://www.arukikata.co.jp/country/samerica/MX/","http://www.arukikata.co.jp/country/oceania/AU/","http://www.arukikata.co.jp/country/oceania/NZ/","http://www.arukikata.co.jp/country/hawaii/9H/","http://www.arukikata.co.jp/country/resort/GU/","http://www.arukikata.co.jp/country/resort/MP/","http://www.arukikata.co.jp/country/resort/PW/","http://www.arukikata.co.jp/country/resort/13/","http://www.arukikata.co.jp/country/resort/FJ/","http://www.arukikata.co.jp/country/meast/AE/","http://www.arukikata.co.jp/country/meast/IL/","http://www.arukikata.co.jp/country/meast/QA/","http://www.arukikata.co.jp/country/meast/TR/","http://www.arukikata.co.jp/country/africa/EG/","http://www.arukikata.co.jp/country/africa/KE/","http://www.arukikata.co.jp/country/africa/TZ/","http://www.arukikata.co.jp/country/africa/ZA/","http://www.arukikata.co.jp/country/africa/MA/");
$arrayNum = 0;

while($arrayNum < 34) {
	$crawler = $cli->request('GET',$urlArray[$arrayNum]);
	$crawler->filter('#cities_link .photo_list p')->each(function($name) {
		echo $name->text() . "\n";
	});
	$arrayNum++;
}

横スクロール推奨。
上手く動いたときに僕が喜んだのは言うまでもない。
これで、複数ページをクロールして特定のデータを取得する、という目的は取り敢えず達成された。

汚いコードだとは思いつつ、先輩に喜々としてコードを見せたところ、苦笑しながら手直しされた結果がこちら。

<?php

require_once __DIR__ . '/vendor/autoload.php';

$cli = new Goutte\Client();
$url = 'http://www.arukikata.co.jp/country/US/';
$crawler = $cli->request('GET',$url);

$urls = $crawler->filter('.country_textlink .top_map .city_list a')->extract('href');

foreach ($urls as $url) {
	$crawler = $cli->request('GET',$url);
	$crawler->filter('#cities_link .photo_list p')->each(function($name) {
		echo $name->text() . "\n";
	});
}

お分かりだろうか・・・

修正ポイント1: URLリスト、わざわざExcelで頑張って加工しなくてよかった。二つのプログラムはマージできる。
修正ポイント2: 配列の各要素に何かしらの処理をぶん回すとき、whileは普通使わない (らしい)

修正入るのは全然嬉しいんだけど、僕は褒められて伸びる人間なのでもう少し褒めてほしかった。

最終的に完成したコード

紆余曲折あり、最終的に出来たコードがこちら。

<?php

require_once __DIR__ . '/vendor/autoload.php';

$cli = new Goutte\Client();
$url = 'http://www.arukikata.co.jp/';
$crawler = $cli->request('GET',$url);

// グローバルナビからリンクを抽出
$urls = $crawler->filter('#header2016 #subnav a')->extract('href');

foreach($urls as $url) {
	// URLに'area'が含まれていたら地域ページ
	if (strpos($url,'area')) {
		$crawler = $cli->request('GET',$url);
		// 地域ページのエリアマップから、各国へのリンクを抽出
		$countryURLs = $crawler->filter('#area_Map a')->extract('href');

			foreach($countryURLs as $url) {
				// URLに'country'が含まれていたら国ページ
				if(strpos($url,'country')) {
					$crawler = $cli->request('GET',$url);
					// ターミナル出力時にどの国の都市か分かりやすいようにh1も抽出
					$crawler->filter('h1')->each(function($name) {
						echo $name->text() . "\n";
					});
					// 都市リストを抽出
					$crawler->filter('#cities_link .photo_list p')->each(function($name) {
						echo $name->text() . "\n";
					});
				}
				// ほんの少しの思いやり
				usleep(500000);
			}
	}
}

配列お化けを書いていた頃に比べれば、大幅な進化では無いだろうか(と、個人的に思っている)。

通算4時間かかってしまったけれど、学びは大いにあった。

学びをまとめると

  • プログラムで出来ること・出来ないことを決めつけない。どうしたら実現できるか?を深く考える
  • 仕様超大切 (input→outputへの道筋とoutputの形式をしっかりイメージするだけでコーディングのスピードが上がる)
  • ググり方めっちゃ大事
  • 解法は1つではない。これで無理ならあれでやる、の選択肢を沢山持ってたほうがいい

お腹がすいたので今日はここまでにします。
汚いコードを見てくれた皆様、ありがとうございました。

※こんなに簡単にスクレイピングできちゃうのですが、アホみたいにリクエスト投げまくるとか、悪用はダメ、絶対。

新米PHPエンジニアがブログを書き始めたようです

初めまして、dallPと申します。


つい先日まで、渋谷のとあるIT企業にて社内SEOコンサルタントとして働いておりましたが、色々な事情と上司及び更にその上の方々の寛大なご配慮により、なんとサーバーサイドエンジニア(PHP)としてデビューすることになりました。


HTMLもCSSJavaScriptPHPもほぼ未経験の小生をPHPエンジニアとして受け入れてくれるというのは、普通の企業では考えられないことです。
元々の仕事を誰に振るか、とか、そもそもそのために入ってきた人のために教育を・・・などなど、考え始めると、短期的・金銭的に見れば確実にGoの判断は下されないでしょう。
普通に募集して普通に雇用したほうがコストはかかりません(雇う方の給与次第ですが)。

まさか自分自身も、今いる会社でエンジニアになろうとは考えてもいませんでした。
しかし給与も待遇も現在のまま素人を雇うというのですから、それならやりましょうと、いやむしろやらせて下さいと、トントン拍子で話が進み、上司と話した翌週明けにはdallPのエンジニア化計画が始まっていたわけです。

とは言え自分が所属する組織は社員規模2〜30人程度の大きいとは言えない部署。
何も書けないプログラマ(?)に一から十まで教えている暇のある人間などいません。


ということで、周囲のエンジニア、マークアッパーの方々からの助言もあり、基本的にはまず自学自習することから始めていきます。
利用するのは、初心者プログラマ、マークアッパーの味方であるドットインストールさんです。


他にもプロゲートさんやらなんやらあると思うのですが、個人的には既に何度かお世話になったこともあるので、一旦ドットインストールで勉強をしようと思っています(というかしています)。


さらに、「動画見るだけじゃだめ」「アウトプットが一番大切」という助言も頂きましたので(当然ですが)、こちらのブログにて勉強の過程やら何やらを更新していこうと思います。

最初は単純なコードや拙いコードをお見せすることにはなると思いますが、特に僕と同じ初心者の方々には役立つようなブログにしていきたいと思います。

その他にも、前職で培ったSEOアフィリエイトの知識などについても、役立つ情報を発信します。
たまに勉強に飽きたら、他社さんのウェブサイトに対して「自分ならこうする」みたいな記事も上げていきたいな、など傲慢なことも考えております・・・


ということで、まだこの記事を読んでいただける方などそうそういないと思いますが、今後共よろしくお願いいたします。