Crawlee 之所以實用,在於它的爬取編排同時支援 HTTP 解析與瀏覽器執行。因此,同一個 URL 會因為選用的爬蟲與就緒條件不同,而產生完全不同的結果。
在公開的 Quotes to Scrape JS 頁面上,CheerioCrawler 在等待 .quote 後找到 0 筆目標 quotes,而 PlaywrightCrawler 則找到了 10 筆。整體爬取流程看起來相似,但這絕不是只要把 class 名稱改一行就能完成的替換:Cheerio 的 handler 用的是 $,Playwright 的 handler 則用 page、明確等待,以及瀏覽器端擷取。
Crawlee 到底是什麼
Crawlee(apify/crawlee 專案,版本 3.17.0)是一個適用於 Node.js 與 TypeScript 的網頁爬取與瀏覽器自動化函式庫。它支援以 Cheerio 或 JSDOM 為基礎的 HTTP 爬取,也支援以 Playwright 或 Puppeteer 為基礎的瀏覽器爬取。這個專案採用 Apache-2.0 授權;若你要將其用於發佈,請確認相關的聲明與標註義務。
真正該掌握的心智模型,是把「抓到頁面」和「讀取頁面」分開看。前者會下載原始 HTML,且不會執行 JavaScript;後者會啟動 Chromium,並能執行頁面腳本,但仍然需要合適的就緒條件,而且可能漏掉需要互動才會出現、延遲載入、shadow DOM、API 失敗,或是被 bot 阻擋的內容。Crawlee 在這兩條路徑上提供的是對應的生命週期概念,而不是可互換的 DOM 原語。
在你寫下第一個 selector 之前,先理解這一點非常值得,因為你選的是哪一種引擎,會決定你的爬蟲在某個網站上到底能拿到資料,還是什麼都拿不到。
主要特性:兩種引擎,一套 API

CheerioCrawler 會抓取 HTML 並用 Cheerio 解析;PlaywrightCrawler 則會驅動 Chromium,還能截圖。兩者都使用 requestHandler、都提供 run(),也共享像是隊列與連結探索這類爬取概念。它們的 handler context 不同:本次測試中的 Cheerio 路徑是透過 $ 擷取資料,而瀏覽器路徑則使用 page、waitForSelector 與 $$eval。隊列與生命週期的結構可以維持熟悉,但擷取程式碼往往需要適配或重寫。
在這些表層之下,Crawlee 提供了真正爬取作業需要的基礎設施。RequestQueue 會管理待訪問 URL 的前沿、去重,並追蹤哪些已完成。enqueueLinks 可以探索並加入新的 URL(搭配 selector 與同主機過濾),讓爬取能自行擴展。Dataset 則負責收集你抓到的資料,方便匯出。預設情況下,Crawlee 會把這些資料持久化到磁碟上的本機 storage/ 目錄——這對於續跑很方便,但第一次在專案裡看到一個你沒特別要求的 storage/ 資料夾時,也會有點惱人(我的測試環境把它導向暫存目錄,並關閉持久化,以保持測試乾淨)。
這些元件單看都不算特別。重點在於它們在兩種引擎之間是共用的,所以無論你是透過 HTTP 爬取,還是透過瀏覽器爬取,隊列、連結探索與 Dataset 的行為都一致。你只需要學一套 API,就能得到兩種抓取策略。
安裝:瀏覽器是另外裝的
本次測試的安裝流程,在套件安裝完成後並沒有包含 Chromium 可執行檔。

對我來說,npm install crawlee playwright 安裝得很順利——85 個套件、0 個漏洞,沒有任何戲劇性情節。如果你只做到這一步,然後執行 CheerioCrawler,一切都會正常,因為 HTTP 爬取不需要瀏覽器。
在這個環境裡,Chromium 必須另外透過 npx playwright install chromium 安裝;否則 PlaywrightCrawler 無法啟動。觀察到的瀏覽器 payload 大約是 82 MiB,但原始筆記沒有保留這個數字到底是傳輸大小還是磁碟佔用。這是特定機器上的安裝觀察,不是固定的產品屬性。文件路徑與套件行為都可能變動,因此本文不主張這個缺漏在所有情況下都成立,也不表示它永遠沒有文件說明。
本次測試可把安裝流程理解為兩步:先安裝 Node 套件,再安裝 Playwright 路徑所使用的瀏覽器。實際部署時,請重新確認你所使用版本與平台對應的 Crawlee 與 Playwright 安裝說明。
實測:同一個頁面,兩種截然不同的答案

