抓取受保护的 GraphQL API:绕过限制、APQ 和 TLS 指纹识别
2026/7/14


Markus_automation
Expert in data parsing and automation
在从现代 Web 资源(尤其是社交媒体平台和大型 Web 应用程序)收集数据时,仅仅通过 GET 请求来请求 HTML 页面已经不够了。大多数主流平台已经转向动态接口和 API,而 GraphQL 已成为采用最广泛的技术之一。
一方面,GraphQL 允许你通过单个入口点检索相关数据,而无需查询多个独立的 REST 端点。你可以构建单个查询并准确指定所需的数据,而不是发送一系列请求。
另一方面,这种架构使 GraphQL 端点成为客户端和服务器之间交互的中心点。因此,它已成为反爬虫系统关注的焦点。以前的防护机制会分析许多不同的请求,而现在它们将大部分控制逻辑集中在 GraphQL 查询的内容、结构、频率和行为上。
为了理解为什么 GraphQL 既便于抓取,又从安全角度来看如此引人关注,让我们首先来看看它是如何工作的。
内容
保持匿名,充分利用多账户功能,借助市面上最优质的反检测浏览器实现您的目标。
想以折扣价体验 Octo Browser 吗?
使用优惠码 OCTOSCRAPER 即可享受任意订阅 7 折优惠。该优惠仅限新用户使用。
单一端点问题
在传统的 REST 架构中,路由有助于分配负载并应用不同的速率限制。例如,/api/users 端点可能有一套限制,而 /api/feed 则有另一套限制。
使用 GraphQL,只有一个端点。所有有用的数据都作为包含查询、变量和操作名称的 JSON,在 POST 请求的主体中发送。在 HTTP 级别,它看起来像这样:
{ "operationName": null, "variables": {}, "query": "query { users { id name posts(last: 5) { id text timestamp } } }" }
{ "operationName": null, "variables": {}, "query": "query { users { id name posts(last: 5) { id text timestamp } } }" }
然而,为了便于阅读,我们习惯于看到展开后的图谱格式的相同请求:
query { users { id name posts(last: 5) { id text timestamp } } }
query { users { id name posts(last: 5) { id text timestamp } } }
这意味着,从负载均衡器和基础安全系统的角度来看,每个请求看起来都是对同一个 URL 的相同调用。
为了确定客户端实际在做什么,反爬虫系统会尝试实时检查 JSON 负载,而应用服务器则会解析由此产生的繁重 AST(抽象语法树)。
现代爬虫和防护策略正是围绕这一架构特点构建的。
当内省(Introspection)被禁用时该怎么办
在开发爬虫之前,先确定目标网站的反爬虫系统具体是如何工作的。GraphQL 分析通常从尝试 Introspection(内省)查询开始。这一内置功能命令服务器返回完整的 API 文档,包括所有类型、字段、关系和参数。

当然,严肃的平台会限制对此类信息的访问。即便如此,仍有办法可以获取它。
分析错误信息。 许多 GraphQL 服务器默认会尝试帮助用户。例如,如果您请求一个不存在的字段
nam,服务器可能会返回类似"Cannot query field "nam". Did you mean "name"?"的错误。这个过程可以自动化,以逐步重构隐藏模式的很大一部分。

寻找中间件泄漏。
Introspection往往在主应用服务器上被禁用,但在可直接访问的微服务或API gateways(API 网关)上却不小心保持了启用状态。
爬取数据总是需要临场发挥。寻找替代路径,因为它们往往没有受到保护。
自动持久化查询 (APQ)
假设您已成功重构了模式,理解了数据结构,并构建了您的 GraphQL 查询。然而服务器不仅没有发送数据,反而返回了验证错误。这就是 APQ 在起作用。
为了避免在网络上重复发送庞大的图查询,开发人员引入了自动持久化查询 (APQ)。这种 GraphQL 优化允许客户端发送哈希(唯一的查询标识符)而不是完整的查询文本。这显著减小了请求体积,加快了传输速度,并允许在 CDN 上缓存查询。
以下是它对典型客户端的工作方式:前端不是发送完整的查询,而是计算其 SHA-256 哈希,并仅将哈希与查询变量一起发送。服务器检查其缓存,如果识别出该哈希,则执行关联的查询。

