serpbase + GraphQL wrapper 实战:让 SERP 数据走 GraphQL schema

背景

前端不用解析 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 步搭好:

  1. 定义 schema(strawberry)
  2. resolver 调用 serpbase
  3. 启动 FastAPI + GraphQLRouter
  4. 客户端用 GraphQL query
  5. AI agent 也能用

30 行代码 + 5 分钟,前端不用解析嵌套 JSON,直接按字段拿。serpbase auto-refund 100% 触发,GraphQL wrapper 失败返 0 数据 + 错误信息,前端不崩。

相关推荐
数字新视界2 小时前
信创动环监控品牌的技术架构及应用解析
数据库·物联网·需求分析·机房管理·动环监控
海兰3 小时前
【高速缓存】RedisVL 高级查询(全文搜索、混合搜索和 多向量搜索)
数据库·人工智能·redis·缓存
DBA_G3 小时前
GBase 8s数据库大对象存储详解
数据库
sbjdhjd3 小时前
SQL 注入零基础详解:原理、手工注入流程与 Sqli-Labs 靶场实战1~5关和第9关思路
数据库·sql·计算机网络·web安全·网络安全·网络攻击模型·安全架构
Tencent_TCB4 小时前
腾讯云 CloudBase 登上 WAIC:我们为 Agent 重新设计了云的生产线
网络·数据库·腾讯云
数智化管理手记5 小时前
预算控制手工核对效率低?预算控制数字化落地实操如何做?
大数据·网络·数据库·人工智能·数据挖掘
MXsoft6185 小时前
成熟的自动化运维平台是什么样的?
运维·数据库·自动化
吴声子夜歌5 小时前
MongoDB 4.x——合理使用索引(二)
数据库·mongodb