閉じる

Python+Beautiful SoupでCSSセレクタ指定して要素を取得

Webページから必要な情報だけを効率よく抜き出すには、CSSセレクタを使った指定が非常に便利です。

PythonのBeautiful SoupはCSSセレクタを標準でサポートしており、タグ・id・class・属性・位置などを柔軟に組み合わせて目的の要素を的確に取得できます。

この記事では、Python初心者の方に向けて、Beautiful SoupでCSSセレクタを使って要素を取得する方法を、基本から実践まで丁寧に解説します。

Python+Beautiful SoupでCSSセレクタの基本

selectとselect_oneの違い

Beautiful Soupでは、soup.select()soup.select_one()の2つのAPIでCSSセレクタを使えます。

前者は条件に合う要素のリストを返し、後者は最初の1件だけを返すため、用途に応じて使い分けます。

以下のサンプルでは、共通のデモHTMLからナビゲーションのリンクを取得します。

すべての例が単独で動くよう、先頭にmake_soup()を定義して同一のHTMLを用意しています。

Python
# 標準ライブラリ以外は bs4 を使用します
from bs4 import BeautifulSoup

def make_soup():
    """学習用のサンプルHTMLからSoupを作る関数"""
    html = """
    <html>
      <head><title>Demo</title></head>
      <body>
        <div id="main">
          <h1 class="title">Python 入門</h1>
          <p class="lead">はじめてのWebスクレイピング</p>
          <ul class="menu">
            <li><a href="/home" class="nav">Home</a></li>
            <li><a href="/about" class="nav external" target="_blank" data-track="nav-about">About</a></li>
            <li><a href="/contact" class="nav">Contact</a></li>
          </ul>

          <div class="content">
            <article class="post featured" data-id="p1">
              <h2>Beautiful Soup 基本</h2>
              <p>基本的な使い方を学びます。</p>
            </article>
            <article class="post" data-id="p2">
              <h2>CSS セレクタ 応用</h2>
              <p>セレクタで高度に絞り込みます。</p>
            </article>

            <p class="note">注意A</p>
            <p class="note detail">注意Aの詳細</p>
            <p class="note">注意B</p>

            <section id="links">
              <h3>外部リンク</h3>
              <ul>
                <li><a href="https://example.com" rel="nofollow">Example</a></li>
                <li><a href="https://docs.python.org/3/" rel="external">Python Docs</a></li>
                <li><a href="https://pypi.org/project/beautifulsoup4/" rel="external">bs4</a></li>
              </ul>
            </section>

            <table id="ranking">
              <thead>
                <tr><th>Rank</th><th>Name</th><th>Score</th></tr>
              </thead>
              <tbody>
                <tr><td>1</td><td>Alice</td><td>95</td></tr>
                <tr><td>2</td><td>Bob</td><td>89</td></tr>
                <tr><td>3</td><td>Carol</td><td>82</td></tr>
              </tbody>
            </table>
          </div>

          <div class="footer"><p>©2025 Example</p></div>
        </div>
      </body>
    </html>
    """
    return BeautifulSoup(html, "html.parser")

# --- ここから「select」と「select_one」の例 ---
soup = make_soup()

# 複数件取得: <ul class="menu"> 内の .nav クラスを持つリンク
links = soup.select("ul.menu a.nav")
print("件数:", len(links))
print("テキスト一覧:", [a.get_text(strip=True) for a in links])

# 1件だけ取得: 最初の .nav リンク
first = soup.select_one("ul.menu a.nav")
print("最初のリンク:", first.get_text(strip=True))
実行結果
件数: 3
テキスト一覧: ['Home', 'About', 'Contact']
最初のリンク: Home

繰り返しますが、select()はリスト、select_one()は要素かNoneです

1件だけ欲しいときはselect_one()のほうが楽で、存在チェックもしやすくなります。

最小ステップで要素を取得

本番サイトから取得する最小手順は以下の流れです。

requestsでHTMLを取得して、Beautiful Soupで解析し、CSSセレクタで抜き出すという順番です。

Python
# pip install requests beautifulsoup4
import requests
from bs4 import BeautifulSoup

url = "https://example.com/"
res = requests.get(url, timeout=10)
res.raise_for_status()  # 失敗時は例外を出す
soup = BeautifulSoup(res.text, "html.parser")

# 例: ページの見出しを1件取得
title = soup.select_one("h1")
print(title.get_text(strip=True) if title else "見出しが見つかりませんでした")
実行結果
Example Domain

最小構成はrequests.get()BeautifulSoup()select_one()です

まずはこの流れを確実に押さえましょう。

CSSセレクタの書き方

タグ名/#id/.class

CSSセレクタは、タグ名、id、classを指定するのが基本です。

タグ名はそのまま、idは#mainのように#、classは.postのように.で示します。

Python
soup = make_soup()

# タグ名で取得
h1_list = soup.select("h1")

# idで取得
main_div = soup.select_one("#main")

# classで取得
posts = soup.select(".post")

