AI

Spider 框架拆解:從並發爬取到暫停恢復、自動 Proxy 輪換與即時統計

2026年6月10日
7 分鐘閱讀
Spider 框架拆解:從並發爬取到暫停恢復、自動 Proxy 輪換與即時統計

Day 3:Scrapling Spider 框架拆解 — 從並發到監控的完整指南

在前兩天的 Scrapling 系列文章中,我們已經掌握了 Scrapling 的基礎 API 使用技巧,從如何選取元素、提取資料,到如何透過 Adaptive 與 Fetcher 兩種 Session 模式應對不同的網站場景。然而,當我們需要大規模、有組織地爬取數百甚至數千個頁面時,單純依靠 Session 搭配迴圈的方式很快就會遇到瓶頸:如何管理並發請求?如何在爬蟲中途斷線後優雅恢復?如何在同一套爬蟲中靈活切換不同的 Session 策略?又如何即時掌握爬蟲的運行健康狀態?

這正是 Scrapling Spider 框架要解決的核心問題。Spider 是 Scrapling 提供的一套類 Scrapy 的爬蟲框架,它將爬蟲從一次性腳本提升為可維護、可監控、可恢復的工程化系統。本文將從頭拆解 Spider 的每個核心機制,並透過實戰範例帶你全面掌握這個強大的框架。

一、Scrapy-like API:熟悉的設計,極低的學習門檻

如果你是 Python 爬蟲開發者,幾乎不可能沒有聽過 Scrapy — 這個誕生於 2008 年的爬蟲框架至今仍是 Python 生態系中最受歡迎的爬蟲工具之一。Scrapling Spider 在設計哲學上向 Scrapy 致敬,採用了非常相似的 API 風格,讓曾有 Scrapy 使用經驗的開發者幾乎可以無痛轉移。

1.1 Spider 類別的基本結構

一個典型的 Scrapling Spider 繼承自 Spider 基底類別,並實作三個核心方法:start_requestsparse 以及可選的 callback 處理。這樣的三段式結構與 Scrapy 的設計如出一轍:start_requests 負責生成初始的 Request 物件,parse 負責處理伺服器回傳的 Response,並從中提取資料或產生新的 Request。

讓我們來看一個最簡單的範例:

from scrapling import Spider
from scrapling import Request

class MySpider(Spider):
    def start_requests(self):
        urls = [
            "https://example.com/page1",
            "https://example.com/page2",
            "https://example.com/page3",
        ]
        for url in urls:
            yield Request(url=url, callback=self.parse_page)
    
    async def parse_page(self, response):
        title = response.css("h1::text").get()
        content = response.css("article").text()
        yield {"title": title, "content": content, "url": response.url}

如果你熟悉 Scrapy,你會發現這段程式碼幾乎可以直接在 Scrapy 中執行。最大的差異有兩點:第一,Scrapling 的 parse callback 是 async 函式,這得益於 Scrapling 從底層就基於 asyncio 的非同步架構;第二,Scrapling 使用 response.css() 等更現代化的選擇器語法,而不是 Scrapy 的 response.css() 雖然名稱相同但底層實作完全不同。

1.2 Request / Response 物件模型

Scrapling Spider 的核心資料模型是 Request 與 Response 物件。Request 封裝了目標 URL、HTTP 方法、標頭、Cookie 以及最重要的 callback 函式指標;Response 則封裝了伺服器回傳的內容、狀態碼、原始 URL,以及經過 Scrapling 引擎解析後的 DOM 樹。

Request 物件支援的關鍵參數包括:

  • url:目標頁面的 URL
  • callback:處理該 Request 回傳 Response 的非同步函式
  • method:HTTP 方法,預設為 GET,可指定 POST、PUT 等
  • headers:自訂 HTTP 標頭
  • cookies:自訂 Cookie
  • data:POST 請求的資料內容
  • meta:傳遞自訂資料的字典,可在不同 callback 之間共享
  • priority:請求優先級,用於控制請求處理順序
  • dont_filter:是否跳過去重過濾

這樣熟悉的設計讓 Scrapy 使用者幾乎不需要學習成本就能直接上手。但 Scrapling 並非僅僅模仿 Scrapy,它在核心架構上做了許多現代化的改進,其中最顯著的就是非同步原生的支援與靈活的 Session 管理。

二、並發機制:concurrent_requests 與 per-domain throttle

