Wikipedia の駅のページからキロ程情報を抽出する
この記事は、lisp アドベントカレンダー2013 12/14 の記事の続きです。ほったらかしにしておくのももったいないので、ぼちぼち続けていこうと思っています。まずはその続きの 第一弾 ということで。
やりたいこと
最終的にやりたいことは、前述のアドベントカレンダーの記事に書きましたが、駅のキロ程情報を Wikipedia から拾ってこよう、ということです。前述の記事を書いた時には、 「lisp アドベントカレンダーらしく」 というのを意識したかったので、HTML の木構造を S式でちゃっちゃとつくって、clojure.data.zipあたりを使ってノードをトラバースして、という方向で考えていました(が時間切れに...)。ですが、もう今となってはそういう縛りは一旦とっぱらって、情報抽出に専念してみます。
今回は、HTML Cleaner のもつ機能をそのまま使う方針で考えます。HTML Cleaner のオブジェクトは、HTML に沿った形の木構造をしているので、「キロ程」というキーワードのあるノード (HTML Cleaner のTagNode) を上位にさかのぼって探す、といった操作が簡単に行えます。具体的には、HTML Cleanerで取得した TagNode の以下のメソッド等を使ってみました。
- 
                        TagNode#getElementByName:自分のノードの子の要素を名前指定で探す。今回、「キロ程」というキーワードは<th>に記述されていたので、'th'に記述されている「キロ程」という文字列、を探すことで良さそうです。
- 
                        TagNode#getParent: 自分のノードの親要素のノードを返します。
書いたコード
書いたコードを晒しておきます。
(ns html-parser.core
  (:import [org.htmlcleaner HtmlCleaner]))
(defn- html->node
  [cleaner html-src]
  (doto (.getProperties cleaner)
    (.setOmitComments true)        ;; HTML のコメントは無視する
    (.setPruneTags "script,style") ;; <script>, <style> タグは無視する
    (.setOmitXmlDeclaration true))
  (.clean cleaner html-src)) ;; cleaner.clean(string) でパース
(defn- station?
  [node]
  (->> node .getText .toString (re-find #".+駅\**$"))) ;; 駅によっては XX駅* のような表記あり
(defn- parse-page-and-extract-kilotei
  [page-src]
  (let [cleaner (HtmlCleaner.)
        rootnode (html->node cleaner page-src)]
    (->> (.getElementListByName rootnode "th" true) ;; <th> を探す
         (filter station?) ;; <th>の内容が XX 駅 のものに限定
         (map #(-> % .getParent .getParent .getText .toString)) ;; 2つ親 (<th> -> <thead> -> <table>) の <table> タグ以下のテキストを取得
         (mapcat #(re-seq #"所属路線■*([^*キ]+)\**キロ程(\d+\.?\d*km)" %)) ;; 路線名、キロ程を抽出
         (map rest)
         distinct)))
(defn get-kilotei-from-wikipedia
  [url]
  (->> (slurp url) ;; TODO: cache
       parse-page-and-extract-kilotei))
;; (get-kilotei-from-wikipedia "http://ja.wikipedia.org/wiki/新大阪駅")
;; => (("東海道新幹線" "552.6km") ("山陽新幹線" "0.0km") ("東海道本線(JR京都線)" "552.6km") ("東海道本線貨物支線\n(梅田貨物線)" "3.8km") ("御堂筋線" "2.9km"))
                正規表現あたり限りなくあやしいですが、そのうちまじめに見直そうと思います。トライアルとして書く分には、まあ良しとします。
今後
今回は単純に、目当ての情報をピンポイントで抽出する、というだけのことをしていますが、そのうちに、
- 
                        一旦取得した Wikipedia のページはキャッシュする 
- 
                        指定した駅から順次次の駅のリンクも合わせて取得する 
- 
                        路線の関連付けまで合わせてデータベース化する 
- 
                        (clojure からは離れるが) 国土数値情報の路線の線形と重ねあわせてみる 
といったことをやってみようと思います。
