Python WebAcademy Blog

PythonのBeautifulSoupとは?Webページから情報を取り出すスクレイピングの基本を初心者向けに解説

|

PythonのBeautifulSoupを、IT初心者向けにやさしく解説します。インストールからHTMLの読み込み、findとfind_allの違い、CSSセレクタを使うselect、requestsとの組み合わせ方、NoneTypeエラーや相対URLといったつまずきポイント、robots.txtと取得マナーまで。手元で動かせるコードと表を並べながら、Webページから欲しいデータを取り出す第一歩を順番に整理していきます。

毎朝おなじサイトを開いて、新着情報の欄だけを目で追って、更新があればメモする。そんな確認作業をしていませんか。

数分で終わる作業でも、毎日となると地味に負担です。しかも見落としたときにかぎって大事な更新だったりします。

Webページの中身をPythonから読み取って、必要なところだけ抜き出す。それができると、この手の確認作業はまるごと自動化できます。

その入り口になるのがBeautifulSoupというライブラリです。HTMLという少し取っつきにくい文字の塊を、Pythonから扱いやすい形に整理してくれます。

この記事では、BeautifulSoupの基本と、初心者がつまずきやすいところを順番に見ていきます。

BeautifulSoupは、HTMLを読み解いてくれる道具

まずは役割をはっきりさせておきましょう。BeautifulSoupは、HTMLの文字列を受け取って、タグの入れ子構造として扱えるようにしてくれるライブラリです。

公式の説明でも、HTMLやXMLのパーサーの上に乗って、探索や検索をPythonらしい書き方で行えるようにするもの、と位置づけられています。

ここで大事なのは、BeautifulSoupはページを取ってくる役目を持っていないという点です。するのは、あくまで手元にあるHTMLを読み解くことだけ。

なので実際の作業は、2つの道具の分担になります。役割を混同すると調べものが遠回りになるので、先に整理しておきます。

道具 役割 よく使う書き方
requests Webサーバーからページを取ってくる requests.get(url)
BeautifulSoup 取ってきたHTMLから欲しい部分を探す soup.select("h2")
csvやopenpyxl 取り出した結果を保存する writer.writerow(row)

ページを取ってくる側の話は別の記事にまとめてあります。あわせて読むと全体像がつかみやすいはずです。【関連記事】Pythonのrequestsとは?WebからデータをとってくるHTTP通信の基本を初心者向けに解説

準備はpipのインストールから

BeautifulSoupは標準ライブラリではないので、まずインストールが必要です。パッケージ名が少し独特なので、そこだけ注意してください。

pip install beautifulsoup4

インポートするときの名前は bs4 です。インストール名とインポート名が違う、初心者泣かせのライブラリの代表格でもあります。

プロジェクトごとに仮想環境を分けておくと、ライブラリのバージョン違いで悩まされることが減ります。【関連記事】Pythonの仮想環境(venv)って何のためにある?プロジェクトごとに混ぜない管理法

パーサーは最初はhtml.parserでいい

BeautifulSoupを作るときには、HTMLをどう解釈するかを決めるパーサーを指定します。選択肢は3つありますが、最初は迷わなくて大丈夫です。

指定 追加インストール 特徴
"html.parser" 不要(Python標準) まずはこれで十分。速度も実用範囲
"lxml" pip install lxml が必要 速い。大量のページを処理するときに効く
"html5lib" pip install html5lib が必要 ブラウザに近い解釈をする。壊れたHTMLに強い

第2引数を省略すると、環境にあるパーサーが勝手に選ばれます。そのとき警告が出るのですが、これは環境が変わると結果も変わりうるという注意です。

私は10年ほどエンジニアとして開発に関わってきましたが、パーサーを省略したまま書いたコードが、自分の手元では動くのにサーバーでは空振りする、という事故を一度やっています。第2引数は必ず書く、とだけ覚えておいてください。

まずはHTMLから1つ取り出してみる

いきなり実在のサイトを相手にすると、うまくいかない原因が絞りづらくなります。最初は手元の文字列で試すのが近道です。

次のコードは、お知らせ一覧のような小さなHTMLから、リンクの文字を取り出す例です。

from bs4 import BeautifulSoup

html = """
<ul class="news">
  <li class="item"><a href="/news/1">セミナー開催のお知らせ</a></li>
  <li class="item new"><a href="/news/2">夏期休業について</a></li>
</ul>
"""

soup = BeautifulSoup(html, "html.parser")

print(soup.find("a").get_text())        # セミナー開催のお知らせ
print(len(soup.find_all("a")))          # 2
print(soup.find("a")["href"])           # /news/1

get_text() はタグの中の文字だけを取り出すメソッドです。角かっこで属性を指定すると、リンク先などの値が取れます。

findとfind_allの違い

名前が似ている2つですが、返ってくるものがまったく違います。ここを取り違えるとエラーの原因になります。

find() は最初の1つ、find_all() は見つかったものすべてを返します。1つだけ欲しいのか、全部欲しいのかで使い分けてください。

クラス名で絞り込むときは、引数名が class_ になります。class はPythonの予約語なので、末尾にアンダースコアが付く決まりです。

items = soup.find_all("li", class_="item")
print(len(items))  # 2

CSSセレクタで狙い撃ちする

タグ名だけで探すと、目的以外のものまで拾ってしまうことがあります。そんなときに便利なのが select() です。

CSSセレクタという、Webページの見た目を指定するときと同じ書き方が使えます。入れ子の条件をそのまま書けるので、複雑な構造でも一行で済みます。

titles = [a.get_text() for a in soup.select("ul.news li a")]
print(titles)  # ['セミナー開催のお知らせ', '夏期休業について']