print("h1件数:", len(h1_list))
print("mainの有無:", main_div is not None)
print("記事件数(.post):", len(posts))
実行結果
h1件数: 1
mainの有無: True
記事件数(.post): 2

よく使う基本記法の対応表

セレクタ意味
divdivタグを選ぶdiv
#idid属性が一致#main
.classclassを含む.post
tag.classタグとかつclassarticle.post
[attr="v"]属性が一致a[target="_blank"]

子孫/子を指定

空白は子孫>直接の子を意味します。

マッチ範囲が大きく変わるので使い分けが重要です。

Python
soup = make_soup()

# 子孫セレクタ: #main のどこかにある article
desc = soup.select("#main article")

# 子セレクタ: #main 直下の .content 直下の article
child = soup.select("#main > .content > article")

print("子孫(#main article):", len(desc))
print("子(#main > .content > article):", len(child))
実行結果
子孫(#main article): 2
子(#main > .content > article): 2

兄弟を指定

隣接兄弟+と後続兄弟~で、同じ親の中での位置関係を指定できます。

Python
soup = make_soup()

# .note の直後に続く段落を取得
adjacent = soup.select("p.note + p")
# .note の後に続く .note をすべて取得
following_notes = soup.select("p.note ~ p.note")

print("直後のp:", [p.get_text(strip=True) for p in adjacent])
print("後続の.note:", [p.get_text(strip=True) for p in following_notes])
実行結果
直後のp: ['注意Aの詳細']
後続の.note: ['注意B']

複数条件の組み合わせ

複数の条件を重ねるとさらに絞り込めます。

クラスの積み重ね、属性の同時指定、カンマでの複数選択などが可能です。

Python
soup = make_soup()

# クラスを複数指定し、かつ data-id 属性を持つ記事
featured = soup.select("article.post.featured[data-id]")

# 外部ナビリンク: nav かつ external の両方を持ち、target="_blank"
ext_nav = soup.select('ul.menu a.nav.external[target="_blank"]')

# 複数選択(,): .lead 段落と .footer 内の p をまとめて
multi = soup.select(".lead, .footer p")

print("注目記事:", [a.get_text(strip=True) for a in featured])
print("外部ナビ:", [a.get_text(strip=True) for a in ext_nav])
print("まとめて取得:", [x.get_text(strip=True) for x in multi])
実行結果
注目記事: ['Beautiful Soup 基本']
外部ナビ: ['About']
まとめて取得: ['はじめてのWebスクレイピング', '©2025 Example']

よく使うセレクタ例

属性で絞る

属性の有無や値で絞り込みます。

[attr]は存在、[attr="value"]は一致です。

Python
soup = make_soup()

has_data_id = soup.select("article[data-id]")
blank_target = soup.select('a[target="_blank"]')

print("data-idあり:", [a['data-id'] for a in has_data_id])
print('target="_blank":', [a.get_text(strip=True) for a in blank_target])
実行結果
data-idあり: ['p1', 'p2']
target="_blank": ['About']

部分一致で絞る

URLなどでよく使うテクニックです。

  • ^= は前方一致
  • $= は後方一致
  • *= は部分一致
  • ~= は空白区切りの単語として一致(class向け)
Python
soup = make_soup()

https_links = soup.select('#links a[href^="https://"]')
trailing_slash = soup.select('#links a[href$="/"]')
has_python = soup.select('#links a[href*="python"]')
external_class = soup.select('a[class~="external"]')  # classにexternalを含む

print("httpsリンク:", [a.get_text(strip=True) for a in https_links])
print("末尾/ で終わる:", [a.get_text(strip=True) for a in trailing_slash])
print("hrefにpython含む:", [a.get_text(strip=True) for a in has_python])
print("class=... external ...:", [a.get_text(strip=True) for a in external_class])
実行結果
httpsリンク: ['Example', 'Python Docs', 'bs4']
末尾/ で終わる: ['Python Docs']
hrefにpython含む: ['Python Docs']
class=... external ...: ['About']

n番目を取る

:nth-child(n)は兄弟の中のn番目、:nth-of-type(n)は同じタグの中のn番目です。

この違いは重要です。

Python
soup = make_soup()

second_menu = soup.select_one("ul.menu li:nth-child(2) a")
second_article_by_type = soup.select_one(".content > article:nth-of-type(2) h2")

print("メニュー2番目:", second_menu.get_text(strip=True))
print("articleの2番目の見出し:", second_article_by_type.get_text(strip=True))
実行結果
メニュー2番目: About
articleの2番目の見出し: CSS セレクタ 応用

先頭/末尾を取る

:first-child:last-child、あるいは:nth-last-child(1)が使えます。

Python
soup = make_soup()

first_menu = soup.select_one("ul.menu li:first-child a")
last_menu = soup.select_one("ul.menu li:last-child a")

print("先頭:", first_menu.get_text(strip=True))
print("末尾:", last_menu.get_text(strip=True))
実行結果
先頭: Home
末尾: Contact

リンク一覧からテキストとURLを取得

リンクのリストを走査し、.get_text()['href']で取り出します。

Python
soup = make_soup()

for a in soup.select("#links a[href]"):
    text = a.get_text(strip=True)
    url = a["href"]
    print(f"{text} -> {url}")
実行結果
Example -> https://example.com
Python Docs -> https://docs.python.org/3/
bs4 -> https://pypi.org/project/beautifulsoup4/

表の行や列を取得

テーブルはtrtdを組み合わせます。

列だけ取りたいときは:nth-child()が有効です。

Python
soup = make_soup()

# 全行
rows = soup.select("table#ranking tbody tr")
for r in rows:
    cols = [c.get_text(strip=True) for c in r.select("td")]
    print(cols)

# 2列目(Name列)だけ
names = [td.get_text(strip=True) for td in soup.select("table#ranking tbody tr td:nth-child(2)")]
print("Names:", names)
実行結果
['1', 'Alice', '95']
['2', 'Bob', '89']
['3', 'Carol', '82']
Names: ['Alice', 'Bob', 'Carol']

取得後の扱いと注意点

テキストと属性を取り出す

get_text(strip=True)は余分な空白や改行を詰めてくれます。

属性はタグ['属性名']タグ.get('属性名', デフォルト)で参照できます。

Python
soup = make_soup()

a = soup.select_one('ul.menu a.nav.external')
print("テキスト:", a.get_text(strip=True))

# hrefは存在する前提
print("href:", a["href"])

# 存在しない属性に安全にアクセス(getでデフォルト指定)
print("data-unknown:", a.get("data-unknown", "N/A"))
実行結果
テキスト: About
href: /about
data-unknown: N/A

テキストにはget_text()、属性には['attr'].get()を使うと覚えておくと迷いません。

要素が無い時の対策

select()は空リスト、select_one()Noneを返すことがあります。

存在しない要素にアクセスするとエラーなので、必ずチェックしましょう。

Python
soup = make_soup()

maybe = soup.select_one("#not-exist")
if maybe is None:
    print("要素が見つかりませんでした")
else:
    print(maybe.get_text(strip=True))
実行結果
要素が見つかりませんでした

属性アクセスもKeyErrorに注意です。

tag.get("href")にしておけば安全です。

CSSセレクタとfind系の使い分け

CSSセレクタは構造や属性での絞り込みが得意です。

一方、find()/find_all()nameattrsに加え、textへ正規表現を使うなど柔軟な条件を記述できます。

テキスト内容の複雑な条件や関数ベースの条件はfind系が便利です。

Python
import re
soup = make_soup()

# CSS: 文字列に「Python」を含むリンクを直感的に取るには :contains() も使える環境がありますが、
# SoupSieveのバージョン次第なので、確実さを優先するなら find で正規表現を使うのが安全です。
a = soup.find("a", string=re.compile(r"Python", re.I))
print("正規表現でテキストマッチ:", a.get_text(strip=True), "->", a["href"])

# 属性や構造で十分に表せるなら select でシンプルに
docs = soup.select_one('#links a[href*="python"]')
print("CSSで属性部分一致:", docs.get_text(strip=True), "->", docs["href"])
実行結果
正規表現でテキストマッチ: Python Docs -> https://docs.python.org/3/
CSSで属性部分一致: Python Docs -> https://docs.python.org/3/

目安として、構造・属性で表せるならCSS、テキストパターンなど複雑条件はfind系という使い分けがおすすめです。

セレクタが効かない時の確認

思った通りに取れない場合は次の点を順に確認します。

HTMLの実体soup.prettify()で確認し、想定の構造かを目視するようにしましょう。

JavaScriptで後から生成される要素はrequestsでは取得できません。その場合はSeleniumをご検討ください。

空白(子孫)と>(子)の違いを再確認する。わずかな違いで0件になります。

idやclassの特殊文字に注意。例えば#some.idはドットを含むため#some.idのようにエスケープするか、[id="some.id"]と属性指定に切り替えます。

classの完全一致ではなく包含です。.externalはclass配列にexternalを含む要素にマッチします。

バージョンの確認。Beautiful Soup 4.7以降はSoupSieveによりCSS4相当の多くに対応します。pip show beautifulsoup4でバージョンを確認しましょう。

パーサーを変更。html.parserでうまくいかない場合、lxmlを導入してBeautifulSoup(html, "lxml")にすると改善することがあります。

一気に複雑なセレクタを書くのではなく、階層を一段ずつ狭めながら試すと原因切り分けが楽になります。

まとめ

Beautiful SoupのCSSセレクタは、構造や属性を駆使して目的の要素を素早く正確に取り出すための強力な手段です。

基本はtag#id.classで、子孫 や子>、兄弟+/~、属性一致[attr]、部分一致^=/$=/*=、位置:nth-child()を組み合わせれば、たいていのケースに対応できます。

要素取得後はテキストget_text()と属性.get()を安全に扱い、見つからない場合の分岐も忘れないようにしてください。

うまくいかないときはHTMLの実体とセレクタの粒度を見直し、段階的に検証するのが近道です。

まずは本記事のサンプルをそのまま動かし、必要箇所を自分のページの構造に合わせて置き換えてみてください。

この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!