2013/06/30

ライブラリ:PyYAML

Pythonの「PyYAML」というライブラリについてご紹介します。

import yaml

「PyYAML」(ライブラリとしてimportするときは「yaml」)は、YAML形式のデータを扱うためのライブラリです。

YAMLはXMLやJSONと比べるとシンプルなので自分の手でparseするのでもさほど大変ではないかとは思いますが、やはり、ライブラリを使うのが早くて確実かと思います。

PyYAMLには大きく2つの機能――読み込みと書き込みとがあります。以下、順に見ていきます。

YAMLデータの読み込み

import yaml

FILEIN_DICT = "sample_dict.yaml"

f = open(FILEIN_DICT, 'r')
data = yaml.load(f)  # 読み込む
f.close()
YAML形式のデータを読み込むには、yaml.load()を使います。引数には、読み込みモードで開いたファイルオブジェクトを渡します。

以下、「sample_dict.yaml」の中身が次のとおりだと仮定して見てみます。
# dict
name: Takeda Shingen
sex: male
class: Daimyo
region: Kai
year: [1521, 1573]
nickname:
- Kazchiyo
- Kai no tora
YAMLに馴染みがなくてYAMLの文法がよくわからない、という方は、公式サイト(この投稿を書いている時点での最新版は1.2です)Wikipediaのページを参照いただくとよいかと思います。

print type(data).__name__  # dict
トップの階層がハッシュ形式となっているデータを読み込んだときは、load()で生成されるのは辞書型の変数です。

import pprint
pprint.pprint(data)
# 次のとおり出力される
# {'class': 'Daimyo',
#  'name': 'Takeda Shingen',
#  'nickname': ['Kazchiyo', 'Kai no tora'],
#  'region': 'Kai',
#  'sex': 'male',
#  'year': [1521, 1573]}
中身は通常の辞書型のデータになっています。バリューにリストがある場合は、それもPythonのリストとして格納されます。

ここでpprint.pprint()というのを使っていますが、これは変数の中身を整形して表示するためのもので、yamlとは関係ありません(pprintも使うにはインストールが必要です)。

次に、トップの階層がリストになっているデータの場合を見てみます。こちらも基本的にはハッシュと同じです。
FILEIN_DICT = "sample_list.yaml"

f = open(FILEIN_LIST, 'r')
data = yaml.load(f)  # 読み込む
f.close()

「sample_list.yaml」の中身は次のとおりとします。
# list
- C
- Java
- Python
- Ruby
- name: Japanese
  region: Japan
- {name: Kagoshima-ben, region: Kagoshima}

print type(data).__name__  # list
こちらはリストに格納されます。

import pprint
pprint.pprint(data)
# 次のとおり表示
# ['C',
#  'Java',
#  'Python',
#  'Ruby',
#  {'name': 'Japanese', 'region': 'Japan'},
#  {'name': 'Kagoshima-ben', 'region': 'Kagoshima'}]
ハッシュ型と同じく、ノードとしてリストやハッシュを含む場合は、それら子孫ノードもリストや辞書として格納してくれます。

続いて、書き込みの方を。書き込みといっても、文字列に展開する機能だけです。

YAMLデータの書き込み

mylist = {'dogs': ['pochi', 'taro', 'kai'],
          'cats': ['shimajiro', 'tyson']}

f = open('out.yaml', 'w')
f.write(yaml.dump(mylist))  # 書き込む
f.close()
書き込みは、yaml.dump()で行います。dumpで生成されるのは文字列なため、ファイルに書き込む場合は別途ファイルオブジェクトを用意して書き込みます。

最後のもうひとつ、型について少しだけ見てみます。

型の指定

YAMLでは「!!int」といった「タグ」を各ノードの付ける(付記する)ことによって、型の指定ができるようです。私自身使ったことはないのですが、たとえば
  • YAML Python
  • !!null None
  • !!float float
  • !!set set
といったように、YAMLでのタグとPython型との間で対応しているそうなので、型をしっかり見て使う場合なんかには公式ドキュメントの該当するパートをごらんいただくのがよいかと思います。

以上です。


インストール
「pip」が入っていれば、「pip install pyyaml」でインストールすることができます。


