見出し画像

XMLの構造を理解する(Using Python to Access Web Data: Week 5)

引き続き、ミシガン大学がCoursera上で開講しているPython for Everybody Specializationの第3コース、Using Python to Access Web Dataを受講した記録です。前回のWeek 4では、Webサーバへの接続と簡単なWebスクレイピングの方法についてを学びました。

Week 5では、XMLと呼ばれる構造言語を学んでいきます。この週でXMLを、次の週でJSONを学び、Webサービスに用いられるAPIなどの初歩を概観して講座を締めくくるカリキュラムになっています。

<テキストの範囲>
Chapter 13: Using Web Services


1.HTTP以外のデータの送受信

これまでは、Webサーバに接続し、ファイルをリクエストし、HTMLで書かれた情報を読み取るといった処理を学んできました。この講義では、Pythonプログラムで書かれ、構成されたデータを、他のWebサービスに伝送する手法を学びます。こちら側がPythonでディクショナリ形式で構造化したデータを送信したいとき、受け取り手が必ずしもPythonプログラムに対応しているとは限りません。そのため、送信元と送信先の両方が統一したデータ構造をもってデータをやり取りする必要がでてきます。これを可能にするのがXMLやJSONです。

送信元である私たちは、Pythonで構築されたデータ構造をXMLやJSONにserialize(テキスト言語に変換)します。受け取り手である送信先は、XMLやJSONをde-serialize(テキスト言語からデータ構造に変換)して自身のプログラムに取り込みます。


2.eXtensible Markup Language (XML)

XMLは、HTMLのようにタグで囲まれた形でデータを構造化したものです。タグの中にタグを作ることもでき、各要素を階層化することもできます。

<class>
    <students>
        <name>Michael Jackson</name>
        <phone type="domestic">+1 999 999 9999</phone>
    </students>
    <students>
        <name>Whitney Houston</name>
        <phone type="domestic">+1 111 111 1111</phone>
        <email hide="yes" />
    </students>
</class>

XMLは、タグの構造によって樹形図のような図を描くことができます。この例では、classの中に2つのstudents要素があり、その2つの要素の一方にはname, phoneの2要素を持つ一方、片方にはname, phone, emailの3つの要素を持っています。ここで、phone要素にはattributeである"domestic"と電話番号が記載されています。

ツリー構造はファイルパスの構造としても書くことができます。

class/students/name Michael Jackson
class/students/phone +1 999 999 9999


3.XMLスキーマ

なるほどよくわかった、Pythonで書かれたデータ構造をXMLに変換してデータを送信すれば、受信側はそのXML構造を理解して受信側のシステムに取り込まれる……というと現実はそうではありません。受け渡しをするXMLの構造について事前に取り決めをしておかなければなりません。XMLがどのような構造を持っており、どのようなattributionを持っているのか、という全体的な設計構造をXMLスキーマといいます。

ホテルや航空機の予約システムを考えてみましょう。旅行代理店やホームページ、空港にある専用の端末などから入力されたデータは、一定の事前に決められた統一された構造にもとづいて送受信されます。送信されたデータはXMLとして正しい構造であるか、に加えてXMLスキーマに沿ったデータが送られてきているかを確認します。この処理をValidationといいます。

例えば、XMLスキーマの条件が以下のように定義されているとします。

<xs:complexType name="person">
  <xs:sequence>
    <xs:element name="lastname" type="xs:string"/>
    <xs:element name="age" type="xs:integer"/>
    <xs:element name="dob" type="xs:date"/>
  </xs:sequence>
</xs:complexType>

まず、1段目では、personというタグがcomplex方式で定義されています。complex方式とは、子のタグが存在するという意味を持ちます。2行目に、子の要素elementは順番でなければならないことを宣言しています。3行目から5行目で、このデータ構造にはlastname, age, dobの3つの要素があることを宣言しています。それぞれのデータの型も指定されています。