在實際的爬蟲專案中,我們很少需要逐頁依序請求 — 那樣的速度太慢了。並發(concurrency)是提高爬取效率的關鍵,但同時也必須注意不要對目標伺服器造成過大的負擔,否則很容易被封鎖 IP 或觸發 rate limit。

Scrapling Spider 提供了兩層並發控制機制:全域的 concurrent_requests 和針對單一站點的 per_domain_throttle

2.1 concurrent_requests:全域並發控制

concurrent_requests 參數控制 Spider 同時進行的最大請求數量。舉例來說,當你設定 concurrent_requests=10 時,Spider 最多會同時發出 10 個 HTTP 請求,當其中一個請求完成並進入 parse callback 處理階段後,才會從佇列中取出下一個 Request 來發送。

這個參數的設定需要根據目標伺服器的承載能力以及你的網路頻寬來決定。一般來說:

  • 小型網站或部落格:建議設定 3–5,避免對小型伺服器造成壓力
  • 中型網站:建議設定 10–20,在速度與禮貌之間取得平衡
  • 大型 API 或 CDN 加速站點:建議設定 20–50,但需注意不要觸發 rate limit
  • 內部系統或 API:可設定到 100 以上,前提是確認伺服器能負荷

設定方式非常直覺:

class MySpider(Spider):
    concurrent_requests = 10
    # 其餘設定...

2.2 per-domain throttle:單站流量控制

很多時候,一個爬蟲需要同時爬取多個不同的網站。在這種情況下,全域的 concurrent_requests 控制雖然能限制整體的並發量,卻無法防止某個特定網站被過度頻繁地請求。例如,你可能同時爬取十個網站,但其中一個網站的回應特別慢,這時全域的並發數量可能全部被這個慢速網站佔用,導致其他網站的請求也被阻塞。

per_domain_throttle 就是用來解決這個問題的。它可以讓你針對特定網域設定獨立的並發限制,確保任何單一站點都不會佔用過多的資源:

class MySpider(Spider):
    concurrent_requests = 20
    per_domain_throttle = {
        "example.com": 5,    # 這個網站最多同時 5 個請求
        "api.example.org": 10,  # 這個 API 可以同時 10 個請求
    }

這樣的設計非常適合需要從多個資料來源聚合資料的爬蟲,例如比價網站、新聞聚合器或資料庫同步工具。

2.3 download_delay:避免被封鎖的緩衝

除了並發數量之外,download_delay 參數可以在每個請求之間插入固定的延遲時間,進一步降低對目標伺服器的請求頻率。設定方式同樣直覺:

class MySpider(Spider):
    download_delay = 1.5  # 每個請求之間等待 1.5 秒

需要注意的是,download_delayconcurrent_requests 是協同作用的。當並發數大於 1 時,延遲只作用在同一個並發通道內,而非全域序列化。Scrapling 內部會自動計算合理的延遲調度,確保在不犧牲太多效率的前提下保持禮貌爬取。

Spider 核心架構解析
圖 1:Spider 核心架構 — concurrent_requests、checkpoint 與 per-domain throttle 的協作關係

三、Checkpoint 機制:Ctrl+C 優雅關閉與斷點續爬

在大型爬蟲專案中,最讓人沮喪的莫過於爬蟲跑了幾個小時、已經抓取了上萬筆資料後,因為網路波動、伺服器重啟或意外的 Ctrl+C 而被迫中斷,所有進度化為烏有。Scrapling Spider 的 checkpoint 機制正是為了解決這個痛點而設計的。

3.1 運作原理

Checkpoint 機制的核心概念是:Spider 會定期將目前爬蟲的狀態(包括已完成的 Request、正在處理中的 Request、待處理的 URL 佇列、已提取的資料等)序列化並儲存到指定位置。當爬蟲意外中斷後重新啟動,Spider 會自動載入最近的 checkpoint,從中斷的地方繼續執行,不會重複爬取已經完成的頁面。

啟用 checkpoint 非常簡單,只需要在初始化 Spider 時指定 checkpoint 目錄:

from scrapling import Spider

class MySpider(Spider):
    checkpoint_dir = "./checkpoints/"
    concurrent_requests = 5
    
    def start_requests(self):
        # ...
        pass
    
    async def parse(self, response):
        # ...
        pass

# 正常啟動
spider = MySpider()
spider.run()

# 恢復執行 — 同一個 checkpoint_dir 會自動載入上次的狀態
spider = MySpider()
spider.run()  # 會自動從上次中斷處繼續

