Python lxml: レガシーシステムにおけるXML/HTML処理の「強力な武器」

Python tutorial - IT technology blog
Python tutorial - IT technology blog

なぜ2024年になってもXMLを扱う必要があるのか?

JSONは現代のアプリケーションにおける「スター」かもしれませんが、XMLは依然として金融、保険、銀行などのシステムの屋台骨となっています。実際、私は500MBに及ぶSOAPのログファイルや、毎分同期が必要な膨大なRSSフィードを処理したことがあります。このようなケースでは、Python標準のxml.etree.ElementTreeライブラリでは速度が追いつかず、複雑なXPathなどの高度な機能も不足しがちです。そこで必要になるのがlxmlです。

lxmlは、非常に強力な2つのCライブラリであるlibxml2libxsltをベースに構築されています。以前、200万件以上のレコードを古いERPシステムからPostgreSQLに移行した際、lxmlのおかげでパース時間を数時間からわずか数分に短縮できました。壊れたHTML(broken HTML)や多層構造のXMLに直面しているなら、これが最適なツールです。

lxmlとElementTreeের決定的な違い

APIは似ていますが、実際のプロジェクトで実感できるlxmlの優れた価値が3つあります。

  • 圧倒的なパフォーマンス: C言語で記述されているため、大容量ファイルの処理においてElementTreeよりも10倍から20倍高速です。
  • 包括的なXPath 1.0対応: 多重のforループを書かなくても、たった1行のコードで深い階層のデータにクエリを投げられます。
  • 寛容なパース能力: lxmlのパーサーは非常にスマートです。古いHTMLによくある閉じタグの欠落や構造の誤りを自動的に修正してくれます。

lxmlの実践的な使い方

ライブラリのインストール

まずはpipでインストールします。ターミナルを開いて以下のコマンドを実行してください:

pip install lxml

1. 文字列またはファイルからXMLをパースする

SOAP APIからのデータをシミュレートしたXMLをlxmlで処理する方法を見てみましょう:

from lxml import etree

xml_data = """
<root>
    <item id="1">
        <name>製品A</name>
        <price>100</price>
    </item>
    <item id="2">
        <name>製品B</name>
        <price>200</price>
    </item>
</root>
"""

# 文字列から直接パース
root = etree.fromstring(xml_data)

# データの取得
for item in root.findall('item'):
    name = item.find('name').text
    price = item.find('price').text
    print(f"ID: {item.get('id')} - 名前: {name} - 価格: {price}")

2. XPathによるコードの最適化

lxmlの最大のメリットはXPathです。ツリーを手動で辿る代わりに、必要なデータへ直接「ジャンプ」できます。例えば、IDが2の製品名を取得するには、以下のように記述するだけです:

# id='2'を持つitemタグ内のnameタグのテキストを取得
product_name = root.xpath("//item[@id='2']/name/text()")
print(product_name[0])  # 結果: 製品B

XPathを使用することで、特にXML構造が頻繁に変更される場合でも、コードを簡潔に保、メンテナンス性を高めることができます。

3. システム連携用のXMLファイル作成

レガシーシステムに送信するためにデータをパッケージ化する必要がある場合、lxmlは直感的な方法を提供します:

root = etree.Element("data")

# 子ノードと属性の作成
user = etree.SubElement(root, "user", status="active")
name = etree.SubElement(user, "fullname")
name.text = "Nguyen Van A"

# 整形された文字列(pretty print)として出力
xml_output = etree.tostring(root, pretty_print=True, encoding='utf-8').decode('utf-8')
print(xml_output)

オートメーションにおける「汚い」HTMLの処理

古い社内サイトからデータをクロールする場合、HTMLが乱れていることがよくあります。lxml.htmlはこの混乱を自動的に「クリーニング」する能力を持っています。

from lxml import html

broken_html = "<html><body><div><p>重要なデータ<li>Item 1</body>" 

tree = html.fromstring(broken_html)
# lxmlは不足しているpタグやdivタグを自動的に閉じます
clean_text = tree.xpath("//p/text()")[0]
print(clean_text)

BeautifulSoupと比較して、lxmlは生のパース速度で勝っています。毎日数百万ページを処理する必要がある場合、パフォーマンス面でlxmlが第一候補となります。

XSLTによるデータ変換

XSLTは、生のXMLをHTMLレポートや他の形式に変換するための非常に強力なツールです。銀行などで明細書を出力する際によく使われる手法です。

xslt_root = etree.XML('''
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:template match="/">
            <html>
                <body>
                    <h2>製品リスト</h2>
                    <table border="1">
                        <tr><th>名前</th></tr>
                        <xsl:for-each select="root/item">
                            <tr><td><xsl:value-of select="name"/></td></tr>
                        </xsl:for-each>
                    </table>
                </body>
            </html>
        </xsl:template>
    </xsl:stylesheet>
''')

transform = etree.XSLT(xslt_root)
result = transform(root)
print(str(result))

トラブルを避けるための実践的なTips

長年lxmlを使ってきた経験から、非常に重要な3つの注意点を挙げます:

  1. 名前空間(Namespace)の処理: SOAPのXMLには、よく名前空間(soap:Envelopeなど)が含まれています。XPath関数では名前空間マップを必ず定義してください。そうしないと、返り値が常に空になってしまいます。
  2. メモリ管理: 数GBクラスの巨大なファイルにetree.fromstring()を使用してはいけません。ストリーミング形式で処理するetree.iterparse()を使用することで、RAMの使用量を数GBから数MBに抑えることができます。
  3. エンコーディング: 古いソースからのデータをパースする際の文字化けを防ぐため、常にバイト文字列として扱い、エンコーディングを明示的に指定することを推奨します。

Lời kết

古いデータの処理は華やかではないかもしれませんが、それはただのプログラマと真のシステムエンジニアを分けるスキルです。lxmlは問題を素早く解決するだけでなく、正規表現(regex)を使うよりもはるかにプロフェッショナルなコードを実現します。SOAP、RSS、あるいはレガシーシステムに立ち向かっているなら、今すぐlxmlを導入しましょう。

Share: