逆向工程做到前半段,無論是機械式拆分、辨識演算法家族,還是差異驗證,最後得到的通常都是同一個結論:「我知道它在算什麼了。」但這還不能直接上線。邏輯仍被綁在原本的執行環境裡,你得決定它要變成可放進 CI 的純函式,還是一個需要人持續照看的外部程序。這一步選錯,前面省下的時間,會在維運階段連本帶利還回去。
四階段總覽只用一句話帶過這件事:「第 4 階段,依傳輸層降級。」這篇把它拆開來談。核心原則很簡單:每往下一層,依賴範圍、故障模式與部署成本都會增加一個量級;因此預設策略是盡力往上推。
落地方式只有三層,順序不能顛倒
逆向邏輯的落地型態只有以下三種,而且這個優先順序應直接寫進團隊規範,不能每次都看「哪個最快跑起來」臨時決定:
原生重寫。用目標語言重寫邏輯,徹底脫離原始 runtime,只依賴標準函式庫。前提是演算法家族已正確辨識:當指紋辨識步驟完成後,90% 的內容可以直接取自公開實作,剩下的差異點再個別處理,最終落成一個純函式。
以本機 JS 引擎執行最小片段。有些邏輯短期內要完全純化,成本確實太高。此時可保留原始 JS 的小片段,在本機 Node/V8 執行必要的數十行程式,而不是搬動整個頁面。
被動式瀏覽器橋接。有一類狀態只存在於真實且已登入的頁面 runtime,例如由 runtime 產生的簽名、與 session 綁定的動態識別碼。這些狀態無法靠靜態重建還原,目前只能在瀏覽器內讀取。這是暫時性的作法,必須在介面說明中明確標記,而且絕不能作為預設方案大規模部署。
真正的成本差距,不是線性增加
之所以要固定這個順序,是因為三層的成本並非逐步上升,而是每降一層就多一個量級。
層級 | 依賴範圍 | 故障模式 | 能否進 CI? |
|---|---|---|---|
原生重寫 | 標準函式庫,不需要外部程序 | 輸出不一致,一個 | 可以,因為它是純函式 |
本機 JS 引擎 | 多一個 Node runtime;V8 context 並非 thread-safe,併發時需要 lock | 引擎版本不符、片段依賴的全域變數缺失 | 勉強可以,但得先安裝引擎 |
被動式瀏覽器橋接 | 真實 Chrome + extension + 人工維護的 session + 本機 loopback 通道 | 頁面沒開、session 過期、結構變更、分頁被關閉 | 不行,因為需要真人在線 |
第一層的故障由單元測試攔下;第三層的故障則可能只是「使用者今天把那個分頁關掉了」。原本能是純函式的東西,一旦做成第三層,就等於每次呼叫都綁上一個人。可供參考的是:演算法家族一旦辨識完成,一套經過混淆的簽名 SDK,往往能以不到 600 行的獨立實作落地,只依賴內建 crypto,直接運行在第一層。那些你以為非得經過瀏覽器橋接的東西,多半只是演算法還沒被完整辨識而已。
被動式橋接的底線:只取快照,絕不主動操作
被動式橋接失控,通常只有一種原因:它開始「幫忙」了——自動刷新、自動登入、自動等待頁面載入。每多加一個「自動」,它就從轉送器更接近爬蟲。因此界線必須畫得極窄。以下限制都是踩過坑後才得出的結論:
每項只探測一次快照,絕不改動頁面。對已開啟的頁面,cookie、session 狀態與頁面 runtime 各做一次、且僅做一次探測。不要建立分頁、不要刷新、不要導頁、不要切換焦點、不要輪詢等待。頁面不存在就是不存在,不要替使用者把它打開。
缺少任何條件時,立刻回傳明確錯誤。找不到相符分頁時回傳 tab_unavailable;頁面存在但未登入時回傳 not_logged_in;已登入但 runtime 尚未就緒時回傳 runtime_unavailable。這三種錯誤碼各自對應一種現實狀態與下一步動作:等待頁面、登入,或切換目標。不要只丟出一個「失敗了」,讓呼叫端自己猜原因。
敏感狀態絕不離開瀏覽器。extension 不要求 cookies / webRequest 權限,只查詢已開啟且符合條件的分頁,在該頁面 context 內完成請求,回傳前再清除欄位。cookie 與頁面簽名狀態從頭到尾都不會跨出 Chrome;通道預設也只連到本機 loopback 位址。橋接層傳遞的是結果,不是憑證。
為何只有幾個平台,卻需要 15 個 scope?
被動式橋接轉送的是經過白名單約束的請求:路徑、參數與 referer 都由 adapter 限制。最違反直覺的是 scope 的切法:明明只有幾個平台,卻共有 15 個 scope,因為scope 是依「頁面 context」切分,不是依「平台」切分。同樣是 TikTok,Creative Center、Top Ads、Creator platform、influencer library 與 Ads Manager 就是五個獨立 scope,各有獨立的 session 狀態與頁面 runtime;登入廣告後台,不代表你就能取得 Creator platform 的 runtime。若每個平台只切一份,很快就會遇到「登入了子站 A,卻無法服務子站 B」的問題。Xiaohongshu 的主站、對應 app 的路徑與創作者市集,同樣是三個 scope。
排程的切分方式也隨之確定:lock 只加在平台家族這一層。同一個家族內的請求,例如 Douyin 家族,必須序列執行,因為它們共用同一個真實分頁;同時往同一個頁面 context 發送請求,彼此很容易互相踩踏。不同家族,例如 Douyin 與 Xiaohongshu,則能平行執行,因為它們是兩個無關的分頁。同一家族內還要額外設定最小請求間隔。粒度太粗,原本能平行的工作被迫排隊;粒度太細,共用分頁的請求就會衝突。平台家族正好就是「共享同一個頁面 runtime」的自然邊界。
登入只能明確交接,不能偷偷觸發
被動式橋接不操作頁面,但 session 總會過期。解法是把人工介入收斂成單一、明確且一次性的動作:互動式指令進入 handoff 後,程式透過作業系統開啟對應的業務頁面,等你手動登入並讓頁面準備完成,再重放原始請求。整個過程中,extension 不點按鈕、不填表單、不匯出 cookie;登入是在真實瀏覽器中由你完成,程式只會在你結束後接回原本的請求。
關鍵限制在於:它絕不能隱式觸發。非互動式指令,例如 CI 或排程任務,絕不自行跳出瀏覽器;它只會乾淨地回傳 session 錯誤,交由上層決定後續處置。handoff 也必須避免誤判:登入成功後,runtime 最多只能再等一小段時間,實作中為 20 秒,之後就必須得出結論。如果業務頁面已重導到缺乏目標介面 context 的帳戶頁,就該立即結束目前階段,不能把一個確定「到不了」的狀況誤認為「還在載入」,然後傻等下去。允許降級的流程,應將這個階段標記為 unavailable 後繼續執行,而非讓整個流程失敗。
階段狀態必須說清楚:完成、刻意略過,還是需要 session
上述所有設計背後,其實有一個共同原則:任何階段都不能只回傳「成功」或「失敗」。在編排流程中,每個階段的結果共有六種:completed(完成)、empty(已執行但沒有資料)、ready(已備妥,等待提交)、skipped(依規則刻意略過)、unavailable(目前不可用,通常代表需要 session)、blocked(前置條件未滿足)。
「刻意略過」、「需要 session」與「確實沒有資料」是三種完全不同的訊號。如果只回傳一個模糊結果,你無法判斷空結果是「本來就該是空的」,還是「session 死了卻沒人發現」;這樣的 pipeline 根本無從維運。把階段狀態建模成有限 enum,編排層無論是腳本或模型,就能據此判斷該降級、重新驗證,或直接中止。這與被動式橋接的三種錯誤碼是同一個原則,只是提升到了流程層級。
讓模型協助判斷:能力該落在哪一層
模型只應在這個環節登場,而且角色必須克制:它不是替你完成純化,而是協助判斷該不該純化,以及純化到什麼程度。這是架構判斷,不是破解簽名。核心問題只有一個:它依賴的狀態能否靜態重建,還是只能在 runtime 取得?接著再衡量純化成本與它的變動頻率。不同步驟對模型的要求也不同:
步驟 | 所需能力 | 選用模型 | model id |
|---|---|---|---|
通讀整個 module,掌握依賴範圍 | 長 context,能一次讀懂 call graph | Kimi K3 |
|
從正反兩面論證層級選擇,反駁「先跑起來再說」 | 強推理能力,願意主張多花一天完成純化 | Claude Opus 5 |
|
對數十到數百項能力做批次初步分流 | 成本低、併發高 | Claude Sonnet 5 |
|
降級後進行歸因 | 中等推理能力,能根據故障日誌解釋原因 | GPT-5.6 Sol |
|
其中最重要的是第二種。做層級判斷時最常犯的錯,是模型順著你「先讓它跑起來」的語氣,交出「瀏覽器橋接最簡單」這種答案——它只是在附和你,並沒有替你計算長期成本。強推理層應該會反駁你:「這段只是標準 hash 加上一個常數擾動,值得花一天做成純函式,不該丟到橋接層。」別只聽我說,自己測試:挑 3 段邏輯,至少 1 段要是你已知正確落點的對照組,將同一段「給出落點、論證理由,並反駁過早採用瀏覽器橋接」的 prompt 分別交給 claude-opus-5 與 gpt-5.6-sol。你只需要看一件事:它會努力把你往上推一層,還是懶得判斷、直接預設第三層?
真正卡人的,是切換模型的成本
三家供應商、四個層級,意味著三套 SDK、三種驗證機制、三種錯誤格式。為了讓不同步驟切換模型而重寫 client 並不划算,於是多數人最後只能用一個模型處理所有事情;偏偏最需要推理能力的落點判斷,卻用了只會附和的模型。
AIReiter把這一層攤平:一把 key、一個 OpenAI 相容介面,四個層級都放在後面。切換時只要修改請求內容中的 model 欄位即可。
# 落點論證:推理層
curl https://aireiter.com/api/v1/chat/completions \
-H "Authorization: Bearer $AIREITER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-opus-5",
"messages": [{"role": "user", "content": "<落點判斷 prompt + 逆向邏輯片段 + 依賴清單>"}]
}'
# 批次初步分流:修改一個欄位
# "model": "claude-sonnet-5"
# 降級歸因:
# "model": "gpt-5.6-sol"
已經使用 OpenAI SDK?把 base_url 指向 https://aireiter.com/api/v1。使用 Anthropic SDK?以同一把 key 呼叫 POST /api/v1/messages。價格方面,Claude 模型定價為牌價 7 折,GPT 模型則為半價。這套流程的成本主要集中在兩處:對數十到數百項能力進行批次初步分流,使用 Sonnet、呼叫量高;以及讀完整個 module 以掌握依賴範圍,使用 Kimi、每次呼叫的 token 數高。批次分流使用 Claude 模型,因此折扣剛好落在最密集的步驟上;推理層的落點論證也是 Claude 模型,同樣享有 7 折。Kimi K3 的長 context 層也可用同一把 key 存取。
免註冊試用——先手動丟幾段邏輯進去,觀察兩個模型是順著你說,還是會反駁「這東西真的該放在瀏覽器橋接嗎」。
結語
逆向邏輯的落地,表面上是技術問題,本質上其實是成本問題。三層階梯——原生重寫 > 本機 JS 引擎 > 被動式瀏覽器橋接——不能倒過來,因為每往下一層,就是把純函式換成帶有外部依賴、人工介入與即時分頁需求的程序。被動式橋接不是禁區,而是必須嚴守邊界的暫時元件:只取一次快照、絕不主動操作;頁面不存在就立刻回報明確錯誤;敏感狀態不離開瀏覽器;登入只能靠明確 handoff;階段狀態永遠清楚可讀。守住這些條件,它就是可信賴的過渡方案;少了任何一項,就會變成沒人敢維護的黑箱。模型的用途,是協助你判斷能力該落在哪一層,並抵抗「先跑起來再說」的慣性;至於每次重寫是否正確,裁判是差異測試,不是模型。