2011年4月12日火曜日

解析木の内部を検索する

先に取り上げたメソッド、 findAllfind は、解析木内のある地点からスタートし、木を下っていきます。
それらは底につくまで、再帰的に contents オブジェクトを反復処理します。

これは、オブジェクト上ではそれらのメソッドを呼び出せないという意味です、なぜなら、これらのオブジェクトは、 contents を持たないからです:これらのオブジェクトは常に、解析木の葉なのです。

しかし、下ることだけが、ドキュメントを反復処理するための唯一の方法ではありません。
解析木を航行する に戻ってみると、他にも多くの方法を示しています: parentnextSibling、などなど。
これらの反復処理の技術は、2つの対応するメソッドを持っています: 1つは、 findAll のように動作するもので、もう一つは、find のように動作するものです。
そして、航行可能オブジェクトはこれらの操作をサポートしているため、タグオブジェクト上や、主な解析オブジェクト上同様、航行可能オブジェクト上で、これらのメソッドを呼び出すことが出来ます。

なぜ、これが役に立つのでしょうか?
時々、欲しいタグや航行可能文字オブジェクトを取得するために、 findAll
find を使うことができないことがあります。
例えば、次のようなHTMLを考えてみてください:
from BeautifulSoup import BeautifulSoup
soup = BeautifulSoup('''<ul>
 <li>An unrelated list
</ul>

<h1>Heading</h1>
<p>This is <b>the list you want</b>:</p>
<ul><li>The data you want</ul>''')

欲しいデータを含む<LI>タグを航行するための方法が多数あります。
最も明らかな方法はこれです:
soup('li', limit=2)[1]
# <li>The data you want</li>

この <LI> タグを取得するための非常に安定した方法ではないということは、同様に明らかです。

もし、あなたがこのページを1度スクレイピングするだけなら、問題はありません、しかし、もし、長期間に渡って、何度も繰り返しスクレイピングするつもりなら、このような考慮が重要になります。
もし、不適合リスト(訳注:マッチしない部分のリスト)が別の <LI> タグを増やしたら、欲しいタグの代わりに、そのタグを取得することになるでしょう、そして、あなたのスクリプトは処理を中断するか、間違ったデータを渡すでしょう。
soup('ul', limit=2)[1].li
# <li>The data you want</li>

これは、不適合リストの変更を生き残ることが出来るので、少しマシです。
しかし、もし、ドキュメントが先頭で別の不適合リストを増やした場合、 <LI> は、あなたの欲しいタグの代わりに、そのリストの最初のタグを取得するでしょう。
あなたの欲しいulタグを参照するためのより信頼性の高い方法は、ドキュメントの構造中で、そのタグの場所を熟慮することです。

このHTMLを見ると、あなたは欲しいリストが 「 <H1> タグ直下の <UL> タグ」 だと考えるかもしれません。
そう考える問題は、タグがH1タグの内部に含まれないということです;問題は、この後で起こります。
<H1> タグを得るには十分ですが、そこから、firstfetch を使って <UL> タグを取得する方法が全くありません、なぜなら、これらのメソッドは <H1> タグの
contents しか検索できないからです。
nextnextSibling のメンバーを使って <UL> を航行する必要があります。
s = soup.h1
while getattr(s, 'name', None) != 'ul':
    s = s.nextSibling
s.li
# <li>The data you want</li>

あるいは、こちらの方が、より安定的だと考えるかもしれません:
s = soup.find(text='Heading')
while getattr(s, 'name', None) != 'ul':
    s = s.next
s.li
# <li>The data you want</li>

しかし、これは、通過する際よりも、より多くの問題があります。
本セクション中のメソッドは、役に立つ速記法を提供しています。
それらは、ナビゲーションのメンバーの1つ以上をwhileループを書きたい時にいつでも、使うことが出来ます。

木の中の開始地点を考えると、それらはいくつかの方法で木を航行し、タグの経路と、あなたが指定した基準にマッチする可航文字オブジェクトを保持します。

上記コード例中の最初のループの代わりに、こう書くことが出来ます:
soup.h1.findNextSibling('ul').li
# <li>The data you want</li>

2番目のループの代わりに、こう書く事が出来ます:
soup.find(text='Heading').findNext('ul').li
# <li>The data you want</li>