3.2 優雅關閉的內部流程

當使用者按下 Ctrl+C 或系統發送 SIGINT 訊號時,Scrapling Spider 不會粗暴地終止程序,而是執行以下優雅關閉流程:

  1. 停止接收新的 Request 進入佇列
  2. 允許正在執行中的 Request 完成其 HTTP 請求與 parse callback
  3. 將所有 pending Request 序列化到 checkpoint 檔案
  4. 將已爬取但尚未輸出的資料寫入 checkpoint
  5. 記錄最後一個成功的 Request 時間戳
  6. 關閉所有網路連接與 Session
  7. 輸出最終的爬蟲統計數據

這樣的設計確保了即使在爬蟲被中斷的情況下,也不會遺失資料或需要從頭開始。

3.3 Checkpoint 的實際應用場景

Checkpoint 機制在以下場景中特別有價值:

  • 大規模資料爬取:需要爬取數十萬甚至上百萬頁面時,單次 session 無法完成所有工作
  • 定時爬蟲:配合 cron job 或排程工具,每次執行時從上次中斷處繼續
  • 不穩定的網路環境:在 VPN 切換、代理不穩或目標網站偶爾回應失敗的情況下,能夠自動恢復
  • 資源受限的環境:在記憶體或時間有限的容器或雲端函式中,分段完成爬取任務

更重要的是,checkpoint 目錄中的檔案是人類可讀的 JSON 格式,你可以直接檢視或編輯它來手動調整爬蟲狀態。例如,移除某個已經爬取過的 URL 讓爬蟲重新爬取它,或手動加入新的目標 URL。

四、多 Session 管理:HTTP 快速通道與 Stealth 隱身通道

Scrapling 最大的特色之一就是對 Session 的靈活管理,而這個特色在 Spider 框架中被發揮到了極致。Spider 允許在同一個爬蟲實例中混用多種 Session 類型,根據不同的頁面需求選擇最合適的通道。

4.1 兩種 Session 類型

Scrapling 提供兩種主要的 Session:

  • FetcherSession:基於純 HTTP 請求的快速通道。它不需要瀏覽器引擎,直接發送 HTTP 請求並解析回應。速度極快、資源消耗低,適用於一般的 REST API、靜態網頁或不需要 JavaScript 渲染的頁面。
  • AsyncStealthySession:基於 Playwright 的隱身瀏覽器通道。它會啟動一個真正的瀏覽器實例(Chromium),能夠執行 JavaScript、處理動態渲染的內容,並內建了多種反偵測機制來繞過 Cloudflare、Akamai 等 WAF 保護。

4.2 在同一個 Spider 中混用 Session

這是最令人興奮的部分:你不需要為不同類型的頁面撰寫兩套爬蟲。在 Scrapling Spider 中,你可以設定多個 Session 並在 Request 層級指定使用哪一個:

from scrapling import Spider, Request, FetcherSession, AsyncStealthySession

class MultiSessionSpider(Spider):
    def __init__(self):
        super().__init__()
        self.fast_session = FetcherSession()
        self.stealth_session = AsyncStealthySession()
    
    def start_requests(self):
        # 一般頁面使用 fast session
        yield Request(
            url="https://example.com/list",
            callback=self.parse_list,
            session="fast"  # 使用 fast_session
        )
        
        # Cloudflare 保護的頁面使用 stealth session
        yield Request(
            url="https://protected-site.com/data",
            callback=self.parse_protected,
            session="stealth"  # 使用 stealth_session
        )
    
    async def parse_list(self, response):
        # 從一般頁面提取資料
        items = response.css(".item a::attr(href)").getall()
        for item_url in items:
            if "protected" in item_url:
                yield Request(
                    url=item_url,
                    callback=self.parse_protected,
                    session="stealth"
                )
            else:
                yield Request(
                    url=item_url,
                    callback=self.parse_detail,
                    session="fast"
                )
    
    async def parse_detail(self, response):
        # 處理一般頁面的資料
        pass
    
    async def parse_protected(self, response):
        # 處理 Cloudflare 保護頁面的資料
        pass

這樣的設計讓爬蟲能夠靈活應對混合型網站。舉例來說,一個電商網站的首頁和商品列表頁可能沒有特別的防護,可以走快速的 FetcherSession;但商品詳情頁或結帳頁面可能受到 Cloudflare 保護,這時可以自動切換到 AsyncStealthySession。同一個爬蟲、同一套 parse 邏輯,無需撰寫兩套程式碼。

