【Python入門】ElementTreeを使ってXMLの解析に挑戦しよう!

こんにちは!フリーランスエンジニア・ライターの平山です。

みなさんはPythonを学習していて次のようなことで困ったことはありませんか?

APIからXMLデータを取得したいけど、どうやったらいいのだろう・・・
XMLデータを解析したいけど、やり方がわからない・・・

XMLデータの取得、解析はPython以外の知識も要求されるため、はじめての方には特に難しく感じられるかもしれません。

そこで、この記事では

  • XMLの基礎知識
  • APIにアクセスしてXMLデータを取得してくる方法
  • XMLを操作するための各種メソッド

これを読めばXMLが一通り扱えるように解説していきます。XMLを利用できるようになり、できることの幅を広げましょう!

本記事を読む前に、Pythonがどんなプログラミング言語なのかをおさらいしておきたい人は次の記事を参考にしてください。

→ Pythonとは?特徴やできること、活用例をわかりやすく簡単に解説

なお、その他のPythonの記事についてはこちらにまとめています。

目次

XMLの基礎

XMLとは?


まずはXMLの基礎をおさえましょう。XMLはExtensible Markup Languageの略称です。「拡張可能なマーク付け言語」と訳されることもありますね。

マークアップ言語(Markup Language)というと、多くの方に馴染みがあるのはHTMLかもしれません。こちらはHyperText Markup Languageの略語で、主にWebサイトの構築に用いられますね。

では、XMLはどのように使われているかというと、次の例があげられます。

  • アプリケーション間でデータのやりとりを行う用途
  • アプリケーションの設定ファイル


みなさんが使っているアプリのフォルダ内にもXMLファイルがあるかもしれません。

また、最近は後述のJSONに押され気味ですが、APIの出力ファイルとしてXMLが使われることも多いです。Pythonを使ってWebスクレイピングをやっているとそこそこ見かけますね。

さて、XMLとはなにか?という話に戻りましょう。XMLは拡張可能なマーク付け言語の略である、とのことでした。拡張可能とは、ユーザーが独自にタグを追加可能である、ということを指します。

HTMLではブラウザが解釈できるタグしか使うことができません。ですが、XMLではユーザーが独自にタグを定義して追加することができます。

次の例を見てみましょう。

<?xml version="1.0"?>
<data>
    <country name="Liechtenstein">
        <rank>1</rank>
        <year>2008</year>
        <gdppc>141100</gdppc>
        <neighbor name="Austria" direction="E"/>
        <neighbor name="Switzerland" direction="W"/>
    </country>
    <country name="Singapore">
        <rank>4</rank>
        <year>2011</year>
        <gdppc>59900</gdppc>
        <neighbor name="Malaysia" direction="N"/>
    </country>
    <country name="Panama">
        <rank>68</rank>
        <year>2011</year>
        <gdppc>13600</gdppc>
        <neighbor name="Costa Rica" direction="W"/>
        <neighbor name="Colombia" direction="E"/>
    </country>
</data>

これはPythonドキュメントにある国名リストになります。
URL:https://docs.Python.jp/3/library/xml.etree.elementtree.html

一見HTMLのようなタグがありますが、 <country name> <rank>など、HTMLでは見かけないタグがありますね。このように、ユーザーが自分の都合に応じて、好きなようにタグを拡張可能な言語がXMLなのです。

JSONとは?

JSONについても軽く紹介しておきましょう。JSONはJavaScript Object Notationの略称です。名前にJavaScriptと入っていますが、JS専用ではありません。

JSONは軽量なデータ記述言語として設計されました。さまざまなソフトウェア、プログラミング言語間のデータの受け渡しができるように作られていることも特徴的です。人間にとって読み書きが簡単で、コンピュータにも簡単に扱えるファイル形式を目指しています。

このような性質から2006年に仕様が策定されて以来、急速に勢力を伸ばしてきました。特にブラウザで非同期通信を行うAjaxと相性がよく、APIの出力フォーマットとしてもよく使われています。

また、Pythonの設定ファイルを記述する際にも使えるので、こちらも習得するとできることの幅が広がりますね!

XMLの操作・解析方法

まずはAPIにアクセス


