/var/www/yatta47.log

/var/www/yatta47.log

やったのログ置場です。スクラップみたいな短編が多いかと。

【第1回】Googleアラートのページからフィードだけをスクレイピングする

さて。宣言したとおり、今日はスクレイピングで必要なデータを取ってこようかと思います。

「なんでこんなことやってる?」とか経緯を知りたい方は以下の記事からどうぞ。

yatta47.hateblo.jp

それではスタート。

HTMLを取ってくる

まず最初に、GoogleアラートのページからHTMLをとってきて保存します。これがないと始まりません。

HTMLソースを全コピー、保存します。いきなり泥臭いです。Ctrl + U のショートカットキーでソース表示とかもあるらしいですが、自分のChromeの場合、なんかうまく効かなかったのでそのへんは各自よしなにやってくださいwwおそらく色々なextension入れているからそっちが上書きされているのかと。

どちらにしても、HTMLファイルがないとスタートしないので、ここは手動で保存してでも確保します。確保したものはalert.htmlという名前で保存しておきます。

保存方法で何か面白い方法あったら教えて下さい。

現在のディレクトリ状態:

$ ls
alert.html 

HTMLファイルのNokogiriで読み込む

保存したHTMLをNokogiriで読み込んで、最終的には必要な物をそこから取り出します。

静的ファイルをNokogiriで読み込むコードは以下。

require 'nokogiri'

file_name = 'alert.html'

f = File.open( file_name ).read
doc = Nokogiri::HTML(f, file_name)

p doc.title

"Google アラート - ウェブ上の面白い新着コンテンツをチェック" と表示されれば正解です。

File.openで読み込んで、Nokogiriを通します。ここでちょっとはまったのがreadのところ。これをつけないと文字化けします。

[readあり]
"Google アラート - ウェブ上の面白い新着コンテンツをチェック"
[readなし]
"Google ã\u0082¢ã\u0083©ã\u0083¼ã\u0083\u0088 - ã\u0082¦ã\u0082§ã\u0083\u0096ä¸\u008Aã\u0081®é\u009D¢ç\u0099½ã\u0081\u0084æ\u0096°ç\u009D\u0080ã\u0082³ã\u0083³ã\u0083\u0086ã\u0083³ã\u0083\u0084ã\u0082\u0092ã\u0083\u0081ã\u0082§ã\u0083\u0083ã\u0082¯"

以下のサイトさんが詳しく書かれていたのでもっと詳しく理由を知りたい場合には参考になると思います。

qiita.com

どの部分を読みだすかを調べる

とりあえず、HTMLをNokogiriオブジェクトに変換はできたかと思います。この変換したオブジェクトをギコギコと整えていこうと思います。

今回欲しいのは画面で言えば1行の単位。

f:id:yatta47:20160220201025p:plain

これがHTMLでいうとどのへんかというのは、Developer Toolsを使うのがわかりやすいと思います。さっきの箇所で右クリック→検証でDeveloper Toolsが開くので確認します。

行はそれぞれli要素としてかかれているのがわかるかと思います。li要素を取ってくればいいかとも思ったのですが、これだと下の方にある関係ないものまで一緒に取ってきてしまうからよろしくない。

HTMLソースを眺めていると、欲しいものはulブロックとして大きく一つにまとまっている様子。

ってことで、結論としてはフィードが固まっているul要素を取ってきて、li要素でバラしていくようにしました。

具体的なソースは以下。

require 'nokogiri'

# 読み込むファイル名
file_name = 'alert.html'

# Nokogiriで読み込む
f = File.open( file_name ).read
doc = Nokogiri::HTML(f, file_name)

# すべてのul要素を取得
ul_all = doc.search('ul')

# その中の3番めが欲しいデータだったのでそれだけを取得
alert = ul_all[2]
# li要素で取得(配列で返ってくる)
items = alert.css('li.alert_instance')

ul要素の3番めが必要というのは完全に目視で確認して、目標定めていますww

itemsはNokogiriオブジェクトの配列で、1要素はp items.first とかで簡単に見れます。こんな感じで内容沢山詰まっています。

