Pythonを利用したWebスクレイピングにおいて、HTMLから特定のテキストデータのみを抽出する作業は非常に頻繁に発生します。
Beautiful Soupを使えば、複雑な構造を持つWebページからも、目的の文字列を効率的に取り出すことが可能です。
本記事では、特定のキーワードを含む要素の検索方法や、効率的なテキスト取得のテクニックについて詳しく解説します。
Beautiful Soupでテキストを抽出する基本メソッド
Beautiful Soupには、HTML要素からテキストを取り出すためのメソッドがいくつか用意されています。
主に使われるのは、.string、.text、そして.get_text()の3種類です。
これらの違いを正しく理解することが、精度の高いデータ抽出を行うための第一歩となります。
.stringと.textの違い
.string属性は、そのタグが直下に持つテキストのみを取得します。
タグの中にさらに別のタグが含まれている場合、.stringはNoneを返すため注意が必要です。
一方で、.text属性は、タグの中に含まれるすべてのテキストを結合して取得します。
以下のコードで、その挙動の違いを確認してみましょう。
from bs4 import BeautifulSoup
html = "<div>Python<span>スクレイピング</span></div>"
soup = BeautifulSoup(html, "html.parser")
div_tag = soup.find("div")
# .stringの場合
print(f"string: {div_tag.string}")
# .textの場合
print(f"text: {div_tag.text}")
string: None
text: Pythonスクレイピング
.get_text()による柔軟な抽出
.get_text()メソッドを使用すると、テキストを取得する際の区切り文字や空白の処理を指定できます。
データのクレンジングを同時に行いたい場合に非常に有効な手段です。
例えば、リスト形式のテキストをカンマ区切りで取得するといった操作が容易になります。
html = "<ul><li>Apple</li><li>Banana</li></ul>"
soup = BeautifulSoup(html, "html.parser")
# 区切り文字を指定して抽出
text_data = soup.find("ul").get_text(",")
print(text_data)
Apple,Banana
特定の文字列を含む要素を検索する方法
特定の文字列が含まれている要素だけを抽出したい場合は、findやfind_allメソッドの引数に条件を指定します。
Beautiful Soup 4.10以降では、string引数を使用してキーワード検索を行うのが一般的です。
完全一致での検索
指定した文字列と完全に一致する要素を探す場合は、そのまま文字列を渡します。
この方法は、特定のメニュー名や固定のラベルを探す際に便利です。
html = "<p>対象のテキスト</p><p>除外するテキスト</p>"
soup = BeautifulSoup(html, "html.parser")
# 完全一致で検索
element = soup.find("p", string="対象のテキスト")
print(element)
<p>対象のテキスト</p>
正規表現を利用した部分一致検索
「特定の文字を含む」といった曖昧な検索を行うには、Pythonのreモジュール(正規表現)を併用します。
正規表現を活用することで、抽出の柔軟性が劇的に向上します。
例えば、価格情報や日付など、パターンが決まっている文字列の抽出に最適です。
import re
from bs4 import BeautifulSoup
html = "<div>現在の価格は 1,500円 です</div><div>昨日の価格は 1,200円 です</div>"
soup = BeautifulSoup(html, "html.parser")
# 「価格」という文字を含むdiv要素をすべて抽出
price_elements = soup.find_all("div", string=re.compile("価格"))
for el in price_elements:
print(el.text)
現在の価格は 1,500円 です
昨日の価格は 1,200円 です
CSSセレクタを活用した高度な特定方法
文字列の内容だけでなく、HTMLの構造を条件に加えて特定したい場合は、selectメソッドが便利です。
CSSセレクタを使うことで、「特定のクラスを持つ要素の中にある特定のテキスト」といった複雑な指定が可能になります。
特定のクラス配下から抽出する
特定のクラス属性を持つ親要素を絞り込むことで、意図しない場所からのデータ取得を防ぐことができます。
スクレイピングにおいて、誤ったデータを取得しないための「絞り込み」は非常に重要な工程です。
html = """
<div class="content">
<p>取得したいテキスト</p>
</div>
<div class="footer">
<p>不要なテキスト</p>
</div>
"""
soup = BeautifulSoup(html, "html.parser")
# contentクラス配下のpタグのみを取得
target = soup.select_one(".content p").text
print(target)
取得したいテキスト
ラムダ関数を用いたカスタム検索条件
Beautiful Soupの検索引数には、関数(ラムダ式)を渡すこともできます。
これにより、標準の引数だけでは表現できない複雑なロジックで要素を特定できます。
例えば、「特定の文字列を含み、かつ特定の属性を持っている」といった条件を1行で記述可能です。
html = """
<a href="/item/1">商品A</a>
<a href="/blog/1">記事A</a>
<a href="/item/2">商品B</a>
"""
soup = BeautifulSoup(html, "html.parser")
# hrefに"/item/"を含み、かつテキストに"商品"を含むリンクを抽出
items = soup.find_all(lambda tag: tag.name == "a" and
"/item/" in tag.get("href", "") and
"商品" in tag.text)
for item in items:
print(item)
<a href="/item/1">商品A</a>
<a href="/item/2">商品B</a>
よくある課題と解決策
文字列抽出の際によく直面するのが、改行や余計な空白が含まれてしまう問題です。
HTMLソース上での整形が、取得したテキストに影響を及ぼすことが多々あります。
空白文字の削除(strip)
get_text(strip=True)を指定することで、前後の空白や改行を自動的に削除して取得できます。
これにより、取得後の文字列処理の手間を大幅に削減することが可能です。
html = "<td> \n 15,000円 \n </td>"
soup = BeautifulSoup(html, "html.parser")
# strip引数で空白を削除
clean_text = soup.find("td").get_text(strip=True)
print(f"'{clean_text}'")
'15,000円'
まとめ
PythonのBeautiful Soupを使用すれば、特定の文字列を抽出するためのアプローチは多岐にわたります。
単純なテキスト取得には.textや.get_text()を使い、複雑な検索にはreモジュールやラムダ関数を組み合わせるのが効率的です。
ターゲットとするWebサイトの構造に合わせて、最適なメソッドを選択することが、安定したスクレイピングを実現する鍵となります。
今回紹介したテクニックを活用して、必要なデータだけを正確に取得するスクリプトを作成してみてください。
