試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

PythonでExpatを使用してXML文書の高速な解析を行う

(バージョン2.0以上の)Pythonでは、Expatというライブラリが提供する機能(xml.parsers.expatモジュール)により、XML文書の構文高速にを解析することができる。
解析器の種類としてはストリーム型かつイベント駆動型となり、文書の内容を先頭から読み進めて、要素などを発見したところで内部的な「イベント」が発生し、特定の(ハンドラ)関数が呼び出される仕組みになっている。
なお、Pythonではこの他に、同じタイプのxml.saxと、XMLツリー全体を保持するタイプのxml.dom(の系列)も用意されていて、いずれもバージョン2.0から利用できる(ここでは扱わない)。

関連URL:

その仕組み上、複雑な構造のXMLファイルを処理して色々とたどるのには向かないと思われるが、逆に、高速であることを活かして、構造の単純な設定ファイルなどの処理に使うと便利かもしれない。
下はリファレンスを参考にした使用例。実際には各ハンドラ関数に色々な処理を追加することになる。
[任意]ファイル名: expattest.py

#! /usr/bin/python
# -*- encoding: utf-8 -*-

from xml.parsers.expat import ParserCreate
import sys

def start_element(name, attrs):
  print u"要素の開始: %s".encode("utf-8") % name.encode("utf-8")
  for (key, val) in attrs.iteritems():  # 辞書の各キーに対する処理を行う
    print u" 属性: %s = %s".encode("utf-8") % (key.encode("utf-8"), val.encode("utf-8"))
def end_element(name):
  print u"要素の終了: %s".encode("utf-8") % name.encode("utf-8")
def char_data(data):
  if data != "\n":
    print u" 文字データ: %s".encode("utf-8") % data.encode("utf-8")

p = ParserCreate()
p.buffer_text = True  # 文字データが細切れにならないように
# ハンドラ関数の関連付け
p.StartElementHandler = start_element  # 要素の開始
p.EndElementHandler = end_element      # 要素の終了
p.CharacterDataHandler = char_data     # 文字データ

if len(sys.argv) < 2:
  print >> sys.stderr, u"エラー: 入力ファイルが指定されていません".encode("utf-8")
  sys.exit(1)
infile = sys.argv[1]
try:
  f = open(infile)
except IOError:
  print >> sys.stderr,  u'エラー: ファイル "%s" が開けませんでした'.encode("utf-8") % infile
  sys.exit(1)
p.ParseFile(f)  # 処理を実行

(2009/3/6)import文とprint文を微調整
ここでは、例として、エクスポートしたはてなダイアリーXMLファイルの形式*1を持ったファイルを用意して実験する。
[任意]ファイル名: test.xml

<?xml version="1.0" encoding="UTF-8"?>
<diary>
<day date="2008-06-01" title="">
<body>
6/1の本文
</body>
<comments>
<comment>
<username>6/1のコメント1ユーザ名</username>
<body>6/1のコメント1本文</body>
<timestamp>6/1のコメント1日時</timestamp>
</comment>
<comment>
<username>6/1のコメント2ユーザ名</username>
<body>6/1のコメント2本文</body>
<timestamp>6/1のコメント2日時</timestamp>
</comment>
</comments>
</day>
<day date="2008-06-02" title="">
<body>
6/1の本文
</body>
<comments>
<comment>
<username>6/2のコメント1ユーザ名</username>
<body>6/2のコメント1本文</body>
<timestamp>6/2のコメント1日時</timestamp>
</comment>
<comment>
<username>6/2のコメント2ユーザ名</username>
<body>6/2のコメント2本文</body>
<timestamp>6/2のコメント2日時</timestamp>
</comment>
</comments>
</day>
<day date="2008-06-03" title="">
<body>
6/3の本文
</body>
<comments>
<comment>
<username>6/3のコメント1ユーザ名</username>
<body>6/3のコメント1本文</body>
<timestamp>6/3のコメント1日時</timestamp>
</comment>
<comment>
<username>6/3のコメント2ユーザ名</username>
<body>6/3のコメント2本文</body>
<timestamp>6/3のコメント2日時</timestamp>
</comment>
</comments>
</day>
</diary>

下は実行例。

$ ./expattest.py test.xml
要素の開始: diary
要素の開始: day
 属性: date = 2008-06-01
 属性: title =
要素の開始: body
 文字データ:
6/1の本文

要素の終了: body
要素の開始: comments
要素の開始: comment
要素の開始: username
 文字データ: 6/1のコメント1ユーザ名
要素の終了: username
要素の開始: body
 文字データ: 6/1のコメント1本文
要素の終了: body
要素の開始: timestamp
 文字データ: 6/1のコメント1日時
要素の終了: timestamp
要素の終了: comment
要素の開始: comment
要素の開始: username
 文字データ: 6/1のコメント2ユーザ名
要素の終了: username
要素の開始: body
 文字データ: 6/1のコメント2本文
要素の終了: body
要素の開始: timestamp
 文字データ: 6/1のコメント2日時
要素の終了: timestamp
要素の終了: comment
要素の終了: comments
要素の終了: day
要素の開始: day
 属性: date = 2008-06-02
 属性: title =
要素の開始: body
 文字データ:
6/1の本文

要素の終了: body
要素の開始: comments
要素の開始: comment
要素の開始: username
 文字データ: 6/2のコメント1ユーザ名
要素の終了: username
要素の開始: body
 文字データ: 6/2のコメント1本文
要素の終了: body
要素の開始: timestamp
 文字データ: 6/2のコメント1日時
要素の終了: timestamp
要素の終了: comment
要素の開始: comment
要素の開始: username
 文字データ: 6/2のコメント2ユーザ名
要素の終了: username
要素の開始: body
 文字データ: 6/2のコメント2本文
要素の終了: body
要素の開始: timestamp
 文字データ: 6/2のコメント2日時
要素の終了: timestamp
要素の終了: comment
要素の終了: comments
要素の終了: day
要素の開始: day
 属性: date = 2008-06-03
 属性: title =
要素の開始: body
 文字データ:
6/3の本文

要素の終了: body
要素の開始: comments
要素の開始: comment
要素の開始: username
 文字データ: 6/3のコメント1ユーザ名
要素の終了: username
要素の開始: body
 文字データ: 6/3のコメント1本文
要素の終了: body
要素の開始: timestamp
 文字データ: 6/3のコメント1日時
要素の終了: timestamp
要素の終了: comment
要素の開始: comment
要素の開始: username
 文字データ: 6/3のコメント2ユーザ名
要素の終了: username
要素の開始: body
 文字データ: 6/3のコメント2本文
要素の終了: body
要素の開始: timestamp
 文字データ: 6/3のコメント2日時
要素の終了: timestamp
要素の終了: comment
要素の終了: comments
要素の終了: day
要素の終了: diary

関連記事:

*1:手元でエクスポートしたものを参考にしたので、完全な形かどうかは不明