参考
PyYAML - PyPI
PyYAMLDocumentation - PyYAML
python - Best way to parse a YAML file - Stack Overflow
YAML - ウィキペディア

2013/06/26

ライブラリ:simplejson

Pythonの「simplejson」というライブラリについてご紹介します。

import simplejson

「simplejson」は、JSON形式のファイルをPythonで手軽に扱うためのライブラリです。Python 2.6から標準ライブラリに組み込まれた「json」ライブラリは、このsimplejsonと同じ内容になっています。

「simlejson」と「json」とでは使える関数なんかも全く同じで、ちがうのは、simplejsonの方が頻繁にアップデートされている点。simplejsonの方は、関数のオプション引数が追加されていたり、パフォーマンスが改善されていたりするそうです。

Pythonを普通に使う分にはjsonでよいかと思うのですが、実行速度が気になる状況や新しい機能を利用したい場合なんかにはsimplejsonを導入するのがよいかと思います。

stack overflowにjsonとsimplejsonのちがいについての質問・回答なんかもありますのでよろしければそちらもどうぞ。

基本的な使い方はjsonとまったくいっしょなので、詳しくはjsonの投稿をご覧ください。


参考
simplejson - PyPI
simplejson — simplejson documentation
simplejson - github
`json` and `simplejson` module differences in Python - Stack Overflow

2013/06/18

ライブラリ:json

Pythonの「json」というライブラリについてご紹介します。

import json

「json」は、その名のとおり、JSON形式のファイルをPythonで扱うためのライブラリです。

jsonライブラリの魅力は、何といってもシンプルな点です。「ElementTree」などに見られるような高度な機能は備えていませんが、本当にシンプルなのですぐに使いはじめることができます。

以下、使い方を見ていきます。

JSONで主に使う機能は次の2つです。
  1. json.load(f)
  2. json.dump(obj, f)

前者のload()はJSONファイルからデータを読み込むときに、dump()はJSONファイルへとデータを書き込むときに使用します。まずは読み込みについて、次に書き込みについて見ていきます。

JSONファイルの読み込み

import json

FILEIN = 'sample_in.json'

f = open(FILEIN, 'r')
data = json.load(f)
f.close()
open()で生成したファイルオブジェクトをload()関数に渡すことで、JSONファイルを読むことができます。

以下、sample_in.jsonには以下のような内容が入っているものとして見ていきます。
{
    "version": "1.0",
    "encoding": "UTF-8",
    "feed": {
        "title": {
            "type": "text",
            "$t": "Life with Python"
        },
        "entry": [{
        },{
            # エントリ1の中身
        },{
            # エントリ2の中身
        }]
    }
}

print type(data).__name__  # dict
load()で生成されるのは、通常の辞書型のオブジェクトです。ですので、いったん読み込んだら、あとはもう通常の辞書型のデータとして扱うことができます。
print data['version']  # 1.0
print data['encoding']  # UTF-8
for k in data:
    print k
# 以下のとおり表示
# feed
# version
# encoding
print data['feed']['title']['$t']
# Life with Pythonと表示
下の階層のデータを取得するには、このようにひとつずつ掘り下げていきます。このあたりもシンプルで、ElementTreeなどにあるような便利な機能はありません。

つづいて保存について。

JSONファイルへの書き込み

FILEOUT = 'sample_out.json'

f = open(FILEOUT, 'w')
json.dump(data, f)
f.close()
ファイルに書き込むときに使うのは、dump()です。引数は最小で2つあり、ひとつめに書き込みたいオブジェクトを、ふたつめに書き込みモードで開いたファイルオブジェクトを渡します。

ここでは辞書型のデータについて見てみましたが、そのほかの形式については次のように対応しています。

対応関係

JSONの各型は右のPythonの各型に対応しています。
  • object: dict
  • array: list
  • string: unicode
  • int: int, long
  • real: float
  • true: True
  • false: False
  • null: None
JSONでのobjectはPythonのdict型に、arrayはlist型に対応しています。load()のときはJSON→Python、つまり左から右に、dump()のときは逆で右から左に変換するイメージです。TrueやFalseなんかもうまく変換してくれるようです。

詳しくは公式ドキュメントのEncoders and Decodersのパートを参照されるとよいかと思います。

