Python | Web / API:スクレイピング基本(BeautifulSoup)

Python Python
スポンサーリンク

概要(BeautifulSoupは「HTMLから欲しい所だけ抜く」ための定番)

BeautifulSoupは、取得したHTMLを“読みやすい木構造”にして、タグ名・クラス名・CSSセレクタで必要部分だけを取り出すためのライブラリです。初心者がまず押さえるのは「HTML取得(requests)→ 解析(BeautifulSoup)→ 抽出(find/select)→ 整形・保存」の流れです。重要ポイントは、文字化け対策、セレクタの選び分け、例外とタイムアウト、サイトの規約と負荷への配慮です。


はじめ方(インストールと最短の抽出)

インストールと基本の流れ

# インストール
# pip install requests beautifulsoup4 lxml

import requests
from bs4 import BeautifulSoup

url = "https://example.com"
resp = requests.get(url, timeout=5)
resp.raise_for_status()
resp.encoding = resp.apparent_encoding  # 文字化け対策(推定で上書き)

soup = BeautifulSoup(resp.text, "lxml")  # 解析(パーサにlxmlを指定)
title = soup.title.get_text(strip=True)
print(title)
Python

「requestsでHTMLを取得」「BeautifulSoupで解析」「get_textや属性アクセスで取り出す」が最短ルートです。パーサは標準のhtml.parserでも動きますが、lxmlの方が速くて頑丈なことが多いです。

見出し・リンク・本文の基本抽出

from bs4 import BeautifulSoup
import requests

resp = requests.get("https://httpbin.org/html", timeout=5)
resp.raise_for_status()
soup = BeautifulSoup(resp.text, "lxml")

h1 = soup.find("h1").get_text(strip=True)
links = [a["href"] for a in soup.find_all("a", href=True)]
paragraphs = [p.get_text(strip=True) for p in soup.find_all("p")]
print(h1, links[:5], paragraphs[:3])
Python

findは最初の一致、find_allは全部。hrefのような属性は辞書アクセスで取り出せます。get_text(strip=True)で前後の空白を除去して整えます。


抽出の要点(タグ・クラス・CSSセレクタの選び分け)

タグ名・クラス名で精度を上げる

soup = BeautifulSoup(html, "lxml")

# 例:記事リストのタイトル(div.article > a.title)
items = soup.find_all("div", class_="article")
titles = [it.find("a", class_="title").get_text(strip=True) for it in items]
print(titles[:10])
Python

class属性でグッと絞ると、不要な要素を拾いにくくなります。idは一意なことが多いので、特定要素の取得に向いています。

CSSセレクタ(select / select_one)が強力

# 例:ul#news li > a.title のテキスト
for a in soup.select("ul#news li > a.title"):
    print(a.get_text(strip=True), a["href"])
Python

CSSセレクタは「#id」「.class」「親子・子孫(> / 空白)」が使え、フロントエンド経験者には直感的です。複雑な構造でも短く書けます。

属性で条件指定(部分一致や存在チェック)

# data-* 属性を持つボタンを抽出
buttons = soup.select('button[data-action]')
# 特定パターンのhrefだけ
docs = soup.select('a[href*="/docs/"]')  # 部分一致はCSSではなく、後からifで絞ることも多い
Python

属性存在([attr])や値指定([attr=”…”])で精度を上げます。部分一致は最終的にPython側でフィルタするのが確実です。


文字コード・整形・保存(使える形に仕上げる)

文字化け対策と安全なテキスト抽出

resp = requests.get(url, timeout=5)
resp.raise_for_status()
resp.encoding = resp.apparent_encoding  # 推定で上書き

soup = BeautifulSoup(resp.text, "lxml")
text = soup.get_text("\n", strip=True)  # ページ全体のテキスト(改行区切り)
Python

apparent_encodingは万能ではありません。日本語サイトでmeta charsetやHTTPヘッダーが正しく付いていない場合、推定上書きが役立ちます。

CSVやJSONでの保存

import csv, json
from pathlib import Path

records = [{"title": t, "href": h} for t, h in zip(titles, links)]
Path("out").mkdir(exist_ok=True)
with open("out/articles.csv", "w", newline="", encoding="utf-8") as f:
    w = csv.DictWriter(f, fieldnames=["title", "href"])
    w.writeheader(); w.writerows(records)

with open("out/articles.json", "w", encoding="utf-8") as f:
    json.dump(records, f, ensure_ascii=False, indent=2)
Python

整形後はCSV/JSONで保存すると、後続の分析や再利用が楽になります。ensure_ascii=Falseで日本語をそのまま保存できます。


実務の型(例外処理・速度・マナー・動的ページ)

