在那些能清楚定義「外洩」的測試樣本中,goose3 的 內容 token 精確率達到 1.0000,而且 零 雜訊 token 混入輸出。導航列、廣告、側欄、留言或宣傳字樣,一個字都沒進到結果裡。六個函式庫的對照中,沒有任何其他工具能做到這點。
但它同時也是這組裡 文章召回率最差 的一個——22 個測試樣本整體只有 0.8243,Mozilla Readability 則是 1.0000——因為其中兩個樣本它直接回傳了空字串。
這兩個指標會被同一套評分規則綁在一起:空結果對條件式精確率沒有貢獻,而召回率則會忠實記錄漏抓。
goose3 是什麼
goose3 是 Python 3 版本的延續工具,承接自 Gravity Labs 在 Scala 寫的 Goose,再經過 python-goose 這條演進路線。它是帶 metadata 的文章擷取器,不是單純把文字整包倒出來的工具:你建立一個 Goose,呼叫 extract(),就會拿到一個 Article 物件,裡面大約有二十八個可存取欄位——整理過的文字、標題、作者、發佈日期、主圖、meta description、標籤、連結、推文等等。
官方參考: goose3 官方倉庫。

測試版本:3.1.22,Apache 授權,912 個 GitHub stars,最後一次推送時間為 2026-07-23——在測試當下仍持續維護中。Python 3.14.2。
它的 API 只要兩步,但有一個一定要做的收尾:
from goose3 import Goose
g = Goose()
try:
article = g.extract(raw_html=html)
text = article.cleaned_text
finally:
g.close() # 用完務必明確關閉
這個 close() 很值得特別提醒,因為很容易忘記,而且它不會跳任何警告。這次評測沒有做迴圈壓力測試去量化在跳過關閉時是否會殘留 session、連線或記憶體,所以直接說成「資源洩漏」會比證據本身更強。就這裡的 API 用法來看,請把明確關閉視為它的生命週期要求。
實測後的取捨
六個擷取器、一組有標註的測試樣本、同一個評分器。每個樣本中的每個區塊都標成 article 或 boilerplate,並帶有唯一的識別 token,所以「有沒有抓回這一段」判定的是精準的子字串包含關係,而不是相似度分數。
| Library | Article recall (all 22) | Boilerplate leak | Content-token precision | Contaminating tokens | Produced output |
|---|---|---|---|---|---|
| Readability | 1.0000 | 0.2353 | 0.9109 | 35 | 22/22 |
| trafilatura | 0.9865 | 0.0588 | 0.9411 | 4 | 22/22 |
| newspaper4k | 0.9865 | 0.0000 | 0.9452 | 0 | 22/22 |
| resiliparse | 0.9054 | 0.0588 | 0.9381 | 7 | 22/22 |
| jusText | 0.8378 | 0.4706 | 0.8760 | 74 | 19/22 |
| goose3 | 0.8243 | 0.0000 | 1.0000 | 0 | 20/22 |
sixway-scores.json。召回率以全部 22 個樣本計算;外洩率與精確率則以同時含有 article 與 boilerplate 單元的 11 個樣本計算。
讀表時,精確率欄位一定要搭配最後一欄一起看。這裡的精確率是以有輸出為前提計算的——某個函式庫在樣本上回傳空字串,不會進入分子,也不會進入分母,所以選擇不輸出在這個平均中是沒有成本的。分子是「擷取出的非停用詞 token」和「標註的文章 token」之間的多重集合重疊;分母則是所有擷取出的非停用詞 token。所謂「雜訊 token」更窄:只看和標註的 boilerplate token 的重疊。若多抓了一個既不屬於文章、也不屬於 boilerplate 的 token,精確率會下降,但這個雜訊計數不會增加;重複 token 超出文章多重集合的部分也可能造成同樣效果。這也是為什麼 newspaper4k 可以出現 0 個雜訊 token,精確率卻仍低於 1.0000。goose3 的 1.0000 是在 11 個樣本中的 10 個上評分得到的;Readability、trafilatura、newspaper4k 與 resiliparse 則是 11/11。
不過在這個合成測試集裡,1.0000 仍然很有意義。就那 10 個有評分的樣本來看,goose3 沒有吐出任何標註過的 boilerplate token;同樣的頁面上,Readability 則吐出了 35 個。如果下游是模型在消化這些輸出,這代表在那 10 個樣本中,boilerplate 標籤沒有占用任何 token。這不代表真實網頁上也完全沒有浪費,而且空結果在整條 pipeline 的其他地方仍可能帶來 fallback 或重試成本。
兩次沉默,代表什麼
goose3 有兩個樣本是直接沒輸出。其實其中一個勉強說得通,另一個則是明確的限制。
接近空白的文件。 只有一個 32 字元文章單元的頁面。goose3 放棄了,jusText 也一樣。在這組測試裡,Readability 對 22 個頁面全都有輸出,所以不能拿它來支持 goose3 在這裡沉默的合理性。至於放棄這種超小文件能不能接受,要看呼叫端對「最低內容量」的要求。

