Python 網頁資料採集指南:合規、穩定、可維護
Python 很適合採集公開網頁資料,但生產級採集不是「發請求、解析 HTML」這麼簡單。真實專案需要權限檢查、禮貌訪問、穩定選擇器、重試邏輯、資料校驗和監控。
這篇文章關注合規、穩定、可維護的資料採集。它不講繞過存取控制、規避反爬、破解驗證碼,或採集登入、付費牆、明確禁止訪問後的資料。如果網站提供 API、資料匯出、Feed 或合作通道,應優先使用官方方式。
參考文件:
先做權限檢查
寫程式前先回答這些問題:
- 目標資料是否公開,且無需登入即可查看?
- 網站是否提供 API、網站地圖、RSS 或匯出?
robots.txt是否允許抓取目標路徑?- 服務條款是否限制自動存取或資料再利用?
- 資料是否可能包含個人資訊、版權內容或受監管紀錄?
- 你的爬蟲是否能標識自己並提供聯絡方式?
如果答案不清楚,不要先寫爬蟲。應先取得許可,或使用官方資料源。
選擇合適工具
| 頁面類型 | 推薦方式 | 原因 |
|---|---|---|
| 靜態 HTML | httpx 或 requests + BeautifulSoup/parsel |
簡單、快速、開銷低 |
| 大量相似頁面 | Scrapy | 調度、重試、管道、匯出更完整 |
| JavaScript 渲染頁面 | Playwright | 可以執行用戶端渲染 |
| API 驅動頁面 | 在允許的情況下呼叫公開 API | 比解析 HTML 更穩定高效 |
| 一次性資料清理 | Pandas + 已保存 HTML/CSV | 避免重複請求 |
原則是:用能穩定、禮貌完成任務的最簡單工具。
一個小型靜態頁面採集器
靜態頁面可以從明確逾時、清晰 User-Agent 和嚴格解析開始。
from __future__ import annotations
import time
from dataclasses import dataclass
from urllib.parse import urljoin
import httpx
from bs4 import BeautifulSoup
BASE_URL = "https://example.com/articles/"
@dataclass
class Article:
title: str
url: str
summary: str
def fetch_html(url: str) -> str:
headers = {
"User-Agent": "ExampleResearchBot/1.0 (+https://example.com/contact)"
}
with httpx.Client(timeout=15, follow_redirects=True, headers=headers) as client:
response = client.get(url)
response.raise_for_status()
return response.text
def parse_articles(html: str) -> list[Article]:
soup = BeautifulSoup(html, "html.parser")
articles: list[Article] = []
for card in soup.select("article.card"):
title_el = card.select_one("h2 a")
summary_el = card.select_one(".summary")
if not title_el:
continue
title = title_el.get_text(" ", strip=True)
url = urljoin(BASE_URL, title_el.get("href", ""))
summary = summary_el.get_text(" ", strip=True) if summary_el else ""
articles.append(Article(title=title, url=url, summary=summary))
return articles
def main() -> None:
html = fetch_html(BASE_URL)
for article in parse_articles(html):
print(article)
time.sleep(2)
if __name__ == "__main__":
main()
選擇器要容易測試。選擇器失效時,應少返回資料並記錄問題,不要靜默寫入錯誤資料。
用 Playwright 處理動態頁面
當目標內容由 JavaScript 生成,且沒有允許使用的 API 或 Feed 時,再考慮 Playwright。
from playwright.sync_api import sync_playwright
def collect_titles(url: str) -> list[str]:
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto(url, wait_until="networkidle", timeout=30_000)
titles = [
item.inner_text().strip()
for item in page.locator("article h2").all()
]
browser.close()
return titles
Playwright 比普通 HTTP 請求重得多。只有頁面確實需要渲染時才用它。如果同樣資料能從 HTML 或允許訪問的公開 API 得到,就不要啟動瀏覽器。
用 Scrapy 擴展規模
當頁面很多、需要重試、匯出和資料管道時,Scrapy 更合適。
import scrapy
class ArticleSpider(scrapy.Spider):
name = "articles"
allowed_domains = ["example.com"]
start_urls = ["https://example.com/articles/"]
custom_settings = {
"ROBOTSTXT_OBEY": True,
"CONCURRENT_REQUESTS_PER_DOMAIN": 2,
"DOWNLOAD_DELAY": 2,
"AUTOTHROTTLE_ENABLED": True,
"AUTOTHROTTLE_TARGET_CONCURRENCY": 1.0,
"USER_AGENT": "ExampleResearchBot/1.0 (+https://example.com/contact)",
"FEEDS": {
"articles.jsonl": {"format": "jsonlines", "encoding": "utf8"}
},
}
def parse(self, response):
for card in response.css("article.card"):
title = card.css("h2 a::text").get()
href = card.css("h2 a::attr(href)").get()
if title and href:
yield {
"title": title.strip(),
"url": response.urljoin(href),
"source_url": response.url,
}
next_url = response.css("a.next::attr(href)").get()
if next_url:
yield response.follow(next_url, callback=self.parse)
關鍵不只是 Spider。ROBOTSTXT_OBEY、網域並發、下載間隔和 AutoThrottle 可以讓訪問流量更可預測。
資料品質管道
原始採集資料很少能直接使用。應盡早做校驗和規範化。
from itemadapter import ItemAdapter
class CleanArticlePipeline:
def process_item(self, item, spider):
adapter = ItemAdapter(item)
title = (adapter.get("title") or "").strip()
url = (adapter.get("url") or "").strip()
if not title or not url:
raise ValueError("Missing title or URL")
adapter["title"] = " ".join(title.split())
adapter["url"] = url
return item
生產環境中,校驗失敗的資料應單獨記錄,這樣選擇器變更能被及時發現。
禮貌採集規則
禮貌採集能減少網站壓力,也更容易長期運行:
- 遵守
robots.txt和網站條款。 - 預設使用較低並發。
- 設定訪問間隔和 AutoThrottle。
- 開發階段快取回應,避免反覆請求。
- 避免重複下載未變化頁面。
- 連續錯誤時停止,不要激進重試。
- 使用清晰的 User-Agent 標識爬蟲。
- 提供聯絡頁面或信箱。
如果網站阻止或挑戰爬蟲,應把它當作邊界。沒有許可時,不應圍繞限制繼續構建。
增量採集
週期性任務不要每次全量抓取。
可以使用:
- 網站地圖或 Feed。
- 上次看到的 URL 或時間戳。
- 網站支援時使用 ETag 和 Last-Modified。
- 內容雜湊偵測變化。
- 持久化的已訪問 URL 儲存。
import hashlib
def content_hash(text: str) -> str:
normalized = " ".join(text.split())
return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
增量採集可以節省頻寬、降低網站壓力,也讓資料變化更容易稽核。
監控指標
生產爬蟲應該報告:
- 請求頁面數。
- 提取記錄數。
- 空頁面數。
- HTTP 狀態碼分布。
- 重試次數。
- 解析失敗次數。
- 重複率。
- 任務耗時。
如果提取記錄突然下降,通常說明頁面結構變了,或爬蟲觸達了限制。
常見錯誤
選擇器太脆弱
.container > div:nth-child(3) > span 這類選擇器很容易失效。優先使用語意標籤、穩定屬性或頁面內嵌的結構化資料。
忽略字元編碼
一定要測試多語言資料。編碼錯誤可能悄悄破壞標題、地址和姓名。
混合採集和業務邏輯
採集、解析、校驗、儲存應分層。這樣某一層變化時,不需要改動全部程式碼。
跑得太快
高並發會帶來封鎖、不完整資料和維運風險。一個慢但穩定的爬蟲,比一個快但經常失敗的爬蟲更有價值。
沒有可重現輸入
保存代表性 HTML 樣本用於解析器測試。網站結構變化後,可以對比新舊標記。
最終檢查清單
重複運行爬蟲前確認:
- 你被允許採集目標資料。
- 已檢查
robots.txt和網站條款。 - 爬蟲有明確身份標識。
- 已配置限速和訪問間隔。
- 解析器測試覆蓋代表性頁面。
- 資料校驗能捕獲空記錄和格式錯誤。
- 增量採集避免不必要請求。
- 日誌和指標能暴露失敗。
- 個人或敏感資料按適用規則處理。
可靠的資料採集主要靠工程紀律:只採集允許採集的資料,溫和請求,驗證所有結果,並在任務運行後持續觀察。
本站提供瀏覽器本地工具,免註冊即可試用 →