4.3 Session 的生命週期管理

Spider 會自動管理每個 Session 的生命週期,包括:

  • 啟用:在 Spider 啟動時初始化所有已註冊的 Session
  • 連線池:維護每個 Session 的連線池,避免重複建立 TCP 連線
  • Cookie 管理:每個 Session 維護獨立的 Cookie jar
  • Proxy 綁定:每個 Session 可以綁定不同的 Proxy 配置
  • 關閉:在 Spider 優雅關閉時,自動關閉所有 Session 並釋放資源

這意味著你不需要手動管理 Session 的開啟與關閉,Spider 框架會幫你處理好所有細節。

混用 Session 實戰技巧
圖 2:同一個爬蟲內混用 FetcherSession 與 AsyncStealthySession 的架構示意

五、Proxy Rotator:自動代理輪換機制

在商業爬蟲或大規模資料採集中,代理輪換是必不可少的環節。Scrapling Spider 內建了 ProxyRotator 模組,支援自訂的代理輪換策略。

5.1 基本配置

ProxyRotator 可以接受多種格式的代理列表:

from scrapling import Spider, ProxyRotator

class ProxiedSpider(Spider):
    proxy_rotator = ProxyRotator(
        proxies=[
            "http://proxy1.example.com:8080",
            "http://proxy2.example.com:8080",
            "socks5://proxy3.example.com:1080",
        ],
        strategy="round_robin",  # 輪換策略
        max_failures=3,          # 連續失敗 3 次後移除該代理
        check_url="http://httpbin.org/ip",  # 健康檢查 URL
        check_interval=60,       # 每 60 秒檢查一次代理健康狀態
    )

5.2 輪換策略

Scrapling 支援多種代理輪換策略,你可以根據需求選擇最合適的方式:

  • round_robin:依序輪換,每個請求使用不同的代理。這是最基本的策略,適合大多數場景
  • random:隨機選取代理,適合需要更高隨機性的場景
  • least_used:優先使用使用次數最少的代理,適合代理品質不均勻的情況
  • latency_based:優先使用延遲最低的代理,適合對速度要求較高的場景
  • custom:自訂策略,你可以傳入一個函式來決定每次要使用哪個代理

自訂策略的實作方式如下:

def custom_proxy_selector(proxies, stats):
    """自訂代理選擇策略:優先選擇地理位置最近的代理"""
    return min(proxies, key=lambda p: p.latency)

proxy_rotator = ProxyRotator(
    proxies=proxy_list,
    strategy=custom_proxy_selector,
)

5.3 代理健康管理

ProxyRotator 不僅僅是輪換代理,它還內建了完整的健康管理系統:

  • 自動檢測:定期對所有代理發送健康檢查請求,確保代理仍然有效
  • 失敗計數:記錄每個代理的連續失敗次數,超過門檻後自動移除
  • 自動恢復:被移除的代理會在指定時間後重新加入可用池進行檢測
  • 統計資訊:提供每個代理的成功率、平均延遲、流量使用等統計數據

這樣的設計讓代理管理完全自動化,開發者只需要提供代理列表即可,其餘的一切都由框架處理。

六、Streaming 模式:即時監控爬蟲健康狀態

當爬蟲開始大規模運作時,你會需要知道它目前的狀態:已經爬取了多少頁面?消耗了多少時間?有多少請求失敗了?記憶體使用量是否正常?Scrapling Spider 的 streaming 模式提供了即時的監控能力。

6.1 啟用 Streaming 模式

Streaming 模式可以透過設定 stream=True 來啟用:

spider = MySpider()
spider.run(stream=True)

當 streaming 模式啟用後,Spider 會即時輸出結構化的狀態資訊,包括:

  • scraped:目前已成功爬取的頁面數量
  • failed:請求失敗的頁面數量
  • pending:仍在佇列中等待處理的 Request 數量
  • active:正在進行中的請求數量
  • qps:每秒查詢數量(QPS),反映當前爬取速度
  • memory_mb:當前記憶體使用量(MB)
  • elapsed:從啟動到現在經過的時間
  • eta:根據當前速度預估的完成時間

6.2 自訂監控回呼

除了即時的狀態輸出,Streaming 模式還允許你註冊自訂的監控回呼函式,將狀態資訊整合到你的監控系統中:

def my_monitor(stats):
    """將爬蟲狀態發送到 Prometheus 或自訂的監控儀表板"""
    if stats["failed"] > 100:
        send_alert(f"爬蟲失敗次數超過 100:{stats}")
    log_to_database(stats)

spider = MySpider()
spider.run(stream=True, monitor_callback=my_monitor)

這使得 Scrapling Spider 可以輕鬆整合進現有的基礎設施監控棧中,無論是 Prometheus + Grafana、Datadog、還是自訂的儀表板系統。

6.3 自動調速

Streaming 模式的另一個進階功能是自動調速。透過監控爬蟲的即時狀態,Spider 可以自動調整並發數量,在發現失敗率升高時自動減速,在一切順利時自動加速:

class AdaptiveSpider(Spider):
    auto_throttle = True
    auto_throttle_max_delay = 5.0  # 最大延遲 5 秒
    auto_throttle_target_concurrency = 10.0  # 目標並發量

這個功能在面對不穩定的目標網站時特別有用。例如,某些網站可能在離峰時段允許較高的請求頻率,但在尖峰時段會開始拒絕請求。Auto-throttle 可以根據伺服器的實際回應自動調整請求頻率,確保爬蟲能夠以最高效率持續運作而不被封鎖。

七、Development Mode:快取頁面加速開發迭代

在開發爬蟲的過程中,最耗時的部分往往不是撰寫選擇器或解析邏輯,而是等待 HTTP 請求完成。每次修改一段 parse 邏輯、想測試新的 CSS 選擇器或 XPath 表達式,都需要重新發送一次 HTTP 請求,等待網路往返。如果目標網站速度較慢或有反爬限制,這個過程會變得極為痛苦。

Scrapling Spider 的 development mode 正是為了解決這個開發體驗問題而設計的。

7.1 啟用 Development Mode

spider = MySpider()
spider.run(dev_mode=True, cache_dir="./.spider_cache/")

7.2 Development Mode 的運作方式

當 dev mode 啟用時,Spider 的行為會發生以下變化:

  1. 首次請求:Spider 正常發送 HTTP 請求,但會在收到回應後將完整的 Response(包括 status code、headers、body)序列化並儲存到本機快取目錄
  2. 後續請求:當 Spider 再次遇到相同的 URL 時,它不會發送真實的 HTTP 請求,而是直接從快取中載入之前儲存的 Response
  3. 快取鍵:預設使用 URL 作為快取鍵,但也可以自訂快取鍵的組成方式,例如包含請求方法、headers 或 POST data
  4. 快取有效期:可設定快取的過期時間,預設為永久有效,直到手動清除

這意味著在你的開發流程中,你只需要在第一次執行時等待網路請求,之後每一次的修改與測試都是瞬間完成的。這對於爬蟲開發的效率提升是革命性的。

7.3 實際開發流程

一個典型的 Dev Mode 開發流程如下:

  1. 撰寫 Spider 的基本架構,包括 start_requests 和初步的 parse 邏輯
  2. 以 dev mode 執行 Spider,確認所有 Request 都能正常發送與接收
  3. 調整 parse 邏輯中的選擇器或資料提取方式
  4. 再次以 dev mode 執行 Spider — 這次不需要等待網路請求,Response 直接從快取讀取
  5. 重複步驟 3–4 直到資料提取邏輯完全正確
  6. 關閉 dev mode,以 production 模式正式執行爬蟲

這樣的流程大幅縮短了開發迭代週期。根據實際使用經驗,Dev Mode 可以將爬蟲開發時間縮短 50%–70%,特別是在選擇器除錯和資料清洗邏輯開發階段。

八、Export Hooks:直接輸出 JSON/CSV 到資料管線

爬蟲最終的目的是產生有價值的資料。Scrapling Spider 內建了 export hooks,讓你可以直接將爬取結果輸出到各種格式和目的地,無需手動編寫匯出程式碼。

8.1 內建 Export 格式

Spider 支援多種輸出格式:

  • JSON:標準 JSON 陣列格式,每個 item 為一個 JSON 物件
  • JSON Lines (JSONL):每行一個 JSON 物件,適合大規模資料串流處理
  • CSV:逗號分隔值,適合 Excel 或 Google Sheets 匯入
  • Parquet:列式儲存格式,適合大數據分析

設定方式極為簡單:

spider = MySpider()
spider.run(
    export_format="json",
    export_path="./output/scraped_data.json"
)

8.2 自訂 Export Hook

如果你的爬蟲需要將資料匯入到資料庫、傳送到 API 或寫入到自訂的儲存系統中,你可以註冊自訂的 export hook:

async def my_custom_export(item):
    """將每個爬取到的 item 寫入 PostgreSQL 資料庫"""
    await db.execute(
        "INSERT INTO scraped_data (title, content, url, scraped_at) VALUES ($1, $2, $3, $4)",
        item["title"], item["content"], item["url"], datetime.now()
    )

spider = MySpider()
spider.run(
    export_hooks=[my_custom_export],
    export_batch_size=100,  # 批次寫入,每 100 筆提交一次
)

Export hooks 是非同步的,這意味著匯出操作不會阻塞爬蟲的主流程。即使匯出到外部系統的速度較慢,Spider 仍然可以繼續爬取新的頁面,匯出操作在背景非同步執行。

8.3 資料管線整合

Scrapling Spider 的 export 系統設計為可擴展的資料管線架構。你可以串聯多個 export hook,將資料同時寫入多個目的地:

spider = MySpider()
spider.run(
    export_format="jsonl",
    export_path="./backup/scraped_data.jsonl",
    export_hooks=[
        write_to_postgres,
        send_to_elasticsearch,
        notify_webhook,
    ],
)

這樣的設計讓 Scrapling Spider 不僅是一個爬蟲框架,更是一個完整的資料管線工具。

Spider 的工程化價值
圖 3:Scrapling Spider 的完整工程化架構 — 從爬取到匯出的一條龍流程

九、實戰範例:完整的 Spider 應用

讓我們將以上所有概念整合到一個完整的實戰範例中。以下是一個多 Session、支援斷點續爬、具備代理輪換與即時監控的完整 Spider:

import asyncio
from scrapling import Spider, Request, ProxyRotator
from scrapling import FetcherSession, AsyncStealthySession

class NewsAggregatorSpider(Spider):
    """新聞聚合爬蟲 — 整合所有 Spider 進階功能"""
    
    # 並發設定
    concurrent_requests = 15
    download_delay = 0.5
    per_domain_throttle = {
        "news.example.com": 5,
        "api.news-source.org": 10,
    }
    
    # Checkpoint 設定
    checkpoint_dir = "./checkpoints/news_aggregator/"
    
    # 代理設定
    proxy_rotator = ProxyRotator(
        proxies=[
            "http://proxy1.pool:8080",
            "http://proxy2.pool:8080",
        ],
        strategy="round_robin",
        max_failures=3,
    )
    
    def __init__(self):
        super().__init__()
        self.fast_session = FetcherSession()
        self.stealth_session = AsyncStealthySession()
    
    def start_requests(self):
        # 一般新聞列表頁 — 走快速通道
        categories = ["tech", "finance", "science"]
        for category in categories:
            yield Request(
                url=f"https://news.example.com/{category}",
                callback=self.parse_category,
                session="fast",
            )
        
        # 受保護的 API — 走 stealth 通道
        yield Request(
            url="https://protected-api.source.com/latest",
            callback=self.parse_protected_api,
            session="stealth",
        )
    
    async def parse_category(self, response):
        """處理新聞列表頁"""
        articles = response.css(".article-card")
        for article in articles:
            yield {
                "title": article.css("h2::text").get(),
                "summary": article.css(".summary::text").get(),
                "url": article.css("a::attr(href)").get(),
                "category": response.url.split("/")[-1],
                "source": "news.example.com",
            }
        
        # 分頁處理
        next_page = response.css(".pagination .next::attr(href)").get()
        if next_page:
            yield Request(
                url=response.urljoin(next_page),
                callback=self.parse_category,
                session="fast",
            )
    
    async def parse_protected_api(self, response):
        """處理受保護的 API 回應"""
        data = response.json()
        for item in data["articles"]:
            yield {
                "title": item["title"],
                "content": item["body"],
                "url": item["url"],
                "category": item["category"],
                "source": "protected-api.source.com",
            }
    
    def monitor_callback(self, stats):
        """自訂監控回呼"""
        print(f"[監控] 已爬取: {stats['scraped']}, "
              f"失敗: {stats['failed']}, "
              f"QPS: {stats['qps']:.2f}, "
              f"記憶體: {stats['memory_mb']:.1f}MB")

