Pricing

旅遊與 SaaS 的跨區域價格監控

5 min read Published Updated 896 words

一間巴黎的飯店房間,從法國 IP 在 Booking.com 上標價 €200,但當同一個瀏覽器從美國 IP 存取同一個網址時,價格卻變成 €260。這不是匯率換算的誤差——而是基於地理位置的刻意動態定價。對於 SaaS 服務,Slack 或 Jira 的同一個席位在美國與印度之間可能相差 40%。要大規模監控這些價格差異,就需要一套代理基礎設施,能夠抵擋航空公司與雲端業者用來對付爬蟲的同一套反詐欺系統。

為什麼相同 SKU 在不同地區價格不同

驅動地理套利定價的機制有三種。第一,匯率轉換加上隱藏加價——飯店的訂房引擎會根據國家不同,收取 3-5% 的外匯價差。第二,當地稅制:歐盟的 VAT、印度的 GST、美國的銷售稅。第三,也是最激進的,基於需求的動態定價。從倫敦飛往紐約的英國航空班機,當請求來自英國 IP 時,顯示的價格會比來自德國 IP 時更高,因為演算法假設英國旅客的支付意願較高。Atlassian 與 Salesforce 等 SaaS 廠商會為每個地區維護不同的價格表,新興市場通常有 30-50% 的折扣。要以程式化方式取得這些價格,唯一的方法就是讓請求看起來像是來自每個目標市場。

多地區價格擷取的代理架構

單一住宅代理池是不夠的。你需要一組出口節點,能夠匹配國家、城市,甚至電信業者(例如法國的移動 ISP 與法國的住宅 DSL)。標準做法是使用一個代理經紀人,維護一份經過驗證的代理輪換清單。以下是一個最小的 curl 指令,透過法國代理擷取飯店價格,設定 Accept-Language 標頭為 fr-FR,並從最新的 Chrome 版本發送一個逼真的 User-Agent

curl -s -x "http://user:pass@fr-proxy.example.com:3128" \
  -H "Accept-Language: fr-FR,fr;q=0.9" \
  -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36" \
  "https://www.booking.com/hotel/fr/paris-ritz.html" | grep -oP '"price":"[^"]+"'

如果該代理已被 DataDome 或 Akamai 等機器人偵測服務所知,這個單一指令會有 60-80% 的機率失敗。只有當你將代理輪換與連線持續性以及符合代理真實 ISP 的標頭指紋辨識結合使用時,失敗率才會下降。

反詐欺機器人偵測:真正的瓶頸

旅遊與 SaaS 平台投入大量資源進行機器人偵測。它們不僅檢查 IP 的信譽,還會檢查 TLS 交握指紋(JA3)、HTTP/2 設定、時間抖動以及 HTTP 標頭的順序。能通過一項檢查的代理,可能在其他檢查上失敗。例如,一個資料中心代理雖然 IP 乾淨,但 JA3 簽名與已知的爬蟲工具相符,就會立刻被封鎖。住宅代理也並非萬無一失——許多來自受感染的裝置,早已出現在黑名單上。最有效的策略是使用一個你已經針對目標網站的偵測堆疊測試過的專用代理池。即使在理想條件下,每個代理的成功率預估只有 10-20%。這意味著每個目標地區至少需要 5-10 個代理,才能維持每 5-10 秒一次請求的穩定爬取速率。

這就是取捨所在:較高品質的代理(住宅、靜態 IP、高信譽)成本是資料中心代理的 10 倍,但成功率可能只增加一倍。對於一個每小時跨 10 個地區監控 100 個 SKU 的價格監控作業,每月的代理費用可能超過 $2,000。另一種選擇——使用免費的公共代理——根本不可行,因為它們的 IP 早已被所有主要的反機器人服務標記。從免費代理發出的單一請求就會觸發 CAPTCHA 或 403 回應。

實務工作流程:速率限制、IP 冷卻時間與錯誤處理

你的爬蟲必須為每個代理 IP 實作一個狀態機。成功請求後,代理進入冷卻期——飯店網站 30 秒,SaaS 管理後台 60 秒。失敗後(HTTP 403、429 或 CAPTCHA 頁面),冷卻期延長至 5 分鐘,並將該代理標記為需重新評估。使用令牌桶速率限制器,對所有代理設定全域上限,例如每秒 2 個請求。以下 Python 片段(使用 asyncioaiohttp)展示了核心迴圈:

import asyncio, aiohttp, random

PROXY_POOL = [{"url": "http://user:pass@fr1:3128", "cooldown_until": 0}]

async def fetch_price(session, proxy, url):
    now = asyncio.get_event_loop().time()
    if now < proxy["cooldown_until"]:
        await asyncio.sleep(proxy["cooldown_until"] - now)
    try:
        async with session.get(url, proxy=proxy["url"],
                               headers={"Accept-Language": "fr-FR"}) as resp:
            if resp.status == 200:
                proxy["cooldown_until"] = now + 30
                return await resp.text()
            else:
                proxy["cooldown_until"] = now + 300
                return None
    except Exception:
        proxy["cooldown_until"] = now + 300
        return None

對同一個代理的連續失敗加入指數退避——三次錯誤後,將該 IP 停用 24 小時。監控成功回應與總嘗試次數的比例;如果某個地區低於 20%,則輪換該國家的整個代理池。最後,記錄每個回應標頭,尤其是 Set-CookieX-Frame-Options,因為它們能揭示網站是否正在執行需要 JavaScript 的機器人偵測腳本。對於依賴客戶端渲染的網站,你必須改用 Playwright 或 Puppeteer 等無頭瀏覽器,這會使延遲與代理成本再增加一個數量級。跨地區價格監控不是一個週末專案——它是一項持續的工程投資,需要針對不斷變動的目標進行持續調校。