llm-scraper:用 Zod schema 把網頁交給 LLM 填欄位
Turn any webpage into structured data using LLMs
秒懂
- 它是什麼?
- 這個 TypeScript 套件把 Playwright 抓到的頁面內容交給 LLM,再依 Zod 或 JSON Schema 吐出結構化物件。它的價值在於把選擇器維護換成 schema 描述,代價是把解析不確定性換成模型不確定性。
- 適合誰用?
- 如果你要抓的是少量、欄位語意複雜、版面常改的頁面,而且能接受每次抽取都付模型費用與延遲,llm-scraper 值得先跑一次 README 的 Hacker News 範例,確認 schema 描述會不會被模型正確理解。若你要的是百萬頁、固定版面的批次抓取,或需要可重現的逐位元組輸出,就不該用它,Playwright 加上手寫選擇器仍然更便宜也更穩定。
- 可以商用嗎?
- 可以。MIT 是寬鬆授權:你可以使用、修改並販售以它為基礎的軟體,只需保留著作權與授權聲明。
- 還在維護嗎?
- 有在維護。儲存庫最近一次提交在 6 天前。
- 用什麼語言寫的?
- 主要是 TypeScript(依據 GitHub 的語言統計)。
以上回答依據專案的 GitHub 資料(最近同步於 2026年9月15日)與我們的分析,不構成法律意見。
開源專案深度解析
它解決的是選擇器維護,不是抓取本身
傳統爬蟲的痛點不在於把 HTML 抓下來,Playwright 已經處理得很好,痛點在於從 HTML 撈出欄位的那段程式碼。電商把價格從 span.price 改成 div[data-price],你的解析器就整批回傳 undefined,而且通常要等到下游發現資料是空的才知道。llm-scraper 的作法是把這段選擇器換成一份 schema 與一句自然語言描述,讓模型自己去頁面裡找對應內容。README 的範例把這個差異講得很清楚:schema 裡寫 z.array(...).length(5).describe('Top 5 stories on Hacker News'),而不是寫第幾層 div。它的目標讀者是需要抽取欄位語意複雜、版面又會變動的頁面的人,例如把新聞列表、商品規格、財報表格轉成 JSON 的資料工程師。反過來說,如果你抓的是自家後台、DOM 結構由你控制,這個套件只是在中間多插一層昂貴且不確定的推理。
六種格式化模式決定了模型看到什麼
這個套件不是把原始 HTML 直接丟給模型,中間有一層內容處理,而 format 參數就是這層的開關。README 列出六種:html 載入前處理過的 HTML、raw_html 不做處理、markdown、text 使用 Mozilla 的 Readability.js 抽取正文、image 載入截圖且僅限多模態模型、custom 用自訂函式提供內容。這個設計選擇值得注意,因為它把「抓什麼」與「怎麼呈現給模型」拆開了。text 模式走 Readability.js,意味著它會丟掉導覽列、側欄與頁尾,對於文章型頁面能省下大量 token,但對於欄位藏在表格或屬性的頁面,Readability 可能把你要的東西一起清掉。image 模式則是完全不同的成本結構,截圖的 token 消耗與解析度直接掛鉤。範例用的是 format: 'html',這是預設的折衷:保留結構標籤,但已經過處理。實務上第一個該做的實驗,就是同一頁在 html 與 text 下模型輸出的差異,這比調 prompt 更早決定成敗。
安裝與最小可跑路徑
安裝指令在 README 寫得很直接:npm i zod playwright llm-scraper。接著要另外裝模型供應商的 AI SDK 套件,OpenAI 是 npm i @ai-sdk/openai,Anthropic 是 npm i @ai-sdk/anthropic,Google 是 npm i @ai-sdk/google。Groq 走的是 createOpenAI 搭配 baseURL: 'https://api.groq.com/openai/v1' 與 apiKey: process.env.GROQ_API_KEY,Ollama 則需要 npm i ollama-ai-provider-v2。初始化只有兩行:const scraper = new LLMScraper(llm),其中 llm 來自 openai('gpt-4o') 這類呼叫。實際執行是 scraper.run(page, Output.object({ schema }), { format: 'html' }),回傳值解構出 data。注意 Output 是從 'ai' 這個套件匯入的,不是從 llm-scraper,這對照出 2.0 版把底層換成 Vercel AI SDK 6 的改動。README 沒有提供任何 API key 的讀取慣例,範例直接寫 openai('gpt-4o'),實務上你得自己確認供應商 SDK 是從環境變數還是別處取金鑰。
stream 與 generate 是兩種不同的省錢方式
把 run 換成 stream,回傳的 stream 可以 for await 逐塊取出部分物件,README 的寫法是 for await (const data of stream) { console.log(data.top) }。串流不會讓模型跑得比較快,它改變的是你多快看到第一個可用欄位,對前端即時顯示有幫助,對批次寫入資料庫沒有意義。generate 走的是完全相反的路線:它讓模型產生一段可重用的 Playwright 腳本,回傳 code,你再自己 page.evaluate(code) 執行,最後用 schema.parse(result) 驗證。README 的註解寫得很白,generate 是「generate code and run it on the page」。這代表你可以只付一次模型費用,之後重複執行產生的腳本。但它同時把風險轉移了:產生的腳本一旦寫死選擇器,頁面改版時它就跟手寫爬蟲一樣會壞,而且壞掉的方式更難察覺,因為你已經不再呼叫模型。要選哪一條取決於頁面變動頻率,不是取決於哪個聽起來比較聰明。
schema 描述是提示詞,也是它最脆弱的地方
這個套件把 Zod schema 同時當成型別定義與提示詞。範例裡的 .describe('Top 5 stories on Hacker News') 不是註解,它會被送進模型。這帶來一個不明顯的耦合:你的 TypeScript 型別設計會直接影響抽取準確度。z.number() 要求模型把 points 轉成數字,z.string() 要求 commentsURL 是完整網址,這些在模型眼中都是額外推理步驟,而每一步都可能出錯。範例還用了 .length(5),這在 Zod 層是可驗證的約束,但 README 沒有說明當模型只找到三則時會發生什麼:是丟出驗證錯誤、重試、還是回傳部分結果。這是文件沒有交代清楚的地方,也是採用前必須自己確認的行為。另一個現實限制是成本與延遲隨頁面大小線性成長,raw_html 模式把整份文件送進去,token 帳單會很難看。若你的目標是每天數十萬頁的價格監控,這個架構的單位成本基本上不可能贏過寫死的 CSS 選擇器。
與直接用 Playwright 加選擇器的差別
最直接的替代方案不是另一個 LLM 爬蟲框架,而是 Playwright 本身加上手寫選擇器,這也是 llm-scraper 底層在做的事。兩者的差別在失敗模式。手寫選擇器失敗時是明確的:找不到元素,回傳 null,你能寫測試斷言。LLM 抽取失敗時是沉默的:模型可能把某個標題的點數猜成相鄰那則的數字,格式完全合法,schema 驗證通過,錯誤一路流進你的資料庫。要讓 LLM 抽取達到可接受的可靠度,通常得加上交叉驗證,例如同一頁跑兩次比對結果,或對關鍵欄位做規則檢查,而這些工在 README 裡完全沒有著墨。另一條路是改用供應商內建的結構化輸出功能,但那會把你綁在單一模型上,而這個套件透過 Vercel AI SDK 至少讓你能在 OpenAI、Anthropic、Google、Groq、Ollama 之間換。選 LLM 路線的理由應該是「選擇器寫不出來或不值得寫」,不是「LLM 比較新」。
授權、維護與版本升級的代價
專案採 MIT 授權,這對商業使用相對寬鬆,但這只是我對授權條款的描述,不構成法律意見,實際條文與你的使用情境該由法務確認。真正需要注意的是維護節奏與依賴鏈。2.0 版把底層換成 Vercel AI SDK 6,README 用 IMPORTANT 標註這件事,說明這是一次會影響既有程式碼的更新:Output 物件、模型初始化方式都跟著 AI SDK 走。這意味著你的升級週期會被兩個上游綁住,一個是 llm-scraper 本身,另一個是 AI SDK 與各供應商套件。供應商套件換模型名稱(例如 claude-3-5-sonnet-20240620 這種帶日期的識別碼)時,你要跟著改。倉庫沒有檢索到正式 release,這讓「這次更新改了什麼」只能從 README 與 commit 推斷,對需要版本鎖定的團隊是個缺點。若你要放進生產環境,先在 package.json 鎖定版本,並把模型名稱抽成設定值,會比散落在程式碼各處好處理。
編輯結論
如果你要抓的是少量、欄位語意複雜、版面常改的頁面,而且能接受每次抽取都付模型費用與延遲,llm-scraper 值得先跑一次 README 的 Hacker News 範例,確認 schema 描述會不會被模型正確理解。若你要的是百萬頁、固定版面的批次抓取,或需要可重現的逐位元組輸出,就不該用它,Playwright 加上手寫選擇器仍然更便宜也更穩定。採用前先驗證三件事:目標頁面在 format: 'html' 與 format: 'text' 下模型輸出的差異、schema 中 .length(5) 這類約束是否真的被遵守、以及 generate 產生的程式碼在頁面改版後是否仍可執行。
社群筆記