htmlsqlより速い軽い。cakephpでスクレイピングgoutte.phpインストールと使い方
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);
さらに便利な使い方
パクったページをそのまま表示
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');
最初のpタグ
$crawler->filter('body > p')->eq(0);
全ての子ノード、親ノード
$crawler->filter('body')->children();
$crawler->filter('body > p')->parents();
実践
今回はついっぷるから、ランキングに入っているユーザーのみを取得する
- ついっぷるのランキングを取得
- 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());
複数フォームがあってうまくログインできない
フォームが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" /> ログイン状態を保存する <input type="submit" name="Submit" value="登録" class="buttons" /></div><div align="right" class="link-text">パスワードをお忘れですか? <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);
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);
重い処理
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]);
$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関連の記事や制作系の記事をどんどんまとめていきます。 宜しくお願いします!
ピックアップ
-
LINEで異性と出会う3つのコツ
view: 83 view
パソコン・ソフトウェア ランキング
2月17日 ( 日 ) にアクセスが多かった記事はこちら!
-
hammer.jsの使い方
view: 711 view -
jquery で ロゴをランダムに変更
view: 8 view -
無料のタイピングソフトまとめ!
view: 2 view -
TwitterBootstrapulliタグ横並べ
view: 413 view -
javascript 正規表現 画像のURL href のみ抜き出し
view: 225 view -
twitter bootstrap オンオフ、トグルボタン
view: 89 view -
cakephp2.x 画像アップロードMediaPlugin を使う
view: 58 view -
google analytics api v3 を cakephp2.x で使う 1
view: 22 view -
twitter bootstrap spacer
view: 181 view -
LINEで回ってきた文章がすごい
view: 9 view