Beautiful Soup オブジェクトにマッチする基準を定義する方法は、いくつかあります。
Beautiful Soup の全ての検索メソッドの中で、最も基本となる
findAll
を徹底的に分析し、実演してみましょう。これまでと同じく、以下のドキュメントで実演します。
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>
ちなみに、本セクションで取り上げる2つのメソッド (
findAll
と find
)は、タグオブジェクトと、トップレベルの解析オブジェクトのみで使用でき、可航文字オブジェクトでは使用できません。「解析木の内部を検索する」で定義されているメソッドは、可航文字オブジェクトでも利用できます。基本検索メソッド: findAll(name, attrs, recursive, text, limit, **kwargs)
findAll
メソッドは解析木を航行し、与えられた地点から開始し、あなたが渡した基準にマッチする、あらゆるタグと可航文字オブジェクトを検索します。findall
メソッドの書き方はこうです:findAll(name=None, attrs={}, recursive=True, text=None,
limit=None, **kwargs)
これらの引数は、Beautiful Soup API中で、繰り返し現れます。
最も重要な引数は、
name
と、キーワード引数です。name
引数は name で、タグの集合を制限します。
name を制限する方法がいくつかあり、これらは Beautiful Soup API 全体で、何度も繰り返し現れます。
- 最も簡単な使用方法は、タグ名を渡すことです。このコードは、ドキュメント中の全てのタグを検索します:
soup.findAll('b') # [<b>one</b>, <b>two</b>]
- また、正規表現を渡すことも出来ます。このコードは、Bから始まるタグ名を全て検索します:
import re tagsStartingWithB = soup.findAll(re.compile('^b')) [tag.name for tag in tagsStartingWithB] # [u'body', u'b', u'b']
- リストやディクショナリで渡すことも出来ます。これら2つの呼び出しは、全ての<TITLE>と、<P>タグを見つけます。
それらは同じように動作しますが、2番目の呼び出しの方がより高速です。
soup.findAll(['title', 'p']) # [<title>Page title</title>, # <p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>] soup.findAll({'title' : True, 'p' : True}) # [<title>Page title</title>, # <p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>]
- 名前付きのタグにマッチする、特別な値
True
を渡すことが出来ます:
これは、いわば、あらゆるタグにマッチします。
allTags = soup.findAll(True) [tag.name for tag in allTags] [u'html', u'head', u'title', u'body', u'p', u'b', u'p', u'b']
これは、役に立つように見えませんが、True
は属性値を制限する際に、非常に役に立ちます。
Tag
オブジェクトを唯一の引き数として取る、呼び出し可能オブジェクトを渡すことが出来ます、これはブール値を返します。
findAll
が出くわす、各タグオブジェクトは、このオブジェクト中に渡され、もし、呼び出しがTrue
を返せば、タグはマッチしたとみなされます。
このコードは属性値をちょうど2つしか持っていないタグを見つけることが出来ます:
soup.findAll(lambda tag: len(tag.attrs) == 2) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>]
このコードは1文字の名前を持ち、属性を持たないタグを検索します:
soup.findAll(lambda tag: len(tag.name) == 1 and not tag.attrs) # [<b>one</b>, <b>two</b>]
- 最も簡単な使用方法は、タグ名を渡すことです。このコードは、ドキュメント中の全てのタグを検索します:
- キーワード引数は、タグの属性を制限します。
このシンプルな例は "align" 属性に "center" 値を持つ全てのタグを検索します:
soup.findAll(align="center") # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>]
name引数と同様に、属性ごとに異なる制限を行うため、キーワード引数に、異なる種類のオブジェクトを渡すことが出来ます。
上で見たように、属性を単一の値に制限するために、文字を渡すことが出来ます。
また、正規表現や、リスト、ハッシュ、特別な値として、True
やNone
、あるいは、(値は None かもしれませんが)引数として属性値を持つ呼び出しを渡すことも出来ます。例として:
soup.findAll(id=re.compile("para$")) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>] soup.findAll(align=["center", "blah"]) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>] soup.findAll(align=lambda(value): value and len(value) < 5) # [<p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>]
特別値True
とNone
は特に興味深いです。
True
は、渡された属性に何らかの値を持つタグにマッチし、None
は、渡された属性に値がないタグにマッチします。
例として:
soup.findAll(align=True) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>] [tag.name for tag in soup.findAll(align=None)] # [u'html', u'head', u'title', u'body', u'b', u'b']
もし、タグ属性に複雑な制限や、連結した制限を課す必要がある場合、上にあるよう、name
に呼び出し可能なオブジェクトを渡し、タグオブジェクトを処理します。
ここで、あなたは問題に気がつくかもしれません。
もし、name
と言う名前の属性を定義しているタグ付きのドキュメントでは、どうすればよいでしょうか?
Beautiful Soup のsearchメソッドは、すでにname
引数を定義しているため、name
という名前のキーワード引数を使うことができません。
また、キーワード引数にfor
のようなPythonの予約語を使うこともできません。
Beautiful Soup は、これらのシチュエーションで使用できるattrs
と呼ばれる特別な引数を提供します。
attrs
は、ちょうど、キーワード引数のように動作するディクショナリです。
soup.findAll(id=re.compile("para$")) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>] soup.findAll(attrs={'id' : re.compile("para$")}) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>]
名前がclass
やfor
やimport
のような、Pythonの予約語の属性や、name
、recursive
、limit
、text
、attrs
など、Beautiful Soup 検索メソッドのキーワード引数以外の属性に対して、制限を課す必要がある場合、attrs
を使うことが出来ます。
from BeautifulSoup import BeautifulStoneSoup xml = '<person name="Bob"><parent rel="mother" name="Alice">' xmlSoup = BeautifulStoneSoup(xml) xmlSoup.findAll(name="Alice") # [] xmlSoup.findAll(attrs={"name" : "Alice"}) # [parent rel="mother" name="Alice"></parent>]
CSSによる検索
attrs
引数はあるものがなければ、かなり曖昧な機能だったでしょう:それはCSSです。
特定のCSSクラスを持つタグを探すのに、とても役に立ちます、しかし、CSS属性のclass
もPythonの予約語なのです。
soup.find("tagName", { "class" : "cssClass" })
として検索出来るでしょうが、このような一般的な操作には、たくさんのコードが必要です。
実際、ディクショナリの代わりに、文字をattrs
に渡すことが出来ます。
渡す文字は、CSSクラスを制限するのに使われます。
from BeautifulSoup import BeautifulSoup soup = BeautifulSoup("""Bob's <b>Bold</b> Barbeque Sauce now available in <b class="hickory">Hickory</b> and <b class="lime">Lime</a>""") soup.find("b", { "class" : "lime" }) # <b class="lime">Lime</b> soup.find("b", "hickory") # <b class="hickory">Hickory</b>
text
は、タグの代わりに、可航文字オブジェクトを検索出来るようにする引数です。
その値は、文字列、正規表現、リスト、ディクショナリ、True
や
None
、引数に可航文字オブジェクトを持つ呼び出し、を取れます:
soup.findAll(text="one") # [u'one'] soup.findAll(text=u'one') # [u'one'] soup.findAll(text=["one", "two"]) # [u'one', u'two'] soup.findAll(text=re.compile("paragraph")) # [u'This is paragraph ', u'This is paragraph '] soup.findAll(text=True) # [u'Page title', u'This is paragraph ', u'one', u'.', u'This is paragraph ', # u'two', u'.'] soup.findAll(text=lambda(x): len(x) < 12) # [u'Page title', u'one', u'.', u'two', u'.']
text
を使う場合、name
に渡すあらゆる値と、キーワード引数は無視されます。recursive
は、Beautiful Soupに、解析木の全ての経路を下るかどうか、あるいは、タグの直下の子や解析木を見るだけかどうかを伝える(デフォルトではTrue
の)ブール値引数です。
ここに違いを示します:
[tag.name for tag in soup.html.findAll()] # [u'head', u'title', u'body', u'p', u'b', u'p', u'b'] [tag.name for tag in soup.html.findAll(recursive=False)] # [u'head', u'body']
recursive
が false の場合、<HTML> タグ直下の子だけが検索されます。
もし、それが検索する必要のあるもの全てだと分かっているなら、この方法である程度時間を省略できます。limit
引数を設定すると、argument lets you
stop the search once Beautiful Soup finds a certain number of matches.
もし、ドキュメント中に千テーブルあるけれども、最初の4つしか必要無いなら、limit
に4を渡せば時間を省略出来ます。デフォルトではlimitは無制限です。
soup.findAll('p', limit=1) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>] soup.findAll('p', limit=100) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>, # <p id="secondpara" align="blah">This is paragraph <b>two</b>.</p>]
タグの呼び出しは findall
を呼び出すのに似ている
小さなショートカットがあります。解析オブジェクトや、タグを関数のように呼び出す場合、 あらゆるfindall
の引き数を渡すことが出来ます、これは findall
を呼び出すのと同じです。 上記のドキュメントに関しては: soup(text=lambda(x): len(x) < 12) # [u'Page title', u'one', u'.', u'two', u'.'] soup.body('p', limit=1) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>]
find(name, attrs, recursive, text, **kwargs)
O.K. では、他の検索メソッドを見ていきましょう。それらは findAll
とほぼ同様の引数を取ります。 find
メソッドは、あらゆるマッチングオブジェクトを検索するというを除き、findAll
と同じです、こちらは、最初のオブジェクトだけを検索します。 結果の集合に、 limit
1を強制したり、結果の配列から1つの結果を抜き出すようなものです。 上記のドキュメントに関しては: soup.findAll('p', limit=1) # [<p id="firstpara" align="center">This is paragraph <b>one</b>.</p>] soup.find('p', limit=1) # <p id="firstpara" align="center">This is paragraph <b>one</b>.</p> soup.find('nosuchtag', limit=1) == None # True一般的に、(
findAll
や findNextSiblings
のような)複数形の名前の検索メソッドを見ると、そのメソッドは limit
引数を取り、結果リストを返します。 ( find
や findNextSibling
のような)複数形ではない名前の検索メソッドを見ると、そのメソッドは limit
引数を取らず、単一の結果を返します。first
に何が起こるか?
以前のバージョンの Beautiful Soup は、 first
、 fetch
、fetchPrevious
などのメソッドを持っていました。 これらのメソッドはまだありますが、それらは推奨されておらず、まもなく削除されるかもしれません。 これらの全ての名前の効果はとてもややこしいです。 新たな名前は一貫性を持っています: 上記で述べたように、名前が複数形や、All
を含んでいたら、複数のオブジェクトを返し、そうでなければ、1つのオブジェクトを返します。
0 件のコメント:
コメントを投稿