← 返回博客

隐藏API替代HTML解析:如何将流量和代理消耗降低10倍

我们分析了为什么解析HTML页面会消耗代理预算,并展示如何切换到隐藏API——实际流量减少10倍。

📅2026年9月27日

如果您通过加载完整的HTML页面来解析Wildberries、Ozon或任何其他网站,您支付的代理流量费用将比实际需要的多出5-10倍。每个商品页面的HTML标记、脚本和样式的大小在200-800 KB之间,而您实际上只需要几个字段:价格、库存、评级。在本文中,我们将讨论如何找到网站的隐藏API,并以紧凑的JSON格式直接获取相同的数据。

为什么HTML解析消耗代理流量

当解析器通过常规HTTP请求或无头浏览器(Selenium、Puppeteer、Playwright)加载页面时,服务器返回完整的HTML文档:标记、内联脚本、样式,有时还有base64图像和数百行JSON数据用于广告小部件,而这些数据对您并没有用。Wildberries上平均的商品页面大小为300-600 KB,而Ozon上则可达到800 KB,如果计算所有相关资源(CSS、字体、跟踪器)。

如果您每天通过3个代理会话监控10,000个商品,这很容易导致每月数十GB的流量。住宅和移动代理通常按流量出售,因此每多出1MB就是直接的费用。同时,您所需的实际数据——价格、折扣、库存、评级——在JSON响应中只占1-5 KB。每个商品的体积差异达到100-200倍,考虑到浏览器渲染的开销,节省的时间和CPU资源会更大。

HTML解析的另一个问题是脆弱性。市场网站定期更改布局、CSS类、DOM结构。每次这样的更改都会破坏基于XPath或CSS选择器构建的解析器。内部API的变化频率要低得多,因为它同时影响移动应用程序和网站前端的工作。

什么是隐藏API,它从哪里来

几乎每个现代网站都是SPA(单页面应用)或混合应用,浏览器首先加载页面的“骨架”,然后通过JavaScript向内部API发出额外请求以获取实际数据:价格、库存、评论、推荐。这些请求被称为隐藏或内部API——它们没有公开文档,但在浏览器流量中完全开放。

从技术上讲,这通常是REST或GraphQL端点,以JSON格式返回数据。例如,Wildberries的商品卡通过类似card.wb.ru和wbx-content-v2.wbstatic.net的请求加载,而价格和库存则通过单独的请求发送到basket-01.wb.ru及类似域名。Ozon的逻辑类似:前端访问内部composer API,该API聚合来自微服务的数据。

重要的是要理解:使用这样的API在形式上并不是黑客行为——您只是重复了普通用户浏览器所做的请求。但是,网站通过反机器人系统保护这些端点,因此接下来需要仔细模拟真实客户的行为,包括使用高质量的代理。

如何通过DevTools找到隐藏API

可以使用Chrome或Firefox内置的浏览器工具找到内部API,而无需编写一行代码。以下是逐步算法:

  1. 在Chrome中打开所需的商品页面,按F12并转到网络选项卡。
  2. 在请求过滤器中选择Fetch/XHR类型——这样您可以排除图像、字体和静态文件的加载。
  3. 刷新页面(F5),查看加载页面骨架后出现的请求列表。
  4. 找到响应中(响应选项卡)显示商品价格、名称或其他所需字段的请求,以JSON格式返回。
  5. 单击该请求并将其复制为cURL(右键→复制→复制为cURL)——这将为您提供完整的头部、Cookie和参数集。
  6. 检查URL中哪些参数是必需的(商品编号、地区、API版本),哪些可以在不丢失数据的情况下去掉。

之后,只需通过普通的HTTP库重复该请求,替换所需的商品编号或ID,而不是渲染整个页面。这适用于大多数市场——Wildberries、Ozon、Avito,以及许多国外平台,如Amazon和eBay。

流量比较:HTML与JSON API

数据量的差异如此之大,以至于值得用数字来展示。以下是对流行市场上单个商品卡的平均测量。

解析方式 平均响应大小 加载时间 需要JS渲染吗
通过Selenium获取完整HTML 400-800 KB 1.5-4秒 是
简单的HTTP请求(requests) 150-300 KB 0.3-0.8秒 否
隐藏的JSON API 3-15 KB 0.1-0.3秒 否

在每天监控50,000个商品的情况下,从无头浏览器切换到直接请求API将流量从大约30-40 GB减少到每月300-700 MB。这不仅节省了代理流量,还减轻了解析器的服务器基础设施负担——减少了渲染所需的CPU,减少了内存,数据收集更快。

Python实用示例

我们来看一个简化的示例:通过直接请求内部API获取商品价格和库存,而不是加载完整页面。这是一个学习模板——确切的端点和参数需要通过DevTools为特定网站确定,因为请求的结构可能因地区和API版本而异。

import requests

