概要(find / find_all は「欲しい要素を的確に拾う」基本手段)
BeautifulSoup の find は「最初の1件」、find_all は「該当する全件」を返す検索メソッドです。タグ名・クラス名・属性・テキストなどで絞り込み、返ってきた Tag オブジェクトからテキストや属性を取り出します。重要なのは「None(見つからない)への安全対策」「属性取得でのKeyError回避」「検索範囲の絞り込み」「件数制御(limit)と深さ制御(recursive)」です。まずは1件→複数件→絞り込み→安全な取り出しの型を身につけましょう。
はじめ方(requests → BeautifulSoup → find / find_all の最短ルート)
最短の取得と解析
# pip install requests beautifulsoup4 lxml
import requests
from bs4 import BeautifulSoup
url = "https://httpbin.org/html"
resp = requests.get(url, timeout=5)
resp.raise_for_status()
resp.encoding = resp.apparent_encoding # 文字化け対策(推定で上書き)
soup = BeautifulSoup(resp.text, "lxml")
print(soup.title.get_text(strip=True))
PythonHTMLを取得したら、soup = BeautifulSoup(…, “lxml”) で解析。title などの基本要素は直接アクセスできます。以降は find / find_all で必要箇所を狙い撃ちします。
find と find_all の違い(「1件」か「全部」かをまず決める)
find(最初の1件)と find_all(全部)
from bs4 import BeautifulSoup
html = """
<ul id="news">
<li><a class="title" href="/a">A News</a></li>
<li><a class="title" href="/b">B News</a></li>
</ul>
"""
soup = BeautifulSoup(html, "lxml")
first = soup.find("a", class_="title") # 最初の1件
print(first.get_text())
all_links = soup.find_all("a", class_="title") # 全件
print([a.get_text() for a in all_links])
Python「1件で十分か、一覧が欲しいか」で使い分けます。find は該当なしで None、find_all は空リストを返すため、後続処理の安全対策が異なります。
使い方の要点(タグ・クラス・属性・テキストで精度を上げる)
タグ名と class / id の指定
# 記事カードのタイトル例:<div class="card"><a class="title" ...>...</a></div>
cards = soup.find_all("div", class_="card")
titles = [c.find("a", class_="title").get_text(strip=True) for c in cards]
Pythonclass_ と id は強力な絞り込み軸です。まずコンテナ(親)を find_all で絞り、内部の特定要素を find する二段構えが定番です。
任意属性(href, data-* など)で絞る
links = soup.find_all("a", href=True) # href属性を持つリンクだけ
docs = soup.find_all("a", attrs={"data-type": "doc"})
Python属性存在(href=True)や値指定(attrs={})で、目的の要素に素早く辿り着けます。
テキスト条件(exact/部分一致は後段で)
a = soup.find("a", string="お問い合わせ") # 完全一致
candidates = soup.find_all("a")
contact = [x for x in candidates if "お問合せ" in x.get_text()] # 部分一致はPython側で
Python完全一致は string、部分一致は get_text() の結果に対する条件で絞ると柔軟です。
抽出範囲と件数の制御(recursive / limit の活用)
深さ制御(recursive=False)で直下だけを見る
container = soup.find("ul", id="news")
direct_items = container.find_all("li", recursive=False) # 直下の<li>だけ
Pythonネストが深い構造で「直下だけ」を見たい時に便利です。デフォルトは子孫すべてが対象です。
件数制限(limit)で軽くする
top3 = soup.find_all("a", class_="title", limit=3) # 先頭3件だけ
Python大量要素のページでは、limit で無駄な探索を減らし負荷を抑えます。
スコープの絞り込みとチェーン(親→子→孫の自然な流れ)
親を絞ってから子を取る(誤抽出を減らす型)
section = soup.find("section", id="ranking")
items = section.find_all("div", class_="item")
names = [it.find("h3").get_text(strip=True) for it in items]
Pythonページ全体から検索する前に「関心領域」を find で特定し、その中だけを探索すると精度・速度ともに向上します。
兄弟・隣接の取得(必要に応じて)
item = soup.find("div", class_="item")
next_item = item.find_next_sibling("div", class_="item")
prev_item = item.find_previous_sibling("div", class_="item")
Python隣接要素を辿ると、並び構造から関連情報を取り出せます(詳細は必要なときだけ使えば十分)。
安全な取り出し(None・KeyError・整形の基本対策)
Noneチェックと安全な属性取得
card = soup.find("div", class_="card")
title_tag = card.find("a", class_="title") if card else None
title = title_tag.get_text(strip=True) if title_tag else ""
href = title_tag.get("href", "") if title_tag else "" # .get でKeyError回避
Pythonfind は該当なしで None。必ず None を考慮し、属性は辞書アクセスではなく .get(“attr”, fallback) を使うと安全です。
テキスト整形(get_text と strip)
raw = soup.find("div", class_="desc")
text = raw.get_text("\n", strip=True) if raw else ""
Pythonstrip=True で前後空白を除去、区切り文字を指定すると段落が読みやすく整います。
実務の型(select 併用・速度・エラー処理)
CSSセレクタ(select / select_one)と使い分け
# CSS経験者に直感的:ul#news li > a.title
for a in soup.select("ul#news li > a.title"):
print(a.get_text(strip=True), a.get("href"))
Pythonfind 系が「Python的に明快」、select 系が「CSS的に明快」。両方を場面で使い分けられると強いです。
パーサとエラー対策
lxml は速くて頑丈。取得時は timeout を付け、resp.raise_for_status() で失敗を例外化。文字化けは resp.encoding を適切に設定してから解析します。
例題で身につける(定番から一歩先まで)
例題1:ニュース一覧のタイトルとリンクを抽出
import requests
from bs4 import BeautifulSoup
r = requests.get("https://httpbin.org/html", timeout=5)
r.raise_for_status(); r.encoding = r.apparent_encoding
soup = BeautifulSoup(r.text, "lxml")
titles = [a.get_text(strip=True) for a in soup.find_all("a", class_="title")]
links = [a.get("href", "") for a in soup.find_all("a", class_="title")]
print(list(zip(titles, links))[:5])
Python例題2:テーブルの行データを直下のみ取得
from bs4 import BeautifulSoup
html = """
<table class="data">
<tr><th>名前</th><th>点数</th></tr>
<tr><td>A</td><td>90</td></tr>
<tr><td>B</td><td>80</td></tr>
</table>
"""
soup = BeautifulSoup(html, "lxml")
rows = soup.find("table", class_="data").find_all("tr", recursive=False)[1:]
data = [[td.get_text(strip=True) for td in r.find_all("td", recursive=False)] for r in rows]
print(data) # [['A', '90'], ['B', '80']]
Python例題3:カードリストから安全に属性取得
from bs4 import BeautifulSoup
html = """
<div class="card"><a class="title" href="/a">A</a></div>
<div class="card"><a class="title">B</a></div> <!-- hrefなし -->
"""
soup = BeautifulSoup(html, "lxml")
cards = soup.find_all("div", class_="card")
records = []
for c in cards:
a = c.find("a", class_="title")
title = a.get_text(strip=True) if a else ""
href = a.get("href", "") if a else ""
records.append({"title": title, "href": href})
print(records)
Python例題4:スコープを絞ってから抽出(速度・精度重視)
from bs4 import BeautifulSoup
page = """
<section id="ranking">
<div class="item"><h3>Alpha</h3></div>
<div class="item"><h3>Beta</h3></div>
</section>
<section id="other"><div class="item"><h3>Noise</h3></div></section>
"""
soup = BeautifulSoup(page, "lxml")
rank = soup.find("section", id="ranking")
names = [x.find("h3").get_text(strip=True) for x in rank.find_all("div", class_="item")]
print(names) # ['Alpha', 'Beta']
Pythonまとめ
find は「最初の1件」、find_all は「全件」。タグ・class_・id・属性・テキストで絞り、必要なら recursive=False で直下だけ、limit で先頭だけに抑えます。検索は「親でスコープを絞ってから子を取る」二段構えが定番。None と KeyError を避けるため、存在確認と .get を使い、安全にテキスト整形を行う。select(CSS)との併用で表現力が上がり、lxml+timeout+raise_for_status+encoding で安定。これらを型として覚えれば、初心者でも短く、正確で壊れない抽出が自然に書けます。
