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

orangain flavor

じっくりコトコト煮込んだみかん2。知らないことを知りたい。

PythonでブログのHTMLから本文抽出 2015

2015-12-20 19:14追記: readabilityの説明を追加・修正しました。

Webページをクロールした時に、ざっくりと本文 (ページ内の重要なコンテンツ) のみを抽出できると便利です。 Google検索すると、特に日本語だとExtractContent以外の情報があまり見つかりません。 ExtractContentは昔使ったことがあり、たしかに便利なのですが、公開が2007年と若干古いので今でも使えるのかという疑問がありました。また、Pythonで他の選択肢として使えるライブラリは、非日本語圏の方が作ったものと思われるので、日本語のページで問題なく使えるのか知りたかったので調べてみました。

比較するライブラリ

比較したのは以下の5つのライブラリです。

dragnet eatiht extractcontent goose readability
パッケージ名 dragnet eatiht extractcontent (GitHubのみ) goose-extractor readability-lxml
最新版 1.0.1 0.1.14 0.0.1 1.0.25 0.6.1
最新版公開日 2015-01-29 2015-03-28 2011-02-25 2015-01-03 2015-08-26
Python 3対応 × ◯ (GitHubのもの) × ×
ライセンス MIT License MIT License 不明 Apache License 2.0 Apache License 2.0
備考 Moz による研究成果。 rodrigo palacios氏 による研究成果。 Rubyextractcontent.rbPythonに移植したもの。 ScalaGoosePythonに移植・改良したもの。 BookmarkletReadabilityPythonに移植・改良したもの。

アルゴリズムはあまり詳しく把握していませんが、extractcontentのみ正規表現ベースで、その他はHTMLの木構造を見ているようです。 extractcontentのみが日本語圏で開発され、その他は非日本語圏で開発されたものと思われます。

検証するサンプル

適当に思いついた日本語と英語のブログサービスから、サンプルとなるページをピックアップしました。サンプルの選定は適当ですが、画像のみのページでは意味がないので、本文が存在するものを選んでいます。本文の長さはいろいろです。

ブログサービス サンプルページ
(ja) はてなブログ 澤なんて大したことない。 - Yukibou's Hideout on Hatena
http://potatostudio.hatenablog.com/entry/2015/12/17/073000
(ja) アメブロ 子育てらくがき20151126「磁石」|絵かき屋picoの「一期一絵」
http://ameblo.jp/pico-art/entry-12099702897.html
(ja) ライブドアブログ 湯布院にあるハリーポッターの世界!フローラルビレッジ : 九州ドライブ 面白スポット ナビ
http://drive-kyuusyuu.blog.jp/floralvillage.html
(ja) WordPress.com ipython_config.pyの設定メモ « ひよっこ。
https://prepro.wordpress.com/2012/01/05/ipython_config-py%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%83%A1%E3%83%A2/
(ja) ココログ 勤勉にサービスしすぎるから生産性が低いのだよ!日本人は: hamachanブログ(EU労働法政策雑記帳)
http://eulabourlaw.cocolog-nifty.com/blog/2015/12/post-ce16.html
(ja) Seesaaブログ やっぱBABYMETALのスタッフは俺たちのサイトをチェックしてるんじゃね? 【海外の反応】: BABYMETALIZE
http://niyaniyakaigai.seesaa.net/article/431409315.html
(en) Medium My journey into the Berlin startup scene — Wandering CTO — Medium
https://medium.com/wandering-cto/my-journey-into-the-berlin-startup-scene-4dc8faecd305
(en) オリジナル (github.com/blog) How the Services team uses GitHub · GitHub
https://github.com/blog/2093-how-the-services-team-uses-github
(en) Blogger Official Google Blog: ICYMI: A few stocking stuffers from around Google
https://googleblog.blogspot.jp/2015/12/icymi-few-stocking-stuffers-from-around.html
(en) Tumblr Marissa's Tumblr
http://marissamayr.tumblr.com/day/2015/12/10
(en) Typepad Third Party Services: the Wrap Up - Everything Typepad
http://everything.typepad.com/blog/2015/12/third-party-services-the-wrap-up.html

検証スクリプト

実行環境はPython 2.7.11です。抽出結果は目視で確認します。

検証結果