#<Nokogiri::XML::Element:0x1105d28 name="li" attributes=[#<Nokogiri::XML::Attr:0x1105b84 name="class" value="alert_instance">, #<Nokogiri::XML::Attr:0x1105b70 name="data-id" value="7721da9dc11d8cf5:5826b8007441f89c:com:ja:JP">] children=[#<Nokogiri::XML::Text:0x12054f8 " ">, #<Nokogiri::XML::Element:0x1205304 name="div" attributes=[#<Nokogiri::XML::Attr:0x12050fc name="class" value="delivery_settings">] children=[#<Nokogiri::XML::Text:0x120127c " ">, #<Nokogiri::XML::Element:0x1200f98 name="div" attributes=[#<Nokogiri::XML::Attr:0x1200d54 name="class" value="delivery_settings_view">] children=[#<Nokogiri::XML::Text:0x11fb9a8 " ">, #<Nokogiri::XML::Element:0x11fb638 name="div" attributes=[#<Nokogiri::XML::Attr:0x11fb390 name="class" value="query_div">] children=[#<Nokogiri::XML::Text:0x11fa0bc " ">, #<Nokogiri::XML::Element:0x11f7e34 name="div" attributes=[#<Nokogiri::XML::Attr:0x11f7d1c name="class" value="entity_div">] children=[#<Nokogiri::XML::Text:0x11f7060 "  ">]>, #<Nokogiri::XML::Text:0x11f6b9c " ">, #<Nokogiri::XML::Element:0x11f6994 name="span" attributes=[#<Nokogiri::XML::Attr:0x11f6890 name="tabindex" value="0">] children=[#<Nokogiri::XML::Text:0x11f1214 "4thライブ">]>, #<Nokogiri::XML::Text:0x11f0a80 " ">]>, #<Nokogiri::XML::Text:0x11eed84 " ">, #<Nokogiri::XML::Element:0x11eea8c name="div" attributes=[#<Nokogiri::XML::Attr:0x11ee938 name="class" value="alert_buttons">] children=[#<Nokogiri::XML::Element:0x11e92f8 name="a" attributes=[#<Nokogiri::XML::Attr:0x11e9168 name="href" value="/alerts/feeds/11666000284305127907/13505103975260490721">, #<Nokogiri::XML::Attr:0x11e9154 name="tabindex" value="0">] children=[#<Nokogiri::XML::Element:0x11deb50 name="span" attributes=[#<Nokogiri::XML::Attr:0x11de894 name="class" value="rss_icon">, #<Nokogiri::XML::Attr:0x11de768 name="title" value="RSS">]>]>, #<Nokogiri::XML::Element:0x11d3a84 name="span" attributes=[#<Nokogiri::XML::Attr:0x11d396c name="class" value="edit_button alert_button">, #<Nokogiri::XML::Attr:0x11d3930 name="title" value="編集">, #<Nokogiri::XML::Attr:0x11d38b8 name="role" value="button">, #<Nokogiri::XML::Attr:0x11d3818 name="tabindex" value="0">]>, #<Nokogiri::XML::Element:0x11cd33c name="span" attributes=[#<Nokogiri::XML::Attr:0x11cd1e8 name="class" value="delete_button alert_button">, #<Nokogiri::XML::Attr:0x11cd1d4 name="title" value="削除">, #<Nokogiri::XML::Attr:0x11cd1c0 name="role" value="button">, #<Nokogiri::XML::Attr:0x11cd1ac name="tabindex" value="0">]>]>, #<Nokogiri::XML::Text:0x11bdc48 " ">, #<Nokogiri::XML::Element:0x11bdb30 name="div" attributes=[#<Nokogiri::XML::Attr:0x11bda40 name="class" value="clear">]>, #<Nokogiri::XML::Text:0x11bd298 " ">]>, #<Nokogiri::XML::Text:0x11bcf00 " ">]>, #<Nokogiri::XML::Text:0x11bc7d0 "    ">, #<Nokogiri::XML::Element:0x11bc4d8 name="div" attributes=[#<Nokogiri::XML::Attr:0x11bc3fc name="class" value="clear">]>, #<Nokogiri::XML::Text:0x11b57f0 " ">]>

話それますけど、.first とか .last って配列の先頭要素をみるのにかなり重宝しています。今回のように構造をちょっとみたいとかそういう時もeachでぐるぐる回さなくてもわかるし。今更、この便利さに感動しています。

xpathcss

ここでハマったのはcssで取得しているalert.css('li.alert_instance')のところ。

最初xpathで取得しようとしたら意図していたものも撮れているんだけど、それ以外のものも取れてしまっていて非常に困った。具体的には、ul要素だけに絞ったalertの中をxpathalert.xpath('//span')という感じでフィルタかけたつもりだったのに、ul要素以外のところの要素まで取ってきてしまっている感じ。

結局、cssでやることによって思い通りのものは得られたけどxpahtで出来なかった理由がいまだモヤモヤしています。

あとは必要なデータをとってあげるだけ

これで1行単位のデータが整ったので、その中から「名前」と「フィードURL」を取ってきてあげれば完成。

ソース全文になるコードは以下。

require 'nokogiri'

# 読み込むファイル名
file_name = 'alart.html'

# Nokogiriで読み込む
f = File.open( file_name ).read
doc = Nokogiri::HTML(f, file_name)

# すべてのul要素を取得
ul_all = doc.search('ul')

# その中の3番めが欲しいデータだったのでそれだけを取得
alert = ul_all[2]
# li要素で取得(配列で返ってくる)
items = alert.css('li.alert_instance')

items.each do |item|
    puts n
    name = item.css('span')[0].text
    feed_p = item.css('a')

    if feed_p.size == 0
        feed = "none"
    else
        feed = item.css('a').attribute('href')
    end

    puts "#{name} -- #{feed}"

end

「名前」はspan要素の一番最初、「フィードのURL」はa要素のhrefに記載されているので、それをname、feedという名前で格納しています。

ここでもハマったのが一つ・・・・。コードでも不可解なこの部分。

    feed_p = item.css('a')

    if feed_p.size == 0
        feed = "none"
    else
        feed = item.css('a').attribute('href')
    end

Googleアラートを全部が全部フィード出力していればイイのですが、中にはメールで受信の項目もあって、メールで受信の場合はフィードへのリンクが表示されていないのでここでエラーが出てしまいます。

a要素がない場合は空配列が返されたので、それで条件分岐させて対応しています。

これで実行するとフィードの名前、URLが一覧表示されたかと思います。

今日はここまで

意外と長かった・・・・・・。

これでフィードの一覧が出来上がったので、これをOPMLに変換することを次回やろうと思います。

それではまた次回!!