今回取り上げたload()、dump()はファイルを対象とした関数ですが、文字列を対象とした関数も用意されています。load()、dump()に対応するのが、それぞれの末尾にsをつけた
  1. loads(string)
  2. dumps(obj)
です。使い方もload()、dump()と全く同じ感じで使えるので、いったん文字列に起こしてから使いたい場合なんかはこちらを使うのがよいかと思います。

いずれの関数も細かいオプション設定ができるようになっているので、そのあたり詳しくは公式のドキュメントなどをご参照ください。

コマンドラインでの使い方

jsonには、コマンドラインで使うためのツールも用意されています。使用するのは、json.toolというモジュールです。
C:\>echo {"json": "obj", "json2": "obj2" } | python -m json.tool
{
    "json": "obj",
    "json2": "obj2"
}
C:\>echo [1, 2, 3] | python -m json.tool
[
    1,
    2,
    3
]
これはWindows XPで実行した場合です。引数の部分を「"」で囲うか囲わないかといったちがいはあるかと思うのですが、他のOSでもおおむね同じような形で使えるかと思います。

整形されていないjsonファイルを整形したい場合なんかには次のようにすると手軽に整形することができます。
C:\>python -m json.tool < 未整形のjson > 整形後のjson

ちょっとしたときに便利ですね。


・・・余談ですが、このBloggerでは、フィードURLにalt=jsonというパラメータをつけるとjson形式で返してくれます。たとえばこのブログならこんな感じです。練習台として便利なのでよろしければどうぞ。


参考
json - Python公式ドキュメント
JSON - python入門から応用までの学習サイト

2013/06/13

ライブラリ:BeautifulSoup

Pythonの「BeautifulSoup」というライブラリについてご紹介します。

import BeautifulSoup

BeautifulSoupは、バージョン3までと現在最新のバージョン4とでパッケージ名が異なります。バージョン4ではBeautifulSoup4もしくはbs4という名前になっています。
  • バージョン3: BeautifulSoup
  • バージョン4: bs4

今回はバージョン3を対象に述べていきます。ちなみに、バージョン3はPyton 3には対応していないため、Python 3で使うならバージョン4の方を使うことになります。

BeautifulSoup(バージョン3まで)には大きく分けて2つのクラスが入っています。BeautifulSoupとBeautifulStoneSoupです。

ちがいは次の点です。
  • BeautifulSoup: HTML用
  • BeautifulStoneSoup: XML用
(ちなみに、BeautifulSoupのバージョン4では、BeautifulStoneSoupはBeautifulSoupに吸収され存在しません)。

基本的な考え方はHTMLでもXMLでも同じなので、今回はElementTreeと対比させる意味も含めて、後者のBeautifulStoneSoupの方を見ていきます。

XMLファイルの読み込み

from BeautifulSoup import BeautifulStoneSoup

XMLFILE = 'sample.xml'

f = open(XMLFILE, 'r')
soup = BeautifulStoneSoup(f.read())
f.close()
XMLファイルを読み込むには、通常のファイルオープンをしてテキストを抽出し、そこからBeautifulStoneSoupインスタンスを生成します。

以下、samle.xmlには以下の内容が入っていると想定してみていきます。
<post attrA="value A" attrB="value B">
    <title>This is a title.</title>
    <categories>
        <!-- This is a comment. -->
        <category term="Asia"/>
        <category term="South America"/>
        <category term="Europe"/>
    </categories>
</post>

データの読み方

上のコードのとおりXMLのおおもととなるインスタンスsoupを生成したら、あとは木を辿ってデータを見ていきます。木を辿るのに使うのは、主に次のふたつのメソッドです。
  • soup.find()
  • soup.findall()

BeutifulStoneSoupでは、各ノードを「Tag」インスタンスとして保持します。各ノードの情報にアクセスするには、主に次のアトリビュート・メソッドを用います。
  • tag.name
  • tag.string
  • tag.attrs
  • tag.get()

以下、いろいろ見ていきます。

print soup.__class__.__name__
# BeautifulStoneSoupと出力
上のコードで生成したsoupはBeautifulStoneSoupのインスタンスです。

