PR

Pythonスクレイピングで取得件数が0件になる原因と対策|robots.txt・待機時間の作法とRequests/Selenium【前編】

Python
スポンサーリンク

はじめに

Pythonスクレイピング 前編

スポンサーリンク

はじめに:価格調査ツールを自作しようとして0件だった話

こんにちは、ニンジンです🥕

この記事は、Pythonで「Webサイトから情報を自動収集するツール」を作ろうとして、いきなり 取得件数0件 の壁にぶつかった記録です。RequestsとSeleniumという定番の2つで挑んで、どちらも0件になった理由と、スクレイピングを始める前に知っておくべき「作法」をまとめます。

この記事は前編です。後編では、HTMLの解析をやめてWordPress REST APIでデータを取得する方法にたどり着きます。

この記事で分かること

  • スクレイピング前に確認する3つの作法(robots.txt・待機時間・練習用サイト)
  • Requests + BeautifulSoup で記事一覧を取ろうとすると0件になる理由(JavaScript描画)
  • Selenium でブラウザを動かしても、クラス名・構造の問題で苦戦する理由
  • 「どんなサイトからでも取れる汎用ツール」が現実的でない理由

スポンサーリンク

スクレイピング前に確認する3つの作法

スクレイピングは強力ですが、相手のサーバーに負担をかける行為でもあります。まず守るべき3つを確認します。

① サイトの意思表示「robots.txt」を確認する

Webサイトには「ここはプログラム(ロボット)でアクセスしてよい/ダメ」という意思表示を書いたファイル robots.txt があります。URLの末尾に /robots.txt を付けてアクセスすると読めます。

robots.txtの内容の例

User-agent: *(すべてのロボット向け)に対して Disallow: /admin/ のような記述があれば、その場所へのアクセスは控えます。技術的な強制力はありませんが、エンジニア同士の暗黙のルールです。

https://example.com/robots.txt

② アクセス間隔(time.sleep)を空ける

time.sleepで待機処理を入れたコード

1秒に何十回もアクセスするのは、実質的にDoS攻撃と同じです。import time して time.sleep(3) のような待機を必ず入れ、「1秒に1回以上は相手への攻撃になり得る」という意識を持ちます。

③ 練習は「練習用サイト」で行う

いきなり商用サイトで試すのはマナー違反です。スクレイピング練習用に公開されている「砂場(サンドボックス)」を使います。定番は Books to Scrapebooks.toscrape.com)や Quotes to Scrape です。ここで基礎を固めてから実戦に進みます。


スポンサーリンク

Requests + BeautifulSoup 編:自分のブログを取得してみる

