このページで解説している内容は、以下の YouTube 動画の解説で見ることができます。

【Python入門】Webページから特定の情報を抽出する(BeautifulSoup)

Webページから特定の情報を抽出する(BeautifulSoup)

 Webページを取得するだけではなく、そのHTML構造を解析して特定の情報を抜き出すのがスクレイピングの本質です。Pythonには標準ライブラリの正規表現(reモジュール)を使う方法のほか、HTMLをツリー状に解析して簡単に要素を取り出せる人気ライブラリBeautifulSoupがあります。
 ここでは、まず正規表現によるパターンマッチの利点と限界に触れつつ、次にBeautifulSoupを使った方法を紹介します。サンプルとして、Webページ(HTMLファイル)を対象にして、一部のテキストを抽出してみましょう。

プログラムのダウンロード

 「ダウンロード」から、JupyterLab で実行できるサンプルプログラムがダウンロードできます。ファイルは、ESET Endpoint Securityでウイルスチェックをしておりますが、ダウンロードとプログラムの実行は自己責任でお願いいたします。

1.今回のターゲットページのHTML(scraping2.html)

 以下のHTMLファイルが、「https://www.study-pg.com/scraping/scraping2.html」に配置されています。演習ではこのファイルをスクレイピング対象として使用します。ローカル開発環境でテストしたい場合は、下記の内容を`scraping2.html`として保存し、同様にアクセスする形を整えてください。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Scraping Demo Page 2</title>
</head>
<body>
  <h1>Scraping Demo Page 2</h1>
  <p>This page is intended for scraping demonstration.</p>
  
  <ul>
    <li>
      <span class="release-number"><a href="release01.html">Demo Release 1.0</a></span>
      <span class="release-date">January 10, 2022</span>
    </li>
    <li>
      <span class="release-number"><a href="release02.html">Demo Release 2.0</a></span>
      <span class="release-date">March 15, 2022</span>
    </li>
    <li>
      <span class="release-number"><a href="release03.html">Demo Release 3.0</a></span>
      <span class="release-date">July 22, 2022</span>
    </li>
  </ul>
  
  <p>
    For further details, visit <a href="https://www.example.com">Example Site</a>.
  </p>
</body>
</html>

このHTMLファイルを開くと下図のように表示されます。

2.正規表現(reモジュール)を使った方法

2.1.正規表現によるパターンマッチ

 Pythonの標準ライブラリreモジュールで文字列パターンマッチを行うことができます。以下のような正規表現を用いて、HTML内の特定要素を探す流れになります。

 正規表現のパターンマッチの詳しい意味については、とりあえず、このようなものだと、あまり深く考えなくてよいです。正規表現を使ったパターンマッチは、複雑で扱いにくいということだけ、理解できればよいです。

  • <li>.+?</li><li>要素の内容を1つずつ取り出す(非貪欲マッチ +? を使う)
  • <span class="release-number"><a href=".*?">(.+?)</a></span> : リリース番号を含む部分
  • <span class="release-date">(.+?)</span> : リリース日の部分

2.2.実装例

 下記はRequestsでページを取得したあと、re.findall()で要素を検出し、正規表現にマッチした内容をリストに追加する例です。

import requests
import re

def extract_releases_by_regex(url="https://www.study-pg.com/scraping/scraping2.html"):
    """
    指定URLのHTMLを取得し、正規表現でリリース番号と日付を抽出して返すサンプル関数。
    """
    resp = requests.get(url)
    html_text = resp.text.replace('\n', '')  # 改行を消しておくと正規表現が使いやすい
    
    releases = []
    
    # <li> ... </li> を全て抜き出す
    li_list = re.findall(r'<li>.+?</li>', html_text)
    for li_item in li_list:
        # リリース番号部分を探す
        x = re.search(r'<span class="release-number"><a href=".*?">(.+?)</a></span>', li_item)
        # リリース日の部分を探す
        y = re.search(r'<span class="release-date">(.+?)</span>', li_item)
        
        if x and y:
            releases.append((x.group(1), y.group(1)))
    
    return releases

# 実行例:抽出結果を表示
result = extract_releases_by_regex()
for name, date in sorted(result):
    print(f"{name:20} {date}")

実行結果