データの送信側は、このXMLスキーマに合うようにXMLを書かなければなりません。

<person>
  <lastname>Suzuki</lastname>
  <age>32</age>
  <dob>1987-10-06</dob>
</person>

こうして作られたXMLがネットワークを介して送受信され、受信側のValidatorが検証して、問題がなければシステムに取り込まれることになります。

日付だけでなく、時刻も指定する場合はXMLスキーマにおいて

<xs:element name="startdate" type="xs:dateTime"/>

といった形で指定します。ただし、送受信の側でタイムゾーンが違っていることがありますので、その考慮をしなければなりません。ISO8601は、日付と時間についての表示について規格を定めています。

<startdate>2020-02-28T05:03:34Z</startdate>

このようにISO8601の規格に合った形式で、日付はYYYY-MM-DD、時刻の前にTを挿入し、hh:mm:ssと時刻を記述し、最後にタイムゾーンを指定します。タイムゾーンは世界標準時(UTC)を指す場合、Zの1文字ですが、日本時間(UTC+9:00)を指定する場合は、

<startdate>2020-02-28T05:03:34+09:00</startdate>

といったように記載します。


4.PythonでXMLを処理する

PythonでXMLを処理するには、xml.etree.ElementTreeライブラリを使います。ただし、毎回xml.etree.ElementTreeと書くのは面倒ですので、asという命令を用いて"ET"などの短縮した形式にすると便利でしょう。

Pythonのプログラム内でXMLを書くには、複数行を持つテキストをトリプルクオーテーションで定義します。

import xml.etree.ElementTree as ET
data = '''<person>
  <lastname>Suzuki</lastname>
  <age>32</age>
  <dob>1987-10-06</dob>
  <email hide="yes" />
</person>''' #...[1]

tree = ET.fromstring(data) #...[2]
print('Name:', tree.find('lastname').text) #...[3]
print('Attr:', tree.find('email').get('hide')) #...[4]

[1]のように、複数行を持つXMLデータをdata変数に読み込みます。次に、[2]でテキストをツリー構造に構築し直し、tree変数に格納します。

[3]では、find()によってlastname要素を読み込みます。ここでは、elementやattributeも一緒に読み込んでしまいますので、欲しいデータであるテキストのみを取り出すという意味で、後に.textと付けています。
同様に、[4]では、findでemail要素を読み込んでいますが、ここで欲しいのはattributeのデータです。そこで、get('hide')としてattributeにあるデータを取り出します。したがって、表示される結果は以下のようになります。

Name: Suzuki
Attr: yes

次に、少し複雑な構造を持つXMLについて、要素やattibuteを取り出してみましょう。

import xml.etree.ElementTree as ET
input = '''<stuff>
 <users>
   <user x="2">
     <id>001</id>
     <name>Goro</name>
   </user>
   <user x="7">
     <id>009</id>
     <name>Hanako</name>
   </user>
 </users>
</stuff>'''

stuff = ET.fromstring(input)
lst = stuff.findall('users/user') #...[1]
for item in lst:
   print('Name:', item.find('name').text)
   print('Id:', item.find('id').text)
   print('Attr:', item.get("x")) #...[2]

今度は、usersの中に2つのuserがおり、それぞれがidとnameを持つ構造になっています。[1]では、users/userの条件を満たす構造を検索し、リスト化する命令をしています。lst変数には、[x=2のツリー, x=7のツリー]という2つの構造が格納されることになります。そのため、lstの中にあるそれぞれのツリーを表示するよう、for文で一つずつツリーを見ていき、中にあるName, Id, Attributeを表示させるようにしています。[2]のget関数はusers/userのツリーの根幹にあるattributeですので、findをする必要はありません。


今回の講義はここまでになります。次のWeek 6ではJSONやAPIを取り扱っていきます。それでは、次回のエントリもお楽しみに!

この記事が気に入ったらサポートをしてみませんか?