作法を確認したところで、自分のブログ(https://py-vbalab.com)を対象に、軽量・高速な定番コンビ「Requests + BeautifulSoup」で挑みます。

インストール

pip install beautifulsoup4 requests の実行画面

必要に応じて、2つのライブラリを入れます。

pip install beautifulsoup4 requests

テストコード

import requests
from bs4 import BeautifulSoup
import openpyxl
from datetime import datetime
import time

# 1. ターゲットURL
TARGET_URL = "https://py-vbalab.com/"

def get_blog_data():
    # 相手サーバー(自分のブログ)への負荷軽減
    time.sleep(3)
    
    try:
        response = requests.get(TARGET_URL)
        response.encoding = response.apparent_encoding # 文字化け防止
        soup = BeautifulSoup(response.text, 'html.parser')
        
        # 記事カードの塊を特定(WordPressなどの一般的な構造を想定)
        # ※実際のHTML構造に合わせて 'entry-card' 等のクラス名は調整が必要な場合があります
        articles = soup.find_all('div', class_='entry-card-content') 
        print(f"取得できた記事数: {len(articles)}")
        
        data_list = []
        for article in articles:
            # タイトルの取得
            title_tag = article.find('h2')
            title = title_tag.text.strip() if title_tag else "タイトルなし"
            
            # URLの取得
            link_tag = article.find('a')
            url = link_tag['href'] if link_tag else "URLなし"
            
            # 投稿日の取得(timeタグや特定クラスのspanを想定)
            date_tag = article.find('span', class_='post-date')
            date_text = date_tag.text.strip() if date_tag else "日付不明"
            
            data_list.append([date_text, title, url])
            
        return data_list

    except Exception as e:
        print(f"エラーが発生しました: {e}")
        return []

def save_to_excel(data):
    # Excel新規作成
    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = "記事リスト"
    
    # ヘッダー
    ws.append(["投稿日", "記事タイトル", "URL"])
    
    # データの書き込み
    for row in data:
        ws.append(row)
    
    # ファイル名に実行日時を付けて保存
    file_name = f"BlogList_{datetime.now().strftime('%Y%m%d_%H%M')}.xlsx"
    wb.save(file_name)
    print(f"✅ Excelファイル '{file_name}' を作成しました!")

# 実行
if __name__ == "__main__":
    print("データ取得中...")
    blog_data = get_blog_data()
    if blog_data:
        save_to_excel(blog_data)
    else:
        print("データが取得できませんでした。HTML構造を確認してください。")

結果:取得できた記事数 0

実行結果が取得できた記事数0と表示された画面

「28記事以上あるはずなのに、0件……」ここで最初の壁にぶつかりました。

原因は、最近のモダンなブログテーマ(SWELLなど)が採用している 遅延読み込み(Lazy Load) でした。記事一覧のデータがHTMLに直接書かれておらず、JavaScriptで後から描画される仕組みだったため、requests.get() が受け取る素のHTMLには記事がまだ存在しなかったのです。


スポンサーリンク

Selenium 編:本物のブラウザを動かしてみる

「素のHTMLがダメなら、本物のブラウザを動かせばいい」——次に投入したのは、ブラウザを自動操作する Selenium です。

インストール

pip install selenium の実行画面

Seleniumを入れます(Chrome用のドライバは近年のSeleniumなら自動で用意されます)。

pip install selenium

テストコード

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
import time

options = Options()
driver = webdriver.Chrome(options=options)

try:
    print("ブラウザを起動してアクセス中...")
    driver.get("https://py-vbalab.com/")
    
    # 読み込み待ち(10秒くらい余裕を持たせてみる)
    time.sleep(8) 

    # --- 修正ポイント:複数の探し方を試す ---
    print("要素を探しています...")
    
    # 案A: SWELLの標準的なタイトルクラス
    elements = driver.find_elements(By.CSS_SELECTOR, ".p-postList__title")
    
    # 案B: もしAがダメなら、記事カード内のh2を全部取る
    if len(elements) == 0:
        elements = driver.find_elements(By.CSS_SELECTOR, "h2.post_title, .c-postitem__title, .p-postList__item h2")

    print(f"取得できた記事数: {len(elements)}")

    for i, el in enumerate(elements):
        # 画面に映っていないとテキストが取れない場合があるので、取得方法を工夫
        title = el.get_attribute("textContent").strip()
        print(f"{i+1}: {title}")

finally:
    print("3秒後に終了します...")
    time.sleep(3)
    driver.quit()

結果:取得できた記事数 0

Selenium版の実行結果が0件の画面

ここでも苦戦しました。「クラス名が一致しない」「特定の要素が見つからない」——サイトの構造は複雑で、少しのデザイン変更や高速化設定のせいで、ピンポイントで「タイトル」を抜くのがいかに難しいかを痛感しました。


スポンサーリンク

前編のまとめ:スクレイピングの甘くない現実

当初は「どんなサイトからでも情報を引っこ抜ける汎用ツール」を夢見ていましたが、現実はそう甘くありませんでした。

  • サイトごとにHTMLの構造が違うため、汎用化が難しい
  • モダンなサイトはJavaScriptで後からデータを描画するため、単純な取得ができない
  • 無理なスクレイピングは、相手にも自分にもリスクがある
  • サイトのデザイン変更のたびにコードを直し続ける、運用の壁

「個人では無理なのか」と諦めかけたとき、HTMLを解析しなくてもデータを取れる「裏口」を見つけました。後編で解説します。

今回の教訓:正面突破(HTML解析)がダメなら、一度立ち止まって「裏口(API)」がないか探してみる。

続きの記事

🔑 後編・完結 スクレイピングをやめてWordPress REST APIでデータ取得する方法|/wp-json/wp/v2/posts でブログ記事一覧を抽出 HTML解析をやめ、整形済みデータを返す公式の窓口(REST API)を使う逆転劇です。 🐍 環境がまだの方へ Windows 11でVS CodeとPythonの開発環境を構築する方法 この記事のコードを動かすための、Python実行環境の作り方です。

スポンサーリンク

よくある質問(FAQ)

Qrobots.txtDisallow が無ければ、何をスクレイピングしてもよいのですか?

Arobots.txtは技術的な許可ではなく、あくまで運営者の意思表示です。サイトの利用規約、著作権、サーバーへの負荷は別の問題として残ります。個人利用の範囲で、十分な間隔を空けて行うのが前提です。

QRequestsで取得件数が0件になるのはなぜですか?

A最近のブログテーマ(SWELLなど)は、記事一覧をJavaScriptで後から描画します。requests.get()が受け取るのは描画前の素のHTMLなので、その時点では記事の要素がまだ存在せず0件になります。

QSeleniumを使えば必ず取得できますか?

Aブラウザを実際に動かすのでJavaScript描画には対応できます。ただし、狙う要素のクラス名や構造がテーマの更新で変わると壊れるため、動いても「メンテナンスし続ける前提」になります。

Qtime.sleep は何秒に設定すればよいですか?

A1リクエストにつき最低1秒、できれば3秒以上を空けます。相手が個人運営の小規模サイトなら、さらに長めにするのが安全です。

Q練習用サイトは他にもありますか?

A同じ運営の Quotes to Scrape(quotes.toscrape.com)があります。HTTPの挙動そのものを確認したいときは httpbin.org も便利です。

Q会社のPCでスクレイピングしてもよいですか?

A会社のネットワークやセキュリティポリシー次第です。まず情報システム部門に確認してください。学習目的なら、自分のブログや練習用サイトで行うのが無難です。


ニンジン🥕アイコン

Follow me on X

本ブログの中の人「ニンジン🥕」はXやってます。新着記事はいち早くポストでお知らせしているので、見逃したくない方は @ninjin_py_vba をフォローしてみてください🥕

@ninjin_py_vba をフォロー

この記事が役に立ったら、シェアしてもらえると嬉しいです🥕

Xでポスト はてブ LINE Pocket

PROJECT LOG — SERIES INDEX
スポンサーリンク

シリーズ記事一覧

電子工作・自動化ツールの製作記録をシリーズごとにまとめています。気になるテーマからお読みください。


超音波距離センサーHC-SR04をRaspberry Pi Picoで使う|仕組み・配線・精度検証
.uds1-block { position: relative; overflow: hidden; border-radius: 12px; border: 1px solid #f0cba8; background: linear-g…
今週の注目ガジェット〜09/07週〜|電子工作・自作向け新製品まとめ(TickrCast/Radxa Linkr ほか)
ニンジンのガジェット通信 09/07週。ESP32-P4の自作気象モニター、極小IP-KVM「Radxa Linkr」、ESP32のDINレール型PLC、LTE開発キットなど、電子工作で使えそうな新製品5つを解説。
サーボモータの角度をタクトスイッチで可変|OLED(SSD1306)に現在角度をリアルタイム表示
Raspberry Pi Pico 2W×MG996RにタクトスイッチとOLED(SSD1306)を追加。ボタンで角度を可変しながら現在角度をリアルタイム表示するMicroPython実装を、framebufの文字拡大やサーボの遊び対策まで実機ベースで解説します。
MG996RサーボモーターをRaspberry Pi Pico 2Wで動かす:PWM制御とキャリブレーション実践
MG996RサーボモーターをRaspberry Pi Pico 2Wで動かす方法を解説。公称180度のはずが実測198度だった個体差の原因究明から、ホーンのズレ補正、タクトスイッチでの角度制御まで実機検証の様子を紹介します。
Tapo P110Mの消費電力をExcelレポート化する【年次集計・電気代試算・セキュリティ判断編】
FastAPI+openpyxlでTapo P110Mの消費電力をExcelレポート化。15秒間隔の生ログを日次集計する設計や、31円/kWhでの電気代試算、外部公開を見送った理由まで解説します。

コメント