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標準)中程度中程度追加インストールなしで手軽に使いたい場合
lxmlpip install lxml最速(C言語実装)高い大量のページを高速に処理する場合
html5libpip install html5lib低速非常に高い(ブラウザと同等)極端に壊れたHTMLを正確に解析する場合
lxml-xmlpip install lxml高速XMLドキュメントの解析(唯一のXMLパーサー)

実際のベンチマークでは、同じHTMLを100回解析した場合にlxmlが約7.5秒、html.parserが約11.8秒、html5libが約22.4秒という結果が報告されています。速度重視ならlxml、依存関係を減らしたいならhtml.parserが適切です。

出典: Beautiful Soup公式ドキュメント

直感的な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_onefind / 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になるため、一般的には.textget_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 SoupSelenium
役割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 3Beautiful Soup 4
パッケージ名BeautifulSoupbeautifulsoup4
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を使用してください。

出典: Beautiful Soup公式ドキュメント

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 SoupScrapy
種類ライブラリ(HTML解析)フレームワーク(クローリング+解析)
学習コスト低い高い
非同期処理なし(同期処理)内蔵(Twisted基盤)
規模小〜中規模大規模
カスタマイズ性他ライブラリとの組み合わせで対応パイプライン・ミドルウェアで拡張

まとめ

Beautiful Soupは、Pythonで手軽にHTMLを解析できるスクレイピングの定番ライブラリです。find()select()といった直感的なメソッドで必要な要素を効率的に取り出せます。

導入手順はpip install beautifulsoup4だけで完了し、html.parserを使えば追加パッケージなしで動作します。処理速度を重視するならlxmlパーサーの導入を検討してください。

JavaScriptで動的に生成されるコンテンツにはBeautiful Soup単体では対応できないため、Seleniumとの併用が有効です。大規模なクローリングにはScrapyも選択肢に入ります。

スクレイピングを実施する際は、対象サイトのrobots.txtと利用規約を確認し、適切なアクセス間隔を設けることを忘れないようにしてください。