整篇文章都由 <li> 元素組成。 六個文章單元,全都不是 <p> 標籤。goose3 直接回傳空字串。
第二個結果讓我特別困惑,因為 goose3 預設設定裡明明有 parse_lists=True。所以我做了三組設定對照一個正常控制樣本的探測;畢竟一次沒結果,不能直接當成對整個函式庫的結論:
| Configuration | List-only page | <p> control |
|---|---|---|
| defaults | 0 chars | 937 chars |
strict=False | 0 chars | 937 chars |
parse_lists=True (explicit) | 0 chars | 937 chars |
三種設定全都 0 字元,但控制樣本三種設定都能回傳 937 字元。所以 parse_lists=True 只是決定清單要不要保留在 goose3 已經找到的文章裡——它並不會讓候選分數器把清單本身當成文章。goose3 的節點評分需要像段落那樣的區塊才能找到正文,而一篇正文全是清單的頁面,根本沒有那種候選區塊。
能被支持的結論比較窄:像這個合成樣本這樣的正文——六個文章單元、全是 <li>、沒有任何 <p> 候選——會回傳空字串。變更記錄、API 參考、食譜、FAQ 頁、比較型文章,這些都很適合作為真實頁面回放的風險樣本,因為它們常常清單很多;但這單一樣本,並不能證明這些頁面類型普遍都會失敗。
空字串只有在呼叫端有檢查「非空輸出」時,才算是機器可偵測。比起表面看起來像文章、其實一個字都不中的內容,它比較容易被攔下;但如果監控只看有沒有丟例外,這仍然是靜默失敗。生產環境的呼叫端需要最小輸出檢查,以及 fallback 或明確的失敗頁面紀錄。
安裝與執行成本
pip install goose3 會拉下 16 個套件、佔用 44.3 MiB,安裝時間大約 6 到 9 秒。全新子程序中的冷啟動 import:2.181 秒。
官方參考: goose3 在 PyPI。
| Library | Packages | site-packages | Cold import | Extraction p50 |
|---|---|---|---|---|
| resiliparse | 5 | 21.0 MiB | 0.015 s | 0.06 ms |
| jusText | 3 | 22.4 MiB | 0.777 s | 0.56 ms |
| goose3 | 16 | 44.3 MiB | 2.181 s | 1.85 ms |
| newspaper4k | 22 | 47.5 MiB | 2.812 s | 2.69 ms |
| trafilatura | 17 | 69.9 MiB | 1.584 s | 0.51 ms |
install-and-import.json。每個函式庫都放在自己獨立的空白 virtualenv 裡,所以不會繼承同層工具的任何殘留體積。
重量居中,速度也居中。它在 Python 3.14.2 上安裝與匯入都順利完成,而這在這個類別裡並不算理所當然。
上線前有三個預設值值得先知道