def get_product_data(product_id: str, proxies: dict = None) -> dict:
    """
    通过内部API获取商品数据,而不是完整的HTML。
    proxies - 包含代理的字典,格式为requests: {"http": "...", "https": "..."}
    """
    url = f"https://card.example-marketplace.ru/v2/detail"
    params = {
        "nm": product_id,
        "dest": "-1257786",  # 地区,通过DevTools确定
        "spp": "0"
    }
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                       "AppleWebKit/537.36 (KHTML, like Gecko) "
                       "Chrome/120.0 Safari/537.36",
        "Accept": "application/json",
        "Referer": f"https://www.example-marketplace.ru/catalog/{product_id}/detail.aspx"
    }

    response = requests.get(
        url,
        params=params,
        headers=headers,
        proxies=proxies,
        timeout=10
    )
    response.raise_for_status()
    data = response.json()

    product = data["products"][0]
    return {
        "id": product["id"],
        "name": product["name"],
        "price": product["salePriceU"] / 100,
        "stock": product.get("totalQuantity", 0),
        "rating": product.get("reviewRating", None)
    }


if __name__ == "__main__":
    proxy = {
        "http": "http://user:pass@proxy-host:port",
        "https": "http://user:pass@proxy-host:port"
    }
    result = get_product_data("123456789", proxies=proxy)
    print(result)

请注意这个示例中的三个要点。首先,我们指定了Referer头,因为许多API会检查请求是否来自“浏览器”,而不是直接通过URL。其次,我们使用了现实的User-Agent,而不是requests库的默认值,这很容易被检测到。最后,整个请求在一次HTTP调用中完成,无需渲染——这就是流量和速度大幅提升的原因。

对于GraphQL端点,逻辑类似,但您发送的是带有JSON格式请求体的POST请求,在其中明确列出所需字段——这进一步减少了响应的大小,因为服务器只返回请求的数据。

API请求中的代理设置

即使在切换到紧凑的JSON格式后,您仍然需要代理——市场限制了来自单个IP的请求数量,并在异常活动时进行封禁。选择合适的代理类型直接影响解析器的稳定性。

对于大规模访问Wildberries或Ozon等市场的API,数据中心代理非常合适——它们提供高速度和低流量成本,这在频繁请求轻量级JSON端点时至关重要。但如果特定API受到更严格的反机器人保护并封禁整个数据中心子网,切换到住宅代理更为合理——它们使用真实家庭用户的IP地址,较少被子网封禁。

对于依赖移动应用程序的API(某些版本的Avito端点或市场仅通过移动流量提供数据),可能需要通过移动代理进行连接——它们模拟真实移动运营商的流量,并通过阻止普通IP的检查。

在解析器中设置代理时,还需分散请求时间并使用IP轮换——即使是紧凑的JSON请求,如果在一分钟内从同一地址重复1000次,也会引起保护系统的怀疑。设置多个代理会话的池,并在它们之间分配负载,在请求之间添加1-3秒的随机延迟。

潜在问题:令牌、签名、反机器人

隐藏的API并不总是完全开放。一些网站通过额外机制保护其端点,这在构建解析器时需要考虑。

  • 会话临时令牌——一些API需要预先请求令牌,然后在后续请求的头部中传递,并且有效时间有限(通常为5-30分钟)。
  • 请求签名(signature)——请求参数在客户端使用页面中的秘密密钥进行哈希。这样的签名要么需要手动重现,解析算法,要么只能通过无头浏览器在获取令牌的阶段执行,之后直接发送轻量请求。
  • 按IP和User-Agent的速率限制——当请求频率超过限制时,网站会暂时阻止访问。通过代理轮换和合理的延迟可以解决。
  • 指纹识别头部——一些系统检查完整的头部集(顺序、是否存在Accept-Language、Sec-Fetch-*),并阻止“缺失”头部的请求,这些请求通常是脚本而非浏览器发出的。
  • 数据的地理依赖性——市场上的价格和库存可能因地区而异,因此在请求中传递正确的地区/仓库参数非常重要,否则数据将不相关。

如果API被请求签名保护,难以重现,折衷的方案是仅使用无头浏览器(Playwright、Puppeteer)来拦截网络请求并提取现成的JSON响应,而不解析DOM。这比直接的HTTP请求慢,但仍然比完整的页面解析快且轻便。

启动解析器前的检查清单

  • 通过DevTools找到端点,复制为cURL并在Postman或通过requests进行测试。
  • 确定请求的必需参数(商品ID、地区、API版本)并排除冗余参数。
  • 设置User-Agent、Referer和Accept-Language头为现实值。
  • 检查是否需要会话令牌或请求签名,并考虑获取它们的方法。
  • 设置代理轮换和请求之间的随机延迟。
  • 根据网站的具体保护选择合适的代理类型——数据中心、住宅或移动。
  • 添加对429和403错误的处理,并自动切换到其他代理。
  • 设置流量日志记录以监控实际节省。

结论

从解析完整HTML转向使用隐藏API不仅仅是技术优化,而是直接降低代理流量和基础设施的开支。您不再需要加载数百KB的多余标记,而是获得紧凑的JSON,正好包含监控价格、库存或评级所需的字段。额外的好处是解析器对网站布局变化的抵抗力,因为内部API的变化频率通常低于前端。

但是,寻找API的方法并不排除对高质量代理的需求——市场的反机器人系统同样关注HTML请求和对JSON端点的访问。如果您大规模监控Wildberries或Ozon,请从快速的数据中心代理开始以降低成本,并在首次出现封禁迹象时切换到住宅或移动IP池,以实现更稳定的解析器工作。