htmlsqlより速い軽い。cakephpでスクレイピングgoutte.phpインストールと使い方

2015年12月9日更新 view: 1320 view

goutte.phpとは?

スクレイピングツール

htmlsqlとか他にもたくさんライブラリがあるが、
これが一番軽くて速い。

速度参考 htmlsql

0.74032497406006
1.5220828056335
2.2691576480865
3.0349636077881
3.8007335662842
4.5622684955597

速度参考 goutte.php

0.57542490959167
1.1356089115143
1.693286895752
2.2634289264679
2.8295919895172
3.4025230407715

インストール

use って何よ?

requireみたいなもの

スポンサードリンク

pharって何よ?

pharとはPHp ARchiveの略で、その名の通りPHPスクリプトのアーカイブです。含まれる内容はPHPスクリプトである必要はなく、複数のファイルを含めることができます。Javaの世界で言うjarに近いもので、拡張子は「.phar」となることが一般的です。

ファイルを用意しよう

ここからダウンロード
http://get.sensiolabs.org/goutte.phar

上記のように Vendor/goutte/goutte.phar に追加

使ってみる

<?php

App::uses('AppController', 'Controller');
App::uses('ComponentCollection', 'Controller');

require "/var/www/html/your/Vendor/goutte/goutte.phar";
use Goutte\Client;//スラッシュではない。\マーク。
use Symfony\Component\DomCrawler\Crawler;

class HogesController extends AppController {

public function index()
{
$client = new Client();

$crawler = $client->request('GET','http://www.yahoo.co.jp');

$crawler->filter('a')->each(function($v,$key)
{
if ($key < 3){
echo h($v->text());
echo h($v->html());
}
});

$this->autoRender = false;
}


}

とりあえず、ここまでで
動くはずです。

foreachの中の処理を使う場合の注意

        $th = [];
$crawler->filter('table.detail th')->each(function($v,$key) use (&$th)
{
$th[] = h($v->text());
});

pr($th);

use (&$th) を使って外部の変数を中に渡す必要がある。

さらに便利な使い方

パクったページをそのまま表示

print_r($crawler->html());

ユーザーエージェント偽装

$client->setHeader('User-Agent', 'Googlebot-Video/1.0');

video タグの src、poster属性を取得

$videos = $dom->filter('video')->each(function( $v,$key ){
return array(
'thumbnail_loc' => $v->attr('poster'),
'content_loc' => $v->attr('src')
);
});

リンクをクリック

$targetLinkText = 'バックエンド(プログラミング)';
$link = $crawler->selectLink($targetLinkText)->link();
$crawler = $client->click($link);

ボタンのテキストクリック

$targetButtonText = '検索';
$button = $crawler->selectButton($targetButtonText)->form();
$crawler = $client->click($button);

フォームを送信

$targetButtonText = '検索';
$form = $crawler->selectButton($targetButtonText)->form();
$searchParameters = ['words' => 'Monaca'];
$crawler = $client->submit($form, $searchParameters);

ローカルファイル

        $html = <<<'HTML'
<!DOCTYPE html>
<html>
<body>
<p class="message">Hello World!</p>
<p>Hello Crawler!</p>
</body>
</html>
HTML;

$crawler = new Crawler($html);

foreach ($crawler as $domElement) {
print $domElement->nodeName;
}

html取得あれこれ

ページ内移動するとき

$crawler = $client->request('get', 'https://secure.sakura.ad.jp/rscontrol/');
$crawler = $client->request('get', 'domainadd-other');

上記のような書き方をすることができるが、
http から始めてしまうとクッキーが切れたりするので同一サイトの場合は相対パス移動するべき

最初のpタグ

$crawler->filter('body > p')->eq(0);

全ての子ノード、親ノード

$crawler->filter('body')->children();
$crawler->filter('body > p')->parents();

htmlsqlやsnoopyは正規表現ゴリゴリ。
しかし、goutteは軽く速い検索ができる。
さぁ、みんなも使ってみましょう。

実践

今回はついっぷるから、ランキングに入っているユーザーのみを取得する

  • ついっぷるのランキングを取得
  • ua偽装
  • まずは大枠のdivを取得
  • その後取得したdivをさらに userNameに 分解

まずは下準備。crawlerも読み込んでおく

require_once "/var/www/html/matomater.com/Vendor/goutte/goutte.phar";
use Goutte\Client;
use Symfony\Component\DomCrawler\Crawler;