print(soup.select_one("li.new a").get_text())  # 夏期休業について

select() が全件、select_one() が1件です。findとfind_allの関係とおなじと考えれば覚えやすいはずです。

なおCSSセレクタの解釈は、BeautifulSoupに同梱されるSoup Sieveというライブラリが担当しています。バージョン4.7.0以降はこれが公式の実装になっているので、かなり細かいセレクタまで通ります。

requestsと組み合わせて実際のページを読む

手元のHTMLで感覚がつかめたら、いよいよ本物のページに向かいます。取ってくる部分をrequestsに任せるだけで、形はほとんど変わりません。

import requests
from bs4 import BeautifulSoup

res = requests.get("https://example.com/news/", timeout=10)
res.raise_for_status()

soup = BeautifulSoup(res.text, "html.parser")
for a in soup.select("ul.news li a"):
    print(a.get_text(strip=True), a["href"])

raise_for_status() は、404などの失敗を見逃さないための一行です。これが無いと、エラーページのHTMLを真面目に解析してしまいます。

get_text(strip=True)strip は、前後の余分な空白や改行を落としてくれる指定です。HTMLは見た目を整えるための改行が多いので、付けておくと結果がきれいになります。

つまずきやすいのは、だいたいこの2つ

ここまで来ると動くコードが書けます。ただ、実際のページを相手にすると必ずと言っていいほど同じところで止まります。

NoneType object has no attribute というエラー

いちばん多いのがこれです。原因はシンプルで、探した要素が見つからなかったから。

find()select_one() は、見つからないとエラーではなく None を返します。その None に対して get_text() を呼ぶので、そこで落ちるわけです。

target = soup.select_one("h1.title")
if target is None:
    print("見つかりませんでした")
else:
    print(target.get_text(strip=True))

セレクタの打ち間違いのほか、JavaScriptで後から表示される部分だった、という場合もあります。ブラウザで見えているのにPythonからは取れないときは、これを疑ってください。

リンクが途中までしか取れない

もうひとつが相対URLです。ページの中のリンクは /news/1 のように途中から書かれていることが多く、そのままでは開けません。

標準ライブラリの urljoin を使うと、元のURLとつなげて完全な形にできます。

from urllib.parse import urljoin

print(urljoin("https://example.com/news/", "/news/1"))
# https://example.com/news/1

なお、HTMLから情報を抜き出す作業を正規表現だけでやろうとすると、たいてい破綻します。タグの入れ子は正規表現が苦手とする構造だからです。【関連記事】正規表現とは?複雑な文字列検索や置換を一瞬で終わらせる

技術より先に、取り方のマナーを知っておく

ここまで読むと何でも取れそうな気がしてきますが、いちばん大事なのはここからです。相手のサーバーは、他人の持ち物だという前提を忘れないでください。

短い間隔で何百回もアクセスすれば、相手にとっては攻撃と区別がつきません。最低限、次の3つは確認する習慣をつけてください。

サイトの利用規約を読むこと。robots.txtで断られている場所には行かないこと。そして、繰り返し取得するなら1回ごとに待ち時間を入れることです。

robots.txtは、クローラーに対してどこを見ていいかを伝えるファイルです。2022年にRFC 9309として標準化されており、Pythonにはこれを読むための標準ライブラリまで用意されています。

import time
from urllib.robotparser import RobotFileParser

rp = RobotFileParser()
rp.set_url("https://example.com/robots.txt")
rp.read()

if rp.can_fetch("*", "https://example.com/news/"):
    print("取得してよい")
    time.sleep(3)   # 次のアクセスまで間を空ける

can_fetch() は、そのURLを取りに行っていいかを真偽値で返します。crawl_delay() で待つべき秒数が書かれていれば、それも読み取れます。

新人のころ、待ち時間を入れないまま検証用のスクリプトを回してしまい、先方から連絡が来たことがあります。取得の間隔を空けるのは技術ではなく礼儀だと、そのとき教わりました。

取り出したデータをどう残すか

画面に表示するだけでは、翌日には消えてしまいます。取り出したものは、後から見返せる形で保存しておきましょう。

表のようなデータなら、標準ライブラリのcsvが手軽です。文字化けを避ける書き方まで含めて、こちらで詳しく扱っています。【関連記事】Pythonのcsvモジュールとは?表データの読み書きと文字化けの防ぎ方を初心者向けに解説

保存までできたら、あとは自動で動かすだけです。毎朝決まった時刻に走らせておけば、起きたときには結果が手元にあります。【関連記事】Pythonで定期実行するには?cronとタスクスケジューラの基本

まとめ

BeautifulSoupは、HTMLという読みにくい塊を、Pythonから扱える形に整えてくれる道具です。ページを取ってくるrequestsと組み合わせて使います。

覚えることは多くありません。findfind_allselectselect_one、そして get_text() の5つで、たいていの用は足ります。

つまずいたときは、まず取れているHTMLを疑ってください。見えているものと取れているものは別だと分かれば、原因の切り分けはぐっと早くなります。

そして、相手のサーバーへの配慮を最優先に。この一点さえ守れれば、スクレイピングは日々の作業を確実にラクにしてくれます。

まずは手元の文字列を相手に、select() を一度動かしてみるところから始めてみてください。

ここまでお読みいただきありがとうございました。

参考情報

次のアクション

記事で学んだ内容を実際に動かしてみよう

Python WebAcademyでは、ブラウザ上でコードを書きながら基礎から実践まで体系的に学べます。

Python WebAcademyの学習画面

あわせて読む

関連記事

ブログ一覧へ

Python学習ロードマップ

まずはこの3講座から

記事で気になったテーマを、順番に手を動かしながら学べます。

ロードマップを見る