jawiki-latest-abstract.xmlは、WikipediaからダウンロードできるWikipediaの全ページ要約xmlです。このファイルは約1750万行ある1.84GByteの巨大ファイルです。このxmlをパースして処理するスクリプトを書いたところ、メモリ消費が激しい使い物にならないプログラムが完成しました。そのプログラムを逐次処理に書き換えてメモリ消費と実行速度の観点で改善するお話です。
最初に書いたコード
xmlを一括でメモリに展開してから、BeautifulSoupで処理するプログラムです。
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
from BeautifulSoup import BeautifulSoup
file_path = '/tmp/jawiki-latest-abstract.xml'
txt = ''
line_count = 17522478
count = 0
# Fileを読む
f = open(file_path, 'r')
for line in f:
txt += line
count += 1
if count % 1000 == 0:
print '{}/{}'.format(count, line_count)
# parseする
soup = BeautifulSoup(txt)
# <doc>タグ毎に処理する
print 'finish'
実行結果
10万行読み込んだ時点で530秒掛かってしまい、BeautifulSoupでパースする前に手動で終了させてしまいました。1.84GByteあるファイルは、全てメモリ上に展開するには巨大すぎたみたいです。
generatorを使うと逐次処理が簡単に書ける
Pythonのgeneratorは遅延評価です。一括で計算せず呼び出され必要になった分だけ計算して返却します。つまりgeneratorを使うと巨大ファイルを必要な部分だけ読み込み処理するコードを書くことができます。
改善したコード
一括読み込みプログラムを改善して、generatorを使ったxmlの<doc>〜</doc>
の範囲を逐次返却するプログラムに書き換えてみました。
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
from BeautifulSoup import BeautifulSoup
file_path = '/tmp/jawiki-latest-abstract.xml'
line_count = 17522478
def file_read_generator():
"""
XMLをパースして<doc> 〜 </doc>を読み込み返却するgenerator
:rtype: str
"""
separate = '</doc>'
f = open(file_path, 'r')
count = 0
txt = ''
# Fileを1行ずつ読む
for line in f:
txt += line
count += 1
if separate in line:
result = txt
txt = ''
yield result
if count % 1000 == 0:
print '{}/{}'.format(count, line_count)
for body in file_read_generator():
# parseする
soup = BeautifulSoup(body)
print soup.title
print soup.abstract
# print soup.findAll('anchor')
print 'finish'
実行結果
10万行読み込みまで25秒と20倍以上高速に処理できるようになりました。また読み込んで処理した変数は上書きして再利用しているため、メモリ消費も1/5の11.6MByteまで削減できました。
jawiki-latest-abstract.xmlについて
Wikipediaではスクレイピング禁止と定められており、全データのdumpが公開されているのでdumpの利用を推奨されています。Wikipediaの全ページ要約xmlであるjawiki-latest-abstract.xmlはそんな公開されているdumpファイルの1つです。Wikipediaのダウンロードできるデータファイル一覧
....
</links>
</doc>
<doc>
<title>Wikipedia: うすた京介</title>
<url>https://ja.wikipedia.org/wiki/%E3%81%86%E3%81%99%E3%81%9F%E4%BA%AC%E4%BB%8B</url>
<abstract>・愛知県</abstract>
<links>
<sublink linktype="nav"><anchor>経歴</anchor><link>https://ja.wikipedia.org/wiki/%E3%81%86%E3%81%99%E3%81%9F%E4%BA%AC%E4%BB%8B#.E7.B5.8C.E6.AD.B4</link></sublink>
<sublink linktype="nav"><anchor>人物</anchor><link>https://ja.wikipedia.org/wiki/%E3%81%86%E3%81%99%E3%81%9F%E4%BA%AC%E4%BB%8B#.E4.BA.BA.E7.89.A9</link></sublink>
<sublink linktype="nav"><anchor>趣味</anchor><link>https://ja.wikipedia.org/wiki/%E3%81%86%E3%81%99%E3%81%9F%E4%BA%AC%E4%BB%8B#.E8.B6.A3.E5.91.B3</link></sublink>
<sublink linktype="nav"><anchor>影響</anchor><link>https://ja.wikipedia.org/wiki/%E3%81%86%E3%81%99%E3%81%9F%E4%BA%AC%E4%BB%8B#.E5.BD.B1.E9.9F.BF</link></sublink>
<sublink linktype="nav"><anchor>メディア出演など</anchor><link>https://ja.wikipedia.org/wiki/%E3%81%86%E3%81%99%E3%81%9F%E4%BA%AC%E4%BB%8B#.E3.83.A1.E3.83.87.E3.82.A3.E3.82.A2.E5.87.BA.E6.BC.94.E3.81.AA.E3.81.A9</link></sublink>
<sublink linktype="nav"><anchor>作品リスト</anchor><link>https://ja.wikipedia.org/wiki/%E3%81%86%E3%81%99%E3%81%9F%E4%BA%AC%E4%BB%8B#.E4.BD.9C.E5.93.81.E3.83.AA.E3.82.B9.E3.83.88</link></sublink>
<sublink linktype="nav"><anchor>アシスタント</anchor><link>https://ja.wikipedia.org/wiki/%E3%81%86%E3%81%99%E3%81%9F%E4%BA%AC%E4%BB%8B#.E3.82.A2.E3.82.B7.E3.82.B9.E3.82.BF.E3.83.B3.E3.83.88</link></sublink>
<sublink linktype="nav"><anchor>脚注</anchor><link>https://ja.wikipedia.org/wiki/%E3%81%86%E3%81%99%E3%81%9F%E4%BA%AC%E4%BB%8B#.E8.84.9A.E6.B3.A8</link></sublink>
<sublink linktype="nav"><anchor>外部リンク</anchor><link>https://ja.wikipedia.org/wiki/%E3%81%86%E3%81%99%E3%81%9F%E4%BA%AC%E4%BB%8B#.E5.A4.96.E9.83.A8.E3.83.AA.E3.83.B3.E3.82.AF</link></sublink>
</links>
</doc>
<doc>
<title>Wikipedia: 浦沢直樹</title>
...
参考
15.2. io — ストリームを扱うコアツール
Wikipediaのダウンロードできるデータファイル一覧
Wikipediaのdumpダウンロードページ