OpenClaw Agent 接入 Google AI Mode:Pangolinfo AI搜索技能原理与最佳实践

0 阅读6分钟

OpenClaw AI搜索技能 - 实时获取 Google AI Mode 数据.png 给 OpenClaw Agent 装上"实时之眼"——AI Mode 数据实时采集技能正式发布

背景

Google AI Mode 的普及速度超出了大多数 Agent 框架的适配节奏。当用户在搜索框输入查询,他们看到的第一屏已经不是传统有机结果,而是 AI 动态生成的摘要——AI Overview。这个信息层的特殊之处在于:它不存在于任何静态 HTML 节点,普通爬虫完全不可见。

对于用 OpenClaw 构建 AI Agent 的开发者来说,这意味着 Agent 的感知层存在一个系统性缺口。Pangolinfo 为此发布了第一个 OpenClaw 官方技能——AI搜索技能,底层封装 AI Mode API,让 Agent 获得对 Google AI Overview 数据的实时读取能力。

本文从技术原理入手,深入拆解 AI Mode 数据采集的难点,并给出完整的 Skill 集成方案。


AI Mode 数据采集的技术难点

动态渲染与 LLM 注入

Google AI Mode(URL 参数 udm=50)在处理搜索请求时,会触发服务端 LLM 推理,将生成结果异步注入 SERP 页面。具体机制:

用户请求 → Google 前端服务器 → 触发 LLM 推理任务
                                        ↓
                              AI Overview 内容生成
                                        ↓
                    通过 XHR/SSE 推送到页面 DOM(JavaScript 动态插入)

这意味着:

  • 直接 HTTP 请求拿到的 HTML 不含 AI Overview
  • 无头浏览器需要等待 JS 执行完成且 AI 内容填充后才能提取
  • 不同查询的 AI 生成延迟差异较大(200ms–3000ms)

多轮对话状态管理

AI Mode 支持用户在同一 SERP 页面发起多轮追问(Multi-turn prompting)。每次追问触发新的 LLM 推理,页面部分刷新。自建爬虫要支持这一特性,需要维护一个完整的 Session 状态机,成本极高。

反检测机制

Google 对 AI Mode 页面实施了比普通搜索更严格的机器人检测:

  • TLS 指纹验证
  • 浏览器行为特征分析
  • 基于 IP 历史的动态封禁

Pangolinfo AI Mode API 架构解析

AI Mode API 调用流程图.png

AI Mode API 完整调用链路:从 OpenClaw Skill 到结构化 JSON 响应

API 规范

POST https://scrapeapi.pangolinfo.com/api/v2/scrape

Headers:
  Content-Type: application/json
  Authorization: Bearer {API_KEY}

Body:
  url         (string, required)    - Google AI Mode 搜索 URL
  parserName  (string, required)    - 固定为 "googleAISearch"
  screenshot  (boolean, optional)   - 是否返回页面截图
  param       (string[], optional)  - 多轮对话提示词(≤5条,超出降速)

响应结构详解

{
  "code": 0,
  "message": "ok",
  "data": {
    "results_num": 1,
    "ai_overview": 1,         // 1=成功获取AI Overview,0=无AI Overview
    "json": {
      "type": "organic",
      "items": [
        {
          "type": "ai_overview",
          "items": [
            {
              "type": "ai_overview_elem",
              "content": [    // AI 生成的文本块数组
                "第一段 AI 内容...",
                "第二段 AI 内容..."
              ]
            }
          ],
          "references": [     // 引用来源数组
            {
              "type": "ai_overview_reference",
              "title": "来源标题",
              "url": "https://...",
              "domain": "域名"
            }
          ]
        }
      ]
    },
    "screenshot": "https://image.datasea.network/screenshots/xxx.png",
    "taskId": "任务ID,用于追踪",
    "url": "实际请求的搜索URL"
  }
}

关键字段说明