print soup.find('category').__class__.__name__
print soup.find('category').name
print soup.find('category').get('term')
print soup.find('category').attrs
# 以下のとおり出力
# Tag
# category
# Asia
# [(u'term', u'Asia')]
tag.find(タグ名)で子孫のノードにアクセスすることができます。各タグはTagのインスタンスです。nameにはそのタグ名が入っています。get()あるいはattrsで、タグのアトリビュート情報にアクセスできます。BeautifulStoneSoupでは、parse読み出した文字列をすべてユニコードに変換して扱います。

print soup.find('title').string
# 以下のとおり出力
# This is a title.
tag.stringにはそのタグ内の文字列が入っています。

es = soup.findAll('category')
for e in es:
    print e.name, e.get('term')
# 以下のとおり出力
# category Asia
# category South America
# category Europe
tag.find(タグ名)が最初に引っかかった要素を1つだけ返すのに対し、tag.findAll(タグ名)は引っかかった要素をすべてリストにして返してくれます。

es = soup.findAll('category', term='Europe')
for e in es:
    print e.name, e.get('term')
# 以下のとおり出力
# category Europe
find()、findAll()を使うときは、タグ名だけでなくアトリビュートも手がかりに検索することができます。ここでは、termの値が「Europe」となる要素を検索しています。

print soup.post.title.string
# 以下のとおり出力
# This is a title.
タグの親子関係をもとにデータを取得することも可能です。この場合は、soup以下にある「post」タグの下にある一番最初の「title」タグを取得しています。

その他の機能

# 整形表示
print soup.prettify()
取得したXMLを整形して表示するために、prettify()というメソッドが用意されています。

# 元データのエンコード
print soup.originalEncoding
元データの文字コードは、originalEncodingで確認することができます。

# 子要素を取得
print soup.post.categories.contents
# 以下のとおり出力
# [u'\n', u' This is a comment. ', u'\n', <category term="Asia"></category>, <category term="South America"></category>, <category term="Europe"></category>]
contentsを使えば、タグだけでなく、コメントなどの文字列も含めて子要素を取得することができます。ただ、元のXMLデータに空白や改行が入っていると、思わぬところでインデックスが変わってしまったりしてハマりがちですので、タグ要素だけに着目するのであれば、find()、findAll()などを使うのがよいかと思います。


以上です。

今回は取り上げませんでしたが、このほかにもBeautifulSoupには
  • あるノードの隣のノードや親ノードを取得する機能
  • XMLを編集する機能
なんかも備わっています。さらに詳しくは参考ページなどをご参照いただければと思います。

また、BeautifulSoupのバージョン3までとバージョン4とでは互換性の無い部分がところどころあるので、その点にも注意が必要かと思います。今回のBeautifulStoneSoupは、バージョン3までの機能です。


インストール
「pip」が入っていれば、コマンドラインから「pip install beautifulsoup」でインストールできます(BeautifulSoup4は「pip install beautifulsoup4」)。


参考
BeautifulSoup - PyPI
Beautiful Soup: We called him Tortoise because he taught us.
Beautiful Soupドキュメント — BeautifulSoup Document 0.1 documentation
BeautifulSoupでスクレイピングのまとめ « taichino.com
BeautifulSoupでHTML解析 - Perl使いのPythonちゃん

Beautifulsoup4 - PyPI
Beautiful Soup 4 Documentation
※最後の2つは BeautifulSoup4 関連です

2013/06/05

ライブラリ:ElementTree

Pythonの「ElementTree」というライブラリについてご紹介します。

from xml.etree.ElementTree import *

PythonにはXMLを扱うためのメジャーなライブラリがいくつか存在します。シェアがどのようになっているかはよくわからないのですが、個人的によく見かけるのは、ElementTree、BeautifulSoup、lxmlあたりでしょうか。minidomもときどき目にします。

今回はそのうちの「ElementTree」について見てみたいと思います。

「ElementTree」は、XMLをファイルやテキストから読み込んだり、加工したり、保存したりするためのライブラリです。Python2.5から標準ライブラリとなり、別途インストールをすることなく最初から使うことができます。

ファイルからの読み込み

from xml.etree import ElementTree

XMLFILE = "sample.xml"

