phpでスクレイピングといえばGoutte(特に内部で使用しているCssSelector)がちょう便利ですが、とはいえスクレイピング対象のページに都合よくidやclassが振られていなこともままありますね。
今回たまたま某生協の注文サイトから注文状況を取得した際にやったあれこれをいくつかメモ。
パラメータを書き換えてpost
postして遷移するページで、post直前にjsで値を書き換えてからpost、という動作をしている所があった。
細かくコードを追うのは面倒なので、実際にpostされた値に合わせる形でページから値をかき集めてsubmitさせる。
書き換えはsetValues、formからの値の取得はgetValuesで。
$form->disableValidation()->setValues(['osk'=> $prev, 'curosk' => $prev, 'odc' => $form->getValues()['curodc']]); $this->crawler = $this->client->submit($form);
selectedな要素の次のoptionを選択
「前回の注文情報」というのを取るにあたり、同じURLでpostされたパラメータによって画面遷移するという箇所があったため、「一度現在の注文ページを開き、option要素がselectedになってる要素の次の要素を選択」という条件で取得してみた。
optionの場所が固定なら$crawler->eq(n)で取れるのだが可変なので、順序が保証されているという前提でこんな感じに対応。
<select name="osk"> <option value="20160702" >7月2回B週</option> <option value="20160701" selected="selected" >7月1回A週</option> <option value="20160605" >6月5回D週</option> </select>
20160605が欲しい
$prev = $this->crawler->filter('.weekOrderSelect select option')->reduce(function($node, $i){ // 直前のノードがselected return (count($node->previousAll()) && $node->previousAll()->first()->attr('selected') == 'selected'); })->attr('value');
previousAllが毎回呼ばれるのがイマイチ感がありますがまあこんな感じで。
一個前の取得はpreviousAll->last()ではなくfirst()でとれた。
余計な要素を除外
テーブルからのデータ取得で、よけいなtr(デザイン上の隙間的な何か)を除外。
reduceで頑張る。
$this->crawler->filter("table#wecpwa0010_{$parent} tbody tr")->reduce(function($node) { $item = $node->filter('tr.cartSubs'); return count($item) === 0; })->each(function($node) use ($previous) { $item = [ 'name' => '', 'price' => '', 'quantity' => '', ]; /// 後続処理
reduceやeachで無名関数を渡すときはuseで外部変数も渡せるのでなかなかベンリですね。
毎度参考にしている諸々
WebスクレイピングライブラリGoutteで遊んでみる
http://d.hatena.ne.jp/hnw/20120115
hnwさんの2012年の記事ですがわかりやすい
The DomCrawler Component
http://symfony.com/doc/current/components/dom_crawler.html#node-filtering
公式のDomCrawlerドキュメント