核心測試把同一個由 JavaScript 渲染的 fixture 交給兩種爬蟲處理。URL 與目標欄位相同,但擷取原語不同。
在本機 fixture 上,CheerioCrawler 回傳 0 個目標卡片,因為它們根本不在原始 HTML 裡。PlaywrightCrawler 則先等待 #dynamic-products article.product-card,之後成功回傳全部 8 個預期卡片,並且截了圖。這個結果只能證明:在該等待條件之後,指定欄位對於這個 fixture 是完整的;它不代表瀏覽器能看見所有可能的頁面狀態。原始檔案與 截圖 都放在 benchmark repo 裡。

在公開的 Quotes to Scrape JS 頁面 上,CheerioCrawler 找到 0 筆目標 quotes,而 PlaywrightCrawler 則在等待 .quote 後擷取到 10 筆。這再次證明了 HTTP 與瀏覽器之間的邊界,同時也說明兩邊使用的爬蟲 class、handler context、等待條件與擷取原語都不同。
真正值得採納的結論更窄:先在 HTTP 路徑上驗證必要欄位,如果原始回應不包含它們,再升級到瀏覽器爬蟲。瀏覽器 handler 也必須等待與那些欄位直接相關的條件。
HTTP 路徑在受控的靜態目錄與文章 fixture 上都產出了預期結果,從直接 JSON 回應中也成功解析出全部 8 個預期項目,還走訪了一個 11 頁的有界圖,並把一個 500 回應導向 failedRequestHandler。這些都是各自獨立的能力檢查,而不是一個單一的準確率分數。對公開的 Books to Scrape 頁面來說,所設定的 selector 也成功抓到 20 個產品,作為 smoke test。
| 測試 | 引擎 | 結果 |
|---|---|---|
| 靜態擷取:目錄 + 分頁 | CheerioCrawler | 12/12 預期產品 |
| 文章擷取 | CheerioCrawler | 標題 + 3/3 段落 |
| 傳輸:直接 JSON 回應 | CheerioCrawler | 8/8 預期產品 |
| 遍歷:內部連結圖 | CheerioCrawler | 11 頁,深度 {0:1, 1:3, 2:7} |
| 錯誤路由:HTTP 500 | CheerioCrawler | 狀態進入失敗處理器 |
| 渲染:本機 fixture | CheerioCrawler | 原始 HTML 中 0 個目標卡片 |
| 渲染:本機 fixture | PlaywrightCrawler | 等待目標 selector 後得到 8/8 |
| 渲染:Quotes JS | CheerioCrawler | 原始 HTML 中 0 筆目標 quotes |
| 渲染:Quotes JS | PlaywrightCrawler | 等待目標 selector 後得到 10 筆 |
完整的時間數據與逐項測試結果可見於 results/crawlee-test-summary.json。
接下來要坦白說明一些限制,因為單機、單次執行的結果本來就有侷限,我也不想假裝沒有。這些是執行時間,不是正式 benchmark——只有一台機器、每項只跑一次,所以瀏覽器路徑較高的單頁成本,請把它理解為「明顯比不到一秒的 Cheerio 執行慢」,而不是一個正式發表的數字。另外,這次我沒有測的項目還有很多:代理輪換、session pool、百頁到千頁等大規模執行、RequestQueue 的持久化與當機後續跑、Puppeteer 引擎,以及 Dataset / KeyValueStore 的匯出體驗(我這裡是手動寫匯出)。我可以對兩種引擎的架構差異與 fixture 級準確性背書,但無法對規模化或反封鎖行為背書,所以我也不會那樣說。
哪些是共通的,哪些必須改

