SERP 接口返回的 JSON,直接当 dict 用,容易踩类型坑------rank 可能是字符串,snippet 可能缺。用 Pydantic 定义模型,强校验 + 类型安全,解析就稳了。
下面以 SerpBase 的 /google/search 接口为例。
为什么用 Pydantic
手写 data["organic"][0]["rank"] 的问题:
- 字段缺了,
KeyError - 类型不对,
rank是字符串,排序就乱 - 没有 IDE 提示,全靠记
Pydantic 定义模型后,字段必填、类型校验、自动转换,还有 IDE 提示。
定义模型
python
from pydantic import BaseModel
from typing import List, Optional
class OrganicItem(BaseModel):
rank: int
position: Optional[int] = None # 别名,兼容不同字段
title: str
link: str
url: Optional[str] = None
snippet: Optional[str] = None
date: Optional[str] = None
class SearchResponse(BaseModel):
status: int
request_id: str
search_type: str = "search"
elapsed_ms: Optional[int] = None
credits_charged: Optional[int] = None
organic: List[OrganicItem] = []
解析
python
import requests
def search_typed(query):
r = requests.post(
"https://api.serpbase.dev/google/search",
headers={"X-API-Key": "你的key"},
json={"q": query, "hl": "zh-CN", "gl": "cn"},
timeout=10,
)
# 直接交给 Pydantic 校验
return SearchResponse.model_validate(r.json())
字段自动类型转换 + 校验。rank 传成字符串,会自动转 int;缺必填字段,直接报错。
别名处理
serpbase 的 organic 同时返回 rank/position、link/url。Pydantic 用 Field(alias=...) 或直接用双字段兼容:
python
from pydantic import Field
class OrganicItem(BaseModel):
rank: int = Field(validation_alias="rank")
position: Optional[int] = None
link: str
url: Optional[str] = None
def effective_rank(self):
"""rank 优先,position 兜底"""
return self.rank if self.rank is not None else self.position
def effective_link(self):
return self.link or self.url or ""
字段校验 + 清洗
Pydantic 还能做字段级校验和清洗:
python
from pydantic import field_validator
class OrganicItem(BaseModel):
title: str
snippet: Optional[str] = None
@field_validator("snippet")
@classmethod
def truncate(cls, v):
if v and len(v) > 200:
return v[:200] + "..."
return v
@field_validator("title")
@classmethod
def strip(cls, v):
return v.strip() if v else ""
错误处理
校验失败会抛 ValidationError,可以统一处理:
python
from pydantic import ValidationError
def safe_search_typed(query):
try:
data = search_typed(query)
return data
except ValidationError as e:
# 记录 + 走 fallback
log_error(f"解析失败: {e}")
return None
喂 LLM 前裁剪
Pydantic 模型还可以直接产出喂 LLM 的精简结构:
python
def to_llm_context(resp: SearchResponse, limit=5) -> str:
lines = []
for i, item in enumerate(resp.organic[:limit], 1):
lines.append(f"[{i}] {item.title}\n{item.snippet or ''}")
return "\n".join(lines)
注意
rank可能是 Optional :没排进结果时字段可能没有,用Optional[int]- 校验失败别崩 :捕获
ValidationError,记录 + fallback - 版本兼容:接口字段演进,模型加字段要向后兼容
完整参数和响应字段参考:serpbase.dev/docs。Pydantic 强校验,解析 SERP 数据不再手抖。