網路爬蟲實戰:從入門到穩定運作的最佳實踐
前言
網路爬蟲是資料工程中最基礎也最關鍵的一環。不論是蒐集租屋資訊、遊樂園等待時間,還是市場價格數據,爬蟲都是取得第一手資料的核心工具。
但在實際開發中,寫出一個「能跑」的爬蟲很簡單,寫出一個「穩定運作」的爬蟲卻需要考慮很多細節。這篇文章整理了我們在多個專案中累積的實戰經驗。
架構設計
分離蒐集與解析
一個常見的錯誤是把 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 解析,更需要在架構設計、錯誤處理、資料儲存和運維監控上下功夫。希望這些經驗能幫助到正在開發爬蟲的你。