字段路径类型说明
data.ai_overviewint1 = 本次获取到 AI Overview(消耗2积点),0 = 无
data.json.items[].typestring"ai_overview" 类型的 item 包含 AI 内容
data.json.items[].items[].contentstring[]AI 生成的文本段落数组
data.json.items[].referencesobject[]引用来源,含 title/url/domain
data.screenshotstring页面截图 URL(screenshot: true 时有值)

完整实现:OpenClaw AI搜索技能

核心 Skill 实现

"""
pangolin_ai_search_skill.py
OpenClaw AI搜索技能 - 基于 Pangolinfo AI Mode API
"""

import time
import asyncio
import aiohttp
import requests
from urllib.parse import quote
from typing import Optional


class AISearchSkill:
    """
    OpenClaw AI搜索技能
    
    功能:
    - 实时采集 Google AI Overview 内容
    - 支持多轮对话追问
    - 支持同步/异步双接口
    - 内置速率控制和错误处理
    
    积点消耗:
    - 获取到 AI Overview: 2积点/次
    - 未获取 AI Overview: 标准积点
    """
    
    SCRAPE_API_URL = "https://scrapeapi.pangolinfo.com/api/v2/scrape"
    GOOGLE_AI_MODE_TEMPLATE = "https://www.google.com/search?num=10&udm=50&q={q}"
    MAX_PARAM_COUNT = 5  # 官方限制:超出此数量响应效率降低
    
    def __init__(self, api_key: str, timeout: int = 30):
        if not api_key:
            raise ValueError("Pangolinfo API Key 不能为空")
        self.timeout = timeout
        self._headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {api_key}"
        }
    
    # --------------------------------------------------------
    # 同步接口
    # --------------------------------------------------------
    
    def search(
        self,
        query: str,
        follow_up: Optional[list[str]] = None,
        with_screenshot: bool = False
    ) -> dict:
        """
        同步搜索(适用于单次查询场景)
        
        Returns:
            dict: {
                "query": str,
                "has_ai_overview": bool,
                "ai_content": list[str],
                "references": list[dict],
                "screenshot_url": str,
                "task_id": str
            }
        """
        payload = self._build_payload(query, follow_up, with_screenshot)
        
        try:
            response = requests.post(
                self.SCRAPE_API_URL,
                headers=self._headers,
                json=payload,
                timeout=self.timeout
            )
            response.raise_for_status()
        except requests.exceptions.Timeout:
            raise TimeoutError(f"请求超时({self.timeout}s),建议检查网络或增大 timeout")
        except requests.exceptions.HTTPError as e:
            raise ConnectionError(f"HTTP 错误: {e}")
        
        return self._parse_response(query, response.json())
    
    # --------------------------------------------------------
    # 异步接口
    # --------------------------------------------------------
    
    async def search_async(
        self,
        query: str,
        session: aiohttp.ClientSession,
        follow_up: Optional[list[str]] = None,
        with_screenshot: bool = False
    ) -> dict:
        """异步搜索(批量查询推荐使用)"""
        payload = self._build_payload(query, follow_up, with_screenshot)
        
        async with session.post(
            self.SCRAPE_API_URL,
            headers=self._headers,
            json=payload,
            timeout=aiohttp.ClientTimeout(total=self.timeout)
        ) as response:
            data = await response.json()
            return self._parse_response(query, data)
    
    async def batch_search(
        self,
        queries: list[str],
        max_concurrent: int = 5,
        delay_between: float = 0.2
    ) -> list[dict]:
        """
        批量异步查询
        
        Args:
            queries: 查询关键词列表
            max_concurrent: 最大并发数(推荐5-10,不超过20)
            delay_between: 每批次间隔秒数
        """
        results = []
        semaphore = asyncio.Semaphore(max_concurrent)
        
        async def bounded_search(query: str, session: aiohttp.ClientSession):
            async with semaphore:
                await asyncio.sleep(delay_between)
                return await self.search_async(query, session)
        
        connector = aiohttp.TCPConnector(limit=max_concurrent)
        async with aiohttp.ClientSession(connector=connector) as session:
            tasks = [bounded_search(q, session) for q in queries]
            results = await asyncio.gather(*tasks, return_exceptions=True)
        
        return list(results)
    
    # --------------------------------------------------------
    # 私有方法
    # --------------------------------------------------------
    
    def _build_payload(
        self,
        query: str,
        follow_up: Optional[list[str]],
        screenshot: bool
    ) -> dict:
        """构建 API 请求体"""
        search_url = self.GOOGLE_AI_MODE_TEMPLATE.format(q=quote(query))
        payload = {
            "url": search_url,
            "parserName": "googleAISearch",
            "screenshot": screenshot
        }
        if follow_up:
            # 官方:超过5条效率降低,截断保护
            payload["param"] = follow_up[:self.MAX_PARAM_COUNT]
        return payload
    
    def _parse_response(self, query: str, raw: dict) -> dict:
        """解析 API 响应为标准化结构"""
        if raw.get("code") != 0:
            raise ValueError(
                f"API 返回错误 [code={raw.get('code')}]: {raw.get('message')}"
            )
        
        data = raw["data"]
        result = {
            "query": query,
            "has_ai_overview": bool(data.get("ai_overview", 0)),
            "ai_content": [],
            "references": [],
            "screenshot_url": data.get("screenshot", ""),
            "task_id": data.get("taskId", "")
        }
        
        for item in data.get("json", {}).get("items", []):
            if item.get("type") != "ai_overview":
                continue
            for sub in item.get("items", []):
                if sub.get("type") == "ai_overview_elem":
                    result["ai_content"].extend(sub.get("content", []))
            result["references"] = [
                {
                    "title": r.get("title", ""),
                    "url": r.get("url", ""),
                    "domain": r.get("domain", "")
                }
                for r in item.get("references", [])
            ]
        
        return result

