読者です 読者をやめる 読者になる 読者になる

FutureInsight.info

AI、ビッグデータ、ライフサイエンス、テクノロジービッグプレイヤーの動向、これからの働き方などの「未来」に注目して考察するブログです。

Webstemmerによるブログの本文抽出

Pythonで記載されたレイアウト+diffベースで本文抽出を行うWebstemmerを用いてブログの本文抽出にチャレンジしました。いつも通り技術エントリーは非常に長いので、興味のある人だけ続きをごらん下さい。
検索エンジンのフロントエンド部分の実装の目処がたったので、次はクローラ、インデクサの実装です。PythonにはTwistedという極めて優秀な非同期Webアプリケーションフレームワークがあるので、クローラを記載することは全く難しくありません。この辺りはPythonクックブックの14章ウェブプログラミングを参照して下さい。

Python クックブック 第2版
Python クックブック 第2版鴨澤 眞夫 當山 仁健 吉田 聡

おすすめ平均
starsリファレンスとセットで
stars2.5以降対応版の第3版を

Amazonで詳しく見る
by G-Tools
さて、クローラ部分の目処は立ったとして、問題はインデクサです。Luxを検索エンジンに使うので、インデクサの基本コードは以下のエントリーで紹介したLuxのPythonバインディングを使えば大丈夫ですが、問題はどのようなデータをつっこむかです。最初はblog検索エンジンを作ろうと思うのですが、RSSのdescription部分のデータをつっこむだけではあまりおもしろみがない。出来たら本文+コメントだけを検索対象としてインデクサにつっこみたいところです。そんなわけでblogの本文抽出にトライしてみました。

HTMLからの本文抽出の現状

HTMLからの本文抽出の現状については以下のエントリーに添付してある資料が参考になりました。

さて、ここで紹介している新山さんの開発したWebstemmerが今回のターゲットです。

Webstemmerが利用する本文抽出方法の詳細に関しては以下のドキュメントを参照してください。

アルゴリズム的には簡単に書くと、

  1. 対象となるサイトの複数ページを取得してレイアウトを解析
  2. 特徴の似通ったレイアウトをまとめてクラスターを作成
  3. 本文抽出対象となるページがどのクラスターに所属するかを決定した上で、クラスター内で内容、構成が似通った部分を削除。diffによる編集距離が大きいものを本文と見なす。
  4. タイトルをレイアウトから推測する。

という手順で構成されています。上の資料ではこのWebstemmerに対して、以下の説明が付けられています。

  • ニュースサイトに強い
  • レイアウトが似た系統でないと抽出できない
  • => 一般のサイトで十分な性能が出るわけではない

確かに見た感じでは高速なアルゴリズムではありません。通常のページで本文を抽出するために、その通常ページに関連するページを取得して、クラスターを作成しなければいけないのですから、このアルゴリズムを実行するにはかなりコストがかかると思います。しかしながら、ほとんどコストなしでこのWebstemmerを適用可能な分野が存在します。それがblogの本文抽出です。

Webstemmerによるblogの本文抽出システム

僕のエンジニアの感覚として、このWebstemmerによる手法が本文抽出の王道だと思います。ほとんど無限の計算リソースがあるGoogleなどは、このアルゴリズムに似た本文抽出方法を採用しているのではないかと思います。それは、この手法が言語に依存せず、もともとそのページに関連するページを取得済み、構成を分析済みであれば、ものすごく効率的に動作することが目に見えているからです。
さらにblogの本文抽出を行う場合、Webstemmerにとって最も都合の良いデータが存在します。それはRSSです。RSSにはだいたいそのブログのエントリーへのリンクが30個から50個ほど含まれています。この50個のリンクはエントリーへのpermlinkになるので、大変都合が良い。レイアウトもほとんど同じだろうし、title情報なども最初から含まれています。つまり、以下のようなフローを確立できれば自動的にblogの本文抽出エンジンが作成出来るのではないかと考えました。

  1. 本文抽出対象となるblogのRSSからエントリーのHTMLを30個から50個ほど取得する。
  2. 取得したHTMLを用いてレイアウトを解析し、Webstemmerのパターンデータを生成する。
  3. パターンデータをDBに突っ込んでおく。それほどデータが貯まるわけではないのでSQLiteなどでOK。
  4. クローラが新規エントリーを取得したら、すでに生成済みのパターンファイルを用いて本文を抽出。
  5. 本文抽出がうまくいかなかった場合は、レイアウトが変わった可能性があるので、DBにフラグを立てておき、1週間に一回くらいフラグがたっているblogに関してパターンファイルの再構築を試みる。

