BeautifulSoupの find() と find_all() では、タグのテキストでタグを検索することができます。
属性からうまくタグを特定できないときに、よく使います。
ですが、text=""
やstring=""
の引数にテキストを渡しても、タグがヒットしないケースが多々あるんですね。
そういった、BeautifulSoupでタグが取得できないときの対策コードの紹介です。
テキストは確かに含まれているのに、正規表現を使ってもヒットしない。
find(text="検索テキスト")
やfind(string="検索テキスト")
が None になる。
find_all(text="検索テキスト")
やfind_all(string="検索テキスト")
が空のリストになる。
その対策を紹介します。
対策といっても、『find()』や『find_all()』に検索用の関数を作って渡すだけです。
とても簡単です。
タグが取得できない原因は、『find()』や『find_all()』が見ている『.string(ドットストリング)』が『None』になるためです。
対策としては、『.string』以外の変数で検索するような関数を作って、『find()』や『find_all()』に渡すアプローチになります。
ここでは、『.text(ドットテキスト)』で検索するコード例を紹介します。
以下、『タグがヒットしない原因の詳細』と『ヒットさせるための検索コード例』です。
なぜタグがヒットしないのか?
原因は、タグのテキストが<br>タグや<span>タグなどを含んでいるためです。
なぜ、テキストがタグを含んでいると検索できないのか?
そういった状態のテキストでは、タグの『.string』が『None』になってしまうんですね。
この『.string』という変数は、『find()』と『find_all()』がテキスト検索のときに見ている変数です。
BeautifulSoupの公式マニュアルに、『find()』と『find_all()』が『.string』を見て検索している旨(むね)が書かれていました。
(参考)Beautiful Soup Documentation – find_all() – The string argument
つまり、『textやstringに渡したテキスト』と『None』を比較する形になっていたので、ヒットしなかったと考えられます。
では、どうしたらヒットするのか?
タグをヒットさせる方法
『.string』以外の変数でタグを検索するようにします。
たとえば、個々のタグの『.text(ドットテキスト)』変数が使えます。
『.text』なら、テキストがさらにタグを含んでいるケースでも、テキストだけを抽出した結果が入っています。
実際に『.text』を参照するような検索用の関数を作って、『find()』や『find_all()』に渡したところ、うまくヒットさせることができました。
検索コード例
テキストで検索できないタグを、検索できるようにします。
サンプルHTML
テキストに<br>タグを含んでいる<a>タグの例です。
<html>
<body>
<a href="https://****">アンカーテキスト</a>
<a href="https://****">アンカー<br>
テキスト</a>
</body>
</html>
2個目の<a>タグは、『find()』や『find_all()』に検索テキストを渡してもヒットしません。
代わりに、以下の検索用関数を渡すとヒットします。
検索用の関数
『find()』や『find_all()』に渡す検索用の関数です。
ヒットさせたい条件の時に True を返すようにすればOKです。
def find_text(x):
"""検索関数"""
if x.name == 'a':
if x.text == 'アンカー\nテキスト':
return True
return False
さて、引数の『x』に何が来るか?です。
これは、関数を『find_all()』の『どの引数に渡したか?』によって変わります。
find_all(name=find_text)
のときは、『x』に個々のタグのインスタンスが入っています。
『name』は、普段タグ名を渡している第1引数ですね。
'a'
とか'div'
とか'table'
などのタグ名を渡して、検索に使っているところです。
『x』にタグのインスタンスが来るので、x.nameでタグ名が取得できますし、x.textでテキストの抽出結果が取得できます。
これらを検索に利用します。
find_all(text=find_text)
または find_all(string=find_text)
のときは、『x』に個々のタグの『.string』変数が入っています。
つまり、『単なる文字列型』か『None』になります。
コード全体
サンプルHTMLをBeautifulSoupに渡して、『find_all()』で検索するコード例です。
2個目の<a>タグにヒットするような検索関数『find_text()』を作って、『find_all()』に渡しています。
"""find_all()に検索関数を渡して検索するコード例"""
from bs4 import BeautifulSoup
html = """<html>
<body>
<a href="https://****">アンカーテキスト</a>
<a href="https://****">アンカー<br>
テキスト</a>
</body>
</html>"""
soup = BeautifulSoup(html, 'lxml')
def find_text(x):
"""検索関数"""
if x.name == 'a':
if x.text == 'アンカー\nテキスト':
return True
return False
for tag in soup.find_all(name=find_text, recursive=True):
print('string: %s' % tag.string)
print('text: %s' % tag.text)
リカーシブ(recursive)は、配下のタグまで再帰的に検索するための引数です。
実行結果は以下の通りです。
string: None
text: アンカー
テキスト
きちんと2個目の<a>タグがヒットして、その『.string』と『.text』が print() で表示されています。
このような感じで、BeautifulSoupのテキスト検索でタグが検索できないときは、『.text』で検索する関数を渡すと、うまくいく場合があります。