ループは、findNextSiblingfindNext の呼び出しで置換されます。
本章の残りは、この種の全てのメソッドへのリファレンスとなっています。
再掲、各航行メンバーに対して2つのメソッドがあります:
1つはリストを返すメソッドは findAll で、スカラーを返すメソッドは find です。

最後に一度、例として、おなじみのドキュメントに詰め込んでみましょう:
from BeautifulSoup import BeautifulSoup
doc = ['<html><head><title>Page title</title></head>',
       '<body><p id="firstpara" align="center">This is paragraph <b>one</b>.',
       '<p id="secondpara" align="blah">This is paragraph <b>two</b>.',
       '</html>']
soup = BeautifulSoup(''.join(doc))
print soup.prettify()
# <html>
#  <head>
#   <title>
#    Page title
#   </title>
#  </head>
#  <body>
#   <p id="firstpara" align="center">
#    This is paragraph
#    <b>
#     one
#    </b>
#    .
#   </p>
#   <p id="secondpara" align="blah">
#    This is paragraph
#    <b>
#     two
#    </b>
#    .
#   </p>
#  </body>
# </html>

findNextSiblings(name, attrs, text, limit, **kwargs)findNextSibling(name, attrs, text, **kwargs)


これらのメソッドは、オブジェクトのタグを収集する nextSibling メンバー、あるいは、あなたが指定した基準にマッチする NavigableText オブジェクトを繰り返し辿ります。
上記ドキュメントにおいて:
paraText = soup.find(text='This is paragraph ')
paraText.findNextSiblings('b')
# [<b>one</b>]

paraText.findNextSibling(text = lambda(text): len(text) == 1)
# u'.'

findPreviousSiblings(name, attrs, text, limit, **kwargs)findPreviousSibling(name, attrs, text, **kwargs)


これらのメソッドは、オブジェクトのタグを収集する previousSibling メンバー、あるいは、あなたが指定した基準にマッチする NavigableText オブジェクトを繰り返し辿ります。
上記ドキュメントにおいて:
paraText = soup.find(text='.')
paraText.findPreviousSiblings('b')
# [<b>one</b>]

paraText.findPreviousSibling(text = True)
# u'This is paragraph '

findAllNext(name, attrs, text, limit, **kwargs)findNext(name, attrs, text, **kwargs)


これらのメソッドは、オブジェクトのタグを収集する next メンバー、あるいは、あなたが指定した基準にマッチする NavigableText オブジェクトを繰り返し辿ります。
上記ドキュメントにおいて:
pTag = soup.find('p')
pTag.findAllNext(text=True)
# [u'This is paragraph ', u'one', u'.', u'This is paragraph ', u'two', u'.']

pTag.findNext('p')
# <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>

pTag.findNext('b')
# <b>one</b>

findAllPrevious(name, attrs, text, limit, **kwargs)findPrevious(name, attrs, text, **kwargs)


これらのメソッドは、オブジェクトのタグを収集する previous メンバー、あるいは、あなたが指定した基準にマッチする NavigableText オブジェクトを繰り返し辿ります。
上記ドキュメントにおいて:
lastPTag = soup('p')[-1]
lastPTag.findAllPrevious(text=True)
# [u'.', u'one', u'This is paragraph ', u'Page title']
# 逆順に注意!

lastPTag.findPrevious('p')
# <p id="firstpara" align="center">This is paragraph <b>one</b>.</p>

lastPTag.findPrevious('b')
# <b>one</b>

findParents(name, attrs, limit, **kwargs) and
findParent(name, attrs, **kwargs)


これらのメソッドは、オブジェクトのタグを収集する parent メンバー、あるいは、あなたが指定した基準にマッチする NavigableText オブジェクトを繰り返し辿ります。
全てのオブジェクトが親に NavigableString を持つ方法が無いので、これらは、text 引数を取りません。
上記ドキュメントにおいて:
bTag = soup.find('b')

[tag.name for tag in bTag.findParents()]
# [u'p', u'body', u'html', '[document]']
# 注記: "u'[document]'" は、解析オブジェクトが自身とマッチしたという意味です。

bTag.findParent('body').name
# u'body'

0 件のコメント:

コメントを投稿

【和訳】Django Rest Framework 目次

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