Wikipedia の駅のページからキロ程情報を抽出する

この記事は、lisp アドベントカレンダー2013 12/14 の記事の続きです。ほったらかしにしておくのももったいないので、ぼちぼち続けていこうと思っています。まずはその続きの 第一弾 ということで。

やりたいこと

最終的にやりたいことは、前述のアドベントカレンダーの記事に書きましたが、駅のキロ程情報を Wikipedia から拾ってこよう、ということです。前述の記事を書いた時には、 「lisp アドベントカレンダーらしく」 というのを意識したかったので、HTML の木構造を S式でちゃっちゃとつくって、clojure.data.zipあたりを使ってノードをトラバースして、という方向で考えていました(が時間切れに...)。ですが、もう今となってはそういう縛りは一旦とっぱらって、情報抽出に専念してみます。

今回は、HTML Cleaner のもつ機能をそのまま使う方針で考えます。HTML Cleaner のオブジェクトは、HTML に沿った形の木構造をしているので、「キロ程」というキーワードのあるノード (HTML CleanerTagNode) を上位にさかのぼって探す、といった操作が簡単に行えます。具体的には、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 からは離れるが) 国土数値情報の路線の線形と重ねあわせてみる

といったことをやってみようと思います。

comments powered by Disqus