0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【比較】BeautifulSoup自作 vs Scraping AI:開発者が知るべき「保守税」と使い分けの境界線

0
Posted at

image.png
PythonでWebスクレイピングを始める際、まず候補になるのが requests と BeautifulSoup です。

BeautifulSoupはHTMLを解析するための非常にシンプルで扱いやすいライブラリで、個人開発やデータ収集、Webスクレイピングの学習に最適です。

一方で、スクレイピングを実際のサービスや社内システムで継続的に運用する場合、単純にHTMLを解析するだけでは解決できない問題も出てきます。

本記事では、Requests + BeautifulSoupを使った自作スクレイピングをベースに、CSSセレクター、JavaScriptによる動的ページ、データ検証、エラーハンドリングなどをどのように設計すればよいかを紹介します。


目次


BeautifulSoupとは

BeautifulSoupは、PythonからHTMLやXMLを解析するためのライブラリです。

例えば、以下のようなHTMLがあるとします。

<div class="product">
    <h1>Example Product</h1>
    <span class="price">¥12,800</span>
    <span class="stock">In Stock</span>
</div>

BeautifulSoupを使えば、簡単に情報を取り出せます。

from bs4 import BeautifulSoup

html = """
<div class="product">
    <h1>Example Product</h1>
    <span class="price">¥12,800</span>
    <span class="stock">In Stock</span>
</div>
"""

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

title = soup.select_one("h1")
price = soup.select_one(".price")

print(title.get_text(strip=True))
print(price.get_text(strip=True))

実行すると、

Example Product
¥12,800

のように取得できます。


基本的なスクレイピング

実際のWebサイトからHTMLを取得する場合は、requests と組み合わせます。

import requests
from bs4 import BeautifulSoup

url = "https://example.com"

response = requests.get(
    url,
    headers={
        "User-Agent": "Mozilla/5.0"
    },
    timeout=10
)

response.raise_for_status()

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

print(soup.title.get_text(strip=True))

基本的な流れは非常にシンプルです。

URL
 ↓
requests.get()
 ↓
HTML
 ↓
BeautifulSoup
 ↓
必要なデータを抽出

CSSセレクターを使ったデータ抽出

BeautifulSoupではCSSセレクターを利用できます。

例えば、

<div class="product">
    <h1 class="product-title">Example Product</h1>
    <span class="price">¥12,800</span>
    <span class="stock">In Stock</span>
</div>

であれば、

title = soup.select_one(".product-title")
price = soup.select_one(".price")
stock = soup.select_one(".stock")

のように取得できます。

複数の商品を取得する場合は、

products = soup.select(".product")

for product in products:
    title = product.select_one(".product-title")
    price = product.select_one(".price")

    print({
        "title": title.get_text(strip=True) if title else None,
        "price": price.get_text(strip=True) if price else None,
    })

とできます。


構造化データとして取得する

スクレイピングしたデータを、そのまま文字列として扱うのではなく、最初から辞書やJSONとして扱うと後続処理が簡単になります。

例えば、

import requests
from bs4 import BeautifulSoup


def scrape_product(url: str) -> dict:
    response = requests.get(
        url,
        headers={
            "User-Agent": "Mozilla/5.0"
        },
        timeout=10
    )

    response.raise_for_status()

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

    title = soup.select_one("h1.product-title")
    price = soup.select_one(".price")
    stock = soup.select_one(".stock")

    return {
        "title": title.get_text(strip=True) if title else None,
        "price": price.get_text(strip=True) if price else None,
        "in_stock": (
            stock is not None
            and "in stock" in stock.get_text(strip=True).lower()
        ),
    }

利用側は、

result = scrape_product(
    "https://example.com/product/123"
)

print(result)

とするだけです。

結果:

{
    "title": "Example Product",
    "price": "¥12,800",
    "in_stock": true
}

このように、スクレイピング処理を関数として切り出しておくと、APIやバッチ処理からも利用しやすくなります。


