BLOGTIMES
2009/02/12

PythonでXPathを使う

  python  xml 
このエントリーをはてなブックマークに追加

久しぶりにPytonのプログラムをいじらないといけないことになったので、ちょっと下調べ。

Pythonは2.5からElementTree XML APIが使えるようになっていて、単純にノードを取り出すにはこんな感じで簡単にできました。

from xml.etree import ElementTree xml = ElementTree.parse("test.xml") parsedxml.find('//foo').text

ここまでは簡単だったのですが、find()に渡せるXPathはpredicate*1がちゃんとサポートされていないようなので、複雑な条件を満たすノードは自分でトラバースする必要がありそうということでちょっと困ってしまいました。

test.xml

<?xml version="1.0" encoding="UTF-8"?> <a> <b> <c>1111</c> <d>a1</d> </b> <b> <c>2222</c> <d>a2</d> </b> </a>

xml.etree.ElementTreeでpredicateを使ったときの出力

>>> from xml.etree import ElementTree >>> xml = ElementTree.parse("test.xml") >>> xml.findall('//b') [<Element b at b7cb98>, <Element b at b7c878>] >>> xml.findall("//b[./c/text() = '2222']/d") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "F:\Python25\lib\xml\etree\ElementTree.py", line 647, in findall return self._root.findall(path) File "F:\Python25\lib\xml\etree\ElementTree.py", line 355, in findall return ElementPath.findall(self, path) File "F:\Python25\lib\xml\etree\ElementPath.py", line 198, in findall return _compile(path).findall(element) File "F:\Python25\lib\xml\etree\ElementPath.py", line 176, in _compile p = Path(path) File "F:\Python25\lib\xml\etree\ElementPath.py", line 93, in __init__ "expected path separator (%s)" % (op or tag) SyntaxError: expected path separator ([) >>>

lxmlを使ってみる

サンプルのXMLは簡単なのでいいのですが、今回扱わなければならないXMLはかなり構造が複雑なものなので、実装の負荷を考えると自分でトラバースするのは避けたいところです。

ということで色々と調べてみると、libxml2を呼び出すlxmlというライブラリがあり、こちらはちゃんとしたXPathが使えそうだということがわかりました。

Python Package Index : lxml 2.0.5

Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API.

Linuxなどを使っていて、libxml2やlibxsltなどのライブラリとコンパイル環境が既に存在している場合には、EasyInstall経由でインストールを行うのが一番簡単です。

easy_install lxml

今回はWindowsへのインストールだったので、lxmlの配布ページでで公開されている「lxml-2.0.5.win32-py2.5.exe (MS Windows installer )」を利用しました。これはコンパイル済みのバイナリなので、コンパイル環境がないWindowsにも簡単に導入できます。

ということで、上記のコードをlxmlを使ったものに書き換えたのがこちら。
predicateがきちんと評価されて結果が帰ってきています。

lxmlでpredicateを含むXPathを実行させた結果

>>> from lxml import etree >>> xml = etree.parse(open("test.xml", 'r'), parser=etree.XMLParser()) >>> xml.xpath("//b") [<Element b at d77060>, <Element b at d77090>] >>> xml.xpath("//b[./c/text() = '2222']/d/text()") ['a2'] >>>

最後に名前空間にはまる

とりあえず簡単なサンプルでフィージビリティが確認できたので、もうちょっと本格的なXMLを食わせてみたら、対象となるXMLにはxmlnsが指定されていてそのままでは動きませんでした。

test.xml w/ namespace

<?xml version="1.0" encoding="UTF-8"?> <a xmlns="http://example.com/foo"> <b> <c>1111</c> <d>a1</d> </b> <b> <c>2222</c> <d>a2</d> </b> </a>

namespaceを指定しなかったとき

>>> from lxml import etree >>> xml = etree.parse(open("test.xml", 'r'), parser=etree.XMLParser()) >>> xml.xpath("//b") [] >>> xml.xpath(".") [<Element {http://example.com/foo}a at baaf30>]

ちょっとベタな填り方なのでググってみたら、全く同じところではまっていたXPathでnamespaceにハマった。 - Humming Via Kitchenというエントリを参考に下記のようにソースを書き換えて事なきを得ました。

namespaceの指定をつけたとき

>>> from lxml import etree >>> xml = etree.parse(open("test.xml", 'r'), parser=etree.XMLParser()) >>> xml.xpath("//n:b", namespaces={"n":"http://example.com/foo"}) [<Element {http://example.com/foo}b at d77060>, <Element {http://example.com/foo}b at d77090>] >>> xml.xpath("//n:b[./n:c/text() = '2222']/n:d/text()", namespaces={"n":"http://example.com/foo"}) ['a2']
  • *1: 述語。[expression] のような部分

こんな記事もあります 「ElementTree lxml findall
Plesk の VPS に yum を入れる
トラックバックについて
Trackback URL:
お気軽にどうぞ。トラックバック前にポリシーをお読みください。[policy]
このエントリへのTrackbackにはこのURLが必要です→https://blog.cles.jp/item/2921
Trackbacks
このエントリにトラックバックはありません
Comments
愛のあるツッコミをお気軽にどうぞ。[policy]
古いエントリについてはコメント制御しているため、即時に反映されないことがあります。
babo (2011/06/28 11:03) <%HatenaAuth()%>

わたしもElementTreeのxpathでinvalid predicateが出て悶々としてこちらのサイトに辿り着いたのですが。

その後、xml.findall("//table[@id = "hoge"]")をxml.findall("//table[@id="hoge"]")のように=周辺のスペースを除去したらちゃんと処理されましたよ@.@

Comments Form

コメントは承認後の表示となります。
OpenIDでログインすると、即時に公開されます。

OpenID を使ってログインすることができます。

Identity URL: Yahoo! JAPAN IDでログイン