2011年4月12日火曜日

解析木を航行する

全てのタグオブジェクトは、(実際のメンバーの値はNoneかもしれませんが)以下で示されているメンバーを全て持っています。
可航文字オブジェクトは、 contentsstring以外、全て持っています。

parent (親)


先の例では、 <HEAD> タグの parent<HTML> タグです。
<HTML> タグの parent は、 BeautifulSoup パーサーオブジェクト自身です。
パーサーオブジェクトの parent はNoneです。
このように、parent を追うことで、解析木を遡ることが出来ます:
soup.head.parent.name
# u'html'
soup.head.parent.parent.__class__.__name__
# 'BeautifulSoup'
soup.parent == None
# True

contents (内容)


parent を使うと、解析木を遡ります。 contents を使うと、解析木を下ります。
contents は、ページ要素中に含まれているタグと可航文字オブジェクトの順序付きリストです。
最上位のパーサーオブジェクトとタグオブジェクトのみ、 contents を持っています。可航文字オブジェクトは単なる文字であり、サブ要素を含むことが出来ません、そのため、contentsを持っていません。

先の例では、最初の <P> タグは、可航文字("This is paragraph ")と、<B> タグと、別の可航文字(".")を含むリストです。
<B> タグの contents:可航文字("one")を含むリストです。
pTag = soup.p
pTag.contents
# [u'This is paragraph ', <b>one</b>, u'.']
pTag.contents[1].contents
# [u'one']
pTag.contents[0].contents
# AttributeError: 'NavigableString' object has no attribute 'contents'

string (文字)


参考までに、タグが子ノードを1つしか持っておらず、そのノードが文字であるとき、子ノードは tag.contents[0] 及び tag.string とすれば利用できるようになります。

先の例の中で、soup.b.string は、Unicode文字 "one" を表す可航文字です。
これは、解析木中の最初の <B> タグの中に含まれる文字です。
soup.b.string
# u'one'
soup.b.contents[0]
# u'one'

しかし、解析木中の<P>タグは、子を1つ以上持っているため、soup.p.stringはNoneです。
soup.head.string もNoneです。
たとえ、 <HEAD> タグが子を1つしか持っていなくても、この子は( <TITLE> )タグであり、可航文字ではありません。
soup.p.string == None
# True
soup.head.string == None
# True

nextSibling (次の兄弟) と previousSibling (前の兄弟)


これらのメンバーを使うと、解析木の同階層の、次のノードや前のノードに移動できます。
先のドキュメントでは、<HEAD> タグの nextSibling は、 <BODY> タグです、これは、 <BODY> タグが <html> タグの直下にある次のノードだからです。
<BODY>nextSibling はNoneです、これは、 <HTML> タグの直下に、他のノードがないためです。
soup.head.nextSibling.name
# u'body'
soup.html.nextSibling == None
# True

逆に、<BODY>タグの previousSiblingは、<HEAD> タグで、<HEAD> タグの previousSiblingはNoneです。
soup.body.previousSibling.name
# u'head'
soup.head.previousSibling == None
# True

さらに例をいくつか:
最初の <P> タグの nextSibling は、2番目の <P> タグです。
2番目の <P> タグ中にある <B> タグの previousSiblingは、可航文字 "This is paragraph" です。
この可航文字の previousSiblingNone であり、最初の <P> タグ中の何かではありません。
soup.p.nextSibling
# <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>

secondBTag = soup.findAlll('b')[1]
secondBTag.previousSibling
# u'This is paragraph'
secondBTag.previousSibling.previousSibling == None
# True

next (次) と previous (前)


これらのメンバーを使えば、解析木中に現れる順番ではなく、パーサーによって処理される順番で、ドキュメントの要素中を移動できます。

例えば、 <HEAD> タグの next<TITLE> タグであり、<BODY> タグではありません。
これは、元のドキュメント中で、 <TITLE> タグが <HEAD> タグの直後に来るためです。

soup.head.next
# u'title'
soup.head.nextSibling.name
# u'body'
soup.head.previous.name
# u'html'
ここで、 nextprevious は関係しており、タグのcontents は、タグのnextSiblingの前に来ます。

あなたは通常、このメンバーを使う必要はありませんが、この方法は、時々、解析木中で何らかの変化するものを取得する、最も簡単な方法になります。


タグの繰り返し処理


タグをリストのように扱うことで、タグのcontents を繰り返し処理できます。
これは、便利なショートカットです。
同様に、タグが子ノードをいくつ持っているかを見るために、 len(tag.contents)の代わりに、len(tag)を呼ぶことも出来ます。
先のドキュメントにおいて:
for i in soup.body:
    print i
# <p id="firstpara" align="center">This is paragraph <b>one</b>.</p>
# <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>

len(soup.body)
# 2
len(soup.body.contents)
# 2

tag名をメンバーとして使用する


欲しいタグ名を、解析木のメンバーやタグオブジェクトのように扱うことで、解析木を航行するのは簡単です。
ここまでの例でもこのことを行なってきました。
先のドキュメントにおいて、 soup.head は、ドキュメント中の(たまたま1つですが)最初の <HEAD> タグを返します。
soup.head
# <head><title>Page title</title></head>

一般的に、 mytag.foo の呼び出しは、たまたま <FOO> であるmytag の最初の子を返します。
もし、mytagの下に <FOO>タグが全くなければ、mytag.foo は、Noneを返します。

これを使えば、解析木を高速で航行出来ます:
soup.head.title
# <title>Page title</title>

soup.body.p.b.string
# u'one'

また、これを使うと、解析木の特定箇所に速やかにジャンプ出来ます。
例えば、 <HEAD>タグの外側の変な場所にある <TITLE> タグについて、心配しなくて良いなら、HTMLドキュメントのタイトルを入手するには、単に、 soup.title を使えば良いでしょう。
soup.head.title を使う必要はありません。
soup.title.string
# u'Page title'

soup.p は、タグがどの場所にあっても、ドキュメント中の最初の <P> タグにジャンプします。
soup.table.tr.td は、ドキュメント中の最初のテーブルの最初の行の最初の列にジャンプします。

最初のメソッドに対する、これらのメンバーの正確なエイリアスは、以下で取り上げられています。
エイリアスを使えば、周知の解析木の関心ある部分をクローズアップするのが非常に簡単になるため、ここで触れています。


この慣用句の代替形式を使えば、.foo の代わりに、 .fooTag として、最初の <FOO> タグにアクセス出来ます。

例えば、 soup.table.tr.td は、soup.tableTag.trTag.tdTag や、soup.tableTag.tr.tdTag としても表現できます、
あなたが自分の行っていることについて、もっと明示したかったり、競合するBeautiful Soupのメソッド名やメンバー名と競合するタグ名を持つXMLを解析しているなら、この方法は役に立ちます。
from BeautifulSoup import BeautifulStoneSoup
xml = '<person name="Bob"><parent rel="mother" name="Alice">'
xmlSoup = BeautifulStoneSoup(xml)

xmlSoup.person.parent                      # A Beautiful Soup member
# <person name="Bob"><parent rel="mother" name="Alice"></parent></person>
xmlSoup.person.parentTag                   # A tag name
# <parent rel="mother" name="Alice"></parent>

もし、あなたが(ハイフン名のように)正式なPython識別子ではないタグ名を探しているなら、 findを使う必要があります。

0 件のコメント:

コメントを投稿

【和訳】Django Rest Framework 目次

目次 【和訳】Django Rest Framework クイックスタート 【和訳】Django Rest Framework チュートリアル1:シリアル化 【和訳】Django Rest Framework チュートリアル2:リクエストとレスポンス 【和訳】Django Res...