メインのコード

    public function index()
{

$url = "http://tr.twipple.jp/tweet/2014/1202/0035.html";
$client = new Client();

// ua偽装
$client->setHeader('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36');
//
$dom = $client->request('GET',
$url);

$res = "";

// まずはランキングの要素をまるごと取得
$dom->filter('#rankWrap01,#rankWrap02,#rankWrap03,#rankWrap04,#rankWrap05,.rankWrap06_10')->each(function($v,$key) use (&$html)
{
$html .= $v->html();
});


// 文字化け防止
$html = mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8');

// 取得した html を 再度 細かく userName に
$crawler = new Crawler($html);
$crawler->filter('.userName')->each(function($v,$key) use (&$res)
{
$res .= $v->text();
});

// .userNameのみ取得できている
pd($res);


$this->autoRender = false;
}

取得した要素の子要素にアクセス

  • header で ajaxでの通信をokにする
  • lBox の中の tweetTitle を取得する
        header('Access-Control-Allow-Origin: *');
$client = new Client();

// ua偽装
$client->setHeader('User-Agent', 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36');
//
$dom = $client->request('GET',
$url);


$html = array();


/*
* <div class="lBox">
* <div class="tweetTitle">らーめん</div>
* </div>
* <div class="lBox">
* <div class="tweetTitle">つけめん</div>
* </div>
*
*
*/

$dom->filter('.lBox')->each(function($v,$key) use (&$html)
{
$html[] = $v->filter('.tweetTitle')->eq(0)->html();
});

pd($html);//らーめん、つけめん

要素があるかないか調べる

要素がない場合、エラーが返ってしまいバグる。
ということで以下の処理をやれば .gazou があるか、ないかを調べ
ある場合のみ処理をする。

if(count($v->filter('.gazou')) != 0){
$html[$key]['gazou'][] = $v->filter('.gazou img')->attr('src');
}

クッキーが必要な場合などはログインさせてから

$client = new Client();

$crawler = $client->request('get', 'http://***.com/'); //ログインページ
$form = $crawler->selectButton('ログイン')->form();
$form['log'] = 'yourname';
$form['pwd'] = 'yourpass';
$client->submit($form); //ログイン

//ログインが必要な適当なページにアクセス
$crawler = $client->request('get', 'http://***');

//とりあえず取得したものをそのまま表示
pd($crawler->html());

selectButton ログインはログインという名前を持つボタンのフォーム という指定。

上記の log や pwd というのは
<input type="text" name="log" class="username" id="username" />
の name に当たる部分を指定する。

複数フォームがあってうまくログインできない

フォームが1ページに複数あるとボタン名を指定してもうまくログインできない時がある。その対処方法。

//スクレイピング先にある、フォームを取得しその部分だけコピー。
//html , body タグを付与して作る。
$html = '<!DOCTYPE html>
<html>
<body><div id="wpmem_login"><a name="login"></a><form action="http://www.rand.com/cast/ayas/" method="POST" id="" class="form"><fieldset><legend>Existing Users Log In</legend><label for="log">ユーザー名</label><div class="div_text"><input name="log" type="text" id="log" value="" class="username" /></div><label for="pwd">パスワード</label><div class="div_text"><input name="pwd" type="password" id="pwd" class="password" /></div><input name="redirect_to" type="hidden" value="http://www.rand.com/cast/ayas/" /><input name="a" type="hidden" value="login" /><div class="button_div"><input name="rememberme" type="checkbox" id="rememberme" value="forever" />&nbsp;ログイン状態を保存する&nbsp;&nbsp;<input type="submit" name="Submit" value="登録" class="buttons" /></div><div align="right" class="link-text">パスワードをお忘れですか?&nbsp;<a href="http://www.rand.com/menbers?a=pwdreset">パスワードリセット</a></div></fieldset></form></div><div id="wpmem_reg"><a name="register"></a></div>
</body>
</html>';

//ダミーで上記のhtmlをスクレイピングしたことにする。
$crawler = new Crawler('', 'http://www.example.com');
$crawler->addHtmlContent($html, 'UTF-8');

//登録ボタンのあるフォームを指定
$form = $crawler->selectButton('登録')->form();

//name属性 log pwd にそれぞれ IDやパスワードを突っ込む
$form['log'] = 'hidelog';
$form['pwd'] = 'hidepass';
$data = $client->submit($form); //ログイン

//ログイン後のフォームのデータ
pd(h($data->html()));


//クッキーを取得
//ログイン後のクッキー
$this->cookie = $client->getCookieJar()->all();

pd($this->cookie);

フォームの初期値の入れ方

フォーム自体を改造して、selectedやvalueを直接入れても行ける。

<option value="2" selected>2</option>
<label class="checkbox"><input type="checkbox" name="falexa" id="falexa" value="1" checked="checked"/> only with Alexa Rank</label>

crul51エラーでsslページが取得できない

こんな感じでdefaultoptionをセットしてやりゃok

$client = new Client();

$client->getClient()->setDefaultOption('config/curl/'. CURLOPT_SSL_VERIFYPEER, false);
$client->getClient()->setDefaultOption('verify', false);


$crawler = $client->request('get', 'https://target.com/'); //ログインページ
$form = $crawler->selectButton('ログイン')->form();
$form['email'] = 'youremail';
$form['password'] = 'yourpass';
$data = $client->submit($form); //ログイン

pr($data->html());

CSVファイルをダウンロードする

サイトにログインしないとダウンロードできないCSVを、

・サイトにログイン
・CSVダウンロード
・CSVを保存

をやる。

$client = new Client();

//まずはログインさせる
$crawler = $client->request('get', 'https://www.hoges.net/login/'); //ログインページ
$form = $crawler->selectButton('Login')->form();
$form['login'] = 'your';
$form['password'] = 'yourppp';
$client->submit($form);

//ログイン情報をもったままメンバーページへ
$crawler = $client->request('GET','https://member.your.net/hove.csv');



$response = $client->getResponse();
$content = $response->getContent();
$content->seek(0);

//ファイルを保存
file_put_contents(WWW_ROOT."files/any.csv",$content->getContents());

ボタンが画像の場合で input などで選択できない場合

$client = new Client();

$crawler = $client->request('get', 'https://secure.sakura.ad.jp/rscontrol/'); //ログインページ
$form = $crawler->selectButton('')->form();
$searchParameters = [
'domain' => 'hfeawa.ne.jp',
'password' => '5feaw8'
];

$crawler = $client->submit($form, $searchParameters);

上記のような感じでテキスト指定するところを''だけにして空にしておくとOK

1ページに同じようなフォームがある場合

同じページに似たようなフォームがあるとうまく送信できない。
その時はページを取得後、送信したフォームのみ抜き出して、
そのフォームを送信するようにすればよい。

//フォームをアクション名で選択
$form = $crawler->filter('form[action=domainadd-other]')->form();

//フォームにデータを入れる
$form->setValues(array('Domain' => "tarouman.com"));

//送信
$crawler = $client->submit($form);


//複数フォームがあるのでフォームの最初だけを選択
//今回は form タグの上に blockquote があるので、その中身を習得することで最初のformタグを取得できる
$clawler = $crawler->filter('blockquote')->first()->html();


//alt代替文字 送信するボタンを選択しDomainの内容をtarouman.com
$form = $crawler->selectButton('送信する')->form();
$searchParameters = [
'Domain' => "tarouman.com"
];


//送信
$crawler = $client->submit($form, $searchParameters);

上記のような感じで、 filter を使い最初のformを取得。
それを crawler に入れて cralwerの中身を最初のformのみにする。
で、それの値を変更して送信する

重い処理

cURL error 28: See http://curl.haxx.se/libcurl/c/libcurl-errors.html
というエラーが出た場合の対応

$client = new Client();

$curlOptions = array(
CURLOPT_CONNECTTIMEOUT => 6000,
CURLOPT_TIMEOUT => 6000
);

$client->getClient()->setDefaultOption('config', ['curl' => $curlOptions]);

$client = new Client();

$curlOptions = array(
CURLOPT_CONNECTTIMEOUT => 6000,
CURLOPT_TIMEOUT => 6000
);

$client->getClient()->setDefaultOption('config', ['curl' => $curlOptions]);

$crawler = $client->request('get', 'https://www.hoge.net/login/');

現在のurlを取得

goutte.phpを使っていると、フォームを送信後リダイレクトする場合がある。
あれ?goutte.phpはどこのページを今見ているんだろ。
と思ったらこれをやると、現在どのuriを参照しているかわかる。

//現在のurlを返す
//http://hoge.com/profile_list.php?condition=56671eb583b2c&SID=1c2m50brqgvsafofup14eggcc0
$now_url = $client->getHistory()->current()->getUri();
スポンサードリンク

関連記事

関連カテゴリ

コロ助

web関連の記事や制作系の記事をどんどんまとめていきます。 宜しくお願いします!

ピックアップ

パソコン・ソフトウェア ランキング

2月26日 ( 日 ) にアクセスが多かった記事はこちら!