Pydanticでデータを検証する

実際のサービスでは、「取得できた」だけでは不十分です。

例えば価格が本来 number であるにもかかわらず、

"¥12,800"

のような文字列で取得される可能性があります。

そこでPydanticを使って、取得したデータを検証できます。

from pydantic import BaseModel


class Product(BaseModel):
    title: str
    price: float
    in_stock: bool

例えば、

product = Product(
    title="Example Product",
    price=12800,
    in_stock=True
)

print(product.model_dump())

結果:

{
    "title": "Example Product",
    "price": 12800.0,
    "in_stock": true
}

これによって、

Webページ
 ↓
BeautifulSoup
 ↓
データ抽出
 ↓
Pydantic
 ↓
検証済みJSON

という流れを作ることができます。


スクレイピングで発生する問題

ここまでを見ると、

BeautifulSoupだけで十分なのでは?

と思うかもしれません。

しかし、実際に複数のWebサイトを継続的にスクレイピングすると、いくつかの問題に遭遇します。

代表的なのが以下です。

1. HTML構造の変更

例えば、

.product-title

を使っていたのに、サイト側の変更によって、

.product-name

に変更されたとします。

すると、

soup.select_one(".product-title")

はデータを取得できなくなります。


2. CSSクラス名の変更

さらに厄介なのが、サイトによってはCSSクラス名が頻繁に変更されるケースです。

例えば、

<div class="product-card">

だったものが、

<div class="product-card-v2">

になるだけでも、既存のセレクターが壊れる可能性があります。


3. JavaScriptによる動的レンダリング

BeautifulSoupが直接解析できるのは、基本的に取得したHTMLです。

しかし、最近のWebサイトではJavaScriptによってページの内容が生成されることがあります。

例えば、

requests
 ↓
HTML取得
 ↓
BeautifulSoup

だけでは、商品情報が存在しないことがあります。

ブラウザで見ると表示されているのに、

requests.get(url).text

では存在しない、というケースです。


4. タイムアウトやネットワークエラー

本番環境では、

  • Connection timeout
  • HTTP 403
  • HTTP 429
  • Connection reset
  • Server error

なども考慮する必要があります。

そのため、最低限のエラーハンドリングは必要です。

import requests


try:
    response = requests.get(
        url,
        timeout=10
    )

    response.raise_for_status()

except requests.RequestException as e:
    print(f"Scraping failed: {e}")

JavaScriptで生成されるページへの対応

JavaScriptでコンテンツが生成されるサイトでは、ブラウザ自動化ツールを利用できます。

代表的なものがPlaywrightです。

pip install playwright beautifulsoup4 requests

そして、

playwright install

を実行します。

例えば、

from playwright.sync_api import sync_playwright
from bs4 import BeautifulSoup


def scrape_dynamic_page(url: str) -> dict:

    with sync_playwright() as p:

        browser = p.chromium.launch(
            headless=True
        )

        page = browser.new_page()

        page.goto(
            url,
            wait_until="networkidle"
        )

        html = page.content()

        browser.close()

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

    title = soup.select_one("h1")

    return {
        "title": title.get_text(strip=True)
        if title else None
    }

この場合、

URL
 ↓
Playwright
 ↓
JavaScript実行
 ↓
Rendered HTML
 ↓
BeautifulSoup
 ↓
データ抽出

という構成になります。


BeautifulSoup + Playwrightという構成

実際には、BeautifulSoupとPlaywrightは競合するものではありません。

それぞれ役割が違います。

ツール 主な役割
Requests HTTPリクエスト
BeautifulSoup HTML解析
Playwright ブラウザ・JavaScript実行
Pydantic データ検証