まぁ、ここで考えても机上の空論になるので手元で簡単に試してみました。

Webstemmerによるdankogaiの本文抽出

今回の本文抽出のターゲットはdankogai氏の404 Blog Not Foundです。いろいろごてごて付いているので難易度が高そうだったのと、404 Blog Not Foundはblogの本文をRSSで配信していないので、まぁ、ちょうど良いかなと思いました。

では、404 Blog Not FoundのRSSから各エントリーのHTMLページを取得します。以下のようなスクリプトを書いて、標準出力をファイルに保存し、そのファイルをshで実行すれば良いでしょう。

# -*- coding: utf-8; -*-
#!/usr/bin/env python                                                                                             
import sys
import os
import feedparser

def main():

    url = sys.argv[1]
    d = feedparser.parse(url)
    for item in d["items"]:
        print "wget " + item["link"]
    
if __name__=="__main__":
    main()

これで404 Blog Not Foundの50個のエントリーのHTMLファイルが取得できました。取得したファイルはWebstemmer用のフォーマットのzipに保存する必要があります。タイムスタンプをディレクトリ名にしたディレクトリ構成の詳細は上記のWebstemmerのサイトを参照してください。ここでは、200904212254.zipというパターンファイルを生成する元となるzipファイルを作成しました。このzipファイルの中に50個のエントリーのHTMLファイルが含まれています。この200904212254.zipに対してWebstemmerに含まれるanalyze.pyを以下のように実行します。

./analyze.py -c euc-jp 200904212254.zip > dankogai.pat

ここで生成されるdankogai.patが404 Blog Not Foundの本文抽出用のパターンファイルです。1年ほど前に購入した2コア、2.0GHzのOpteronのサーバで50個のHTMLを元にパターンファイルを生成するのに3分8秒かかりました。今のマシンならたぶん2分くらいで生成できるのではないかと思います。
あとは、このパターンファイルを使って本文抽出を実行します。今回の本文抽出対象のページはRSSに含まれない以下のページにしました。

このHTMLをWebstemmer用のzipファイルフォーマットにして(200904212254.zipという名前にしました)Webstemmerに含まれるextract.pyを以下のように実行します。

./extract.py -C utf-8 -c euc-jp dankogai2.pat 200904212255.zip > entry.txt

この段階の実行時間は2秒ほどです。つまりパターンファイルを生成しておけば、実際のクローラの場面では2秒ほどで本文抽出が完了することになります。

!UNMATCHED: 200904212255/

!UNMATCHED: 200904212255/blog.livedoor.jp/

!UNMATCHED: 200904212255/blog.livedoor.jp/dankogai/

!UNMATCHED: 200904212255/blog.livedoor.jp/dankogai/archives/