それではXMLを実際に操作・解析する方法を見ていきましょう。まずは、PythonでAPIに接続して、XMLを取得するところまでです。

例として郵便番号から住所を取得するAPIをつかって、XMLを取得してみましょう。

今回は「郵便番号検索API」を使用させていただきました。有用なAPIを公開してくれている作者の方に感謝ですね。

ソースは次のようになります。

import urllib.request
import urllib.parse

params = {"zn":1030000}

p = urllib.parse.urlencode(params)
url = 'http://zip.cgis.biz/xml/zip.php?' + p

req = urllib.request.Request(url)

with urllib.request.urlopen(req) as response:
    xml_string = response.read()

まず、URLを扱うために、urllib.request、urllib.parseをインポートします。urllib.requestはURLへの接続、ファイルの読み込みを担当します。urllib.parseはURLの解釈(パース)を担当します。

次に検索したい郵便番号をznに指定します。そして、pにパラメータをURL形式に変換したものを代入し、次の行でGETパラメータを追加したURLを生成します。変数urlを処理しやすくするため、Requestオブジェクトに格納します。

最後にファイルをオープンして、無事APIの中身が取得ができました。なお、この段階ではxml_stringに格納されているのは「XMLファイルの形式をしたただの文字列」であることに注意してください。XMLとして解析するために、新しいライブラリが必要になります。

次節で解析の手順を見ていきましょう。

XMLを解析する

つづいて、取得してきたXMLを解析しましょう。解析には標準ライブラリのElementTreeを使います。解析の一歩目は取得してきたデータのルート要素を取得することです。ルートを取得することで解析の起点を得るわけですね。

また、WebAPIから取得してきたデータは、先にも言ったとおり、そのままでは単なる文字列です。そのため、fromstringを使い、Element オブジェクトにパースしましょう。

import xml.etree.ElementTree as ET

root = ET.fromstring(xml_string)

APIに接続してからXMLにパースするまでのソースをまとめると次のようになります。

import urllib.request
import urllib.parse

params = {"zn":1030000}

p = urllib.parse.urlencode(params)
url = 'http://zip.cgis.biz/xml/zip.php?' + p

req = urllib.request.Request(url)

with urllib.request.urlopen(req) as response:
    xml_string = response.read()

import xml.etree.ElementTree as ET

root = ET.fromstring(xml_string)

今回取得したXMLデータが下になります。

<ZIP_result>
    <result name="ZipSearchXML"/>
    <result version="1.01"/>
    <result request_url="http%3A%2F%2Fzip.cgis.biz%2Fxml%2Fzip.php%3Fzn%3D1030000"/>
    <result request_zip_num="1030000"/>
    <result request_zip_version="none"/>
    <result result_code="1"/>
    <result result_zip_num="1030000"/>
    <result result_zip_version="0"/>
    <result result_values_count="1"/>
    <ADDRESS_value>
        <value state_kana="トウキョウト"/>
        <value city_kana="チュウオウク"/>
        <value address_kana="none"/>
        <value company_kana="none"/>
        <value state="東京都"/>
        <value city="中央区"/>
        <value address="none"/>
        <value company="none"/>
    </ADDRESS_value>
</ZIP_result>

では、このデータに対して簡単な解析を行ってみましょう。まずはじめにルートのタグと属性を取得してみます。

root は tag , attrib を使うことでタグと属性を取得できます。

print(root.tag,root.attrib)

結果

ZIP_result {}

タグはノードとも呼ばれます。自分の下の階層にあるノードを子ノード。自分の上の階層にいるノードを親ノードと呼びます。さらに、子供の子供を孫ノードと呼びます。

それ以上の親子関係は名前としてはほぼ使われません。それではルートの子ノードを取り出してみましょう。子ノードの取り出し方は2種類あります。

まずは特定の値を取り出す方法から。

child = root[0]
child.tag
child.attrib

結果

result {'name': 'ZipSearchXML'}

子ノードへのアクセスはリストと同様にできます。そのため、取り出したいインデックスを指定して、その後、 tag , attrib を使うことでタグと属性を取得できます。

子ノードを一括で取り出したい場合は次のようにします。