そのため、以下のような構成にすると扱いやすくなります。

                  URL
                   │
                   ▼
          ┌─────────────────┐
          │   HTTP Fetcher  │
          │    Requests     │
          └────────┬────────┘
                   │
             HTML available?
              ┌────┴────┐
             Yes        No
              │          │
              ▼          ▼
       BeautifulSoup  Playwright
              │          │
              └────┬─────┘
                   ▼
          ┌─────────────────┐
          │ Data Extraction │
          └────────┬────────┘
                   ▼
          ┌─────────────────┐
          │    Pydantic     │
          │   Validation    │
          └────────┬────────┘
                   ▼
              Structured
                 JSON

実運用を意識した構成

個人開発では、

soup.select_one(".price")

だけでも十分です。

しかし、本番運用を考えるなら、スクレイピング処理を複数のコンポーネントに分けることをおすすめします。

例えば、

scraper/
├── fetcher.py
├── parser.py
├── extractors/
│   ├── product.py
│   └── article.py
├── models.py
├── validators.py
└── exceptions.py

という構成です。

fetcher.py

HTMLを取得する責任を持たせます。

import requests


class Fetcher:

    def fetch(self, url: str) -> str:

        response = requests.get(
            url,
            headers={
                "User-Agent": "Mozilla/5.0"
            },
            timeout=10
        )

        response.raise_for_status()

        return response.text

parser.py

HTMLをBeautifulSoupに変換します。

from bs4 import BeautifulSoup


class Parser:

    def parse(self, html: str) -> BeautifulSoup:

        return BeautifulSoup(
            html,
            "html.parser"
        )

models.py

取得したデータの構造を定義します。

from pydantic import BaseModel


class Product(BaseModel):

    title: str
    price: float
    in_stock: bool

こうすることで、各コンポーネントの責任を明確にできます。


BeautifulSoupの最大のメリット

BeautifulSoupを使った自作スクレイピングの最大のメリットは、シンプルであることです。

requests
+
BeautifulSoup
+
Python

だけでも、多くのWebサイトからデータを取得できます。

また、外部のスクレイピングAPIに依存する必要がないため、

  • API利用料金が不要
  • 自分で処理を完全にコントロールできる
  • データ処理を自由にカスタマイズできる
  • Pythonの学習にもなる
  • 小規模な処理なら非常に軽量

というメリットがあります。

特に、

  • 学習目的
  • 個人開発
  • 1回限りのデータ収集
  • HTML構造が安定したサイト

では非常に有効です。


一方で、運用コストには注意

BeautifulSoup自体は無料ですが、スクレイピングシステム全体の運用コストがゼロになるわけではありません。

例えば、

サイトA
 ↓
CSSセレクター変更
 ↓
スクレイパー修正

という問題が発生する可能性があります。

さらに、

HTML変更
JavaScript変更
API変更
Cloudflare
Rate Limit
robots.txt
Timeout

なども考慮する必要があります。

つまり、

ライブラリが無料 = スクレイピングシステムの運用コストが無料

ではありません。

実際には、サイト数が増えるほどメンテナンスコストが増えていきます。


まとめ

BeautifulSoupは、PythonでWebスクレイピングを始めるうえで非常に優れたライブラリです。

基本的には、

Requests
   ↓
BeautifulSoup
   ↓
CSS Selector
   ↓
Pydantic
   ↓
JSON

という構成から始めることができます。

JavaScriptによる動的ページが必要になった場合は、

Requests
   ↓
Playwright
   ↓
BeautifulSoup
   ↓
Pydantic

のように拡張できます。

重要なのは、最初から複雑な仕組みを作るのではなく、

「HTTPで取得する → HTMLを解析する → 必要なデータを抽出する → 検証する」

という基本的な流れを理解することです。

BeautifulSoupはシンプルだからこそ、スクレイピングの仕組みを理解するための良い入口になります。

そして、実際に複数サイトを長期間運用してみることで、

「なぜセレクターの保守が問題になるのか」

「どの部分を自動化すべきなのか」

が見えてきます。

まずはBeautifulSoupで自分自身のスクレイパーを作ってみることをおすすめします。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?