!MATCHED: 200904212255/blog.livedoor.jp/dankogai/archives/51189969.html
PATTERN: 200904212254/blog.livedoor.jp/dankogai/archives/51196937.html
SUB-0: 404 Blog Not Found:警告は当たった、脱出はなるか - 書評 - 世界大不況からの脱出
SUB-4: 警告は当たった、脱出はなるか - 書評 - 世界大不況からの脱出
SUB-5: 早川書房小都様より献本御礼。
SUB-6: 世界大不況からの脱出
SUB-6: Paul Krugman / 三上義一
SUB-6: [原著:The Return of Depression Economics
SUB-6: and the Crisis of 2008]
MAIN-7: あの名著「世界大不況への警告」(The Return of Depression Economics)が、刷新の上お求めやすくなって再登場。早川書房、GJ!
MAIN-7: 本書「世界大不況からの脱出は、著者のノーベル賞受賞後初の書籍。ただし前述のとおり、すべて書き下ろしたのではなくて、10年前に現在の状況を予測した「世界大不況への警告」に、その後の出来事と今後の展望を増補し改訂したという形を取っている。質もよいが値段もよい本が多い早川書房のタイトルにあって、Amazonプライスきっかりというのがうれしい。
SUB-8: 世界大不況からの脱出:ハヤカワ・オンライン
MAIN-16: 〈ハヤカワ・ノンフィクション〉グリーンスパン、金融規制緩和、そして恐怖の連鎖……ノーベル賞経済学者が二〇〇八年の経済崩壊を導いた構造を解き明かし、いま我々が何をすべきかを示唆。『世界大不況への警告』改訂増補版。
MAIN-17: 率直に言って、私はクルーグマンの提言部分が正しいかどうかはわからない。しかしその警告の正しさは今回「世界大不況」が本当に来たことで証明されたし、そして本書に限らずクルーグマンの本というのは正しさを抜きにしても実に面白くてためになる。さすが「ソウルフルな科学者」の最右翼、いや最左翼か。今では有名になった「ベビーシッター協同組合」、実は本著、性格には本著の前版がオリジナルである。その言葉に耳を傾ける価値はこれまでになく高いだろう。
MAIN-17: それだけに、むしろ「本当にこれで見落としはないか」という点はなお強く感じるのだ。なぜならば、本書は「貨幣経済に対して閉じている」のだ。いわゆる外部不経済は一切登場しない。確かに金が起こした問題であれば、金というものを正しく扱えば問題は解決へと向かうはずであり、著者もそう主張していると私は受け取った。
MAIN-17: しかし、そろそろその「なぜ我々は金をこうも不適切にしか扱えないのか」という、金ではなく、その使用者である我々の心理学まで踏み込まないと、またもや一時しのぎになりはしないか。著者も大ファンである「ファウンデーション」の言葉を借りれば、psychohistory の psycho の面にまで踏み込むべき時が来てはいないか。
MAIN-17: もちろんそれは history をないがしろにせよ、ということではなく、そしてこの history の面において本書は最近出た本の中で最も面白くかつ説得力がある一冊であることは間違いない。その上今度はハードカバーではなくソフトカバーである。相対的コスト(コストパフォーマンス)だけではなく絶対的コストまで低いのである。これを読まずして今年の経済は語れないというのは確かだろう。
MAIN-17: それでも、本書はもう一人のノーベル賞受賞者、ムハマド・ユヌスの言うところの「資本主義の未完成の半分」を完成へと導く本でもない。
MAIN-17: まだ、何かが足りない。
MAIN-17: しかし、本書は「最後のピース」ではなくとも、「欠くことの出来ないピース」であることは存分に感じられた。手元においておくことを勧める次第である。
MAIN-17: Dan the Economic Animal
MAIN-26: 書評リンク - 世界大不況からの脱出-なぜ恐慌型経済は広がったのか
SUB-27: 世界大不況からの脱出-なぜ恐慌型経済は広がったのか【書評リンク】at 2009年03月20日 20:42
MAIN-29: こんにちは.
MAIN-29: 性格→正確でしょうか?
MAIN-29: 最近、大前研一が心理経済学という言葉を使ってますね。
MAIN-29: ファウンデーションを知ってか知らずか。

簡単に説明しますと、SUBが抽出された本文のなかでタイトルである可能性が高い部分。MAINが抽出された本文でタイトルでない可能性が高い部分です。もれなくゴミ無くかなり的確に本文を抽出されているようです。ここから、さらに他のサイトも試して精度を検証したいところですが、ちょっと今日は時間の関係でここまでです。

まとめ

Webstemmerを用いてblogの本文抽出に挑戦しました。まだ、1つのblogにしか適用していませんが、かなりの可能性を感じさせる良い結果でした。ここからさらに他のblogにも適用し結果を検証したうえで、上のシステムをライブラリとし2、3関数で動作するblogの本文抽出モジュールを作成したいと思います。