Demo Release 1.0     January 10, 2022
Demo Release 2.0     March 15, 2022
Demo Release 3.0     July 22, 2022
  • re.findall(r'<li>.+?</li>', html_text)<li>...</li>のまとまりを非貪欲(必要最小限の文字にマッチ)に取得してリストにする。
  • re.search(r'<span class=...>(.+?)</span>', li_item) : 個々のliブロックに対し、さらに必要な情報を検索。
  • x.group(1)( )で囲んだ部分にマッチした文字列を取得。番号を1以降にすると丸括弧で囲んだ順番で左から数えた順に、マッチした部分文字列を返します。

 正規表現のコードが複雑化すると読み書きがしづらい点や、HTMLの構造が変わるとパターンが合わなくなるなどの脆さもあるため、大規模スクレイピングではBeautifulSoupのようなHTML解析ツールを使うのが一般的です。

3.BeautifulSoupでHTMLを解析する

 正規表現でタグをパターンマッチさせる方法は汎用的ですが、HTML構造が複雑になると保守が難しくなります。そこでBeautifulSoupライブラリを使うと、要素の階層・属性名をもとに要素抽出が簡単に行えます。

3.1.BeautifulSoupのインストールと使い方

Anaconda環境ではすでに含まれる場合も多いですが、ない場合は

pip install beautifulsoup4

で導入します。PythonスクリプトまたはJupyterLabで使用する際は、

from bs4 import BeautifulSoup

とインポートします。HTML文字列を解析するには、

soup = BeautifulSoup(html_string, 'html.parser')

のように書きます。

3.2.実装例

 下記は先ほどのHTMLからリリース番号とリリース日を抽出する例です。正規表現を使わず、階層構造をたどる形で実装します。

import requests
from bs4 import BeautifulSoup

def extract_releases_by_bs4(url="https://www.study-pg.com/scraping/scraping2.html"):
    """
    BeautifulSoupを使って指定URLのHTMLを解析し、
    リリース番号と日付のリストを返すサンプル関数。
    """
    resp = requests.get(url)
    soup = BeautifulSoup(resp.text, 'html.parser')
    
    releases = []
    
    # <li>要素を全部取得
    li_tags = soup.find_all('li')
    for li in li_tags:
        # release-numberクラスのspan要素 → 内部のa要素からリリース番号を取り出す
        span_number = li.find('span', class_='release-number')
        if span_number:
            a_tag = span_number.find('a')
            
            # release-dateクラスのspan要素を探す
            span_date = li.find('span', class_='release-date')
            
            if a_tag and span_date:
                name = a_tag.get_text(strip=True)   # リリース番号
                date = span_date.get_text(strip=True)  # リリース日
                releases.append((name, date))
    
    return releases

# 実行例:抽出結果を表示
result_bs4 = extract_releases_by_bs4()
for name, date in sorted(result_bs4):
    print(f"{name:20} {date}")

実行結果

Demo Release 1.0     January 10, 2022
Demo Release 2.0     March 15, 2022
Demo Release 3.0     July 22, 2022
  • soup.find_all('li') : <li>タグを全検索しリストを得る。
  • span_number = li.find('span', class_='release-number')spanタグのうちclass="release-number"のものを探す。
  • a_tag = span_number.find('a') : さらに子要素のaタグを探す。
  • span_date = li.find('span', class_='release-date') : 日付情報のあるspanを同じli内で探す。
  • .get_text(strip=True) : タグ内のテキストを取得。strip=Trueで前後の空白を除去。

 こうした階層的な探索によって、HTML構造が変化してもある程度柔軟に対応しやすくなるのがBeautifulSoupの利点です。正規表現と違い、タグの境界や属性に基づいて要素を探せるため、コードの可読性・保守性が高まります。

まとめ

 ここでは、スクレイピングの概要について解説しました。より、詳しい内容については、専門的な書籍などを参考にしてください。
 正規表現を使った抽出方法は汎用性がありますが、HTML構造が変化するたびに修正が必要になる可能性が高いです。一方、BeautifulSoupはタグや属性名を指定して要素を取得するため、HTML文書を「ツリー構造」として解析し、柔軟に情報を取り出せます。大規模なスクレイピングプロジェクトでは、多くの場合BeautifulSoupをはじめとするHTMLパーサが利用されます。
 今回はscraping2.htmlというサンプルページを例に、リリース番号と日付を取得しました。実際の業務などで複雑なWebページを解析する際は、適宜JavaScriptの動的生成やログイン認証などの課題が出てきますが、まずは正規表現とBeautifulSoupの基本を押さえておけば、ほとんどの静的HTMLの解析には対応できるでしょう。