
PythonでWebスクレイピングを始める際、まず候補になるのが requests と BeautifulSoup です。
BeautifulSoupはHTMLを解析するための非常にシンプルで扱いやすいライブラリで、個人開発やデータ収集、Webスクレイピングの学習に最適です。
一方で、スクレイピングを実際のサービスや社内システムで継続的に運用する場合、単純にHTMLを解析するだけでは解決できない問題も出てきます。
本記事では、Requests + BeautifulSoupを使った自作スクレイピングをベースに、CSSセレクター、JavaScriptによる動的ページ、データ検証、エラーハンドリングなどをどのように設計すればよいかを紹介します。
目次
- BeautifulSoupとは
- 基本的なスクレイピング
- CSSセレクターを使ったデータ抽出
- 構造化データとして取得する
- Pydanticでデータを検証する
- スクレイピングで発生する問題
- JavaScriptで生成されるページへの対応
- BeautifulSoup + Playwrightという構成
- 実運用を意識した構成
- まとめ
BeautifulSoupとは
BeautifulSoupは、PythonからHTMLやXMLを解析するためのライブラリです。
例えば、以下のようなHTMLがあるとします。
<div class="product">
<h1>Example Product</h1>
<span class="price">¥12,800</span>
<span class="stock">In Stock</span>
</div>
BeautifulSoupを使えば、簡単に情報を取り出せます。
from bs4 import BeautifulSoup
html = """
<div class="product">
<h1>Example Product</h1>
<span class="price">¥12,800</span>
<span class="stock">In Stock</span>
</div>
"""
soup = BeautifulSoup(html, "html.parser")
title = soup.select_one("h1")
price = soup.select_one(".price")
print(title.get_text(strip=True))
print(price.get_text(strip=True))
実行すると、
Example Product
¥12,800
のように取得できます。
基本的なスクレイピング
実際のWebサイトからHTMLを取得する場合は、requests と組み合わせます。
import requests
from bs4 import BeautifulSoup
url = "https://example.com"
response = requests.get(
url,
headers={
"User-Agent": "Mozilla/5.0"
},
timeout=10
)
response.raise_for_status()
soup = BeautifulSoup(
response.text,
"html.parser"
)
print(soup.title.get_text(strip=True))
基本的な流れは非常にシンプルです。
URL
↓
requests.get()
↓
HTML
↓
BeautifulSoup
↓
必要なデータを抽出
CSSセレクターを使ったデータ抽出
BeautifulSoupではCSSセレクターを利用できます。
例えば、
<div class="product">
<h1 class="product-title">Example Product</h1>
<span class="price">¥12,800</span>
<span class="stock">In Stock</span>
</div>
であれば、
title = soup.select_one(".product-title")
price = soup.select_one(".price")
stock = soup.select_one(".stock")
のように取得できます。
複数の商品を取得する場合は、
products = soup.select(".product")
for product in products:
title = product.select_one(".product-title")
price = product.select_one(".price")
print({
"title": title.get_text(strip=True) if title else None,
"price": price.get_text(strip=True) if price else None,
})
とできます。
構造化データとして取得する
スクレイピングしたデータを、そのまま文字列として扱うのではなく、最初から辞書やJSONとして扱うと後続処理が簡単になります。
例えば、
import requests
from bs4 import BeautifulSoup
def scrape_product(url: str) -> dict:
response = requests.get(
url,
headers={
"User-Agent": "Mozilla/5.0"
},
timeout=10
)
response.raise_for_status()
soup = BeautifulSoup(
response.text,
"html.parser"
)
title = soup.select_one("h1.product-title")
price = soup.select_one(".price")
stock = soup.select_one(".stock")
return {
"title": title.get_text(strip=True) if title else None,
"price": price.get_text(strip=True) if price else None,
"in_stock": (
stock is not None
and "in stock" in stock.get_text(strip=True).lower()
),
}
利用側は、
result = scrape_product(
"https://example.com/product/123"
)
print(result)
とするだけです。
結果:
{
"title": "Example Product",
"price": "¥12,800",
"in_stock": true
}
このように、スクレイピング処理を関数として切り出しておくと、APIやバッチ処理からも利用しやすくなります。
Pydanticでデータを検証する
実際のサービスでは、「取得できた」だけでは不十分です。
例えば価格が本来 number であるにもかかわらず、
"¥12,800"
のような文字列で取得される可能性があります。
そこでPydanticを使って、取得したデータを検証できます。
from pydantic import BaseModel
class Product(BaseModel):
title: str
price: float
in_stock: bool
例えば、
product = Product(
title="Example Product",
price=12800,
in_stock=True
)
print(product.model_dump())
結果:
{
"title": "Example Product",
"price": 12800.0,
"in_stock": true
}
これによって、
Webページ
↓
BeautifulSoup
↓
データ抽出
↓
Pydantic
↓
検証済みJSON
という流れを作ることができます。
スクレイピングで発生する問題
ここまでを見ると、
BeautifulSoupだけで十分なのでは?
と思うかもしれません。
しかし、実際に複数のWebサイトを継続的にスクレイピングすると、いくつかの問題に遭遇します。
代表的なのが以下です。
1. HTML構造の変更
例えば、
.product-title
を使っていたのに、サイト側の変更によって、
.product-name
に変更されたとします。
すると、
soup.select_one(".product-title")
はデータを取得できなくなります。
2. CSSクラス名の変更
さらに厄介なのが、サイトによってはCSSクラス名が頻繁に変更されるケースです。
例えば、
<div class="product-card">
だったものが、
<div class="product-card-v2">
になるだけでも、既存のセレクターが壊れる可能性があります。
3. JavaScriptによる動的レンダリング
BeautifulSoupが直接解析できるのは、基本的に取得したHTMLです。
しかし、最近のWebサイトではJavaScriptによってページの内容が生成されることがあります。
例えば、
requests
↓
HTML取得
↓
BeautifulSoup
だけでは、商品情報が存在しないことがあります。
ブラウザで見ると表示されているのに、
requests.get(url).text
では存在しない、というケースです。
4. タイムアウトやネットワークエラー
本番環境では、
- Connection timeout
- HTTP 403
- HTTP 429
- Connection reset
- Server error
なども考慮する必要があります。
そのため、最低限のエラーハンドリングは必要です。
import requests
try:
response = requests.get(
url,
timeout=10
)
response.raise_for_status()
except requests.RequestException as e:
print(f"Scraping failed: {e}")
JavaScriptで生成されるページへの対応
JavaScriptでコンテンツが生成されるサイトでは、ブラウザ自動化ツールを利用できます。
代表的なものがPlaywrightです。
pip install playwright beautifulsoup4 requests
そして、
playwright install
を実行します。
例えば、
from playwright.sync_api import sync_playwright
from bs4 import BeautifulSoup
def scrape_dynamic_page(url: str) -> dict:
with sync_playwright() as p:
browser = p.chromium.launch(
headless=True
)
page = browser.new_page()
page.goto(
url,
wait_until="networkidle"
)
html = page.content()
browser.close()
soup = BeautifulSoup(
html,
"html.parser"
)
title = soup.select_one("h1")
return {
"title": title.get_text(strip=True)
if title else None
}
この場合、
URL
↓
Playwright
↓
JavaScript実行
↓
Rendered HTML
↓
BeautifulSoup
↓
データ抽出
という構成になります。
BeautifulSoup + Playwrightという構成
実際には、BeautifulSoupとPlaywrightは競合するものではありません。
それぞれ役割が違います。
| ツール | 主な役割 |
|---|---|
| Requests | HTTPリクエスト |
| BeautifulSoup | HTML解析 |
| Playwright | ブラウザ・JavaScript実行 |
| Pydantic | データ検証 |
そのため、以下のような構成にすると扱いやすくなります。
URL
│
▼
┌─────────────────┐
│ HTTP Fetcher │
│ Requests │
└────────┬────────┘
│
HTML available?
┌────┴────┐
Yes No
│ │
▼ ▼
BeautifulSoup Playwright
│ │
└────┬─────┘
▼
┌─────────────────┐
│ Data Extraction │
└────────┬────────┘
▼
┌─────────────────┐
│ Pydantic │
│ Validation │
└────────┬────────┘
▼
Structured
JSON
実運用を意識した構成
個人開発では、
soup.select_one(".price")
だけでも十分です。
しかし、本番運用を考えるなら、スクレイピング処理を複数のコンポーネントに分けることをおすすめします。
例えば、
scraper/
├── fetcher.py
├── parser.py
├── extractors/
│ ├── product.py
│ └── article.py
├── models.py
├── validators.py
└── exceptions.py
という構成です。
fetcher.py
HTMLを取得する責任を持たせます。
import requests
class Fetcher:
def fetch(self, url: str) -> str:
response = requests.get(
url,
headers={
"User-Agent": "Mozilla/5.0"
},
timeout=10
)
response.raise_for_status()
return response.text
parser.py
HTMLをBeautifulSoupに変換します。
from bs4 import BeautifulSoup
class Parser:
def parse(self, html: str) -> BeautifulSoup:
return BeautifulSoup(
html,
"html.parser"
)
models.py
取得したデータの構造を定義します。
from pydantic import BaseModel
class Product(BaseModel):
title: str
price: float
in_stock: bool
こうすることで、各コンポーネントの責任を明確にできます。
BeautifulSoupの最大のメリット
BeautifulSoupを使った自作スクレイピングの最大のメリットは、シンプルであることです。
requests
+
BeautifulSoup
+
Python
だけでも、多くのWebサイトからデータを取得できます。
また、外部のスクレイピングAPIに依存する必要がないため、
- API利用料金が不要
- 自分で処理を完全にコントロールできる
- データ処理を自由にカスタマイズできる
- Pythonの学習にもなる
- 小規模な処理なら非常に軽量
というメリットがあります。
特に、
- 学習目的
- 個人開発
- 1回限りのデータ収集
- HTML構造が安定したサイト
では非常に有効です。
一方で、運用コストには注意
BeautifulSoup自体は無料ですが、スクレイピングシステム全体の運用コストがゼロになるわけではありません。
例えば、
サイトA
↓
CSSセレクター変更
↓
スクレイパー修正
という問題が発生する可能性があります。
さらに、
HTML変更
JavaScript変更
API変更
Cloudflare
Rate Limit
robots.txt
Timeout
なども考慮する必要があります。
つまり、
ライブラリが無料 = スクレイピングシステムの運用コストが無料
ではありません。
実際には、サイト数が増えるほどメンテナンスコストが増えていきます。
まとめ
BeautifulSoupは、PythonでWebスクレイピングを始めるうえで非常に優れたライブラリです。
基本的には、
Requests
↓
BeautifulSoup
↓
CSS Selector
↓
Pydantic
↓
JSON
という構成から始めることができます。
JavaScriptによる動的ページが必要になった場合は、
Requests
↓
Playwright
↓
BeautifulSoup
↓
Pydantic
のように拡張できます。
重要なのは、最初から複雑な仕組みを作るのではなく、
「HTTPで取得する → HTMLを解析する → 必要なデータを抽出する → 検証する」
という基本的な流れを理解することです。
BeautifulSoupはシンプルだからこそ、スクレイピングの仕組みを理解するための良い入口になります。
そして、実際に複数サイトを長期間運用してみることで、
「なぜセレクターの保守が問題になるのか」
「どの部分を自動化すべきなのか」
が見えてきます。
まずはBeautifulSoupで自分自身のスクレイパーを作ってみることをおすすめします。