for child in root:
    print(child.tag,child.attrib)

結果

result {'name': 'ZipSearchXML'}
result {'version': '1.01'}
result {'request_url': 'http%3A%2F%2Fzip.cgis.biz%2Fxml%2Fzip.php%3Fzn%3D1030000'}
result {'request_zip_num': '1030000'}
result {'request_zip_version': 'none'}
result {'result_code': '1'}
result {'result_zip_num': '1030000'}
result {'result_zip_version': '0'}
result {'result_values_count': '1'}
ADDRESS_value {}

若干発展的な話になりますが、rootはタグと属性の辞書を持ち、イテレート可能な子ノードを持ちます。これさえ覚えておけば、これらの操作も当然として扱えるでしょう。

応用的なXMLの操作

それでは最後に応用的なXMLの操作も紹介します。覚えておくと便利な操作なので、ぜひ御覧ください。

タグ名で抽出するiter()

iter()はタグ名を引数に持つメソッドで、指定されたタグ名をルートから順次取り出します。

for value in root.iter('value'):
    print(value.attrib)

結果

{'state_kana': 'トウキョウト'}
{'city_kana': 'チュウオウク'}
{'address_kana': 'none'}
{'company_kana': 'none'}
{'state': '東京都'}
{'city': '中央区'}
{'address': 'none'}
{'company': 'none'}

タグ名で子ノードを抽出するfindall()

findall()はタグ名を引数に持つメソッドで、指定されたタグ名の子ノードをルートから順次取り出します。

for result in root.findall('result'):
    print(result.attrib)

結果

{'name': 'ZipSearchXML'}
{'version': '1.01'}
{'request_url': 'http%3A%2F%2Fzip.cgis.biz%2Fxml%2Fzip.php%3Fzn%3D1030000'}
{'request_zip_num': '1030000'}
{'request_zip_version': 'none'}
{'result_code': '1'}
{'result_zip_num': '1030000'}
{'result_zip_version': '0'}
{'result_values_count': '1'}

先に紹介した itter() との違いは、次のとおりです。

  • iter() :配下の子ノードや孫ノードを深さによらず取得
  • findall() :直下の子ノードのみを取得

XPath 表現でより自由に要素を取得する

XPath はXML形式の文章から簡潔に要素を指定して抽出するために作られた言語です。ErementTreeはXPath 表現を一部サポートしているため、これを使ってより簡潔に要素を抽出できます。

まずは次の構文をおさえておくと良いでしょう。

  • . (ピリオド)現在のノード。今回の例の場合、ルートを指します。
  • タグ名 名前が一致するタグのすべての小要素を選択します。
  • * すべての小要素を選択します。
  • / タグ名を接続するときに使います。

# ADDRESS_valueタグ配下のvalueタグをすべて選択します
for i in root.findall('./ADDRESS_value/value'):
    print(i.attrib)
{'state_kana': 'トウキョウト'}
{'city_kana': 'チュウオウク'}
{'address_kana': 'none'}
{'company_kana': 'none'}
{'state': '東京都'}
{'city': '中央区'}
{'address': 'none'}
{'company': 'none'}

他にも更に複雑な操作が可能なので、気になる方は公式ドキュメントをご参照ください。
URL:https://docs.python.jp/3/library/xml.etree.elementtree.html#xpath-support

XMLの取得・解析まとめ

いかがでしたか?

今回は次のことをお伝えしました。

  • XMLの基礎
  • APIからXMLを取得する方法
  • XMLを解析・操作する方法


最近JSONに押され気味のXMLではありますが、まだまだ官公庁のAPIなど、さまざまな場面で現役で活躍しています。PythonでXMLを扱えるようになることで、より様々なデータが扱えるようになるでしょう。

もし、XMLの操作がわからなくなったら、またこの記事を読み直してくださいね!

この記事を書いた人

フリーのエンジニア・ライター。
プログラミング、ライティング、マーケティングなど、あらゆる手段を駆使して、
ハッピーなフルリモートワーカーを目指し中。

最近興味がある分野は深層強化学習。
積みゲー、積ん読がどんどん増加しているのがここ数年の悩み。
実は侍エンジニア塾の卒業生だったりします。

目次