以下の表で、◯は正常に本文を取得できたもの、△は一部のみ取得できたもの、×は空白や関係ないコンテンツが返ってくるなど正常に取得できたとは言えないものを表します。

dragnet eatiht extractcontent goose readability
(ja) はてなブログ × (エラー) × ×
(ja) アメブロ × × (エラー) ×
(ja) ライブドアブログ × (エラー) ×
(ja) WordPress.com △ (ソースコード部分のみ) ×
(ja) ココログ △ (一部のみ) × (エラー) ×
(ja) Seesaaブログ × (エラー) × (エラー)
(en) Medium
(en) オリジナル (github.com/blog)
(en) Blogger × × × ×
(en) Blogger + Splash ×
(en) Tumblr × (エラー)
(en) Typepad
備考 改行は消える。 明らかに処理速度が遅い。筆者は違いを理解していないがv2とetv2という別の手法も使える。 言語の情報を使用している。画像や映像を抜き出す機能もある。 Bookmarkletという出自のせいか、プレーンテキストではなくHTMLの結果が得られる。

Bloggerは本文が<script type='text/template'>タグ内に格納されており、JavaScriptレンダリングされているという特殊性からか、eatiht以外の全ライブラリで本文を抽出できませんでした。このため、先日の記事で紹介したSplashを使ってレンダリングしたものからの抽出も試しました。

Seesaaブログは唯一エンコーディングShift_JISで、HTMLの構造がおかしいのかdragnetとgooseで他では起きないエラーが起きました。

処理速度について

およそ (速) extractcontent, dragnet > readability, goose >>>>> eatiht (遅) という傾向でした。

考察

dragnet

英語のページでは問題ありませんが、日本語のページでは一部エラーが出ました。 改行が失われるので若干使いづらそうです。

元々以下の記事を見て比較を始めたのですが、英語のページでは精度が高いようです。

eatiht

唯一Bloggerから正確に本文を抽出できましたが、日本語のページの多くではエラーが出て使用できませんでした。 処理速度は他のライブラリに比べて10〜50倍程度遅かったです。

今回はeatihtという一番古くからあると思われるモジュールを使いましたが、v2とetv2という別のモジュールも使用できます。特にetv2を使うと日本語のページからもエラーなく本文を取得できましたが、Bloggerからは本文を取得できなくなりました。また、処理速度が遅いのも変わりません。

extractcontent

正規表現ベースのシンプルな手法にも関わらず、はてなブログ以外は正しく抽出でき、精度は高かったです。 2011年からメンテナンスされていないので大丈夫かと思っていましたが、2.7で実行時にエラーが発生することはありませんでした。

はてなブログから正常に取得できないのは不可解なので調べてみると、Google Adsense用のコメントによって本文を抽出するための正規表現が間違っているのが原因のようです。 forkされた以下のものを使うと正常に取得できますが、Google検索しても見つからないので難しい気がします。

https://github.com/petitviolet/python-extractcontent

goose

基本的にエラーにはならないものの、日本語のページからはまったく本文を抽出できませんでした。 (試していませんが) 言語の設定があるので、予めWebページの言語がわかっている場合には指定してやることでうまく取得できるのかもしれません。 SeesaaブログはHTMLが壊れているためか、再帰呼出しの回数が上限を超えてRuntimeErrorになりました。

readability

Blogger以外からは正しく本文を抽出できました。 Python 3にも対応しており、使いやすそうです。 細かい点ですが、本文として取得した文字列の前後に空白が残っており、自分でstripする必要がありそうです。 Bookmarkletという出自のせいか、取得できるのはプレーンテキストではなくHTMLです。 プレーンテキストが欲しい場合には自分でタグを削除する必要があります。

おまけ: Webstemmerを使ってBloggerから本文を抽出する

これまで紹介したような、ルールに従って単一のページから本文を取得する手法のほかに、似た構造の複数のページを比較することで本文を取得する手法もあります。この手法では、最終更新が2009年とやや古いですが、Webstemmerが有名なようです。

Webstemmerを使って、ルールベースのライブラリが苦手としていたBloggerのHTMLから本文を取得してみます。 ただし、公式ページに公開されているバージョン0.7.1では以下の問題があったので、修正したものを使用しました。

  • bsddbモジュールを使用していてPython 2.7では動かない
  • HTTPSのWebページをクロールできない。

