ウェブスクレイパーの作り方:ゼロからのPythonガイド
Advanced Data Extraction Specialist
TL;DR:
- ウェブスクレイパーは3つのことを行います:ページを取得し、HTMLからフィールドを抽出し、次のページに移動します。 これら3つの操作が理解されれば、その後作成するすべてのスクレイパーは同じループのバリエーションになります。
requestsと BeautifulSoupを使えば、30行未満で静的ウェブを処理できます。 練習用サイトbooks.toscrape.comでは、1つのループで50ページのリストをすべて走査し、1,000の構造化された書籍レコードを返します。- ページネーションは、"次へ"リンクをたどるだけです。 各ページの次ページのアンカーを読み取り、それを絶対URLに解決し、リンクが消えるまで進み続けます。
- 通常のHTTPではJavaScriptでレンダリングされたコンテンツを見れません。 練習用ページquotes.toscrape.com/js/は、リクエストで0の引用要素を返します。これは、マークアップがページロード後にクライアントサイドで描画されるためです。
- Scrapeless Scraping Browserはこれらのページをクラウドサイドでレンダリングし、描画されたDOMを返します。 同じJavaScriptページは、クラウドブラウザが実行すると10の引用要素を返し、既存のBeautifulSoup解析がそのまま機能し続けます。
- 無料で始められます。 新しいScrapelessアカウントには、無料のScraping Browserランタイムが含まれています — app.scrapeless.comでサインアップできます。
はじめに:実際のスクレイパーを構築し、対抗するページを扱う
すべてのウェブスクレイパーは同じ3つのことを行います:ページを取得し、HTMLから必要なフィールドを抽出し、次のページに移動します。ライブラリやサイトは変わりますが、そのループは変わりません。安全にアクセスできるページで一度学べば、そのパターンは後でその他のほとんどのスクレイピングに適用できます。
摩擦は後で現れます。ほとんどの人が最初に書くスクレイパーは静的HTMLページで完璧に動作し、次に試すサイトでは空のシェルを返します。これは、そのサイトが初期応答後にJavaScriptでコンテンツをレンダリングするためです。通常のHTTPはサーバーが送信するバイトをダウンロードしますが、ページを実行はしません。そのため、ブラウザで見ることができるデータは、スクレイパーが受け取る応答には存在しません。
このガイドでは、Pythonで全てをゼロから構築します。静的層はrequestsとBeautifulSoupを使い、本のカタログスクレイピングサンドボックス と 引用のスクレイピングサンドボックス — スクレイプ用に特別に存在する2つのサイトに対して行います。次に、誠実な限界に移行します:通常のHTTPで空のページが返されたとき、仕事はScrapeless Scraping Browserに移り、クラウドブラウザがページをレンダリングし、ローカルで解析するのと同じDOMを返します。JavaScriptで作業したい場合は、静的と動的の分割はCheerioとPuppeteerのウォークスルーでカバーされています。
できること
- 商品カタログを収集する。 ページネーションされたリストを走査し、タイトル、価格、および在庫状態をCSVまたはJSONファイルに引き出す。
- 時間経過に伴う価格を追跡する。 同じスクレイパーを定期的に再実行し、数値の差分を確認して価格の変動を監視する。
- 分析やAIのためのデータセットを構築する。 整理されていないHTMLのページを、ノートブックやモデルが読み取れるクリーンな行に変換する。
- 在庫状況を監視する。 手動でクリックすることなく、多くのページの在庫状況を確認する。
- 研究パイプラインに供給する。 引用、記事、またはレビューを構造化されたストアに引き出し、その後の処理に使用する。
- JavaScriptでレンダリングされたページにアクセスする。 通常のHTTPで空のシェルを返すページをクラウドブラウザに昇格させ、同じ解析コードを維持する。
なぜScrapeless Scraping Browserなのか
Scrapeless Scraping Browserは、ウェブクロールツールとAIエージェント用に設計されたカスタマイズ可能で検出対策が施されたクラウドブラウザです。特にゼロからのスクレイパーにとって、requestsでは解決できない1つの問題 - ページを実行すること - を解決し、以下の機能を提供します:
- クラウドサイドのJavaScriptレンダリング。 クライアントサイドでコンテンツを描画するページは、完全にレンダリングされたDOMを返すため、BeautifulSoupは静的ページと同じように結果を解析します。
- 195か国以上の住宅用プロキシ。 国コードで流出地理を指定し、ページが地元の訪問者が見るのと同じコンテンツを返すようにします。
- 検出対策のフィンガープリンティング。 クラウドブラウザは、素のHTTPクライアントサインの代わりに一貫した人間のようなブラウザサーフェスを提供します。
- 全体のための1つのAPIキー。 Python SDKは
browser_ws_endpointを作成し、Playwrightで接続します。同じキーがランタイムもカバーします。
無料プランでAPIキーを取得するには、app.scrapeless.comにアクセスしてください。
前提条件
- Python 3.10以上
- 静的層のための
requestsとbeautifulsoup4 - JavaScriptでレンダリングされた層のための
scrapelessSDKとplaywright - Scrapeless アカウントと API キー - app.scrapeless.com でサインアップしてください。
- ターミナルの基本的な知識
インストール
まず、2 つのスタティックティアライブラリをインストールします:
bash
pip install requests beautifulsoup4
requests は HTTP 経由でページを取得し、beautifulsoup4 は返された HTML を解析します — HTML 標準 で定義されたマークアップを CSS セレクタでクエリ可能なツリーに変換します。このペアで、ステップ 1 から 4 までのすべての静的ページに対応できます。ステップ 5 の JavaScript レンダリングティアでは、さらに 2 つのパッケージが追加されます。
ステップ 1 — ページを取得し、実際の HTML を取得したことを確認する
スクレイパーは 1 回のリクエストから始まります。ターゲット URL に GET リクエストを送り、HTTP セマンティクス に従ってステータスコードを確認し、実際に HTML が期待したレコードを含んでいることを確認します。その後、最初のセレクタを記述します。
python
import requests
from bs4 import BeautifulSoup
url = "http://books.toscrape.com/"
resp = requests.get(url, headers={"User-Agent": "Mozilla/5.0 (tutorial-scraper)"}, timeout=30)
print("status:", resp.status_code, "bytes:", len(resp.text))
soup = BeautifulSoup(resp.text, "html.parser")
books = soup.select("article.product_pod")
print("このページのブックカード:", len(books))
books.toscrape.com に対してこれを実行すると、status: 200 と このページのブックカード: 20 が表示されます。User-Agent ヘッダーはクライアントを識別します;多くのサーバーはこれを送信しないリクエストを拒否します。もしカウントが実際のサイトでゼロになった場合、それはページが JavaScript によってレンダリングされているという初期信号です — この状況はステップ 5 で処理されます。
ステップ 2 — レコードを発見し、フィールドを抽出する
まず繰り返し出現するコンテナを選択し、その内部のフィールドを読み取ります。セマンティックタグ、data-* 属性、またはデータを説明するクラスなど、安定したフックを好みましょう — 次回のデザイン変更で壊れるハッシュ化されたクラスよりも。books.toscrape.com では、各本は article.product_pod であり、フィールドは予測可能な子セレクタからぶら下がっています。
python
import requests
from bs4 import BeautifulSoup
resp = requests.get(
"http://books.toscrape.com/",
headers={"User-Agent": "Mozilla/5.0 (tutorial-scraper)"},
timeout=30,
)
resp.encoding = resp.apparent_encoding # カタログは £ を使用しています;requests に検出させる
soup = BeautifulSoup(resp.text, "html.parser")
pod = soup.select_one("article.product_pod")
title = pod.h3.a["title"]
price = pod.select_one("p.price_color").get_text(strip=True)
in_stock = "In stock" in pod.select_one("p.instock.availability").get_text()
print(title, "|", price, "|", "在庫あり" if in_stock else "在庫なし")
これにより、A Light in the Attic | £51.77 | 在庫あり が表示されます。resp.encoding = resp.apparent_encoding の行は重要です:これがなければポンド記号がモジベイクとしてデコードされます。なぜなら、レスポンスヘッダーとドキュメントの実際のエンコーディングが一致しないからです。フィールドはすでに保持している要素から読み取ります — タイトルはアンカーの title 属性に存在するので、1 つのセレクタで発見と抽出の両方を行います。
ステップ 3 — ページネーションを追跡し、「次へ」リンクが消えるまで
リストは通常 1 ページに収まりません。信頼できるパターンは、各ページの次ページリンクを読み取り、現在の URL に対して解決し、次のリンクがなくなるまでループすることです。予想するためのページカウンタは必要なく、維持するためのハードコードされた範囲も必要ありません。
python
import requests
from urllib.parse import urljoin
from bs4 import BeautifulSoup
session = requests.Session()
session.headers.update({"User-Agent": "Mozilla/5.0 (tutorial-scraper)"})
records = []
url = "http://books.toscrape.com/catalogue/page-1.html"
pages = 0
while url:
resp = session.get(url, timeout=30)
resp.encoding = resp.apparent_encoding
soup = BeautifulSoup(resp.text, "html.parser")
for pod in soup.select("article.product_pod"):
records.append({
"title": pod.h3.a["title"],
"price": pod.select_one("p.price_color").get_text(strip=True),
"in_stock": "In stock" in pod.select_one("p.instock.availability").get_text(),
})
pages += 1
next_link = soup.select_one("li.next > a")
url = urljoin(url, next_link["href"]) if next_link else None
print("クローリングしたページ数:", pages, "| 収集したレコード数:", len(records))
books.toscrape.com に対してこれを実行すると、すべての 50 のリスティングページを訪れ、1,000 のレコードを収集します。requests.Session はリクエスト間で基盤となる接続を再利用するため、新しい requests.get よりも高速です。urljoin は相対 href(例えば page-2.html)を絶対 URL に変換し、次のリクエストが正しく解決されるようにします。
無料プランで API キーを取得してください: app.scrapeless.com
ステップ 4 — 構造化出力を書く
スクレイピングしたデータは、構造化されて初めて有用です。ステップ 3 の records リストは、一貫したキーを持つ辞書のリストとしてすでに構成されているため、CSV または JSON に書き込むのは数行で済みます。事前にスキーマを決定してください — 各行に同じキーを使い、欠如したフィールドは None になり、クラッシュは決して起こりません。
python
import csv
import json
# records は、ステップ 3 で構築された辞書のリストです。
with open("books.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["title", "price", "in_stock"])
writer.writeheader()
writer.writerows(records)
with open("books.json", "w", encoding="utf-8") as f:
json.dump(records, f, ensure_ascii=False, indent=2)
print("books.csv と books.json に", len(records), "行を書き込みました")
これは完全に機能するスクレイパーです: 取得し、発見し、抽出し、ページ分割を行い、保存します。セレクタをそのサイトのマークアップに入れ替えれば、任意の静的 HTML サイトに対応できます。
プレーン HTTP の限界: JavaScript でレンダリングされたページ
上記のスクレイパーは、サイトが JavaScript でコンテンツをレンダリングする瞬間に壊れます。練習用ページである quotes.toscrape.com/js/ は、まさにこのレッスンのために作られています — 引用は、ページが読み込まれた後にスクリプトによって挿入され、初期 HTML には存在していません。
python
import requests
from bs4 import BeautifulSoup
resp = requests.get("https://quotes.toscrape.com/js/",
headers={"User-Agent": "Mozilla/5.0"}, timeout=30)
soup = BeautifulSoup(resp.text, "html.parser")
print("バイト数:", len(resp.text), "| 引用要素:", len(soup.select("div.quote")))
このコードは バイト数: 5806 と 引用要素: 0 を返します。リクエストは成功しました — ステータス 200、実際のバイト数 — しかし、解析されたページには引用がなく、なぜなら requests はそれらを生成する JavaScript を実行しないからです。セレクタを調整しても解決しません; データは本当にレスポンスに存在しないのです。ページはブラウザで実行する必要があります。
ステップ 5 — Scrapeless スクレイピングブラウザでページをレンダリング
すべてのページのためにローカルで完全なブラウザを実行するのは負担が大きく、すぐにブロックされます。よりクリーンな方法はクラウドブラウザです: Scrapeless がセッションを生成し、ページを自側でレンダリングし、最終的な DOM を返します — これは、すでに書いた同じ BeautifulSoup コードを使用して解析できます。2つの追加パッケージをインストールし、その後 Playwright を Chrome DevTools プロトコルで接続します。
bash
pip install scrapeless playwright
playwright install chromium
playwright install chromium はローカルプロトコルクライアントを1回ダウンロードします; 実際のレンダリングは Scrapeless のクラウドで実行されます。まずはあなたのキーをエクスポートしてください(export SCRAPELESS_API_KEY=your_api_token_here)、その後:
python
from scrapeless import Scrapeless
from scrapeless.types import ICreateBrowser
from playwright.sync_api import sync_playwright
from bs4 import BeautifulSoup
client = Scrapeless() # 環境から SCRAPELESS_API_KEY を読み取ります
session = client.browser.create(
ICreateBrowser(proxy_country="US", session_ttl=180)
)
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(session.browser_ws_endpoint)
ctx = browser.contexts[0] if browser.contexts else browser.new_context()
page = ctx.pages[0] if ctx.pages else ctx.new_page()
page.goto("https://quotes.toscrape.com/js/", wait_until="domcontentloaded", timeout=60_000)
page.wait_for_timeout(4_000) # クライアントサイドのスクリプトが引用を描画するのを待つ
html = page.content()
browser.close()
soup = BeautifulSoup(html, "html.parser")
quotes = soup.select("div.quote")
print("レンダリングされたバイト数:", len(html), "| 引用要素:", len(quotes))
print(quotes[0].select_one("small.author").get_text(strip=True))
このコードは レンダリングされたバイト数: 9246 と 引用要素: 10 を返し、アルバート・アインシュタイン を印刷します — プレーン HTTPではゼロを返した同じページです。session.browser_ws_endpoint は wss://browser.scrapeless.com/... の URL で、Playwright の connect_over_cdp はそれに DevTools プロトコルを使用して話しかけます。そのため、ページはクラウドで実行され、あなたのコードはローカルで実行されます。wait_until="domcontentloaded" と短い固定の定着は、スクリプト駆動のページでネットワークアイドルを待つよりも信頼性があります。SDK と CLI のインターフェースは docs.scrapeless.com に文書化されています。
返ってくるもの
クラウドブラウザがレンダリングされた HTML を返すと、解析は静的層とまったく同じになります — 同じセレクタ、同じ辞書の形です:
json
[
{
"text": "私たちが作り出した世界は私たちの考えのプロセスです。",
"author": "アルバート・アインシュタイン",
"tags": ["変化", "深い思考", "思考", "世界"]
}
]
// スキーマは div.quote の解析によって出力される内容と正確に一致します。値は例示的なサンプルです。
レンダリングされたページについてのいくつかの率直な観察:
- **レンダリングされたページではタイミングが重要です。**引用はページのスクリプトが実行された後にのみ表示されるため、
domcontentloadedの後の短い間隔がそれらを表示させます。間隔が短すぎると、空のページを解析してしまいます。 - セレクタは依然として変動します。
div.quote、span.text、small.authorはこのサンドボックスのマークアップです。本物のサイトでは、ページが変更されたときにセレクタを再確認してください。 - **欠落したフィールドは普通です。**レコードにはタグや価格が欠けていることがあります。すべてのフィールドをnullableと見なし、ライターがクリーンな行を生成し続けるようにしてください。
- 出口を固定してください。
proxy_country="US"は、米国の訪問者と一致するようにレンダリングされたコンテンツを一貫させます。必要な地域に合わせてコードを変更してください。
結論: 一つのページから実際のパイプラインへ
機能するスクレイパーは4つの動きに集約されます: ページを取得する、繰り返されるレコードを発見する、そのフィールドを抽出する、そしてページネーションを追い続ける。これは、requestsとBeautifulSoupで静的ウェブをカバーします。JavaScriptでコンテンツを構築するページという唯一のケースは、クラウド側でページをレンダリングし、パーサーがすでに理解している同じDOMを返すSrapeless Scraping Browserに段階が上がります。
ここからは、すべての生産スクレイパーがスケールする方法と同じようにスケールしてください: セレクタをタイトに保ち、マークアップが変わったときに再確認し、出口地理をページが提供する視聴者に合わせて固定し、欠落フィールドをnullableとして扱い、実際にレンダリングが必要なページでのみクラウドブラウザにアクセスします。JavaScriptの同じ分岐に対して、CheerioとPuppeteerガイドはNode.jsにおける静的と動的の選択を扱います。ボリュームで実行する準備ができたら、価格ページでランタイムオプションを比較してください。
AIパワードデータパイプラインの構築準備はできましたか?
無料プランを請求し、スクレイピングパイプラインを構築する開発者とつながるために私たちのコミュニティに参加してください: Discord · Telegram。
app.scrapeless.comに登録して、無料のScraping Browserランタイムを取得し、上記のパターンをあなたのスクレイパーが必要とするサイト、ページ、および地域に適応させてください。
よくある質問
Q: ウェブスクレイピングは合法ですか?
公開されているデータのスクレイピングは多くの法域で広く許可されていますが、ルールは国やサイトによって異なります。ターゲットサイトの利用規約とロボット排除プロトコルを確認し、個人情報やゲートデータを避け、商業的なものについては弁護士に相談してください。このガイドに掲載されているサイトは、特にスクレイピングされるために存在します。
Q: スクレイパーを構築するのにプロキシが必要ですか?
許可されているサイトに対する低ボリュームのスクレイパーには、必要ありません。IPによってレート制限があるサイトや地域によってコンテンツが異なるサイトには、必要です - 住宅用プロキシを介してルートし、ページがローカル訪問者が見るコンテンツを返すように国を固定します。ステップ5のScraping Browserティアには、proxy_country設定付きのプロキシ出口が含まれています。
Q: なぜ私のスクレイパーが空のページを返すのですか?
ほとんどの場合、ページが初期レスポンスの後にJavaScriptでコンテンツをレンダリングするためです。プレーンなrequestsはサーバーのバイトをダウンロードしますが、ページのスクリプトは実行しないため、ブラウザで見るデータはレスポンスにありません。要素のカウントを印刷して確認してください。「プレーンHTTPが停止する場所」セクションのように、次にクラウドブラウザでページをレンダリングします。
Q: "アクセス denied"またはチャレンジページが表示されるサイトにどう対処すればいいですか?
それはリクエストに対するボット対策チェックです。米国の住宅用出口を固定した状態でScrapeless Scraping Browserを介してページをレンダリングし、リクエストが一貫した人間のようなブラウザ表面を持つように同じブラウザセッションでサイトのホームページをまず読み込んでセッションを温めます。
Q: デザイン改修後にセレクタが機能しなくなりました — どうすればいいですか?
マークアップは回転します。ページを再検査し、ハッシュ化されたCSSクラスよりも安定したフック(セマンティックタグ、data-*属性、データを説明するクラス)を優先してセレクタを更新してください。最も耐久性のあるフックに対して構築することで、修正の間にスクレイパーがより長く生き残ります。
Q: 同時にスクレイピングできるページ数はどのくらいですか?
並列性は控えめに保ちます — 単一のサイトに対しては、ホストごとに約3つのワーカーが合理的な上限です。これにより、丁寧なリクエストレートを維持し、レート制限を回避できます。クラウドブラウザセッションはHTTPリクエストよりも希少ですので、静的な取得よりも厳しく制限してください。
Q: すべてにブラウザを使用する必要がありますか?
いいえ、あなたはそうすべきではありません。requests と BeautifulSoup の静的 tier はより速く、コストも安いため、レンダリングされた HTML を提供するすべてのページに使用してください。JavaScript の実行が本当に必要なページに対してのみ、クラウドブラウザにエスカレートしてください。
Scrapelessでは、適用される法律、規制、およびWebサイトのプライバシーポリシーを厳密に遵守しながら、公開されているデータのみにアクセスします。 このブログのコンテンツは、デモンストレーションのみを目的としており、違法または侵害の活動は含まれません。 このブログまたはサードパーティのリンクからの情報の使用に対するすべての責任を保証せず、放棄します。 スクレイピング活動に従事する前に、法律顧問に相談し、ターゲットウェブサイトの利用規約を確認するか、必要な許可を取得してください。



