注意 - @2015-08-20(木)
今日の段階ではまだサイトは作れませんでした。
Naverまとめから画像を全ページ分引っ張ってくる所までしか出来てませんが、今週中に目的のサイトは完成させます!
背景
なんか、例えばこう・・2chまとめサイトとかみてて、画像のファイルがいっぱいあったら、それをDLしていつでも見れるようにしたいなーって時あるじゃないですか。
それを作ってみようと思いました。
Goutteというライブラリを使ってみる
まずはインストール
composerの基本的な使い方については過去のクソみたいな記事を見てください。
composertを使ってインストールします。
{
"require": {
"fabpot/goutte": "v3.1.0"
}
}
packagistによると最新バージョンは3.1.0だったのでこれを使います。
./composer.phar install
これでOK.
さっそく使ってみる
とりあえず、大好きな剛力彩芽ちゃんの画像をNaverまとめから引っ張ってきましょう。
Crawler
オブジェクトは、filterメソッドにHTMLのタグの名前かCSSのセレクタを指定する。
で、このfilter
メソッドは、そのURLの中から指定したタグやセレクタの要素を持ってきて、そのDOMをもったcrawlerオブジェクトを返すっぽい。
で、each
メソッドってのは↓みたいになってて、自分自身のオブジェクト(filterで一致したDOMのオブジェクトなのかな?)に、引数に渡したclosureを通して、通したあとのCrawlerオブジェクトを返すようになってる。
343 public function each(\Closure $closure)
344 {
345 $data = array();
346 foreach ($this as $i => $node) {
347 $data[] = $closure(new static($node, $this->uri, $this->baseHref), $i);
348 }
349
350 return $data;
351 }
さらに、今回はimgタグのsrc要素がほしいので、attrでsrcを指定してみました。
<?php
require_once __DIR__."/../lib/vendor/autoload.php";
use \Goutte\Client;
$client = new Client();
// naverまとめに大好きな剛力彩芽のまとめページがあったので使ってみる
$crawler = $client->request('GET', 'http://matome.naver.jp/odai/2138493836638647001');
/**
* filterメソッドの引数は下記のように書かれてるので、
* CSSのセレクターを指定すればいいんだと思う
*
* @param string $selector A CSS selector
*/
$crawler->filter('img')->each(function ($node) {
echo $node->attr('src')."\n";
});
続けるにはENTERを押すかコマンドを入力してください
http://rr.img.naver.jp:80/mig?src=http%3A%2F%2Ft1.gstatic.com%2Fimages%3Fq%3Dtbn%3AANd9GcR3Oh7t3PY5DYEA22NwTlQB0TaELgMHZI_FCElxkvdI71TBJBk3-6PihNY&twidth=95&theight=95&qlt=80&res_format=jpg&op=sc
http://rr.img.naver.jp:80/mig?src=http%3A%2F%2Fimgcc.naver.jp%2Fkaze%2Fmission%2FUSER%2F20140208%2F35%2F3012705%2F0%2F300x300x555a66adc7e01cd8213fbbbb.jpg%2F120%2F120&twidth=33&theight=33&qlt=80&res_format=jpg&op=sc
http://b.st-hatena.com/images/entry-button/button-only.gif
http://rr.img.naver.jp:80/mig?src=http%3A%2F%2Fimgcc.naver.jp%2Fkaze%2Fmission%2FUSER%2F20150802%2F35%2F3012705%2F224%2F500x505x54189f15878b8fb723bf6373.jpg%2F300%2F600&twidth=300&theight=600&qlt=80&res_format=jpg&op=r
http://rr.img.naver.jp:80/mig?src=http%3A%2F%2Fimgcc.naver.jp%2Fkaze%2Fmission%2FUSER%2F20150802%2F35%2F3012705%2F226%2F500x352x08256d4a5cd44817fd1b4e5c.jpg%2F300%2F600&twidth=300&theight=600&qlt=80&res_format=jpg&op=r
http://rr.img.naver.jp:80/mig?src=http%3A%2F%2Fimgcc.naver.jp%2Fkaze%2Fmission%2FUSER%2F20150707%2F35%2F3012705%2F23%2F800x566x8302f00e3db5b6246a6d28d0.jpg%2F300%2F600&twidth=300&theight=600&qlt=80&res_format=jpg&op=r
http://rr.img.naver.jp:80/mig?src=http%3A%2F%2Fimgcc.naver.jp%2Fkaze%2Fmission%2FUSER%2F20150707%2F35%2F3012705%2F21%2F599x444xf2227f50661db381815f4182.jpg%2F300%2F600&twidth=300&theight=600&qlt=80&res_format=jpg&op=r
....
とれてはいるんだけど、横の広告の画像とかもとって来ちゃってる。
そんなのはいらない。俺は剛力ちゃんだけがほしいのだ!!
ということで、Naverまとめのページ構造をもうちょい見てみよう。
どうやらdiv
のLyMain
というクラスがメインの記事?っぽい部分になってて、その中の画像だけ持ってくれば良さそう。
再挑戦
$crawler->filter('div.LyMain img')->each(function ($node) {
echo $node->attr('src')."\n";
});
いい感じになったけど、まだ余計な画像入ってる。
すげーDOMをたどってみると、Naverの記事の中の画像はMTMItemThumb
ってクラスがついてるっぽいので、これで絞り込んでみる。
$crawler->filter('div.LyMain img.MTMItemThumb')->each(function ($node) {
echo $node->attr('src')."\n";
});
おぉ!!めっちゃいい感じ!!!!
でも、待ってこれじゃ1ページ分しか取得出来ないよね。
でもNaverって複数ページあるじゃない。
どうせなら全ページ持ってきたい。
しかもこのClientオブジェクト使えばなんかいけそう!!
2ページ目も取得してみる
ちなみに、Naverはdiv.MdPagination03
って要素が、ページングになってるっぽいので、ここを解析して最後のページまで取得出来る気がする。
次のページもクロールするには、次のページのURLさえわかれば同じことの繰り返しでなんとか出来そう。
Naverの場合は、URLの最後に?page=xx
ってつければページングするっぽい。
このページングは、strongタグが今表示してるページっぽいので、その次の要素のテキストが次ページ数のはず!
で、次のページのURLを生成してcrawlerオブジェクトを生成して、同じことを繰り返せば出来そう!!!
<?php
require_once __DIR__."/../lib/vendor/autoload.php";
use \Goutte\Client;
$client = new Client();
// naverまとめに大好きな剛力彩芽のまとめページがあったので使ってみる
$crawler = $client->request('GET', 'http://matome.naver.jp/odai/2138493836638647001');
/**
* filterメソッドの引数は下記のように書かれてるので、
* CSSのセレクターを指定すればいいんだと思う
*
* @param string $selector A CSS selector
*/
$page = 1;
while(1) {
$crawler->filter('div.LyMain img.MTMItemThumb')->each(function ($node) {
echo $node->attr('src')."\n";
});
// 今表示してるページの次のページの数字を取得
$nextPageNode = $crawler->filter('div.MdPagination03 strong')->nextAll();
// 次のページがなければ終了
// @TODO ここは空ではなくて、Crawlerオブジェクトが返ってくるくるからemptyで終了を判別できないから例外飛んじゃう
if (empty($nextPageNode)) {
break;
}
// 次ページのページ数を取得
$nextPageNum = $nextPageNode->text();
// 次ページのcrawlerオブジェクトをつくる:w
$crawler = $client->request('GET', 'http://matome.naver.jp/odai/2138493836638647001?page='.$nextPageNum);
// 一応1秒だけ置いとく
sleep(1);
}
できたぁああああああ!!!よし!
番外編
Clientオブジェクトと、Crawlerオブジェクトがどんなもので、それぞれ何が出来そうなのかを紹介しておきます。
Clientクラス
$client = new Client();
$methods = get_class_methods($client);
var_dump($methods);
今回はrequest
メソッドを使いましたが、Auth認証のサイトを見たり、サイト上でクリックしたりとかなんか色々出来そう。
なんかWEBページそのもの的な?
array(29) {
[0] =>
string(9) "setClient"
[1] =>
string(9) "getClient"
[2] =>
string(9) "setHeader"
[3] =>
string(12) "removeHeader"
[4] =>
string(7) "setAuth"
[5] =>
string(9) "resetAuth"
[6] =>
string(13) "addPostFields"
[7] =>
string(11) "__construct"
[8] =>
string(15) "followRedirects"
[9] =>
string(15) "setMaxRedirects"
[10] =>
string(8) "insulate"
[11] =>
string(19) "setServerParameters"
[12] =>
string(18) "setServerParameter"
[13] =>
string(18) "getServerParameter"
[14] =>
string(10) "getHistory"
[15] =>
string(12) "getCookieJar"
[16] =>
string(10) "getCrawler"
[17] =>
string(19) "getInternalResponse"
[18] =>
string(11) "getResponse"
[19] =>
string(18) "getInternalRequest"
[20] =>
string(10) "getRequest"
[21] =>
string(5) "click"
[22] =>
string(6) "submit"
[23] =>
string(7) "request"
[24] =>
string(4) "back"
[25] =>
string(7) "forward"
[26] =>
string(6) "reload"
[27] =>
string(14) "followRedirect"
[28] =>
string(7) "restart"
}
Crawler
Crawlerオブジェクトは、データ抽出に関する振る舞いをするっぽいです。
array(58) {
[0] =>
string(11) "__construct"
[1] =>
string(5) "clear"
[2] =>
string(3) "add"
[3] =>
string(10) "addContent"
[4] =>
string(14) "addHtmlContent"
[5] =>
string(13) "addXmlContent"
[6] =>
string(11) "addDocument"
[7] =>
string(11) "addNodeList"
[8] =>
string(8) "addNodes"
[9] =>
string(7) "addNode"
[10] =>
string(2) "eq"
[11] =>
string(4) "each"
[12] =>
string(5) "slice"
[13] =>
string(6) "reduce"
[14] =>
string(5) "first"
[15] =>
string(4) "last"
[16] =>
string(8) "siblings"
[17] =>
string(7) "nextAll"
[18] =>
string(11) "previousAll"
[19] =>
string(7) "parents"
[20] =>
string(8) "children"
[21] =>
string(4) "attr"
[22] =>
string(8) "nodeName"
[23] =>
string(4) "text"
[24] =>
string(4) "html"
[25] =>
string(7) "extract"
[26] =>
string(11) "filterXPath"
[27] =>
string(6) "filter"
[28] =>
string(10) "selectLink"
[29] =>
string(12) "selectButton"
[30] =>
string(4) "link"
[31] =>
string(5) "links"
[32] =>
string(4) "form"
[33] =>
string(25) "setDefaultNamespacePrefix"
[34] =>
string(17) "registerNamespace"
[35] =>
string(12) "xpathLiteral"
[36] =>
string(7) "getNode"
[37] =>
string(6) "attach"
[38] =>
string(6) "detach"
[39] =>
string(8) "contains"
[40] =>
string(6) "addAll"
[41] =>
string(9) "removeAll"
[42] =>
string(15) "removeAllExcept"
[43] =>
string(7) "getInfo"
[44] =>
string(7) "setInfo"
[45] =>
string(7) "getHash"
[46] =>
string(5) "count"
[47] =>
string(6) "rewind"
[48] =>
string(5) "valid"
[49] =>
string(3) "key"
[50] =>
string(7) "current"
[51] =>
string(4) "next"
[52] =>
string(11) "unserialize"
[53] =>
string(9) "serialize"
[54] =>
string(12) "offsetExists"
[55] =>
string(9) "offsetSet"
[56] =>
string(11) "offsetUnset"
[57] =>
string(9) "offsetGet"
}