tree = ElementTree.parse(XMLFILE)  # ファイルから読み込み
root = tree.getroot()
print root.tag  # feedと出力
ファイルからの読み込みにはparse()を使います。続いてgetroot()でルートとなるノードを取得しから処理を進める、というのが定番の流れとなっています。

以下、sample.xmlの中身が次のようなものだと仮定して見ていきます。
<feed attrA="value A" attrB="value B">
    <title>title C</title>
    <categories>
        <category term="Asia" />
        <category term="South America" />
        <category term="Europe" />
    </categories>
</feed>

要素情報の検索と取得
XML要素のタグ、属性はtag、attribアトリビュートに格納されています。
print root.tag  # feed
print root.attrib  # {'attrB': 'value B', 'attrA': 'value A'}
attribは、属性と値のセットを辞書として格納しています。

要素の子要素を見るには、次のようにします。
for e in root:
    print e.tag  # title, categoriesと出力

子要素の中から特定の子だけを取り出すには、find()を使います。
e = root.find('title')
print e.tag  # titleと出力
print e.text  # title Cと出力
textアトリビュートには、その要素の中にあるテキストが格納されています。

find()は上から走査して、最初に見つかった要素1つだけを返します。すべての要素を取得する場合はfindall()を使います。
es = root.findall('category')
for e in es:
    print e.tag, e.attrib  # 出力なし

ただこの場合は、categoryはfeedの直接の子ではなく孫要素にあたるため、findall('category')では取得することができません。直接の子要素だけでなく、すべての子孫要素の中からすべて取得するにはfindall()の引数の頭に「.//」を追加します。
es = root.findall('.//category')
for e in es:
    print e.tag, e.attrib
# 以下のように出力
# category {'term': 'Asia'}
# category {'term': 'South America'}
# category {'term': 'Europe'}

さらに、属性値も手がかりにして要素を検索するには次のようにします。
es = root.findall(".//category[@term='Asia']")
for e in es:
    print e.tag, e.attrib
# category {'term': 'Asia'}と出力
属性名の前に@をつけ、属性値と=でつなげて使います。クオートは必ず「'」(シングルクオート)を使います。「'」が「"」になっていたり、間に空白が挟まっていたりするとうまく検索できないので注意が必要です。

この他にも、find()・findall()には「指定した要素の子要素だけ」、「指定した要素を子に持った親要素だけ」を抽出するような機能なんかも備わっています。find()・findall()の文法については、公式サイトのXPathシンタックスに関するパートにまとめられていますので詳しくはそちらをご参照ください。

次に、文字列から起こすやり方を見てみます。

文字列からの読み込み

xml = """

    text B
    
    
    

"""

e = ElementTree.fromstring(xml)  # 文字列からの読み込み
文字列からElementを生成するには、fromstring()を使います。fromstring()で作った変数は、ファイルから読み込んだときのrootと同じように使うことができます。

最後に、ファイルに保存するやり方を。

ファイルへの書き込み

XMLFILEOUT = 'sampleout.xml'

tree = ElementTree.ElementTree(e)
tree.write(XMLFILEOUT)  # ファイルへの書き込み
上で文字列から読み込んだeをファイルに保存するには、それを内包するElementTreeというものを生成した上で、write()メソッドで行います。

ファイルから開いたtreeの場合は直接write()で保存できます。

ちなみに、この「ファイルから読み込んだ場合と文字列から読み込んだ場合のちがい」は、ファイルからparse()で開いた場合はElementTreeオブジェクトのインスタンスが生成されるのに対し、文字列からfromstring()で開いた場合はElementオブジェクトのインスタンスが生成される、というちがいに起因しています。詳しくは公式ドキュメントをあたっていただくのがよいかと思います。

以上です。


ElementTreeにはこれらの他にも、文字列を検索するfindtext()、属性を追加するset()、子要素を追加するinsert()など、XMLを手軽に扱える便利な機能が数多く用意されています。

もっと詳しい部分については公式のドキュメントや、以下参考リンクをご参照ください。


インストール
「ElementTree」はPython 2.5から標準ライブラリに含まれています。Pythonと別途インストールする必要はありません。


参考
ElementTree - Python公式ドキュメント
PythonでElementTreeを使ってXMLを処理する方法 - Sticker@Something
easiest way to parse xml in python - Stack Overflow