直接讀出打包內建的 Configuration 物件,而不是只看文件,會發現它有十九個設定。其中有三個特別容易讓人踩坑。
它會主動報上名號。 browser_user_agent 預設是 Goose/3.1.22。如果讓 goose3 自己去抓頁面,你碰到的每台伺服器都會記下這個函式庫名稱和精確版本。這很誠實,但也很像指紋。最好自己明確設定,或者直接抓好 HTML,再把 raw_html 傳進去。
它指向的是 MacPorts 的二進位檔。 imagemagick_convert_path 預設是 /opt/local/bin/convert,imagemagick_identify_path 則是 /opt/local/bin/identify。在我的機器上這兩個都不存在——/opt/local 是 MacPorts 的路徑,多數人其實沒有;Homebrew 則會把二進位檔放在 /opt/homebrew。這個預設在你沒開啟圖片抓取時不會出事(enable_image_fetching 預設是 False,這點很合理),但如果你以為打開後主圖擷取就會正常,這裡會默默失效。
它預設把英文當主場。 target_language 預設是 en,且 use_meta_language=True,所以有頁面自己的語言宣告時它會跟著走,沒有時就回到英文。對英文內容沒問題;如果是其他語言,最好明確設定。
其餘設定都算合理:parser_class 是 lxml,http_timeout 30 秒,strict 開啟,log_level 為 ERROR,parse_headers 和 keep_footnotes 都開著,images_min_bytes 是 4,000。
記憶體,以及壞 HTML 對它的影響
這批評測裡每篇都列為未測的兩件事,現在也量到了。
更完整的壓力測試背景可參考 十個函式庫的記憶體與錯誤 HTML 對照。
峰值駐留記憶體 透過 /usr/bin/time -l 測得,每個欄位都是新程序;import floor 是函式庫載入後閒置的成本,峰值則包含文件本身。
| Library | Runtime | Import floor | 226 KB peak | 10 MB peak | |---|---|--:|--:|--:|---| | html2text | python3.14 | 18.7 | 19.9 | 71.2 | | pyquery | python3.14 | 30.3 | 33.9 | 172.5 | | resiliparse | python3.14 | 20.5 | 25.1 | 225.1 | | markdownify | python3.14 | 23.9 | 28.9 | 278.5 | | goose3 | python3.14 | 44.1 | 52.4 | 398.5 | | cheerio | node22 | 66.8 | 76.5 | 398.5 | | justext | python3.14 | 30.3 | 36.6 | 431.2 | | newspaper4k | python3.14 | 52.6 | 61.8 | 668.5 | | trafilatura | python3.14 | 52.5 | 64.8 | 927.1 | | turndown | node22 | 47.8 | 68.4 | 2947.1 |
memory-results.json。Python 與 Node 的基準彼此不能直接比較;兩者裡面都包含了解譯器。
goose3 的基礎占用是 44.1 MiB,在 10 MB 文件上峰值到 398.5 MiB。上面列出的 Python 函式庫裡,它的 import floor 排名第三高,對冷啟動敏感的部署來說,這點很值得知道。
壞 HTML。 十二份文件各自只壞掉一件事——未閉合標籤、錯誤巢狀的行內元素、帶空格卻沒加引號的屬性、多餘的關閉標籤、根本沒有 <html>、重複屬性、在標籤中間被截斷的文件、錯誤實體、未關閉的 <script>、騙人的 charset 宣告、含有 markup 的註解,以及 600 層巢狀——再加上 兩份同尺寸的良好控制樣本,因為「它什麼都沒回」只有在函式庫對同尺寸的乾淨文件也一樣沉默時,才真能說明它處理錯誤 HTML 的能力。
goose3 在 14 個樣本中 0 次拋例外,10 次回傳空結果,在壞掉的樣本裡只恢復了 2/33 個識別 token(malformed-results.json)。有一個樣本不算在這個數字裡:依 HTML5 規則,未關閉 <script> 後面的內容都算 script content,所以在那裡丟失是正確結果,若還能恢復才算偏離。
優點與缺點
優點。 在 10 個內容忠實度樣本中,只要它有輸出,就沒有任何標註過的 boilerplate token 混入。雖然約有二十八個文章欄位可用,但這次只做盤點,尚未逐項驗證準確性。圖片抓取預設是關閉的,這點很合理。Python 3.14 可乾淨安裝。持續維護中。Apache-2.0 授權。只要呼叫端有明確檢查,空結果很好攔截。
缺點。 文章召回率在這組裡最低,只有 0.8243,而且原因完全是它直接不回,而不是回錯。文章是一整串清單的頁面,無論設定如何都會得到空字串。44.3 MiB 的占用和 2.2 秒冷啟動,跟 resiliparse 的 21.0 MiB 與 15 ms 相比重很多。必須記得 close()。還有兩個預設值指向多數機器上都不對的東西。
誰適合用,誰不適合
適合用 goose3 的情境,是輸出的文字會進模型或資料庫,而且標註過的 boilerplate 成本很高、頁面又多半是有段落結構的傳統文章。這組測試裡,只要它有回答,就沒有吐出標註過的 boilerplate token。metadata 欄位雖然存在,但這裡尚未驗證;標題、作者、日期和主圖的準確性,還需要另外的真值樣本才能把它變成選用優勢。
不建議用 的情況,是你的資料來源清單很多——你會拿到空字串,而且沒有任何解釋。若冷啟動成本很重要,也別用,因為 resiliparse 的匯入速度快了 145 倍。還有,如果你需要每一頁都一定有答案,也不適合,因為這裡的「沒答案」是真實結果:22 個樣本裡有 2 個,而且都悄悄發生,因為空字串不是例外。
值得一試的搭配: 把 goose3 當主力,當 cleaned_text 為空或低於你的最低內容門檻時再接 fallback。在這 22 個樣本裡,Readability 把 goose3 那兩個空結果都補回來了。這個合成結果支持的是架構模式,不代表真實世界裡 fallback 就絕不會漏。
受管 API 的位置
這次基準測的是 goose3 的 raw_html 擷取路徑:HTML 早就在 goose3 看到之前被抓回來了。goose3 也有自己的網路抓取路徑,從它的 User-Agent 設定就看得出來,但那條路徑這次沒有測。JavaScript 渲染與反機器人行為也都沒測。
要看同一批測試樣本在六個擷取器上的完整比較,可參考 六個函式庫的擷取對照。
像我們自己的 Thunderbit 這類受管的抓取/渲染/擷取服務,對應的是另一個責任邊界。Thunderbit 沒有被納入這次基準。真正的差別,在於「你已經提供 HTML 的文章擷取」和「由代管服務去取得並處理 URL」;這篇文章沒有提供同標準下的效能或品質比較。
公平地說,goose3 的欄位集合固定,而且就是文章型態,當你的頁面真的是文章時,它非常合適;但如果頁面是商品列表,那就不合適。若你手上已經有 HTML,且頁面又是文章,goose3 幾乎是免費而且很乾淨的選擇。
若看代管方案,我們的 網頁爬蟲 API 總整理 會提供更廣的視角;若看自架替代方案,則可參考 開源爬蟲總支柱。如果文字最後要交給模型,則可看 用 Python 將 HTML 轉成 Markdown,那會說明 fidelity 到底是在哪裡流失的。
你該用 goose3 嗎?
如果你的工作負載很適合段落型文章,而且呼叫端把空輸出視為擷取失敗而不是成功,那答案是:可以。
在這組測試裡,goose3 只要有回應,就沒有吐出任何標註過的 boilerplate token,但也回了兩次空字串。其中一次是接近空白的頁面,另一次是整篇都由清單組成的合成正文。這是精確率與覆蓋率之間的取捨,不是它整體性格的證明。
如果你更重視召回率,建議搭配 fallback,並加上明確的最小輸出檢查。Readability 在這 22 個樣本裡把所有文章單元都抓回來了;newspaper4k 則在全數 22 個樣本都有輸出的同時,boilerplate 單元外洩為 0、召回率 0.9865。這些結果只代表這組合成樣本,不代表未知的真實工作負載。
goose3 值得被放進你的工具箱,當「錯一個字」的代價高於「漏掉一頁」的代價時。
試用 Thunderbit 進行網頁資料擷取 Get Started Free
常見問題
goose3 的完美精確率是真的,還是因為它常常拒絕輸出? 兩者都有,而且可以分開看。它在 11 個含有 boilerplate 的樣本中只有 10 個被納入平均,所以平均值少了一個樣本——這部分確實是因為它拒絕輸出。但在那 10 個樣本上,它對刻意設計成對抗的 boilerplate 內容沒有吐出任何雜訊 token;相較之下,Readability 吐出了 35 個。精確率對它有回答的頁面是真的;召回率欄位才會反映出它拒答的情況。
為什麼 goose3 在文章是清單的頁面上會什麼都不回?
它的候選評分需要像段落那樣的區塊才能定位正文,而由 <li> 組成的頁面沒有那種結構。parse_lists=True 的預設值不會改變這件事——我明確試過再加 strict=False,三種設定都回 0 字元,而 <p> 控制樣本三種設定都回 937 字元。parse_lists 只是在文章已經被找到之後,決定清單要不要保留在裡面。
我一定要呼叫 close() 嗎?
要,請像上面那樣用 try/finally 明確關閉。這篇評測沒有量測如果不關閉會累積什麼,所以不能聲稱有量化的迴圈洩漏;但它確實證明 Goose 有一個需要由呼叫端管理的生命週期。
goose3 預設會送出什麼 User-Agent?
預設是 Goose/3.1.22——也就是函式庫名稱加上精確版本。這只會在你讓它自己去抓頁面時生效;如果你直接傳 raw_html,就完全不會碰到這個問題。若你真的讓它去抓,最好明確設定 User-Agent;預設值會讓每台伺服器都知道是誰來訪。
這次評測沒有測什麼?
真實世界的頁面,完全沒有——這些都是受控樣本。多語言擷取也沒有測,雖然 target_language 是一個正式設定。metadata 欄位(標題、作者、日期、主圖)有做盤點,但沒有逐項驗證準確性。圖片抓取也沒測;它預設關閉,而且 ImageMagick 路徑指向的是多數機器沒有的套件管理器位置。並行或持續負載下的記憶體行為、以及負載下的吞吐量也沒測;記憶體表只測了單一新程序處理單一文件。