例外・タイムアウト・リトライ(壊れない取得)

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

session = requests.Session()
session.mount("https://", HTTPAdapter(max_retries=Retry(total=3, backoff_factor=0.5, status_forcelist=[500, 502, 503, 504])))

try:
    r = session.get("https://example.com", timeout=5)
    r.raise_for_status()
except requests.Timeout:
    print("タイムアウト")
except requests.ConnectionError:
    print("接続エラー")
except requests.HTTPError as e:
    print("HTTPエラー:", e)
Python

タイムアウトは必須です。raise_for_statusで失敗を例外にし、5xx限定で短いバックオフのリトライが有効です。

robots.txt・規約・アクセス間隔(マナーと法的配慮)

スクレイピングが許可されているかをサイトの利用規約やrobots.txtで確認します。取得間隔を空け、必要最小限のアクセスに留め、APIが提供されているならAPIを利用します。機密ページや認証が必要な領域の無断取得は避けます。

動的レンダリング(JavaScript生成)への対処

BeautifulSoupは“取得時点の静的HTML”しか見えません。JavaScriptで後からDOMが生成されるサイトは、APIを探す、ネットワークタブでJSONを特定する、どうしても必要なら自動ブラウザ(Selenium/Playwright)を使うなどで対応します。

速度と安定性(セレクタ・パーサ・分割保存)

セレクタはできる限り絞り、不要な全ページ取得を避けます。パーサはlxmlが速い傾向。大量ページでは小分け保存と途中再開(チェックポイント)で安定させます。


例題で身につける(定番から一歩先まで)

例題1:ニュース一覧からタイトルとリンクを抽出

import requests
from bs4 import BeautifulSoup

url = "https://httpbin.org/html"
r = requests.get(url, 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.select("a.title")]
links = [a["href"] for a in soup.select("a.title[href]")]
for t, h in zip(titles, links):
    print(t, h)
Python

CSSセレクタで狙い撃つと、短いコードで読みやすく書けます。

例題2:テーブルから行データを抽出してCSV化

import csv, requests
from bs4 import BeautifulSoup

r = requests.get("https://httpbin.org/html", timeout=5)
r.raise_for_status()
soup = BeautifulSoup(r.text, "lxml")

rows = []
for tr in soup.select("table.data tr")[1:]:  # ヘッダ行を除外
    cells = [td.get_text(strip=True) for td in tr.select("td")]
    rows.append(cells)

with open("table.csv", "w", newline="", encoding="utf-8") as f:
    csv.writer(f).writerows(rows)
Python

テーブルはtr/tdで素直に辿ります。ヘッダ行の扱いだけ意識しましょう。

例題3:ページネーションを辿ってまとめ取得

import time, requests
from bs4 import BeautifulSoup

base = "https://example.com/list?page={}"
all_titles = []

for page in range(1, 4):
    r = requests.get(base.format(page), timeout=5)
    r.raise_for_status()
    soup = BeautifulSoup(r.text, "lxml")
    titles = [a.get_text(strip=True) for a in soup.select("h2.item-title")]
    all_titles.extend(titles)
    time.sleep(0.3)  # 負荷軽減の小休止

print(len(all_titles), "items")
Python

複数ページは「上限を決めて」「間隔を置いて」取得します。次ページのリンク(a.next)を辿る方式でもOKです。

例題4:詳細ページへジャンプして属性を収集

import requests
from bs4 import BeautifulSoup

index = requests.get("https://example.com/products", timeout=5); index.raise_for_status()
soup = BeautifulSoup(index.text, "lxml")

items = []
for a in soup.select("a.product[href]"):
    detail_url = a["href"]
    r = requests.get(detail_url, timeout=5); r.raise_for_status()
    d = BeautifulSoup(r.text, "lxml")
    name = d.select_one("h1.product-name").get_text(strip=True)
    price = d.select_one("span.price").get_text(strip=True)
    items.append({"name": name, "price": price})

print(items[:3])
Python

「一覧→詳細」の2段構えは定番です。失敗時の例外処理と途中保存を入れると実務で安定します。


まとめ

スクレイピングの骨子は「requestsでHTML取得→BeautifulSoupで解析→セレクタで抽出→整形・保存」です。重要なのは、文字化け対策(encoding)、findとselectの使い分け、例外処理とタイムアウト、ページネーションの小休止、サイト規約と負荷への配慮です。静的HTMLが基本で、動的生成はAPIや自動ブラウザの検討が必要。これらを型として覚えれば、初心者でも短いコードで“必要な情報だけを正確に抜く”スクレイピングが安定して書けます。

タイトルとURLをコピーしました