Python3でxmlをparseする

背景

構造化されたデータを読み込むことになり、そのデータが特定のURLにアクセスするとxml形式で取得できることがわかりました。

Python3をあまり知らないこともあり、同じような処理をこれからも行っていく予定なので、まとめようと思います。

HTTPクライアント

特定のURLにアクセスするためにHTTPクライアントを生成する必要があります。

私の知っている言語だとHTTPクライアントはいろんなライブラリがあったりして、それぞれ一長一短があります。Python3ではどうかなと思い、入門Python3を調べてみると、標準ライブラリurllib.requestが紹介された後に、requestsが紹介されていて、ほとんどの目的ではrequestsを使った方がウェブ開発が簡単になるようだと記載されています。わたしも倣ってrequestsを使ってみようと思います。

requestsのインストール

まずはじめにインストールする必要があります。

現在インストールされているパッケージを確認しましょう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ docker-compose run --rm web python -m pip list
Creating network "django_default" with the default driver
Creating django_web_run ... done
Package Version
--------------------------------- ---------
asgiref 3.4.1
backports.entry-points-selectable 1.1.0
boto3 1.18.38
botocore 1.21.38
certifi 2021.5.30
distlib 0.3.2
Django 3.2.7
filelock 3.0.12
jmespath 0.10.0
pip 21.2.4
pipenv 2021.5.29
platformdirs 2.3.0
python-dateutil 2.8.2
pytz 2021.1
s3transfer 0.5.0
setuptools 57.5.0
six 1.16.0
sqlparse 0.4.1
urllib3 1.26.6
virtualenv 20.7.2
virtualenv-clone 0.5.7
wheel 0.37.0

インストールされていないようなのでインストールします。Pipfileに追記します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
Django = "==3.2.7"
boto3 = "*"
requests = "*"

[dev-packages]

[requires]
python_version = "3.9"

インストールします。

1
2
3
4
$ docker-compose run --rm web python -m pipenv install --system --skip-lock
Creating django_web_run ... done
Installing dependencies from Pipfile...
🐍 ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 1/1 —

インストールされたかどうか確認します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$ docker-compose run --rm web python -m pip list
Creating django_web_run ... done
Package Version
--------------------------------- ---------
asgiref 3.4.1
backports.entry-points-selectable 1.1.0
boto3 1.18.38
botocore 1.21.38
certifi 2021.5.30
charset-normalizer 2.0.6
distlib 0.3.2
Django 3.2.7
filelock 3.0.12
idna 3.2
jmespath 0.10.0
pip 21.2.4
pipenv 2021.5.29
platformdirs 2.3.0
python-dateutil 2.8.2
pytz 2021.1
requests 2.26.0
s3transfer 0.5.0
setuptools 57.5.0
six 1.16.0
sqlparse 0.4.1
urllib3 1.26.6
virtualenv 20.7.2
virtualenv-clone 0.5.7
wheel 0.37.0

無事インストールされました。

requestsの基本的な使い方

次に基本的な利用方法を見ていきましょう。試すだけなのでDjango Shellを利用します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ docker-compose run --rm web python manage.py shell
Creating django_web_run ... done
Python 3.9.7 (default, Aug 31 2021, 19:01:35)
[GCC 10.3.1 20210424] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> import requests
>>> res = requests.get('https://google.co.jp')
>>> res.status_code
200
>>> res.headers['content-type']
'text/html; charset=Shift_JIS'
>>> res.encoding
'Shift_JIS'
>>> res.text
'<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="ja"><head><meta content="世界中のあらゆる情報を検索するためのツールを提供しています。さまざまな検索機能を活用して、お探しの情報を見つけてください。" name="description">...(省略)

HTTPクライアントを作成し、コンテンツを取得できました。requestsはかなり直感的でわかりやすいと思います。では実際にXMLコンテンツを取得し、parseしたいと思います。

XML parserの選定

python xml parserでググるとxml.etree.ElementTreeというライブラリが出てきます。

ドキュメント: https://docs.python.org/ja/3/library/xml.etree.elementtree.html

これは標準ライブラリのようなのでインストールは不要でimportすれば利用できます。

サンプルのXMLを使って、試してみましょう。

1
2
3
4
5
6
7
8
>>> import requests
>>> import xml.etree.ElementTree as ET
>>> res = requests.get('https://www.w3schools.com/xml/simple.xml')
>>> res.status_code
200
>>> root = ET.fromstring(res.text)
>>> root.tag
'breakfast_menu'

XMLのサンプルは

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<breakfast_menu>
<food>
<name>Belgian Waffles</name>
<price>$5.95</price>
<description>Two of our famous Belgian Waffles with plenty of real maple syrup</description>
<calories>650</calories>
</food>
<food>
<name>Strawberry Belgian Waffles</name>
<price>$7.95</price>
<description>Light Belgian waffles covered with strawberries and whipped cream</description>
<calories>900</calories>
</food>
<food>
<name>Berry-Berry Belgian Waffles</name>
<price>$8.95</price>
<description>Light Belgian waffles covered with an assortment of fresh berries and whipped cream</description>
<calories>900</calories>
</food>
<food>
<name>French Toast</name>
<price>$4.50</price>
<description>Thick slices made from our homemade sourdough bread</description>
<calories>600</calories>
</food>
<food>
<name>Homestyle Breakfast</name>
<price>$6.95</price>
<description>Two eggs, bacon or sausage, toast, and our ever-popular hash browns</description>
<calories>950</calories>
</food>
</breakfast_menu>

なので、正しくrootが取得できています。

また、先程のrootはイテレート可能な子ノードを持ちます。

1
2
3
4
5
6
7
8
>>> for child in root:
... print(child.tag, child.attrib)
...
food {}
food {}
food {}
food {}
food {}

上から構造化データを辿っていくことが可能です。

データ構造を調査する

上記の例で言うと、foodの下の階層の要素がわからない場合はどうしたらよいでしょうか?

そう言った場合は先程のrootと同じように以下のようにすると下の階層の要素のtagがわかります。

1
2
3
4
5
6
7
8
>>> foods = root.findall('food')
>>> for e in foods[0]:
... print(e.tag)
...
name
price
description
calories

そうすると、どう言ったtagを持った要素が存在するのかわかります。tagがわかればtag名でアクセス可能です。

1
2
>>> foods[0].find('name').text
'Belgian Waffles'

まとめ

Python3でHTTPクライアントとXMLパーサを触ってみました。

HTTPクライアントのrequestsはとても利用しやすく、直感的にわかる感じのパッケージでした。今後もrequestsを利用していこうと思います。

XMLパーサについては直感的に理解するのが難しく、Elementの要素に対してアクセスするのも一苦労でした。利用しながら慣れていこうと思います。

参考図書