Python 網頁資料採集指南:合規、穩定、可維護

编程语言

Python 很適合採集公開網頁資料,但生產級採集不是「發請求、解析 HTML」這麼簡單。真實專案需要權限檢查、禮貌訪問、穩定選擇器、重試邏輯、資料校驗和監控。

這篇文章關注合規、穩定、可維護的資料採集。它不講繞過存取控制、規避反爬、破解驗證碼,或採集登入、付費牆、明確禁止訪問後的資料。如果網站提供 API、資料匯出、Feed 或合作通道,應優先使用官方方式。

參考文件:

先做權限檢查

寫程式前先回答這些問題:

  • 目標資料是否公開,且無需登入即可查看?
  • 網站是否提供 API、網站地圖、RSS 或匯出?
  • robots.txt 是否允許抓取目標路徑?
  • 服務條款是否限制自動存取或資料再利用?
  • 資料是否可能包含個人資訊、版權內容或受監管紀錄?
  • 你的爬蟲是否能標識自己並提供聯絡方式?

如果答案不清楚,不要先寫爬蟲。應先取得許可,或使用官方資料源。

選擇合適工具

頁面類型 推薦方式 原因
靜態 HTML httpxrequests + 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 和網站條款。
  • 爬蟲有明確身份標識。
  • 已配置限速和訪問間隔。
  • 解析器測試覆蓋代表性頁面。
  • 資料校驗能捕獲空記錄和格式錯誤。
  • 增量採集避免不必要請求。
  • 日誌和指標能暴露失敗。
  • 個人或敏感資料按適用規則處理。

可靠的資料採集主要靠工程紀律:只採集允許採集的資料,溫和請求,驗證所有結果,並在任務運行後持續觀察。

本站提供瀏覽器本地工具,免註冊即可試用 →

#Python爬虫#反爬虫#Scrapy#Playwright爬虫#2026#编程语言