Webクローリングの基礎 — BeautifulSoup
このトピックを終えると
requestsを使ってWebページを取得し、BeautifulSoupを使って必要なデータを抽出する基本的な流れを実装できるようになります。
クローリングとは
Webクローリングとは、プログラムがWebページにアクセスしてデータを自動的に収集することです。スクレイピングとも呼ばれます。
ブラウザが行うことをコードで実現します。
- Webサーバーにリクエストを送信します。
- HTMLを取得します。
- HTMLから必要なデータを抽出します。
1段階 — HTMLの取得 (requests)
import requests
url = "https://example.com"response = requests.get(url)
print(response.status_code) # 200 = 正常print(response.text[:200]) # HTMLテキストの最初の200文字requests.get(url)はブラウザのようにサーバーにリクエストを送信し、サーバーから送信されたHTMLをresponse.textで取得します。
# ステータスコードの確認if response.status_code == 200: html = response.textelif response.status_code == 404: print("ページが見つかりません")elif response.status_code == 403: print("アクセスが拒否されました")2段階 — HTMLの解析 (BeautifulSoup)
from bs4 import BeautifulSoup
html = """<html><body> <h1 class="title">ニュースの見出し</h1> <div class="content"> <p>最初の段落です。</p> <p>2番目の段落です。</p> </div> <ul id="tags"> <li>Python</li> <li>データ</li> <li>クローリング</li> </ul></body></html>"""
soup = BeautifulSoup(html, "html.parser")BeautifulSoupはHTML文字列を解析して、ツリー構造のオブジェクトを作成します。このオブジェクトからタグ、クラス、IDなどで必要な部分を見つけることができます。
3段階 — データの抽出
タグで検索
# 最初のh1タグtitle = soup.find("h1")print(title.text) # "ニュースの見出し"print(title["class"]) # ["title"]
# すべてのpタグparagraphs = soup.find_all("p")for p in paragraphs: print(p.text)# "最初の段落です。"# "2番目の段落です。"CSSセレクターで検索
# select — CSSセレクターを使用 (querySelectorAllと同じ)items = soup.select("ul#tags li")for item in items: print(item.text)# "Python"# "データ"# "クローリング"
# クラスで選択content = soup.select_one("div.content")print(content.text.strip())find/find_allよりも、select/select_oneの方がCSSに慣れている人にとっては直感的です。
実践的な例 — テーブルデータの抽出
table_html = """<table> <tr><th>名前</th><th>スコア</th></tr> <tr><td>철수</td><td>85</td></tr> <tr><td>영희</td><td>92</td></tr> <tr><td>민수</td><td>78</td></tr></table>"""
soup = BeautifulSoup(table_html, "html.parser")rows = soup.select("tr")
data = []for row in rows[1:]: # ヘッダーを除く cols = row.find_all("td") data.append({ "名前": cols[0].text, "スコア": int(cols[1].text) })
print(data)# [{'名前': '철수', 'スコア': 85}, {'名前': '영희', 'スコア': 92}, {'名前': '민수', 'スコア': 78}]# pandas DataFrameに直接変換import pandas as pddf = pd.DataFrame(data)print(df)注意点
リクエスト間隔
import time
urls = ["https://example.com/page/1", "https://example.com/page/2"]for url in urls: response = requests.get(url) time.sleep(1) # 1秒待機 — サーバーに負担をかけないようにする連続して高速にリクエストすると、サーバーに負担をかけたり、IPがブロックされたりする可能性があります。
User-Agentの設定
headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/91.0"}response = requests.get(url, headers=headers)一部のサーバーは、ボットをブロックするためにUser-Agentを確認します。
robots.txt
https://example.com/robots.txtこのファイルには、クローリングが許可されているパスと禁止されているパスが記載されています。サイトのクローリングポリシーを最初に確認することは、礼儀であり、法的安全策です。
クローリングの限界
BeautifulSoupは静的なHTMLのみを処理できます。JavaScriptで動的にロードされるコンテンツ(SPA、無限スクロールなど)はHTMLに含まれないため、抽出できません。
| 状況 | ツール |
|---|---|
| 静的なHTML | requests + BeautifulSoup |
| JavaScriptによる動的ロード | Selenium, Playwright |
| APIを提供 | requestsでAPIを直接呼び出す(最も効率的) |
APIがある場合は、クローリングよりもAPIを優先的に使用します。構造化されたデータを正確に取得でき、サーバーへの負担も軽減されます。
基本的な流れの要約
1. requests.get(url) → HTMLテキスト
2. BeautifulSoup(html) → 解析されたツリー
3. soup.select("CSSセレクター") → 必要な要素
4. element.text / element["attr"] → データ抽出エラー処理
実際のクローリングでは、さまざまなエラーが発生します。
import requestsfrom bs4 import BeautifulSoup
def safe_fetch(url, retries=3): for attempt in range(retries): try: response = requests.get(url, timeout=10) response.raise_for_status() return response.text except requests.exceptions.Timeout: print(f"タイムアウト ({attempt + 1}/{retries})") except requests.exceptions.HTTPError as e: print(f"HTTPエラー: {e}") return None except requests.exceptions.ConnectionError: print(f"接続失敗 ({attempt + 1}/{retries})") return Nonetimeoutを設定しないと、応答のないサーバーでプログラムが無限に待機します。raise_for_status()は、4xx/5xxレスポンスで例外を発生させます。
複数のページをクローリング
import time
base_url = "https://example.com/articles?page="all_titles = []
for page in range(1, 11): html = safe_fetch(f"{base_url}{page}") if html is None: continue soup = BeautifulSoup(html, "html.parser") titles = soup.select("h2.article-title") all_titles.extend([t.text.strip() for t in titles]) print(f"ページ {page}: {len(titles)}件 収集") time.sleep(1)
print(f"合計 {len(all_titles)}件 収集完了")クローリング結果の保存
import jsonimport csv
# JSONで保存with open("articles.json", "w", encoding="utf-8") as f: json.dump(all_titles, f, ensure_ascii=False, indent=2)
# CSVで保存with open("articles.csv", "w", encoding="utf-8", newline="") as f: writer = csv.writer(f) writer.writerow(["番号", "タイトル"]) for i, title in enumerate(all_titles, 1): writer.writerow([i, title])収集したデータは、JSONまたはCSVで保存して、pandasで直接分析できるようにします。
リンクの抽出 — href属性
# ページ内のすべてのリンクを抽出links = soup.select("a[href]")for link in links: url = link["href"] text = link.text.strip() print(f"{text}: {url}")
# 特定のパターンのリンクのみarticle_links = [ a["href"] for a in soup.select("a[href]") if "/article/" in a.get("href", "")]クローリングは、「データを取得する技術」の第一歩です。データ分析、機械学習、モニタリングなど、あらゆる場所でデータ収集が出発点となります。