使用示例

# 初始化
skill = AISearchSkill(api_key="YOUR_PANGOLINFO_API_KEY")

# 单次查询
result = skill.search("best Python scraping library 2026")
if result["has_ai_overview"]:
    for line in result["ai_content"]:
        print(f"• {line}")

# 多轮对话
result = skill.search(
    "asyncio tutorial",
    follow_up=["show me basic example", "how to handle exceptions"]
)

# 批量查询(异步)
keywords = ["OpenClaw Agent", "Google AI Mode API", "Pangolinfo review"]
results = asyncio.run(skill.batch_search(keywords, max_concurrent=3))
for r in results:
    if not isinstance(r, Exception):
        print(f"{r['query']}: AI Overview={'Yes' if r['has_ai_overview'] else 'No'}")

最佳实践

Skill 设计原则

  1. 单一职责:AI搜索技能只负责获取 AI Mode 数据,内容解析交给 Agent 逻辑
  2. 幂等性:相同参数多次调用应产生一致结果(利用 taskId 实现请求去重)
  3. 优雅降级has_ai_overview = False 时返回空列表而非抛出异常

性能指标参考

查询类型平均响应时间AI Overview 触发率
信息型(How/What/Why)2-5s~70%
导航型(品牌词)1-3s~30%
交易型(购买相关)2-4s~50%
带追问的多轮(3条)5-10s基于首轮

积点优化策略

  • 查询前预判触发概率,低概率词可用标准解析器
  • 批量任务设置每日积点预算上限
  • 热词结果缓存 1-2 小时,避免重复消耗

总结

Google AI Mode 代表了搜索信息架构的一次范式迁移,而 Pangolinfo AI Mode API 是目前为 OpenClaw 生态提供最低接入成本的 AI Overview 数据方案。本文提供的生产级代码可以直接用于 OpenClaw Skill 注册,开发者只需替换 API Key 即可开始。