Webページからデータを自動収集したい場面は多くあります。価格調査、競合分析、ニュース収集など、手作業では非効率な情報取得を自動化する技術がWebスクレイピングです。Pythonでスクレイピングを行う際に最も広く使われているライブラリがBeautiful Soupです。
Beautiful Soup(ビューティフルスープ)は、HTMLやXMLドキュメントを解析し、必要なデータを抽出するためのPythonライブラリです。2004年にLeonard Richardson氏によって公開され、20年以上にわたりPythonのスクレイピング分野で標準的な存在となっています。現在の最新バージョンはBeautiful Soup 4.14.3(2025年11月リリース)で、MITライセンスのもとオープンソースで提供されています。
ライブラリ名は、ルイス・キャロルの小説『不思議の国のアリス』に登場する「Beautiful Soup」という詩と、不正なHTMLを指す俗語「tag soup(タグスープ)」に由来しています。構造の崩れたHTMLでも美しく解析できるという意味が込められています。
出典: PyPI beautifulsoup4 / Beautiful Soup公式サイト
Beautiful Soupの特徴と他ライブラリとの違い
Beautiful Soupには、他のHTML解析ツールと比較して際立つ3つの特徴があります。
壊れたHTMLへの耐性
実際のWebページには、閉じタグの欠落やネストの不整合など、仕様に準拠しないHTMLが数多く存在します。Beautiful Soupは、こうした不正なマークアップを自動的に補正しながら解析できます。正規表現だけでHTMLを処理しようとすると、こうした不整合に対処しきれず、抽出に失敗するケースが頻発します。
複数パーサーの切り替え
Beautiful Soupはそれ自体がHTMLパーサーではなく、外部パーサーのラッパーとして機能します。用途に応じて複数のパーサーを切り替えられる設計になっています。
| パーサー | インストール | 処理速度 | 壊れたHTMLへの寛容性 | 適した用途 |
|---|---|---|---|---|
| html.parser | 不要(Python標準) | 中程度 | 中程度 | 追加インストールなしで手軽に使いたい場合 |
| lxml | pip install lxml | 最速(C言語実装) | 高い | 大量のページを高速に処理する場合 |
| html5lib | pip install html5lib | 低速 | 非常に高い(ブラウザと同等) | 極端に壊れたHTMLを正確に解析する場合 |
| lxml-xml | pip install lxml | 高速 | ― | XMLドキュメントの解析(唯一のXMLパーサー) |
実際のベンチマークでは、同じHTMLを100回解析した場合にlxmlが約7.5秒、html.parserが約11.8秒、html5libが約22.4秒という結果が報告されています。速度重視ならlxml、依存関係を減らしたいならhtml.parserが適切です。
直感的なAPI設計
Beautiful Soupは、Pythonのイディオムに沿ったシンプルなAPIを提供しています。HTMLの要素をPythonオブジェクトとして扱え、辞書のように属性にアクセスできるため、学習コストが低い点も特徴です。
Beautiful Soupのインストール手順
Beautiful Soupの導入は、pipコマンド1行で完了します。
pip install beautifulsoup4
パッケージ名はbeautifulsoup4です。beautifulsoup(数字なし)は旧バージョン(BS3)のパッケージなので注意が必要です。
高速なlxmlパーサーも合わせてインストールする場合は、以下のコマンドを実行します。
pip install beautifulsoup4 lxml
Anaconda環境を使用している場合は、condaでもインストール可能です。
conda install beautifulsoup4
インストールの確認
正しくインストールされたかは、Pythonインタープリタで確認できます。
from bs4 import BeautifulSoup
print(BeautifulSoup("<p>test</p>", "html.parser").p.string)
# 出力: test
エラーなくtestと表示されれば、インストールは成功です。
インストールできない場合の対処法
よくあるトラブルと対処法を以下にまとめます。
| 症状 | 原因 | 対処法 |
|---|---|---|
ModuleNotFoundError: No module named 'bs4' | パッケージ未インストール | pip install beautifulsoup4 を実行 |
| pipコマンドが見つからない | PythonのPathが通っていない | python -m pip install beautifulsoup4 を使用 |
| 権限エラー | システム全体のPythonにインストールしようとしている | pip install --user beautifulsoup4 または仮想環境を使用 |
| バージョン競合 | 古いPythonを使用している | Python 3.7以上にアップデート |
Beautiful Soupの基本的な使い方
Beautiful Soupを使ったHTML解析は、大きく3つのステップで進めます。
ステップ1:HTMLの取得
まず、解析対象のHTMLを取得します。Webページからデータを取得する場合はrequestsライブラリを併用します。
import requests
from bs4 import BeautifulSoup
url = "https://example.com"
response = requests.get(url)
response.encoding = response.apparent_encoding # 文字化け防止
html = response.text
ローカルのHTMLファイルを読み込む場合は以下の方法です。
with open("sample.html", encoding="utf-8") as f:
html = f.read()
ステップ2:BeautifulSoupオブジェクトの生成
取得したHTMLをBeautifulSoupに渡して、解析可能なオブジェクトを生成します。
soup = BeautifulSoup(html, "html.parser")
第2引数にはパーサーを指定します。省略可能ですが、明示的に指定しないと環境によって異なるパーサーが使用され、結果が変わる可能性があります。常にパーサーを明示することを推奨します。
ステップ3:要素の抽出
生成したsoupオブジェクトから、目的のデータを抽出します。
# タイトルを取得
title = soup.title.string
print(title)
# 最初のh1タグを取得
h1 = soup.h1
print(h1.text)
findメソッドとfind_allメソッドの使い方
Beautiful Soupの要素検索で中心となるのがfind()とfind_all()です。
find() — 最初の1件を取得
find()はマッチする最初の要素を1つだけ返します。
# タグ名で検索
first_p = soup.find("p")
# class属性で検索
header = soup.find("div", class_="header")
# id属性で検索
main = soup.find("div", id="main-content")
# 複数の属性で検索
item = soup.find("span", attrs={"data-type": "price", "class": "amount"})
マッチする要素がない場合、find()はNoneを返します。そのため、取得結果を使う前にNoneチェックを行うとエラーを防げます。
element = soup.find("div", class_="target")
if element:
print(element.text)
find_all() — 条件に合う全要素をリストで取得
find_all()はマッチするすべての要素をリストとして返します。
# すべてのaタグを取得
links = soup.find_all("a")
for link in links:
print(link.get("href"))
# 複数のタグを一度に検索
headers = soup.find_all(["h2", "h3"])
# 件数を制限
first_three = soup.find_all("li", limit=3)
# 正規表現で検索
import re
tags_with_b = soup.find_all(re.compile("^b")) # b, body, br など
findとfind_allの使い分け
| 項目 | find() | find_all() |
|---|---|---|
| 戻り値 | Tag オブジェクト or None | リスト(空リストも含む) |
| 取得件数 | 最初の1件 | 全件(limitで制限可) |
| 主な用途 | 特定の1要素の取得 | 同種の要素をまとめて処理 |
| 存在しない場合 | None | 空リスト [] |
selectメソッドとselect_oneメソッドの活用
CSSセレクタに慣れている方には、select()とselect_one()が使いやすいです。
CSSセレクタによる要素指定
# クラス名で指定
items = soup.select(".item-list li")
# IDで指定
content = soup.select_one("#main-content")
# 属性セレクタ
external_links = soup.select('a[href^="https://"]')
# 子孫セレクタ
nested = soup.select("div.container > ul > li")
# n番目の要素
third_item = soup.select_one("ul li:nth-of-type(3)")
selectとfindの比較
| 観点 | select / select_one | find / find_all |
|---|---|---|
| 指定方法 | CSSセレクタ文字列 | タグ名 + 属性の引数 |
| 学習コスト | CSS経験者は低い | Python初心者に分かりやすい |
| 複合条件 | 1つの文字列で記述可能 | 辞書やattrsで指定 |
| 正規表現 | 使用不可 | 使用可能 |
| 柔軟性 | 階層構造の指定が簡潔 | カスタム関数でのフィルタが可能 |
実務では、単純な要素取得にはfind()/find_all()を、複雑な階層指定にはselect()を使い分けるのが効率的です。
テキストと属性値の取得方法
HTML要素からテキストや属性を取り出す方法を整理します。
テキストの取得
element = soup.find("p")
# .string — 直接の子テキストが1つだけの場合
print(element.string)
# .text / get_text() — 子孫要素のテキストをすべて結合
print(element.text)
print(element.get_text())
# 区切り文字を指定して取得
print(element.get_text(separator="\n"))
# 前後の空白を除去
print(element.get_text(strip=True))
.stringは要素が直接1つのテキストノードだけを持つ場合にしか値を返しません。子要素を含む場合はNoneになるため、一般的には.textやget_text()を使う方が安全です。
属性値の取得
link = soup.find("a")
# 辞書形式でアクセス
href = link["href"]
# getメソッド(属性がなくてもエラーにならない)
href = link.get("href")
class_names = link.get("class", []) # デフォルト値を指定可能
# すべての属性を辞書として取得
attrs = link.attrs
print(attrs) # {'href': '...', 'class': ['...'], 'id': '...'}
HTML要素のツリー構造を辿る方法
Beautiful Soupでは、要素間の親子・兄弟関係をプロパティで辿ることができます。
element = soup.find("li")
# 親要素
parent = element.parent
# すべての先祖要素
for ancestor in element.parents:
print(ancestor.name)
# 子要素のリスト
children = list(element.children)
# すべての子孫要素
for desc in element.descendants:
print(desc)
# 次の兄弟要素
next_sib = element.next_sibling
# 前の兄弟要素
prev_sib = element.previous_sibling
注意点として、next_siblingは改行やスペースのテキストノードを返す場合があります。HTMLタグだけを取得したい場合はnext_elementの代わりにfind_next_sibling()を使います。
# テキストノードをスキップしてHTMLタグの兄弟を取得
next_tag = element.find_next_sibling()
テーブルデータの抽出とCSV・Pandas連携
Webページの表(table要素)からデータを抽出し、加工する実践的なパターンです。
テーブルからデータを取り出す
table = soup.find("table")
rows = table.find_all("tr")
data = []
for row in rows:
cells = row.find_all(["th", "td"])
row_data = [cell.get_text(strip=True) for cell in cells]
data.append(row_data)
# ヘッダー行とデータ行を分離
headers = data[0]
body = data[1:]
CSVファイルへの保存
import csv
with open("output.csv", "w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerow(headers)
writer.writerows(body)
Pandasでの読み込み
pandasと組み合わせると、テーブルの取り込みを1行で行えます。
import pandas as pd
tables = pd.read_html(str(soup)) # ページ内の全テーブルをDataFrameのリストで取得
df = tables[0] # 最初のテーブルを使用
print(df.head())
Beautiful SoupとSeleniumの使い分け
スクレイピングでは、Beautiful SoupとSeleniumのどちらを使うか迷う場面があります。両者は役割が異なるため、対象ページの特性に応じて選択します。
| 比較項目 | Beautiful Soup | Selenium |
|---|---|---|
| 役割 | HTMLの解析・データ抽出 | ブラウザの自動操作 |
| JavaScript実行 | 不可 | 可能 |
| 動的コンテンツ | 取得できない | 取得できる |
| 処理速度 | 高速 | 低速(ブラウザ起動が必要) |
| リソース消費 | 少ない | 大きい(メモリ・CPU) |
| ログイン操作 | 困難(Cookieの手動設定が必要) | 容易(フォーム操作が可能) |
組み合わせて使うパターン
JavaScriptで描画されるページの場合、SeleniumでHTMLを取得し、Beautiful Soupで解析する方法が効果的です。
from selenium import webdriver
from bs4 import BeautifulSoup
driver = webdriver.Chrome()
driver.get("https://example.com/dynamic-page")
# JavaScriptの実行完了を待機
import time
time.sleep(3)
# レンダリング後のHTMLを取得
html = driver.page_source
driver.quit()
# Beautiful Soupで解析
soup = BeautifulSoup(html, "html.parser")
items = soup.find_all("div", class_="item")
静的なページであればrequests + Beautiful Soupだけで十分です。Seleniumはブラウザ起動のオーバーヘッドがあるため、必要な場合にのみ使用します。
スクレイピング時の文字化け対策
日本語サイトをスクレイピングすると、文字化けが発生することがあります。原因と対策は以下のとおりです。
requestsでのエンコーディング指定
response = requests.get(url)
# 方法1: apparent_encodingを使用(自動判定)
response.encoding = response.apparent_encoding
# 方法2: 明示的に指定
response.encoding = "utf-8"
# または
response.encoding = "shift_jis"
BeautifulSoupのfrom_encoding引数
# エンコーディングを直接指定して解析
soup = BeautifulSoup(response.content, "html.parser", from_encoding="shift_jis")
response.textではなくresponse.content(バイト列)を渡し、from_encodingでエンコーディングを指定すると、文字化けを確実に防止できます。
Webスクレイピングの法的・倫理的な注意点
スクレイピングを実施する前に、対象サイトの利用規約とrobots.txtを必ず確認します。
確認すべき項目
- robots.txt:
https://example.com/robots.txtにアクセスし、Disallowで禁止されているパスを確認します - 利用規約: スクレイピングや自動アクセスを禁止している場合があります
- アクセス頻度: サーバーに過度な負荷をかけないよう、リクエスト間に
time.sleep()を入れます
アクセス間隔の設定例
import time
urls = ["https://example.com/page1", "https://example.com/page2"]
for url in urls:
response = requests.get(url)
soup = BeautifulSoup(response.text, "html.parser")
# データ処理...
time.sleep(1) # 1秒以上の間隔を空ける
日本では、不正競争防止法の「限定提供データ」の保護規定により、利用規約で禁止されているデータの無断取得が不正競争行為に該当する可能性があります。また、著作権法上もデータベースの著作物の複製にあたるケースがあるため、個人利用であっても対象サイトのルールを守ることが重要です。
実践:ニュースサイトの見出しを収集する
ここまでの知識を組み合わせた実践的なスクレイピング例として、ニュースサイトの見出し一覧を取得するスクリプトを紹介します。
import requests
from bs4 import BeautifulSoup
import csv
import time
def scrape_headlines(url):
"""指定URLからニュースの見出しとリンクを取得する"""
headers = {"User-Agent": "Mozilla/5.0 (compatible; MyBot/1.0)"}
response = requests.get(url, headers=headers)
response.encoding = response.apparent_encoding
soup = BeautifulSoup(response.text, "html.parser")
headlines = []
for article in soup.find_all("article"):
title_tag = article.find(["h2", "h3"])
link_tag = article.find("a")
if title_tag and link_tag:
headlines.append({
"title": title_tag.get_text(strip=True),
"url": link_tag.get("href", "")
})
return headlines
def save_to_csv(data, filename="headlines.csv"):
"""取得したデータをCSVファイルに保存する"""
with open(filename, "w", encoding="utf-8", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["title", "url"])
writer.writeheader()
writer.writerows(data)
# 実行
results = scrape_headlines("https://example.com/news")
save_to_csv(results)
print(f"{len(results)}件の見出しを取得しました")
このスクリプトでは、articleタグ内の見出しとリンクを抽出し、CSV形式で保存しています。対象サイトのHTML構造に合わせて、セレクタを調整して使用してください。
よくある質問(FAQ)
Beautiful Soup 3とBeautiful Soup 4の違いは?
Beautiful Soup 4(BS4)は2012年3月に初回リリースされ、BS3から大幅に改善されました。主な違いは以下のとおりです。
| 項目 | Beautiful Soup 3 | Beautiful Soup 4 |
|---|---|---|
| パッケージ名 | BeautifulSoup | beautifulsoup4 |
| import文 | from BeautifulSoup import ... | from bs4 import BeautifulSoup |
| パーサー | Python標準のSGMLParser固定 | html.parser / lxml / html5libを選択可能 |
| メソッド名 | キャメルケース(findAll) | スネークケース(find_all) |
| CSSセレクタ | 外部アドオンが必要 | .select()メソッドを内蔵 |
| Python対応 | Python 2のみ | Python 3対応(現在はPython 3.7以上) |
| 保守状況 | 2020年12月31日にサポート終了 | 現在もメンテナンス継続中 |
BS3からBS4への移行は、多くの場合import文の変更とメソッド名のスネークケース化だけで対応できます。新規プロジェクトでは必ずBS4を使用してください。
XPathは使えますか?
Beautiful Soup単体ではXPathをサポートしていません。XPathを使いたい場合はlxmlライブラリを直接使用するか、Beautiful Soupで取得した要素をlxmlのツリーに変換する方法があります。
from lxml import etree
# Beautiful Soupの結果をlxmlに変換
dom = etree.HTML(str(soup))
elements = dom.xpath('//div[@class="target"]/p')
Scrapy との違いは?
Scrapyはクローリングとスクレイピングの両方を担うフレームワークです。非同期処理、リクエスト管理、パイプライン処理などが組み込まれており、大規模なデータ収集プロジェクトに向いています。Beautiful Soupはあくまで「HTML解析」に特化したライブラリであり、小〜中規模のスクレイピングや、他のフレームワークと組み合わせて使うのに適しています。
| 観点 | Beautiful Soup | Scrapy |
|---|---|---|
| 種類 | ライブラリ(HTML解析) | フレームワーク(クローリング+解析) |
| 学習コスト | 低い | 高い |
| 非同期処理 | なし(同期処理) | 内蔵(Twisted基盤) |
| 規模 | 小〜中規模 | 大規模 |
| カスタマイズ性 | 他ライブラリとの組み合わせで対応 | パイプライン・ミドルウェアで拡張 |
まとめ
Beautiful Soupは、Pythonで手軽にHTMLを解析できるスクレイピングの定番ライブラリです。find()やselect()といった直感的なメソッドで必要な要素を効率的に取り出せます。
導入手順はpip install beautifulsoup4だけで完了し、html.parserを使えば追加パッケージなしで動作します。処理速度を重視するならlxmlパーサーの導入を検討してください。
JavaScriptで動的に生成されるコンテンツにはBeautiful Soup単体では対応できないため、Seleniumとの併用が有効です。大規模なクローリングにはScrapyも選択肢に入ります。
スクレイピングを実施する際は、対象サイトのrobots.txtと利用規約を確認し、適切なアクセス間隔を設けることを忘れないようにしてください。