以下のコマンドでインストールできます。

$ pip install https://github.com/orangain/webstemmer/archive/master.tar.gz#egg=webstemmer

以下のように実行できます。 textcrawler.py コマンドに -a オプションをつけずに実行すると、 (今回は不要な) アーカイブページが大量にクロールされるので時間がかかります。

$ textcrawler.py -o googleblog -a 'https://googleblog\.blogspot\.jp/\d+/\d+/[^/]+\.html$' https://googleblog.blogspot.jp/
$ analyze.py googleblog.201512201516.zip > googleblog.pat
$ extract.py googleblog.pat googleblog.201512201516.zip > googleblog.txt

以下のようにいい感じに取得できました。素晴らしいですね。

...
!MATCHED: 201512201516/googleblog.blogspot.jp/2015/12/icymi-few-stocking-stuffers-from-around.html
PATTERN: 201512201516/googleblog.blogspot.jp/2015/12/meet-pixel-c-our-take-on-tablet.html
SUB-0: Official Google Blog: ICYMI: A few stocking stuffers from around Google
TITLE: ICYMI: A few stocking stuffers from around Google
MAIN-5: Between last-minute gift shopping, airport pickups, cookie baking, and ugly-sweater parties, there’s a lot to do this season. So you may have missed a few updates from around Google that can actually make your holiday season a little brighter (or at least make your to-do list go a little faster. Won’t make your sweater any less ugly, though). Here’s a look at what we’ve unwrapped recently:
MAIN-5: Add this one to your to-do list: Reminders in Google Calendar
MAIN-5: Whether it’s “send holiday cards” or “use up FSA,” you can now add Reminders to Google Calendar to help you complete your to-do list. These aren’t like those calendar entries you create yourself that you plain-old ignore completely and that then disappear. With Reminders, if you don’t complete the task and dismiss the Reminder, it’ll pop up on your calendar again the next day. And the next. And the … until you can’t take it anymore and just send those holiday cards already. You’ll thank us when your list is checked off. Twice.
MAIN-5: Now on Tap gets handier for the holidays
MAIN-5: Now on Tap helps you get quick information without leaving the app you're using by tapping and holding the home button on Android phones—and new updates make it even handier for the holidays. So if you get a text with your cousin’s flight number, you can tap and hold to see the flight’s status, then respond without having to juggle between searching and texting. If you ordered a gift online and want to know if it will make it down the chimney and under the tree on time, tap and hold your confirmation email to get tracking info. Consider it your own personal Santa’s Little Helper.
MAIN-5: Tell the family when to expect you with trip bundles
MAIN-5: There’s probably a lot going on in your email right now if you’ve got an upcoming trip home or holiday getaway planned. From your flight confirmation to rental car details, Inbox by Gmail already groups these emails into trip bundles so you can find everything you need for your trip quickly. Those bundles just got even more useful—you can now access them offline (good for on the plane), share the trip summary with friends or family, and add other pertinent emails (like that message with your aunt’s new address) to the bundle.
MAIN-5: Templates in Google Docs go mobile
MAIN-5: If you’re collecting family recipes or planning a trip, templates in Docs, Sheets and Slides help you get started faster, so you can spend more time concentrating on the words you’re writing and less time worrying about how it looks. These pre-made templates are now available on Android and iOS so you can do more while on the go. Ho, ho, ho!
SUB-6: Posted by Abbi Tatton, Google Editorial Elf
SUB-7: IMAGE URL
SUB-8: Abbi Tatton
SUB-9: Editorial Elf
SUB-10: Google

まとめ

検証した範囲ではおおむね問題なく本文を取得でき、Python 3にも対応しているreadabilityが良さそうです。もちろん検証に使用するサンプルによって結果は変わってくるので、あくまで参考と捉えてください。

extractcontentは正規表現ベースの単純なスクリプトで高速かつ精度よく本文を抽出できます。ただ、Python版はGoogle検索のトップに引っかかるものは不具合があり、メンテナンスされているとも言えないので、人には勧めづらいです。

Webstemmerはアプローチが異なりますが、複数のページをクロールできる場合には精度よく本文を取得できそうです。

参考