← 返回部落格
·5 min 閱讀

網路爬蟲實戰:從入門到穩定運作的最佳實踐

Python爬蟲資料蒐集技術筆記

前言

網路爬蟲是資料工程中最基礎也最關鍵的一環。不論是蒐集租屋資訊、遊樂園等待時間,還是市場價格數據,爬蟲都是取得第一手資料的核心工具。

但在實際開發中,寫出一個「能跑」的爬蟲很簡單,寫出一個「穩定運作」的爬蟲卻需要考慮很多細節。這篇文章整理了我們在多個專案中累積的實戰經驗。

架構設計

分離蒐集與解析

一個常見的錯誤是把 HTTP 請求和資料解析混在一起。建議將這兩個步驟分開:

# 不建議:蒐集與解析混在一起
def scrape_listing(url):
    response = requests.get(url)
    soup = BeautifulSoup(response.text, "html.parser")
    title = soup.find("h1").text
    price = soup.find(".price").text
    return {"title": title, "price": price}

# 建議:分離蒐集與解析
def fetch_page(url: str) -> str:
    response = requests.get(url, timeout=10)
    response.raise_for_status()
    return response.text

def parse_listing(html: str) -> dict:
    soup = BeautifulSoup(html, "html.parser")
    return {
        "title": soup.find("h1").text.strip(),
        "price": soup.find(".price").text.strip(),
    }

這樣做的好處是:蒐集失敗時可以單獨重試,解析邏輯可以用已儲存的 HTML 離線測試。

使用佇列管理任務

當需要爬取大量頁面時,建議使用佇列(Queue)來管理待爬取的 URL:

from collections import deque

class Crawler:
    def __init__(self):
        self.queue = deque()
        self.visited = set()

    def add_url(self, url: str):
        if url not in self.visited:
            self.queue.append(url)

    def run(self):
        while self.queue:
            url = self.queue.popleft()
            if url in self.visited:
                continue
            self.visited.add(url)
            html = fetch_page(url)
            # 處理 html ...

反爬蟲應對策略

適當的請求間隔

這是最基本也最重要的一點。不要對目標網站發送過於密集的請求:

import time
import random

def polite_fetch(url: str) -> str:
    # 隨機延遲 1~3 秒
    time.sleep(random.uniform(1, 3))
    response = requests.get(url, timeout=10)
    return response.text

設定合理的 User-Agent

許多網站會檢查 User-Agent 標頭。使用合理的瀏覽器 User-Agent 可以避免被直接封鎖:

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
                  "AppleWebKit/537.36 (KHTML, like Gecko) "
                  "Chrome/120.0.0.0 Safari/537.36"
}

response = requests.get(url, headers=HEADERS, timeout=10)

處理 JavaScript 渲染

越來越多的網站使用前端框架渲染內容,傳統的 requests 無法取得完整的頁面。這時可以使用 Playwright:

from playwright.sync_api import sync_playwright

def fetch_dynamic_page(url: str) -> str:
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        page = browser.new_page()
        page.goto(url, wait_until="networkidle")
        content = page.content()
        browser.close()
        return content

錯誤處理與重試機制

網路請求天生就不穩定,必須有完善的錯誤處理:

from tenacity import retry, stop_after_attempt, wait_exponential

@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=2, max=30),
)
def fetch_with_retry(url: str) -> str:
    response = requests.get(url, headers=HEADERS, timeout=10)
    response.raise_for_status()
    return response.text

使用 exponential backoff(指數退避)策略,讓重試的間隔逐漸拉長,避免在目標網站有問題時持續施加壓力。

資料儲存策略

保留原始資料

永遠保留一份原始的 HTML 或 JSON 回應。這樣當解析邏輯需要修改時,不需要重新爬取:

import json
from pathlib import Path

def save_raw(url: str, content: str, output_dir: str = "raw_data"):
    path = Path(output_dir)
    path.mkdir(exist_ok=True)
    filename = hashlib.md5(url.encode()).hexdigest() + ".html"
    (path / filename).write_text(content, encoding="utf-8")

增量更新

不要每次都全量爬取,而是記錄上次爬取的時間或最後一筆資料的 ID,只抓取新增或更新的內容。這不僅減少對目標網站的負擔,也大幅節省處理時間。

排程與監控

使用 cron 或排程服務

生產環境中的爬蟲通常需要定期執行。可以使用系統的 cron job,或是雲端的排程服務(如 Google Cloud Scheduler)。

監控與告警

設定基本的監控機制,在爬蟲出錯時及時通知:

  • 記錄每次執行的成功/失敗筆數
  • 當失敗率超過閾值時發送通知
  • 定期檢查資料的新鮮度

結語

穩定的爬蟲不只是寫好 HTTP 請求和 HTML 解析,更需要在架構設計、錯誤處理、資料儲存和運維監控上下功夫。希望這些經驗能幫助到正在開發爬蟲的你。