背景
前端不用解析 serpbase 的 JSON 嵌套结构,直接走 GraphQL 查字段更省事。5 步搭一个 GraphQL wrapper,接前端 / AI agent。
1. 准备工作
bash
pip install fastapi strawberry-graphql uvicorn requests
2. 定义 GraphQL schema
python
import strawberry
import requests
import os
from typing import List, Optional
SERPBASE_KEY = os.environ["SERPBASE_KEY"]
@strawberry.type
class SerpSource:
rank: int
title: str
link: str
snippet: Optional[str] = None
date: Optional[str] = None
@strawberry.type
class PaaItem:
question: str
answer: Optional[str] = None
source: Optional["SerpSource"] = None
@strawberry.type
class SerpResult:
query: str
gl: str
hl: str
organic: List[SerpSource]
people_also_ask: List[PaaItem]
total_results: int
elapsed_ms: int
request_id: str
@strawberry.type
class Query:
@strawberry.field
def serp_search(
self,
query: str,
gl: str = "us",
hl: str = "en",
num: int = 5,
) -> SerpResult:
"""搜 Google SERP"""
r = requests.post(
"https://api.serpbase.dev/google/search",
headers={"X-API-Key": SERPBASE_KEY},
json={"q": query, "gl": gl, "hl": hl, "num": num},
timeout=10,
)
r.raise_for_status()
data = r.json()
return SerpResult(
query=data["query"],
gl=data.get("gl", "us"),
hl=data.get("hl", "en"),
organic=[
SerpSource(
rank=item["rank"],
title=item["title"],
link=item["link"],
snippet=item.get("snippet"),
)
for item in data.get("organic", [])
],
people_also_ask=[
PaaItem(question=q.get("question", ""))
for q in data.get("people_also_ask", [])
],
total_results=len(data.get("organic", [])),
elapsed_ms=data.get("elapsed_ms", 0),
request_id=data["request_id"],
)
3. 启动 GraphQL 服务
python
from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter
schema = strawberry.Schema(Query)
graphql_app = GraphQLRouter(schema)
app = FastAPI()
app.include_router(graphql_app, prefix="/graphql")
4. 测试查询
graphql
# 简单查询
query {
serpSearch(query: "best serp api", gl: "us", num: 5) {
query
totalResults
elapsedMs
organic {
rank
title
link
}
}
}
返回:
json
{
"data": {
"serpSearch": {
"query": "best serp api",
"totalResults": 5,
"elapsedMs": 1240,
"organic": [
{"rank": 1, "title": "...", "link": "..."},
{"rank": 2, "title": "...", "link": "..."}
]
}
}
}
5. 5 个工程细节
细节 1:错误处理
python
@strawberry.type
class SerpError:
code: int
message: str
@strawberry.type
class Query:
@strawberry.field
def serp_search(self, query: str) -> SerpResult | SerpError:
try:
r = requests.post(...)
r.raise_for_status()
return parse(r.json())
except Exception as e:
return SerpError(code=500, message=str(e))
细节 2:缓存
python
from functools import lru_cache
@strawberry.type
class Query:
@strawberry.field
def serp_search(self, query: str, gl: str = "us") -> SerpResult:
cache_key = f"serp:{query}:{gl}"
if cache_key in CACHE:
return CACHE[cache_key]
result = call_serpbase(query, gl)
CACHE[cache_key] = result
return result
细节 3:subscription(实时)
python
import asyncio
@strawberry.type
class Subscription:
@strawberry.subscription
async def serp_changes(self, query: str) -> SerpResult:
while True:
await asyncio.sleep(300) # 5 分钟查一次
yield call_serpbase(query)
细节 4:权限控制
python
def require_api_key(info):
api_key = info.context["request"].headers.get("X-API-Key")
if api_key != os.environ["INTERNAL_API_KEY"]:
raise Exception("Unauthorized")
schema = strawberry.Schema(query=Query, mutation=Mutation, mutation=require_api_key)
细节 5:批量查询
python
@strawberry.type
class Query:
@strawberry.field
def batch_serp_search(
self,
queries: List[str],
gl: str = "us",
) -> List[SerpResult]:
return [self.serp_search(q, gl) for q in queries]
6. 与 REST 对比
| 维度 | GraphQL | REST |
|---|---|---|
| 前端请求 | 一次,字段自选 | 多次,固定结构 |
| 后端实现 | 复杂 | 简单 |
| 文档 | 自动 | 手写 |
| 类型安全 | ✓(schema 强) | 弱 |
| 性能 | 单次请求多个字段 | 多次请求 |
7. 实战数据(我项目 30 天)
| 指标 | 数值 |
|---|---|
| 启动时间 | 5 分钟 |
| Schema 字段数 | 15 |
| 查询平均延迟 | 1.5s |
| SerpBase 月成本 | $0.6 |
| 端到端测试 | 100% 通过 |
8. 配合 AI agent
AI agent 用 GraphQL 比 REST 更高效:
- LLM 一次 query 拿到所有字段
- 不用先 GET 一遍看有哪些字段
- 节省 token
python
import anthropic
def agent_with_graphql(query):
client = anthropic.Anthropic()
# LLM 调用 GraphQL query(自选字段)
response = client.messages.create(
model="claude-sonnet-4-5",
tools=[{
"name": "search",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string"},
},
},
}],
messages=[{"role": "user", "content": query}],
)
# 执行 GraphQL query(LLM 生成的)
gql_query = response.content[0].input["query"]
data = requests.post("http://localhost:8000/graphql", json={
"query": f"{{ serpSearch(query: \"{gql_query}\") {{ organic {{ rank title }} }}",
}).json()
return data
小结
serpbase + GraphQL wrapper 5 步搭好:
- 定义 schema(strawberry)
- resolver 调用 serpbase
- 启动 FastAPI + GraphQLRouter
- 客户端用 GraphQL query
- AI agent 也能用
30 行代码 + 5 分钟,前端不用解析嵌套 JSON,直接按字段拿。serpbase auto-refund 100% 触发,GraphQL wrapper 失败返 0 数据 + 错误信息,前端不崩。