Python BeautifulSoup完全ガイド – HTMLパース・Webスクレイピングの決定版
BeautifulSoupは、PythonでHTMLやXMLを解析するための最も人気の高いライブラリです。直感的なAPIと強力な検索機能により、Webスクレイピングやデータ抽出タスクを簡単に実行できます。この記事では、BeautifulSoupの基本的な使い方から高度なテクニックまで、実践的なサンプルコードとともに詳しく解説します。
BeautifulSoupとは
BeautifulSoupは、HTMLやXMLドキュメントを解析してPythonオブジェクトとして操作できるライブラリです。壊れたHTMLでも柔軟に処理でき、要素の検索、抽出、編集を直感的に行えます。
主な特徴
- 柔軟なHTMLパース: 不正なHTMLでも適切に処理
- 直感的なAPI: Pythonらしい分かりやすい記法
- 強力な検索機能: CSS セレクタや正規表現をサポート
- 複数パーサー対応: lxml、html.parser、html5libに対応
インストールと基本設定
ライブラリのインストール
pip install beautifulsoup4 lxml requests
基本的なインポート
from bs4 import BeautifulSoup
import requests
基本的な使い方
HTMLの読み込み
html = "<html><body><h1>Hello World</h1></body></html>"
soup = BeautifulSoup(html, 'html.parser')
print(soup.prettify())
Webページの取得と解析
url = "https://quotes.toscrape.com/"
response = requests.get(url)
soup = BeautifulSoup(response.content, 'html.parser')
print(soup.title.text) # Quotes to Scrape
ファイルからの読み込み
with open('sample.html', 'r', encoding='utf-8') as file:
soup = BeautifulSoup(file, 'html.parser')
パーサーの選択
html.parser(標準)
soup = BeautifulSoup(html, 'html.parser')
# 利点: 標準ライブラリ、高速
# 欠点: 古いPythonでは機能制限
lxml(推奨)
soup = BeautifulSoup(html, 'lxml')
# 利点: 非常に高速、豊富な機能
# 欠点: 外部依存
html5lib(最も厳密)
soup = BeautifulSoup(html, 'html5lib')
# 利点: ブラウザと同等の解析
# 欠点: 低速
要素の検索
find() – 最初の要素を取得
soup = BeautifulSoup('<div class="content"><p>段落1</p><p>段落2</p></div>', 'html.parser')
first_p = soup.find('p')
print(first_p.text) # 段落1
find_all() – すべての要素を取得
all_p = soup.find_all('p')
for p in all_p:
print(p.text) # 段落1, 段落2
属性での検索
html = '<div class="main"><span id="title">タイトル</span></div>'
soup = BeautifulSoup(html, 'html.parser')
# クラスで検索
main_div = soup.find('div', class_='main')
# IDで検索
title_span = soup.find('span', id='title')
print(title_span.text) # タイトル
複数属性での検索
html = '<a href="https://example.com" class="external" target="_blank">リンク</a>'
soup = BeautifulSoup(html, 'html.parser')
link = soup.find('a', {'class': 'external', 'target': '_blank'})
print(link['href']) # https://example.com
CSSセレクタの使用
基本的なセレクタ
html = '<div class="container"><p class="text">内容1</p><p class="text">内容2</p></div>'
soup = BeautifulSoup(html, 'html.parser')
# クラスセレクタ
texts = soup.select('.text')
print([t.text for t in texts]) # ['内容1', '内容2']
子要素・子孫要素の選択
html = '<div><ul><li>項目1</li><li>項目2</li></ul></div>'
soup = BeautifulSoup(html, 'html.parser')
# 子孫セレクタ
items = soup.select('div li')
# 直接子セレクタ
items = soup.select('ul > li')
print([item.text for item in items]) # ['項目1', '項目2']
属性セレクタ
html = '<input type="text" name="username"><input type="password" name="password">'
soup = BeautifulSoup(html, 'html.parser')
# 属性値での選択
text_input = soup.select('input[type="text"]')[0]
print(text_input['name']) # username
疑似セレクタ
html = '<ul><li>1番目</li><li>2番目</li><li>3番目</li></ul>'
soup = BeautifulSoup(html, 'html.parser')
first_item = soup.select('li:first-child')[0]
last_item = soup.select('li:last-child')[0]
print(first_item.text, last_item.text) # 1番目 3番目
属性とテキストの取得
属性の取得
html = '<a href="https://example.com" title="例サイト">リンク</a>'
soup = BeautifulSoup(html, 'html.parser')
link = soup.find('a')
print(link['href']) # https://example.com
print(link.get('title')) # 例サイト
print(link.get('class', 'なし')) # なし(デフォルト値)
テキストの取得
html = '<div>外側<p>内側<span>最内側</span></p></div>'
soup = BeautifulSoup(html, 'html.parser')
div = soup.find('div')
print(div.text) # 外側内側最内側
print(div.get_text()) # 外側内側最内側
print(div.get_text(separator=' ')) # 外側 内側最内側
HTMLタグを含むテキスト
html = '<p>これは<strong>重要</strong>な文章です</p>'
soup = BeautifulSoup(html, 'html.parser')
p = soup.find('p')
print(str(p)) # <p>これは<strong>重要</strong>な文章です</p>
print(p.decode_contents()) # これは<strong>重要</strong>な文章です
ナビゲーション
親要素・子要素の取得
html = '<div><p>段落<span>スパン</span></p></div>'
soup = BeautifulSoup(html, 'html.parser')
span = soup.find('span')
print(span.parent.name) # p
print(span.parent.parent.name) # div
兄弟要素の取得
html = '<div><h1>タイトル</h1><p>段落1</p><p>段落2</p></div>'
soup = BeautifulSoup(html, 'html.parser')
h1 = soup.find('h1')
next_sibling = h1.find_next_sibling()
print(next_sibling.text) # 段落1
子要素の反復処理
html = '<ul><li>項目1</li><li>項目2</li><li>項目3</li></ul>'
soup = BeautifulSoup(html, 'html.parser')
ul = soup.find('ul')
for child in ul.children:
if child.name == 'li':
print(child.text) # 項目1, 項目2, 項目3
実践的なスクレイピング例
1. ニュースサイトのヘッドライン抽出
import requests
from bs4 import BeautifulSoup
def scrape_headlines(url):
response = requests.get(url)
soup = BeautifulSoup(response.content, 'html.parser')
headlines = soup.find_all('h2', class_='headline')
return [h.text.strip() for h in headlines]
# headlines = scrape_headlines('https://news.example.com')
2. テーブルデータの抽出
def extract_table_data(html):
soup = BeautifulSoup(html, 'html.parser')
table = soup.find('table')
headers = [th.text.strip() for th in table.find('tr').find_all('th')]
rows = []
for tr in table.find_all('tr')[1:]:
row = [td.text.strip() for td in tr.find_all('td')]
rows.append(dict(zip(headers, row)))
return rows
3. 商品情報の抽出
def scrape_product_info(url):
response = requests.get(url)
soup = BeautifulSoup(response.content, 'html.parser')
product = {
'name': soup.find('h1', class_='product-title').text.strip(),
'price': soup.find('span', class_='price').text.strip(),
'description': soup.find('div', class_='description').text.strip()
}
return product
4. フォームの情報抽出
def extract_form_fields(html):
soup = BeautifulSoup(html, 'html.parser')
form = soup.find('form')
fields = []
for input_tag in form.find_all(['input', 'select', 'textarea']):
field_info = {
'type': input_tag.get('type', input_tag.name),
'name': input_tag.get('name'),
'required': input_tag.has_attr('required')
}
fields.append(field_info)
return fields
正規表現との組み合わせ
テキストパターンでの検索
import re
html = '<div>電話: 03-1234-5678</div><div>メール: test@example.com</div>'
soup = BeautifulSoup(html, 'html.parser')
# 正規表現でテキスト検索
phone_div = soup.find(text=re.compile(r'\d{2,3}-\d{4}-\d{4}'))
print(phone_div.strip()) # 電話: 03-1234-5678
属性値のパターンマッチ
html = '<a href="/page1">リンク1</a><a href="/page2">リンク2</a><a href="http://external.com">外部</a>'
soup = BeautifulSoup(html, 'html.parser')
# 内部リンクのみ抽出
internal_links = soup.find_all('a', href=re.compile(r'^/'))
print([link.text for link in internal_links]) # ['リンク1', 'リンク2']
カスタム関数での検索
def has_phone_number(tag):
return tag.string and re.search(r'\d{3}-\d{4}-\d{4}', tag.string)
html = '<p>連絡先: 080-1234-5678</p><p>住所: 東京都</p>'
soup = BeautifulSoup(html, 'html.parser')
phone_tag = soup.find(has_phone_number)
print(phone_tag.text) # 連絡先: 080-1234-5678
HTMLの編集と作成
要素の編集
html = '<p>古いテキスト</p>'
soup = BeautifulSoup(html, 'html.parser')
p = soup.find('p')
p.string = '新しいテキスト'
print(soup) # <p>新しいテキスト</p>
属性の追加・変更
html = '<img src="old.jpg">'
soup = BeautifulSoup(html, 'html.parser')
img = soup.find('img')
img['src'] = 'new.jpg'
img['alt'] = '新しい画像'
print(soup) # <img alt="新しい画像" src="new.jpg"/>
新要素の作成と追加
soup = BeautifulSoup('<div></div>', 'html.parser')
div = soup.find('div')
# 新要素の作成
new_p = soup.new_tag('p')
new_p.string = '新しい段落'
# 要素の追加
div.append(new_p)
print(soup) # <div><p>新しい段落</p></div>
要素の削除
html = '<div><p>保持</p><p class="remove">削除対象</p></div>'
soup = BeautifulSoup(html, 'html.parser')
# 要素の削除
remove_p = soup.find('p', class_='remove')
remove_p.decompose() # またはremove_p.extract()
print(soup) # <div><p>保持</p></div>
パフォーマンス最適化
パーサーの選択による最適化
import time
html = '<html>' + '<p>段落</p>' * 1000 + '</html>'
# lxmlパーサー(最高速)
start = time.time()
soup_lxml = BeautifulSoup(html, 'lxml')
print(f"lxml: {time.time() - start:.4f}秒")
# html.parserパーサー
start = time.time()
soup_html = BeautifulSoup(html, 'html.parser')
print(f"html.parser: {time.time() - start:.4f}秒")
SoupStrainerによる部分解析
from bs4 import SoupStrainer
# テーブルタグのみ解析
only_tables = SoupStrainer('table')
soup = BeautifulSoup(large_html, 'lxml', parse_only=only_tables)
効率的な検索方法
# 効率的: 具体的なセレクタ
fast_result = soup.select('div.container > p.text')
# 非効率的: 曖昧な検索
slow_result = soup.find_all(lambda tag: tag.name == 'p' and 'text' in tag.get('class', []))
エラーハンドリング
要素が見つからない場合の処理
def safe_extract_text(soup, selector, default=''):
element = soup.select_one(selector)
return element.text.strip() if element else default
# 使用例
title = safe_extract_text(soup, 'h1.title', 'タイトル不明')
属性の安全な取得
def safe_get_attr(element, attr, default=''):
return element.get(attr, default) if element else default
# 使用例
img = soup.find('img')
src = safe_get_attr(img, 'src', 'no-image.jpg')
例外処理の実装
from requests.exceptions import RequestException
def scrape_with_error_handling(url):
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
soup = BeautifulSoup(response.content, 'html.parser')
title = soup.find('title')
return title.text.strip() if title else 'タイトルなし'
except RequestException as e:
print(f"リクエストエラー: {e}")
return None
except Exception as e:
print(f"解析エラー: {e}")
return None
高度なテクニック
名前空間の処理(XML)
xml = '''<root xmlns:book="http://example.com/book">
<book:title>Python入門</book:title>
<book:author>著者名</book:author>
</root>'''
soup = BeautifulSoup(xml, 'lxml-xml')
title = soup.find('title')
print(title.text) # Python入門
カスタムパーサーの作成
from bs4 import BeautifulSoup
from bs4.builder import TreeBuilder
class CustomTreeBuilder(TreeBuilder):
def __init__(self):
super().__init__()
# カスタム処理の実装
# 必要なメソッドをオーバーライド
メモリ効率的な大量データ処理
def process_large_html_iteratively(html_content):
# 大きなHTMLを小さなチャンクに分割して処理
chunk_size = 1000000 # 1MB chunks
for i in range(0, len(html_content), chunk_size):
chunk = html_content[i:i+chunk_size]
soup = BeautifulSoup(chunk, 'lxml')
# 必要なデータを抽出
yield extract_data_from_chunk(soup)
# メモリ解放
soup.decompose()
実際のプロジェクト例
Webスクレイピングクラス
class WebScraper:
def __init__(self, base_url, headers=None):
self.base_url = base_url
self.session = requests.Session()
if headers:
self.session.headers.update(headers)
def get_soup(self, path=''):
url = self.base_url + path
response = self.session.get(url)
return BeautifulSoup(response.content, 'lxml')
def extract_links(self, soup, selector='a'):
links = soup.select(selector)
return [link.get('href') for link in links if link.get('href')]
def extract_text_list(self, soup, selector):
elements = soup.select(selector)
return [elem.text.strip() for elem in elements]
データ構造化クラス
class HTMLDataExtractor:
def __init__(self, html):
self.soup = BeautifulSoup(html, 'lxml')
def to_dict(self, selectors):
"""セレクタ辞書からデータを抽出"""
result = {}
for key, selector in selectors.items():
element = self.soup.select_one(selector)
result[key] = element.text.strip() if element else None
return result
def extract_list(self, item_selector, data_selectors):
"""リスト形式のデータを抽出"""
items = self.soup.select(item_selector)
results = []
for item in items:
data = {}
for key, selector in data_selectors.items():
element = item.select_one(selector)
data[key] = element.text.strip() if element else None
results.append(data)
return results
デバッグとトラブルシューティング
HTMLの構造確認
def debug_html_structure(soup, max_depth=3):
def print_tree(element, depth=0):
if depth > max_depth:
return
indent = " " * depth
if element.name:
attrs = " ".join([f'{k}="{v}"' for k, v in element.attrs.items()])
print(f"{indent}<{element.name} {attrs}>")
for child in element.children:
if child.name:
print_tree(child, depth + 1)
print_tree(soup)
セレクタのテスト
def test_selectors(soup, selectors):
"""複数のセレクタをテスト"""
for name, selector in selectors.items():
elements = soup.select(selector)
print(f"{name}: {len(elements)}個の要素が見つかりました")
if elements:
print(f" 最初の要素: {elements[0].name} - {elements[0].text[:50]}")
パフォーマンス測定
import time
from functools import wraps
def measure_time(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__}: {end - start:.4f}秒")
return result
return wrapper
@measure_time
def scrape_page(url):
response = requests.get(url)
soup = BeautifulSoup(response.content, 'lxml')
return soup.find_all('p')
まとめ
BeautifulSoupは、PythonでHTMLを操作するための非常に強力で使いやすいライブラリです。基本的な要素検索から高度なデータ抽出まで、幅広い用途に対応できます。
主なポイント:
- 柔軟な検索: find()、select()、正規表現を組み合わせた強力な検索
- 直感的なAPI: Pythonらしい読みやすい記法
- 豊富な機能: ナビゲーション、編集、作成機能
- パフォーマンス: 適切なパーサー選択と最適化テクニック
- エラー処理: 堅牢なスクレイピングのための例外処理
Webスクレイピングを行う際は、対象サイトの利用規約を確認し、適切なマナーを守って使用することが重要です。BeautifulSoupの強力な機能を活用して、効率的なデータ抽出を実現しましょう。
参考リンク
■プロンプトだけでオリジナルアプリを開発・公開してみた!!
■AI時代の第一歩!「AI駆動開発コース」はじめました!
テックジム東京本校で先行開始。
■テックジム東京本校
「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。
<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。
<月1開催>放送作家による映像ディレクター養成講座
<オンライン無料>ゼロから始めるPython爆速講座


