はてなブログ、カテゴリー情報を取得するサンプルコード
はじめに
自分のブログ記事の中で、特定カテゴリーのタグが付いている記事一覧が必要になりました。
それぞれの記事について必要な情報は、記事タイトルと記事URLだけ。
それっぽい API なんて公式、非公式を問わずググれば見つかるでしょ。
ってことでググるが、あれ、見つからない。。。
調べる
とりあえず、カテゴリー毎のリンクがあるのでそのページを確認する。
“自転車日本一周” - 記事一覧 - プログラミング的な何か、あと自転車日本一周とか
わかったこと
- カテゴリ一覧ページにほしい情報(タイトル、記事のリンク)が存在する。
- それぞれの記事リンクには、クラス情報として entry-title が付いている。
- 記事総数が1ページに入らない(50記事以上?)場合、ページネートが作成されている。
- ページネートはクエリーパラメータ(page=1, page=2, page=3 ...) で実現されている。
- ページネートのクエリーパラメータとして、不正(page=-1とか、記事が存在しないインデックス等)な値を入力すると、この期間に記事はありませんと表示される。
ふむふむ。
スクレイピングで問題なく情報を抽出できそう。
実装の流れ
こんな感じで書いてみる。
- ブログURL, カテゴリ名を指定する。
- 指定カテゴリのページ(page=1)から、class が entry-title のアンカー情報を抽出する。
- 次のページ(page=2) から同様に class が entry-title のアンカー情報を抽出する。
- page=3, page=4, page=5 ... も同様。取得したアンカー情報が空だったら情報抽出を打ち切る。
- 取得した情報を扱いやすいフォーマット変換する。
終了判定は、"次のページ" リンクの有無でも問題なさそう。
サンプルコード
Clojure
html のパースに enlive を使用。:dependencies に
[enlive "1.1.1"]
を追加しておきます。
(ns hatena-blog.core (require [clojure.java.io :refer [reader]] [net.cgrand.enlive-html :refer [select html-resource]])) (defn category-entries [blog-url category] (loop [result [] index 1] (let [entries (-> (format "%s/archive/category/%s?page=%d" blog-url category index) reader html-resource (select [:a.entry-title]))] (if (empty? entries) result (recur (concat result entries) (inc index)))))) (defn formatted-entries [blog-url category] (let [entries (category-entries blog-url category)] (map #(array-map :title (first (:content %)) :url (str blog-url (get-in % [:attrs :href]))) entries)))
こんな感じで使用する。
(formatted-entries "http://snufkon.hatenablog.com" "自転車日本一周")
結果として {:title "記事タイトル", :url "記事URL"} を持つ遅延シーケンスが返ってくる。
({:title "【旅120日目 2012/09/26】能登半島の先端へ", :url "http://snufkon.hatenablog.com/ ..."} {:title "【旅119日目 2012/09/25】富山から能登半島へ", :url "http://snufkon.hatenablog.co ..."} {:title "【旅118日目 2012/09/24】日本一の落差、称名滝へヒルクライム", :url "http://snufko ..."} {:title "【旅117日目 2012/09/23】雨で停滞、とくになし", :url "http://snufkon.hatenablog. ..."} {:title "【旅116日目 2012/09/22】青い海と空、そして再会。", :url "http://snufkon.hatenabl ...})
Ruby
html パーサとして nokogiri を使用。
# -*- coding: utf-8 -*- require 'uri' require 'open-uri' require 'nokogiri' def category_entries(blog_url, category, index=1) url = URI.escape("#{blog_url}/archive/category/#{category}?page=#{index}") doc = Nokogiri::HTML(open(url)) nodes = doc.css('a.entry-title') if nodes.size == 0 nodes else nodes + category_entries(blog_url, category, index+1) end end def formatted_entries(blog_url, category) entries = category_entries(blog_url, category) entries.map do |entry| {title: entry.text, url: blog_url + entry['href']} end end
こんな感じで使用する。
entries = formatted_entries("http://snufkon.hatenablog.com", "自転車日本一周")
entries に {:title => "記事タイトル", :url "記事URL"} を持つ配列が格納される。
[{:title=>"【旅120日目 2012/09/26】能登半島の先端へ", :url=>"http://snufkon.hatenablog.com/..."} {:title=>"【旅119日目 2012/09/25】富山から能登半島へ", :url=>"http://snufkon.hatenablog.com..."} {:title=>"【旅118日目 2012/09/24】日本一の落差、称名滝へヒルクライム", :url=>"http://snufkon...."} {:title=>"【旅117日目 2012/09/23】雨で停滞、とくになし", :url=>"http://snufkon.hatenablog.co..."} {:title=>"【旅116日目 2012/09/22】青い海と空、そして再会。", :url=>"http://snufkon.hatenablo..."}]
終わりに
Clojure のほうが Ruby よりいけてる! って言いたかったけそうでもなかったという残念な結果に。
もっとスマートなコードを書けるようになりたい。