Pythonでスクレイピングに最適なライブラリはlxmlな気がした。時間的な意味で
ここ数日でHTMLからTagを除去する方法を、色々知った。とても勉強になりました。教えてくれた人ありがとうです。
具体的には、BeautifulSoupとHTMLParserとlxmlという3つのライブラリでそれぞれTag除去が可能な事が分かった。実際どれも満足な挙動で、じゃあどれを使えばいいのさ!と、迷ったので実行速度を適当に測ってみた。
時間を計るところのコードが激しく恥ずかしい。ホントは、3つのファンクションを配列に入れて、forで回したかったけど、配列に入れる時に評価されてしまってNG、map関数で、関数と関数(計測したい関数と、計測する関数)を2つ渡すやり方がわかんなかったので、同じ事を3回書く事にした。マジ恥ずかしいがこれしか思いつかなかった。
計測用のHTMLには、はてダのトップページとした、コメント、Style、Script、htmlがそこそこのボリュームで入っていた為
計測環境
# coding:utf-8 from urllib import urlopen from BeautifulSoup import BeautifulSoup from lxml.html import fromstring from HTMLParser import HTMLParser from timeit import Timer from time import time class TagStrip(HTMLParser): # id:aodag兄さん提供 def __init__(self): HTMLParser.__init__(self) self.datum = [] self.instyle = False def handle_data(self, data): if data.strip() and not self.instyle: self.datum.append(data) def getString(self): return "".join(self.datum) def handle_starttag(self, tag, attrs): if tag == 'style' or tag == 'script': self.instyle = True def handle_endtag(self, tag): if tag == 'style' or tag == 'script': self.instyle = False def getHtml(url): return urlopen(url).read() def useBS(html): # id:y_yanbe さん提供モノ # http://python.g.hatena.ne.jp/y_yanbe/20081025/1224910392 soup = BeautifulSoup(html) text = '\n'.join([e.string for e in soup.findAll() if e.string!=None and e.name not in ('script','style')]) return text def useLXML(html): # id:Alexandre さん提供モノ # http://d.hatena.ne.jp/a2c/20081025/1224924646#c1225076104 et = fromstring(html) xpath = r'//text()[name(..)!="script"][name(..)!="style"]' text = ''.join([text for text in et.xpath(xpath) if text.strip()]) return text def useHP(html): p = TagStrip() p.feed(html) return p.getString() if __name__ == '__main__': url = 'http://www.hatena.ne.jp/' repeatCnt = 30 htmlSource = getHtml(url) tmpDelta, timeList = [],{} print 'BS start!' for i in range(repeatCnt): start = time() useBS(htmlSource) tmpDelta.append( time() - start) timeList['BS'] = sum(tmpDelta)/repeatCnt print 'LXML start!' tmpDelta = [] for i in range(repeatCnt): start = time() useLXML(htmlSource) tmpDelta.append( time() - start) timeList['LXML'] = sum(tmpDelta)/repeatCnt print 'HP start!' tmpDelta = [] for i in range(repeatCnt): start = time() useHP(htmlSource) tmpDelta.append( time() - start) timeList['HP'] = sum(tmpDelta)/repeatCnt print timeList
とりあえづ、30回くらいの平均でやってみた。何回かやったけど、それほどばらつき無く同じような結果が出たので、信じる事にした。以下結果(改行たしてあります)
{ 'BF': 0.58448076248168945, 'LXML': 0.01511224110921224, 'HP': 0.03491028149922689 }
PySpaで作ってた時にBSつかってて、何となく遅いなぁって思ってたけど、ひょっとしたら、BSが引っかかってたのかも知れないと思った。100個くらいのHTMLからTag除去したら1分(BS)と1秒(lxml)くらいの差が出来ると思うと、lxmlしか選択肢は無いなぁと思った。HTMLParseもまぁまぁ速いけど、クラスをオーバライドしなくちゃscriptとかstyleに対応できないのがめんどくさい。
それぞれ適材適所が有るかと思いますが、大量のhtmlファイルからTagを除去するには、lxmlが向いていると思いました。
id:aodag兄さん、id:y_yanbeさん、id:Alexandreさん 教えてくれて有り難うございました。大変勉強になりました。
宿題として、華麗にTimerを使いこなせるようになる事が追加されました。
追記
タグを除去したテキストファイルがなにげに各ライブラリで差があるなぁと感じた。テキストをまんま載せるのは丸ごと引用になりそうだったので、載せるのを躊躇った。代わりにwcの結果
% cat lxml_log.txt|wc 35 168 7586 % cat BeautifulSoup_log.txt|wc 24 121 6464 % cat HTMLParce_log.txt|wc 34 166 7582
短かけりゃ or 長けりゃ 良いってモンでもないがタグを完全に除去してル事を考えたら、誤判定除去が少ない方が長いので、ここでもlxmlが優秀なきがした。BSだと、nbsp とかがそのまま出てしまっている。それでこれだけ短いという事は他になにかが消えてるって事か。