例如,Reddit 的前端根本不发送查询主体。相反,它只发送预先注册的操作名称 (operation: "ExposeVariant") 以及所需的变量
为什么这会增加爬取难度: 您无法简单地在脚本中编写自己的自定义查询并将其发送到服务器。服务器需要一个 64 位的哈希值。如果您以纯文本形式发送查询,将会收到验证错误。
绕过此限制的方法:
静态资源解析。 所有合法的哈希值都嵌入在编译后的前端 JavaScript 代码包中。自动化爬虫脚本应首先下载这些 JS 文件,提取哈希到查询的映射关系,然后先建立一个数据库,最后再发出实际请求。
后备请求。 APQ 的设计是,如果服务器识别不出哈希值,客户端就会重新发送哈希和完整查询。许多服务器保持这种行为启用,允许爬虫动态注册任意查询——甚至包括前端从未打算使用的查询。
传输层混淆 (TLS 指纹识别)
为了避免消耗您的 代理池,请注意传输层。通过确保您的 TLS 握手格式正确,来开始您的伪装。
TLS(传输层安全性协议)是一种建立安全连接的协议。在握手期间,客户端会发送一条包含一组加密参数的 ClientHello 消息。这些参数向服务器透露了大量关于您的信息。
诸如 Cloudflare、Akamai 和 DataDome 等防护系统会分析这些网络指纹。像 Python 的 requests 或 Node.js 的 axios 等标准库具有极高的可识别特征,与真实的 Google Chrome 或 Safari 不同,因为它们在 ClientHello 数据包中发送不同顺序的加密套件、协议版本和扩展。
大规模数据采集主要有两种方法:
专用库。 用能够模拟 TLS 指纹的库替换标准的 HTTP 客户端,例如 Python 的
curl-cffi或 Go 的tls-client。这些工具在请求级别模拟浏览器行为,同时保持高性能。反检测浏览器。 如果目标平台具有特别强烈的防护,您可以使用先进的反检测浏览器,例如 Octo Browser。在这种情况下,TLS 指纹伪装发生在浏览器内核级别。虽然这种方法稍慢,但也是最可靠的。服务器看到的行为与真实浏览器无异,从而将检测和封锁的风险降至最低。
绕过速率限制
一旦您的爬虫运行起来,您可能会遇到另一层防护:速率限制。
对于 REST API,防护系统通常会计算来自单个 IP 地址每秒的 HTTP 请求数量。然而,GraphQL 允许单个 HTTP 请求检索海量的相关数据。爬虫通常利用两个内置的 GraphQL 功能来绕过传统的请求计数器:
批量请求。原本引入批量处理是为了让前端可以将多个独立的 API 请求组合成一个 JSON 数组并一起发送。对于爬虫来说,这意味着无需发起 100 个单独请求来检索 100 个用户配置文件,而是可以发送一个包含 100 个操作对象的数组的单个请求。如果速率限制器只在 HTTP 级别分析流量,它将仅计为一次请求。
别名。如果服务器封锁了 JSON 批量处理,您可以使用另一个 GraphQL 功能:别名。别名允许您在单个操作中多次请求带有不同参数的相同字段。这增加了服务器数据库的负载,但对于网络防火墙来说,它看起来仍然是一个合法的请求。
query GetMultipleUsers { user_1: user(id: "1001") { name, email } user_2: user(id: "1002") { name, email } # … and so on }
query GetMultipleUsers { user_1: user(id: "1001") { name, email } user_2: user(id: "1002") { name, email } # … and so on }
但是,必须明白的是,如今无论是批量请求还是别名,都不能保证解决问题。
许多平台现在不再只是简单地统计请求,而是实施查询成本分析。模式中的每个字段和关系都被赋予了权重。一个简单的标量字段(如 name)可能花费一个点,而请求嵌套列表如 posts(last: 100) 则可能花费十个点。
在执行您的查询之前,服务器会解析它,计算其总成本,如果总成本超过预设限制,则会拒绝它并返回诸如“Max query complexity exceeded(超出最大查询复杂度)”等错误——例如,如果 500 个别名将查询推过了 1,000 点的阈值。
防护系统还严格执行查询嵌套深度的限制。如果爬虫试图在一个请求中检索关系递归图(例如 user → their friends → friends' posts → post comments),它几乎肯定会触发预设的嵌套深度限制。
如何应对: 与其试图为深度查询寻找完美的大小(例如,将其拆分为 20-30 个别名的批次,而不是 500 个),通常更好的做法是围绕平面检索策略重新设计爬虫。
平面查询。 从轻量级的顶级查询开始,仅检索所需对象 ID 的平面列表,不包含它们的关系。
分块和工作线程。 将检索到的 ID 分割成小而安全的批次(分块)。然后使用工作线程池并行获取这些 ID 的详细信息。
本地图重构。 在爬虫端重建关系结构。将数据存储在本地(或中间数据库中),并以编程方式进行合并。
这种方法需要额外的编排逻辑和本地缓存。作为回报,它提供了稳定、多线程的爬取,并且不会触发目标平台的 WAF 规则、影子禁令或数据库超时。
结论
爬取现代 GraphQL API 需要与复杂的分布式系统进行全面的交互,包括传输层方面的考虑(如 TLS 模拟),以及对 AST 处理和服务器端缓存机制的理解。
尽管防护系统不断演进,但正是让 GraphQL 如此吸引开发者的架构灵活性,也为设计精良的数据采集系统留下了大量的机会。
保持匿名,充分利用多账户功能,借助市面上最优质的反检测浏览器实现您的目标。
想以折扣价体验 Octo Browser 吗?
使用优惠码 OCTOSCRAPER 即可享受任意订阅 7 折优惠。该优惠仅限新用户使用。
单一端点问题
在传统的 REST 架构中,路由有助于分配负载并应用不同的速率限制。例如,/api/users 端点可能有一套限制,而 /api/feed 则有另一套限制。
使用 GraphQL,只有一个端点。所有有用的数据都作为包含查询、变量和操作名称的 JSON,在 POST 请求的主体中发送。在 HTTP 级别,它看起来像这样:
{ "operationName": null, "variables": {}, "query": "query { users { id name posts(last: 5) { id text timestamp } } }" }
然而,为了便于阅读,我们习惯于看到展开后的图谱格式的相同请求:
query { users { id name posts(last: 5) { id text timestamp } } }
这意味着,从负载均衡器和基础安全系统的角度来看,每个请求看起来都是对同一个 URL 的相同调用。
为了确定客户端实际在做什么,反爬虫系统会尝试实时检查 JSON 负载,而应用服务器则会解析由此产生的繁重 AST(抽象语法树)。
现代爬虫和防护策略正是围绕这一架构特点构建的。
当内省(Introspection)被禁用时该怎么办
在开发爬虫之前,先确定目标网站的反爬虫系统具体是如何工作的。GraphQL 分析通常从尝试 Introspection(内省)查询开始。这一内置功能命令服务器返回完整的 API 文档,包括所有类型、字段、关系和参数。

当然,严肃的平台会限制对此类信息的访问。即便如此,仍有办法可以获取它。
分析错误信息。 许多 GraphQL 服务器默认会尝试帮助用户。例如,如果您请求一个不存在的字段
nam,服务器可能会返回类似"Cannot query field "nam". Did you mean "name"?"的错误。这个过程可以自动化,以逐步重构隐藏模式的很大一部分。

寻找中间件泄漏。
Introspection往往在主应用服务器上被禁用,但在可直接访问的微服务或API gateways(API 网关)上却不小心保持了启用状态。
爬取数据总是需要临场发挥。寻找替代路径,因为它们往往没有受到保护。
自动持久化查询 (APQ)
假设您已成功重构了模式,理解了数据结构,并构建了您的 GraphQL 查询。然而服务器不仅没有发送数据,反而返回了验证错误。这就是 APQ 在起作用。
为了避免在网络上重复发送庞大的图查询,开发人员引入了自动持久化查询 (APQ)。这种 GraphQL 优化允许客户端发送哈希(唯一的查询标识符)而不是完整的查询文本。这显著减小了请求体积,加快了传输速度,并允许在 CDN 上缓存查询。
以下是它对典型客户端的工作方式:前端不是发送完整的查询,而是计算其 SHA-256 哈希,并仅将哈希与查询变量一起发送。服务器检查其缓存,如果识别出该哈希,则执行关联的查询。

例如,Reddit 的前端根本不发送查询主体。相反,它只发送预先注册的操作名称 (operation: "ExposeVariant") 以及所需的变量
为什么这会增加爬取难度: 您无法简单地在脚本中编写自己的自定义查询并将其发送到服务器。服务器需要一个 64 位的哈希值。如果您以纯文本形式发送查询,将会收到验证错误。
绕过此限制的方法:
静态资源解析。 所有合法的哈希值都嵌入在编译后的前端 JavaScript 代码包中。自动化爬虫脚本应首先下载这些 JS 文件,提取哈希到查询的映射关系,然后先建立一个数据库,最后再发出实际请求。
后备请求。 APQ 的设计是,如果服务器识别不出哈希值,客户端就会重新发送哈希和完整查询。许多服务器保持这种行为启用,允许爬虫动态注册任意查询——甚至包括前端从未打算使用的查询。
传输层混淆 (TLS 指纹识别)
为了避免消耗您的 代理池,请注意传输层。通过确保您的 TLS 握手格式正确,来开始您的伪装。
TLS(传输层安全性协议)是一种建立安全连接的协议。在握手期间,客户端会发送一条包含一组加密参数的 ClientHello 消息。这些参数向服务器透露了大量关于您的信息。
诸如 Cloudflare、Akamai 和 DataDome 等防护系统会分析这些网络指纹。像 Python 的 requests 或 Node.js 的 axios 等标准库具有极高的可识别特征,与真实的 Google Chrome 或 Safari 不同,因为它们在 ClientHello 数据包中发送不同顺序的加密套件、协议版本和扩展。
大规模数据采集主要有两种方法:
专用库。 用能够模拟 TLS 指纹的库替换标准的 HTTP 客户端,例如 Python 的
curl-cffi或 Go 的tls-client。这些工具在请求级别模拟浏览器行为,同时保持高性能。反检测浏览器。 如果目标平台具有特别强烈的防护,您可以使用先进的反检测浏览器,例如 Octo Browser。在这种情况下,TLS 指纹伪装发生在浏览器内核级别。虽然这种方法稍慢,但也是最可靠的。服务器看到的行为与真实浏览器无异,从而将检测和封锁的风险降至最低。
绕过速率限制
一旦您的爬虫运行起来,您可能会遇到另一层防护:速率限制。
对于 REST API,防护系统通常会计算来自单个 IP 地址每秒的 HTTP 请求数量。然而,GraphQL 允许单个 HTTP 请求检索海量的相关数据。爬虫通常利用两个内置的 GraphQL 功能来绕过传统的请求计数器:
批量请求。原本引入批量处理是为了让前端可以将多个独立的 API 请求组合成一个 JSON 数组并一起发送。对于爬虫来说,这意味着无需发起 100 个单独请求来检索 100 个用户配置文件,而是可以发送一个包含 100 个操作对象的数组的单个请求。如果速率限制器只在 HTTP 级别分析流量,它将仅计为一次请求。
别名。如果服务器封锁了 JSON 批量处理,您可以使用另一个 GraphQL 功能:别名。别名允许您在单个操作中多次请求带有不同参数的相同字段。这增加了服务器数据库的负载,但对于网络防火墙来说,它看起来仍然是一个合法的请求。
query GetMultipleUsers { user_1: user(id: "1001") { name, email } user_2: user(id: "1002") { name, email } # … and so on }
但是,必须明白的是,如今无论是批量请求还是别名,都不能保证解决问题。
许多平台现在不再只是简单地统计请求,而是实施查询成本分析。模式中的每个字段和关系都被赋予了权重。一个简单的标量字段(如 name)可能花费一个点,而请求嵌套列表如 posts(last: 100) 则可能花费十个点。
在执行您的查询之前,服务器会解析它,计算其总成本,如果总成本超过预设限制,则会拒绝它并返回诸如“Max query complexity exceeded(超出最大查询复杂度)”等错误——例如,如果 500 个别名将查询推过了 1,000 点的阈值。
防护系统还严格执行查询嵌套深度的限制。如果爬虫试图在一个请求中检索关系递归图(例如 user → their friends → friends' posts → post comments),它几乎肯定会触发预设的嵌套深度限制。
如何应对: 与其试图为深度查询寻找完美的大小(例如,将其拆分为 20-30 个别名的批次,而不是 500 个),通常更好的做法是围绕平面检索策略重新设计爬虫。
平面查询。 从轻量级的顶级查询开始,仅检索所需对象 ID 的平面列表,不包含它们的关系。
分块和工作线程。 将检索到的 ID 分割成小而安全的批次(分块)。然后使用工作线程池并行获取这些 ID 的详细信息。
本地图重构。 在爬虫端重建关系结构。将数据存储在本地(或中间数据库中),并以编程方式进行合并。
这种方法需要额外的编排逻辑和本地缓存。作为回报,它提供了稳定、多线程的爬取,并且不会触发目标平台的 WAF 规则、影子禁令或数据库超时。
结论
爬取现代 GraphQL API 需要与复杂的分布式系统进行全面的交互,包括传输层方面的考虑(如 TLS 模拟),以及对 AST 处理和服务器端缓存机制的理解。
尽管防护系统不断演进,但正是让 GraphQL 如此吸引开发者的架构灵活性,也为设计精良的数据采集系统留下了大量的机会。
随时获取最新的Octo Browser新闻
通过点击按钮,您同意我们的 隐私政策。
随时获取最新的Octo Browser新闻
通过点击按钮,您同意我们的 隐私政策。
随时获取最新的Octo Browser新闻
通过点击按钮,您同意我们的 隐私政策。
