しょしんしゃえんじにあです。
サイトを数件スクレイピングしたときにつまずいた点と、その対処方法の個人的メモです。
はじめに
大切だなとおもったこと
「あなたの内面がどんなに汚くても、私は絶対にあなたを受け止める」
スクレイピングするサイトへの愛かなと思いました。
つかったもの
- ruby
- nokogiri
- Chromeデベロッパーツール
クローリングしたパターン
トップページのカテゴリ一覧 => カテゴリ一覧ページ => 詳細ページ
つまずいた点&対処方法
そもそも要素をどう取得するか
NokogiriではXPathやCSSセレクタで要素を取得できる。
WEBエンジニア的に馴染みがあるCSSセレクタを選んだ。
# CSSセレクタはこんなかんじ
doc.css('#main > article> h1')
# XPathはこんなかんじ
doc.xpath('//*[@id="main"]/article/h1')
HTML構造が複雑で要素を指定できない
実際にサイトの要素を取得しようとすると結構長くなる。
#main > article > div.p-items_wrapper > div > div.p-items_main > div.p-items_article > div.it-Header > h1
目視では到底追えないので、「Chromeデベロッパーツール」にCSSセレクタを自動生成してもらった。
こちらの記事がとてもわかりやすい!
Python Webスクレイピング テクニック集「取得できない値は無い」JavaScript対応@追記あり2/28
ChromeデペロッパーツールのCSSセレクタの注意点
tbodyには気をつける
例えばtable > tbody > tr > td
と自動生成された場合、そのまま素直に利用すると、サイトによって取得が失敗したりする。
原因としては単純で、実サイトではtable > tr > td
とコーディングをしているから取得できない、というケース。ページのソースを表示すればすぐわかる。
Chromeさんがなんか勝手にtbodyを自動補完してくれるみたい。きちょうめんだ。
とりあえずなぜかとれないときは
- 「>」(直下セレクタ)を取っ払ったら、いいことあった。
- 自動生成のCSSセレクタを無視してサイトへの愛によるCSSセレクタをひねりだす
- :nth-child
- + (隣接セレクタ)
- [foo=bar](属性セレクタ)
このあたりを結構活用した。
同サイト内で詳細ページのHTML構造が微妙にばらばら
ページによってあったりなかったりする要素もあるのでぼっち演算子とかで、
doc.css('img')&.attribute("src")
などとやりそうになるが、undefined method `attribute' for nil:NilClass
でコケる。
css、xpathメソッドは、該当する要素がない場合、nilではなく空の配列(NodeSet)が返ってきているから。
HTMLが文字化けする
kconvつかう。toutf8つける。
require 'open-uri'
require 'nokogiri'
require 'kconv'
url = "http://example.com"
html = URI.parse(url).read
doc = Nokogiri::HTML(html.toutf8, nil, 'utf-8')
URLに日本語が含まれている
URI.encodeつかう。
html = URI.parse(URI.encode(url)).read
###なんかこういうケースもあった
url = doc.css('a#hoge').attribute('href')
next_html = URI.parse(URI.encode(url)).read
undefined method `gsub' for #<Nokogiri::XML::Attr:...
が出た。
to_sしたらうまくいった。
url = doc.css('a#hoge').attribute('href').to_s
next_html = URI.parse(URI.encode(url)).read
ページネーションをどうくぐりぬけるか
こうした
カテゴリ一覧ページのリンク達.each do |カテゴリ|
カテゴリAの一覧ページをノコギる
begin
カテゴリAの詳細ページのリンク達.each do |s|
詳細ページをノコギる
end
# ↓↓次のページがあるかどうか判定↓↓
next_link = document.css('#pager > ul > li > a.next')
unless next_link.empty?
カテゴリAの一覧の次のページをノコギる
end
end until next_link.empty?
end
"【タイトル】</br>内容…"みたいな構成のときに「タイトル」と「内容」を取りたい
正規表現でごにょごにょしてなんとかした。下記のさらにもっと汚い版
irb(main):081:0> "【あああ】<br>いいい".split(/【((?!【).*?)】/)
=> ["", "あああ", "<br>いいい"]
要素を取得したときに値にブランクやタブやbrタグがはいる
ひたすらえすけーぷえすけーぷえすけーぷえすけーぷ
どこまで進行したかわからない
ログなりなんなり出そう(結構大事)
さいごに
スクレイピングして、うまくいったとおもったら、つまずいて、サイトのソース読んでを繰り返すとサイト愛が芽生えてきてしまいました。何言ってるんだろう私。
かなり作業の泥臭さを感じたので、もう少しスマートにできる方法を模索したいです。次の機会があれば…。