news 2026/7/23 17:15:52

GraphQL 接口如何抓取?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
GraphQL 接口如何抓取?

随着前端架构的演进,GraphQL 正在逐步替代传统 REST API 成为众多现代 Web 应用的数据交互标准。相较于 REST 接口固定的端点和返回结构,GraphQL 以其单端点、按需取数的特性大幅提升了前端开发效率,但也给数据采集带来了新的挑战 —— 你无法再通过 URL 路径区分接口功能,也不能依赖固定的 JSON 结构解析数据。

本文将从基础原理到实战代码,系统讲解 GraphQL 接口的完整抓取流程,涵盖接口识别、Schema 解析、查询构造、分页处理、反爬绕过等核心环节。

一、GraphQL 与 REST 接口抓取的核心差异

在开始抓取之前,首先要理解两者在架构上的本质区别,这是所有抓取策略的出发点:

表格

对比维度REST APIGraphQL
端点设计多端点,一个资源对应一个 URL(如/users/1/orders/1单端点,所有请求统一走/graphql路径
数据控制服务端决定返回结构,易出现数据冗余或不足客户端通过查询语句精确指定返回字段
请求方式通过 HTTP 方法(GET/POST/PUT/DELETE)区分操作类型统一用 POST(少数支持 GET),通过 query/mutation 区分操作
关联数据获取关联资源需发起多次请求(N+1 问题)单次查询可获取多级嵌套的关联数据
类型系统无强制类型约束,返回结构依赖文档强类型 Schema,支持内省查询自我描述

简单来说,REST 抓取的核心是「找对 URL 和参数」,而 GraphQL 抓取的核心是「写对查询语句」。所有数据都从同一个入口进出,你需要通过构造不同的 query 来获取目标数据。

二、第一步:识别与定位 GraphQL 接口

2.1 典型特征识别

GraphQL 接口有非常鲜明的特征,通过浏览器开发者工具即可快速识别:

  1. 固定路径特征:接口路径通常包含/graphql/api/graphql/graphql/v1等关键词
  2. 请求体特征:POST 请求的 JSON body 中包含query字段,常见配套字段还有variablesoperationName
  3. 响应体特征:返回 JSON 固定包含data顶层字段,错误信息在errors数组中

2.2 快速验证方法

找到疑似端点后,可以发送一个最简查询验证是否为 GraphQL 服务:

bash

运行

curl -X POST https://target.com/graphql \ -H "Content-Type: application/json" \ -d '{"query": "{ __typename }"}'

如果返回{"data":{"__typename":"Query"}},即可确认这是一个有效的 GraphQL 入口。

2.3 常见隐藏场景

部分站点会做路径伪装,需要结合网络面板进一步排查:

  • 统一走/api路径,通过 body 内的字段区分 GraphQL 请求
  • 使用 GET 请求,将 query 编码到 URL 参数中
  • WebSocket 协议承载 GraphQL 订阅(Subscription)操作

三、第二步:解析 Schema 与接口结构

Schema 是 GraphQL API 的「完整说明书」,定义了所有可查询的字段、类型、参数和关联关系。拿到 Schema 就等于拿到了接口的全部能力清单。

3.1 利用内省查询获取完整 Schema

GraphQL 内置了标准的内省(Introspection)机制,通过发送特定查询即可让服务端返回完整的 Schema 定义:

graphql

query IntrospectionQuery { __schema { queryType { name } mutationType { name } types { name kind fields { name args { name type { name ofType { name } } } type { name kind ofType { name } } } } } }

将上述查询发送到目标端点,即可获得全量类型定义。返回结果可以导入 GraphQL Voyager 等工具生成可视化的关系图谱,直观梳理数据结构。

3.2 内省查询被禁用的绕过方案

生产环境中很多服务会关闭内省功能,直接查询会返回introspection is not allowed错误。此时可尝试以下绕过手段:

  1. 换行 / 空格注入绕过:针对简单的关键字正则拦截,在__schema{之间插入换行符

    json

    {"query": "{ __schema\n { queryType { name } } }"}

    部分实现只做了单行关键字匹配,换行即可突破检测博客园。

  2. Fragment 分片绕过:将内省字段拆分到片段中

    graphql

    fragment SchemaFrag on __Schema { queryType { name } } query { ...SchemaFrag }
  3. 请求方式与 Content-Type 切换

    • 尝试 GET 请求,将 query 放在 URL 参数中
    • Content-Type改为application/x-www-form-urlencoded,以表单形式提交 query
  4. 字段建议信息利用:即使内省被禁,发送错误字段名时,服务端通常会返回「你是否想找 xxx」的提示,可通过穷举逐步推导可用字段。

3.3 逆向前端请求推导结构

如果以上方法都失效,最稳妥的方式是通过抓包逆向:

  1. 打开浏览器 DevTools 的 Network 面板,操作页面触发数据加载
  2. 筛选出所有 GraphQL 请求,逐个查看请求体中的 query 和 variables
  3. 收集同一业务场景下的所有查询,拼接还原出完整的字段结构

这也是针对私有 GraphQL 接口最常用的分析手段。

四、第三步:核心抓取实现

4.1 基础请求构造

GraphQL 请求本质上就是带特定 body 的 HTTP POST 请求,任何支持 HTTP 的工具都能发送。

curl 方式(调试用)

bash

运行

curl -X POST https://api.example.com/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer your_token" \ -d '{ "query": "query GetUser($id: ID!) { user(id: $id) { id name email } }", "variables": {"id": "123"} }'

Python requests 方式(最常用)

python

运行

import requests GRAPHQL_URL = "https://api.example.com/graphql" HEADERS = {"Content-Type": "application/json"} query = """ query GetProduct($productId: ID!) { product(id: $productId) { id title price stock category { name } } } """ variables = {"productId": "1001"} response = requests.post( GRAPHQL_URL, headers=HEADERS, json={"query": query, "variables": variables} ) data = response.json()["data"]["product"]

4.2 使用专业 GraphQL 客户端

对于复杂场景,可以使用gql库,它内置了 Schema 校验、自动重试、传输层优化等能力:

python

运行

from gql import gql, Client from gql.transport.requests import RequestsHTTPTransport transport = RequestsHTTPTransport( url="https://api.example.com/graphql", headers={"Authorization": "Bearer token"}, use_json=True, ) client = Client(transport=transport, fetch_schema_from_transport=False) query = gql(""" query { products(first: 20) { edges { node { id name price } } } } """) result = client.execute(query)

4.3 分页抓取处理

GraphQL 有两种主流分页模式,抓取策略完全不同:

偏移量分页(Offset-based)

和 REST 类似,通过page+limit控制,逻辑简单:

graphql

query { products(page: 3, limit: 50) { items { id name price } totalPages } }
游标分页(Cursor-based)

这是 GraphQL 最推荐的分页方式(Relay 风格),通过after游标和first数量翻页,需要循环处理:

python

运行

def crawl_products(): all_products = [] after_cursor = None has_next = True while has_next: query = """ query GetProducts($after: String) { products(first: 50, after: $after) { edges { node { id title price stock } } pageInfo { hasNextPage endCursor } } } """ variables = {"after": after_cursor} resp = requests.post( GRAPHQL_URL, json={"query": query, "variables": variables} ).json() page_data = resp["data"]["products"] for edge in page_data["edges"]: all_products.append(edge["node"]) has_next = page_data["pageInfo"]["hasNextPage"] after_cursor = page_data["pageInfo"]["endCursor"] return all_products

核心逻辑是:每次请求后提取endCursor,作为下一次请求的after参数,直到hasNextPage为 false。

4.4 批量查询优化

部分 GraphQL 服务支持批量请求(Batching),可以在一次 HTTP 请求中发送多个查询,大幅减少网络开销:

python

运行

queries = [ {"query": "query { product(id: 1) { name price } }"}, {"query": "query { product(id: 2) { name price } }"}, {"query": "query { product(id: 3) { name price } }"}, ] response = requests.post(GRAPHQL_URL, json=queries) # 返回一个数组,顺序与请求对应 results = response.json()

是否支持批量取决于服务端实现,需要自行测试验证。

五、常见反爬机制与应对策略

5.1 查询复杂度限制

GraphQL 的速率限制通常不是按请求数,而是按查询复杂度计算。嵌套层级越深、关联字段越多,单次请求消耗的额度越高。

应对策略:

  • 扁平化查询:将深层嵌套的关联查询拆分为多次独立请求
  • 减少单次返回字段数:只取必需字段,避免一次性拉取全量属性
  • 控制分页大小:不要把first设置过大,50-100 通常是安全区间

5.2 认证与鉴权

绝大多数业务型 GraphQL 接口都需要身份认证,常见形式:

  • Bearer Token:放在Authorization请求头中,注意 token 过期时间和刷新逻辑
  • Cookie + Session:保持会话状态,和普通网页抓取一致
  • CSRF Token:部分站点要求额外携带 CSRF 头字段,需要从页面或 Cookie 中提取

5.3 字段级权限控制

不要试图通过内省发现的所有字段都能访问。很多字段会做权限校验,未登录或低权限用户请求会返回 null 或报错。

应对方式:以页面实际加载的查询为准,不要盲目添加内省发现的额外字段。

5.4 频率与行为检测

和 REST API 一样,GraphQL 接口也会有 IP 频率限制、异常请求检测。常规的反爬策略同样适用:

  • 合理控制请求间隔,添加随机延迟
  • 使用代理池分散 IP
  • 模拟正常的请求头和 UA
  • 保持和浏览器一致的查询结构,不要随意修改字段顺序和参数

六、完整实战案例:商品列表全量抓取

下面给出一个可直接运行的完整示例,演示如何抓取一个标准 Relay 风格的商品 GraphQL 接口:

python

运行

import requests import time import json class GraphQLScraper: def __init__(self, endpoint, token=None): self.endpoint = endpoint self.headers = { "Content-Type": "application/json", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } if token: self.headers["Authorization"] = f"Bearer {token}" def fetch_products(self, category_id, page_size=50): all_products = [] after = None page = 0 while True: query = """ query ProductList($categoryId: ID!, $first: Int, $after: String) { productList(categoryId: $categoryId, first: $first, after: $after) { edges { node { id sku title price originalPrice stock salesCount images { url } } } pageInfo { hasNextPage endCursor } totalCount } } """ variables = { "categoryId": category_id, "first": page_size, "after": after } try: resp = requests.post( self.endpoint, headers=self.headers, json={"query": query, "variables": variables}, timeout=10 ) resp.raise_for_status() data = resp.json() except Exception as e: print(f"请求失败: {e}") time.sleep(3) continue if "errors" in data: print(f"GraphQL 错误: {data['errors']}") break result = data["data"]["productList"] for edge in result["edges"]: all_products.append(edge["node"]) page += 1 print(f"已抓取第 {page} 页,累计 {len(all_products)}/{result['totalCount']} 条") if not result["pageInfo"]["hasNextPage"]: break after = result["pageInfo"]["endCursor"] time.sleep(0.5) # 限速保护 return all_products if __name__ == "__main__": scraper = GraphQLScraper("https://api.example.com/graphql") products = scraper.fetch_products(category_id="100", page_size=50) with open("products.json", "w", encoding="utf-8") as f: json.dump(products, f, ensure_ascii=False, indent=2) print(f"抓取完成,共 {len(products)} 条数据已保存")

七、合规与风险提示

  1. 法律合规:抓取数据前请确认目标网站的服务条款和 robots.txt,不得抓取受保护的个人信息或商业敏感数据,不得用于非法用途。
  2. 访问压力:控制抓取频率,避免对目标服务造成过大负载,高频大规模抓取可能触发法律追责。
  3. 数据使用:通过接口获取的数据受版权和数据保护法规约束,二次分发或商用需获得授权。
  4. 账号安全:使用认证账号抓取时,注意账号风控策略,频繁异常请求可能导致账号封禁。

总结

GraphQL 接口抓取的核心思路可以归纳为三步:定位端点 → 解析结构 → 构造查询。相较于 REST 接口,它的学习门槛稍高,但一旦掌握了 Schema 分析和查询构造方法,抓取效率反而更高 —— 返回结构高度规整,无需做复杂的 HTML 解析,数据一致性更好。

实际工作中,大多数场景下通过抓包复用前端的查询语句是最高效的方式,不需要完整推导整个 Schema。只有在需要批量枚举数据、挖掘隐藏字段等进阶场景下,才需要深入使用内省查询和类型推导。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/23 17:15:28

用Python和PyGame实现2.5D沙盒游戏:从伪3D渲染到完整游戏架构

1. 项目概述与核心价值 最近在整理自己的代码仓库,翻出来一个几年前用Python和PyGame写的“我的世界”风格小游戏。当时写它纯粹是为了好玩,想看看用最简单的工具能还原出多少那种方块世界的建造乐趣。没想到后来断断续续完善,加了些基础功能…

作者头像 李华
网站建设 2026/7/23 17:12:42

AI工具如何提升学术写作效率:从文献管理到自动润色

1. 学术写作的痛点与AI工具的价值作为在高校混迹十年的科研狗,我太清楚写专著时那种抓耳挠腮的痛苦了。去年完成我那本《多智能体系统前沿》时,光是整理参考文献就耗掉三周,更别提反复修改的章节结构。直到偶然发现同事在用AI工具自动生成文献…

作者头像 李华
网站建设 2026/7/23 17:11:01

嵌入式看门狗定时器原理、配置与实战避坑指南

1. 嵌入式系统看门狗定时器:你的代码“保镖”与“安全绳” 在嵌入式开发这个行当里摸爬滚打十几年,我见过太多因为程序“跑飞”或陷入死循环而导致的现场事故。从产线上突然停机的工业控制器,到户外因“假死”而失联的物联网终端,…

作者头像 李华
网站建设 2026/7/23 17:09:36

AI自动生成研究计划实用指南:高效搭建科学规范的研究推进方案

对于科研人员来说,文献工作往往伴随着两个极端的痛苦:一是搜索时的大海捞针,为了几篇核心文献,不得不花费数小时翻阅成百上千条琐碎的摘要;二是阅读时的翻译折磨,在专业术语和复杂的 LaTeX 公式间反复推敲&…

作者头像 李华
网站建设 2026/7/23 17:08:25

GraphRAG 别只拼检索率,图谱更新才是生产环境的真账本

聊《会用GraphRAG只是起点,能解释失败才算真正入门》之前,先说一句实在的:别急着背概念,先看它在真实项目里到底解决什么问题。摘要先把这篇文章的目标说清楚:看完之后,你应该能判断这件事值不值得做&#…

作者头像 李华
网站建设 2026/7/23 17:05:57

GEO数据系统深度介绍(5):正负面面板

这个系列写到第五篇,总览、词条、竞品对比、引用来源都聊过了。压轴的这一篇,讲讲正负面——也是最容易被做浅了的一个模块。 一、正负面监测最大的坑:把"AI瞎说"和"真负面"混为一谈 大部分正负面监测工具的逻辑很简单&a…

作者头像 李华