HTMLからの本文自動抽出

今日のテーマは、「HTMLファイルから「本文」だけを抽出するアルゴリズム」です。
(本格的な数理というよりは、アドホックな計算式を使ったテクニックになります)

では早速。

動機:Webサイトから本文を抜き出したい

ニュースサイトや、ブログ、など「テキストがコンテンツ」なWebサイトにおいて、単純にテキストだけを抜き出すと「本文とは関係のない広告テキスト」とかも一緒に入ってしまって残念なことがある。欲しいのは「本文」だけなのに。

なので「本文が埋め込まれているタグ」を特定するアルゴリズムがあると便利だ。

もちろん、毎日新聞朝日新聞といったニュースサイトなどのメジャーなサイトならば、本文を示すタグ(大抵はdivタグ)にidかclassの属性が付いていてXPath一発で引けるようになっているが、そんな「サイトごとのidやclassを自動作成する」という意味でも「本文タグ抽出アルゴリズム」が欲しいところ。

本文タグ抽出アルゴリズム

HTMLのDOMツリーのルートノードは、当然「本文」を含んでいるが、広告など余計なものも含まれてしまう。「本文を含む最小のテキスト」を含むタグを特定したい。「本文っぽさ」の基準として以下を考えた。

  • そのノードのテキストのサイズが大きいほど「本文」っぽい
  • 直下のノードのテキストは「段落」とかだろうから「本文」っぽい
  • 深い子孫ノードは、あまり「本文」に寄与しないようにすれば「最小のテキスト」をうまく表現できそう

なので、次のような指標を考えてみた。

TextVolume(node_i)=TextLength(node_i)+\sum_j\{TextVolume(child_j)\}
TextScore(node_i)=TextLength(node_i)+\alpha * \sum_j\{TextScore(child_j)\} (0 \leq \alpha < 1)
TextRate(node_i)=\frac{TextScore(node_i)^2}{TextVolume(node_i)}

\alphaは、深い子孫ノードのテキストを減衰させるためのパラメータ。例えば\alpha=0.5なら、ノード自身のテキストサイズに、直接の子ノードのサイズの半分、孫ノードのサイズの1/4が加算される。TextVolumeはノード配下の全テキストサイズの合計、TextRateは全テキストのうちどれくらいの割合を「そのノードのテキストサイズ(=TextScore)」が占めているかの指標で、さらにテキストサイズの絶対量が大きいものを優先するため分子を二乗している。

これでうまくいくだろうか。

実装&検証

PerlのHTML::TreeBuilderを使って実装した。ソースはgistで見れます。
\alphaによる減衰の例外として、pタグだけは対象外としている。
(テキスト抽出の意味ではpタグがあっても階層はフラットな方がいいため。)

で、実行してみたら結構うまくいった。

isobe@ubuntu:~/sample% perl honbun.pl TKY201111140426.html 20111115k0000m040022000c.html appsec01.html
TKY201111140426.html
==> id=, class=BodyTxt
20111115k0000m040022000c.html
==> id=, class=NewsBody
appsec01.html
==> id=centercol, class=

与えたhtmlファイルは順に、朝日新聞の今日の記事毎日新聞の今日の記事@ITの今日追加の記事、である。(出力された本文は省略。)
classやidが抜き出せているので、次からはXPathで一発でいける感じ。

試してみたい方は、gist.githubからダウンロード出来ますのでどうぞ。
(まぁこの手のアルゴリズムはあちこちにあるとは思いますが…)

感想

PerlのHTML::TreeBuilderを使ったのが初めてだったので調べるのに時間がかかりました。
あと計算式で「二乗するところ」は、実はコーディングしながら計算式を改良したので最初から思いついてたわけではなかったりします。