# 執行爬蟲
async def main():
    spider = NewsAggregatorSpider()
    
    # 開發階段使用 dev mode
    # await spider.run(dev_mode=True, cache_dir="./.cache/")
    
    # 正式執行
    await spider.run(
        stream=True,
        monitor_callback=spider.monitor_callback,
        export_format="jsonl",
        export_path="./output/news_data.jsonl",
    )

if __name__ == "__main__":
    asyncio.run(main())

這個範例爬蟲展示了以下所有功能的整合應用:

  • 並發控制與 per-domain throttle
  • 多 Session 混用(FetcherSession + AsyncStealthySession)
  • Checkpoint 斷點續爬
  • Proxy 自動輪換
  • Streaming 即時監控
  • Export Hook 資料匯出

十、Spider 的工程化價值總結

經過以上深入的分析與實戰範例,我們可以看到 Scrapling Spider 不僅僅是一個爬蟲框架,它更是一個完整的工程化解決方案。以下是 Spider 帶給開發者的核心價值:

10.1 從腳本到系統的升級

大多數爬蟲專案始於一個簡單的 Python 腳本:發送請求、解析回應、儲存資料。但當爬蟲規模增長到需要處理數萬個頁面、需要團隊協作維護、需要整合到 CI/CD 管線中時,腳本的模式就顯得力不從心了。Spider 框架透過以下設計讓爬蟲升級為真正的工程系統:

  • 模組化架構:start_requests、parse callback、export hooks 各司其職,程式碼組織清晰
  • 可配置性:所有行為參數化,透過類別屬性或執行參數即可調整行為,無需修改核心邏輯
  • 可測試性:可以單獨測試每個 parse callback 的邏輯
  • 可擴展性:透過自訂 Session、自訂 Export Hook、自訂 Proxy 策略等方式無限擴展

10.2 生產環境就緒

Scrapling Spider 從設計之初就考慮了生產環境的實際需求:

  • 優雅退出:不會因為網路波動或手動中斷而遺失資料
  • 自動恢復:Checkpoint 機制確保任務可以分段完成
  • 即時監控:Streaming 模式讓你可以隨時掌握爬蟲狀態
  • 資源控制:並發限制、per-domain throttle、記憶體管理,確保爬蟲不會失控
  • 錯誤處理:內建重試機制、錯誤分類處理

10.3 開發效率的提升

Dev Mode 的設計充分體現了 Scrapling 對開發體驗的重視。開發者不再需要忍受漫長的等待來測試每一個細微的修改,這不僅提升了開發速度,更重要的是保持了開發者的心流狀態。配合完整的 export hooks,從開發到上線的流程被大幅簡化。

10.4 適用場景一覽

Scrapling Spider 特別適合以下場景:

  • 大規模電商監控:同時監控數百個電商平台的價格與庫存
  • 新聞與內容聚合:從多個新聞來源定期抓取內容
  • 社交媒體數據採集:爬取社群平台的公開資料
  • 研究與學術資料收集:從學術資料庫、政府開放資料平台收集資料
  • SEO 與網站分析:大規模分析網站結構與內容
  • 資料庫遷移與同步:從舊系統大量遷移資料到新系統

結語:Scrapling Spider 為爬蟲開發樹立了新的標竿

回顧 Scrapling Spider 的核心設計,它巧妙地平衡了三個看似矛盾的需求:強大與簡單、彈性與規範、效率與穩定性。它借鑒了 Scrapy 經過時間驗證的優秀設計,但又在非同步支援、Session 管理、Dev Mode 等方面做出了現代化的創新。

對於有 Scrapy 經驗的開發者來說,Scrapling Spider 幾乎無需學習成本;對於爬蟲新手來說,它清晰的文件與直覺的 API 設計也讓入門門檻降到最低。更重要的是,它將爬蟲開發從「撰寫一次性腳本」的思維模式提升到了「設計可維護工程系統」的高度。

在下一篇文章中,我們將深入探討 Scrapling 的進階實戰技巧,包括如何處理 JavaScript 動態渲染的 SPA 頁面、如何繞過更複雜的反爬機制,以及如何將 Scrapling 與 AI 模型結合實現智能資料提取。敬請期待 Day 4 的精彩內容!

如果你對 Scrapling 有任何問題,歡迎在下方留言討論。如果這篇文章對你有幫助,也請分享給更多需要的朋友!

Related Reading

延伸閱讀