巨大なWikipedia全ページ要約xmlをジェネレータを使って良い感じに扱う

jawiki-latest-abstract.xmlは、[Wikipediaからダウンロード](https://dumps.wikimedia.org/jawiki/latest/)できるWikipediaの全ページ要約xmlです。このファイルは約1750万行ある1.84GByteの巨大ファイルです。このxmlをパースして処理するスクリプトを書いたところ、メモリ消費が激しい使い物にならないプログラムが完成しました。そのプログラムを逐次処理に書き換えてメモリ消費と実行速度の観点で改善するお話です。

![スクリーンショット 2015-11-05 14.46.18.png](https://qiita-image-store.s3.amazonaws.com/0/65312/8548cf79-dc30-ceda-0461-fcf5b25302b2.png "スクリーンショット 2015-11-05 14.46.18.png")



# 最初に書いたコード
xmlを一括でメモリに展開してから、BeautifulSoupで処理するプログラムです。

```py:xmlを一括でメモリに展開するコード
# -*- 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>`の範囲を逐次返却するプログラムに書き換えてみました。

```py:部分読み込みコード
# -*- 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のダウンロードできるデータファイル一覧](http://www.mwsoft.jp/programming/munou/wikipedia_data_list.html)

```xml:jawiki-latest-abstract.xml
....
</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 — ストリームを扱うコアツール](http://docs.python.jp/2/library/io.html)
[Wikipediaのダウンロードできるデータファイル一覧](http://www.mwsoft.jp/programming/munou/wikipedia_data_list.html)
[Wikipediaのdumpダウンロードページ](https://dumps.wikimedia.org/jawiki/latest/)