共通的部分是爬取編排。兩種 crawler class 都接受 requestHandler,也都提供 run()。隊列、request metadata、連結探索、失敗鉤子與儲存概念,都可以圍繞任一執行路徑用一致的方式組織。這能減少團隊在某個目標需要瀏覽器時,必須重新學習整套基礎設施的成本。
但頁面存取的介面並不共通。CheerioCrawler 的 handler 會收到像 $ 這樣的 Cheerio 風格存取方式,不需要瀏覽器也能處理 response body。這次測試中的 PlaywrightCrawler handler 則收到 page;它會等待 selector,並在瀏覽器 DOM 上執行評估。即使兩個 handler 最後輸出同樣的 record schema,它們也是透過不同 API 抵達結果。可重複使用的 adapter 或許能隱藏其中一部分差異,但本次測試並沒有實作或示範這樣的東西。
這個區別對估時非常重要。更換 crawler class 可能保留隊列、Dataset 與 URL 規則,但 selector、就緒檢查、截圖、互動步驟與錯誤處理仍然可能改變。因此本文把「共用的爬取基礎設施」視為經過驗證的優點,而把「一行就能遷移」視為沒有被支援的說法。
實務上的引擎選擇流程
如果回傳的 HTML 或直接 JSON 回應裡已經包含所需欄位,先走 HTTP 路徑。定義好完整性契約——例如必備 key、最低項目數,或目標 selector——若不符合就明確失敗。空陣列不等於頁面沒有資料;在這兩個 JavaScript 案例裡,空陣列只代表所選的表示形式沒有目標元素。
| 目標條件 | 先用 | 何時升級 |
|---|---|---|
| 必要欄位已存在於回傳 HTML 中 | CheerioCrawler | 必要的 selector 或欄位不存在 |
| 可重現的 JSON 回應已包含資料 | CheerioCrawler | 請求依賴只能在瀏覽器中建立的狀態 |
| 頁面會在執行後插入目標元素 | PlaywrightCrawler | 不適用;請定義針對該目標的就緒條件 |
| 不確定目標是哪一種 | 先用 HTTP 並驗證完整性 | 驗證失敗且回傳型別化的「表示不完整」結果 |
當執行是必要條件時,再把這個型別化失敗交給瀏覽器 handler。這次測試中,本機頁面等待的是 #dynamic-products article.product-card,而公開 quotes 頁面等待的是 .quote。這些條件都是擷取契約的一部分。單純的 load event 無法證明應用資料已經到位,而這次測試也不支持任何通用的等待規則。
升級之後,即使 DOM 原語不同,也要維持輸出 schema 穩定。記錄是哪個引擎產生了結果、哪個就緒條件通過、以及必要欄位驗證是否成功。這樣 HTTP 轉瀏覽器的 fallback 才會是可觀察的,而不是把缺欄位悄悄當成可接受資料。
最後,把瀏覽器安裝與運行成本視為部署輸入。約 82 MiB 的觀察值只適合作為本機量級參考;在你的環境中,請針對實際的瀏覽器 build、平台、快取行為與映像影響進行量測。代理輪換、session、持久化、當機恢復與持續併發,仍然需要各自的測試,才能讓這個 fixture 的結果對 production 級選型有參考價值。
優點與缺點
優點:
- HTTP 與瀏覽器爬蟲共享生命週期概念,同時保留各自引擎專用的擷取 context。
- 對靜態目錄、文章與 JSON API 的 HTTP 擷取準確度達到 1.0。
- 兩種引擎共用基礎設施:
RequestQueue、具深度控制的enqueueLinks、Dataset。 - 瀏覽器路徑能執行 fixture 腳本,並在兩個 JS 渲染測試中找回全部預期項目。
- 錯誤處理乾淨——HTTP 500 會被正常導向處理器,不會讓程式崩潰。
- Apache-2.0 授權;下游使用者應檢視相關的聲明與標註義務。
缺點:
- 在測試環境中,瀏覽器引擎需要另外安裝 Chromium;若沒有它,
PlaywrightCrawler無法啟動。 - HTTP 路徑無法呈現原始 HTML 中不存在的目標元素;如果不做完整性驗證,這會看起來像是一個合理的空結果。
- 瀏覽器路徑在本次執行中多了一個瀏覽器 binary,且單頁成本更高;實際大小與時間會因 build 與平台而異。
- 預設執行會在磁碟上留下
storage/目錄。 - 只支援 Node/TypeScript——如果你的技術棧是 Python,這就幫不上忙。
適合誰,又該跳過誰
Crawlee 適合需要在 Node 或 TypeScript 中,於 HTTP 與瀏覽器爬取之間共享隊列與生命週期概念的團隊。一個實際做法是:先嘗試 HTTP crawler、驗證必要欄位,若出現型別化的完整性失敗,再以目標專屬的就緒條件升級到瀏覽器 handler。即使隊列與連結探索的基礎設施是共享的,handler 的 DOM 存取程式碼仍然是引擎專用的。
如果你是 Python 團隊就調整期待,或直接看別的方案(Crawlee 是 Node/TS;雖然有另一個 Python port,但這份評測測的是 Node library),如果你的目標全部都是靜態頁面,反而想要更輕量、單一用途的 HTTP scraper,或者你需要已被驗證的大規模行為——像是代理輪換、session pool、當機後續跑——而這次實測沒有涵蓋,那也不適合直接採用。若你要用 PlaywrightCrawler,請先安裝 Chromium,不然它根本跑不起來。
替代方案,以及 Thunderbit 在哪裡
Crawlee 是開源軟體,需要你自行部署與維護。它沒有按次收費的供應商費用,但瀏覽器運算、頻寬、代理、儲存、可觀測性與工程人力都會形成營運成本。爬蟲選型、瀏覽器 binary、儲存狀態與就緒邏輯,都由你自己負責。
相關評測:scrapy-playwright review。
託管式資料擷取服務則會把資料取得與 schema shaping 的責任轉移給供應商。我們在打造 Thunderbit,但沒有把它拿來跑這些 fixture,所以本文不能提供品質、延遲、功能等價性或成本比較。真正該比較的是:你的團隊要的是 Crawlee 這種進程內的控制權,還是按次服務邊界。
相關 benchmark 評測:完整開源爬蟲比較、Playwright 與 Puppeteer 在同一批頁面上的表現、以及 Scrapy 無瀏覽器 request-replay 評測。
結論
對於想在 HTTP 與瀏覽器執行之間共享爬取編排的 Node 或 TypeScript 團隊來說,Crawlee 是一個很有競爭力的選擇。本次測試的 handler 並不能互換:切到 Playwright 之後,需要 page、目標 selector 等待,以及瀏覽器端擷取。至於代理、session、持久化、續跑與大規模行為,仍然還有待驗證。
試用 Thunderbit 進行網頁資料擷取 Get Started Free
常見問題
Crawlee 的兩種爬蟲到底差在哪?
CheerioCrawler 是透過 HTTP 抓取 HTML,不會執行 JavaScript。PlaywrightCrawler 則會驅動 Chromium,可以執行頁面腳本,也能截圖,但每頁本機成本較高。兩者共享生命週期概念,但 handler context 並不相同:這次測試在 HTTP 路徑用 $,在 Playwright 路徑則用 page、目標 selector 等待與瀏覽器端評估。
我已經安裝 Crawlee,為什麼 PlaywrightCrawler 還是不能跑?
在測試環境中,套件安裝並沒有提供瀏覽器可執行檔。使用 npx playwright install chromium 安裝 Chromium 後,啟動失敗的問題就解決了。觀察到的 payload 約為 82 MiB,但原始測量沒有保留這是傳輸大小還是磁碟大小,所以請在你的平台與 build 上重新量測。
CheerioCrawler 能抓取 JavaScript 渲染的頁面嗎?
它不能執行頁面的 JavaScript。不過,它仍然可以請求客戶端所使用、且可直接存取的 JSON endpoint,像直接回應的 fixture 就是這種情況。當必要資料只會在瀏覽器執行後才出現時,就應該改用瀏覽器 crawler,並搭配與那些欄位相關的就緒條件。
Crawlee 在一般靜態擷取上準嗎? 在受控的 fixtures 上,這些 handler 產出了 12/12 預期的目錄產品、3/3 預期的文章段落,以及 8/8 預期的直接 JSON 項目。這些是 fixture 完整性檢查,不是針對未測試網站的通用準確率分數。
Crawlee 可以商業使用嗎? 它是以 Apache-2.0 授權發布。請在 repository 中確認最新授權內容,並檢查你的發佈所需的聲明與標註義務。
在正式導入前,請測試這個 fixture 沒涵蓋的部分:代表性頁面的重複併發、代理與 session 行為、中斷後的持久化隊列恢復、瀏覽器程序清理,以及失敗情況下的 dataset 匯出。也請保留已確認的瀏覽器版本與安裝路徑。兩種 crawler class 雖然減少了編排差異,但並沒有消除對引擎專屬就緒檢查、資源預算與營運失敗處理的需求。


