8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

特定のURLにある画像をまるっとzipでダウンロードするサイトを作ってみる

Posted at

注意 - @2015-08-20(木)

今日の段階ではまだサイトは作れませんでした。
Naverまとめから画像を全ページ分引っ張ってくる所までしか出来てませんが、今週中に目的のサイトは完成させます!

背景

なんか、例えばこう・・2chまとめサイトとかみてて、画像のファイルがいっぱいあったら、それをDLしていつでも見れるようにしたいなーって時あるじゃないですか。
それを作ってみようと思いました。

Goutteというライブラリを使ってみる

まずはインストール

composerの基本的な使い方については過去のクソみたいな記事を見てください。

composertを使ってインストールします。

composer.json
{
    "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オブジェクトを返すようになってる。

Crawlerのeachメソッド
 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まとめのページ構造をもうちょい見てみよう。

どうやらdivLyMainというクラスがメインの記事?っぽい部分になってて、その中の画像だけ持ってくれば良さそう。

再挑戦

filterの部分変えてみた

$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ページそのもの的な?

Goutte:Client
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オブジェクトは、データ抽出に関する振る舞いをするっぽいです。

Symfony\Component\DomCrawler\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"
}
8
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?