返回博客

Google搜索抓取器API:五个返回空数据的默认值

Daniel Kim
Daniel Kim

Lead Scraping Automation Engineer

20-Aug-2026

TL;DR:

  • 来自 Google Search actor 的 200 不是数据的证明:响应可能携带一个空的 organic_results 数组、一个广告占位符而不是一个列表,或者一个设计上为空的字段。
  • Dify 预填充了两个 API 密钥默认值,它们均生成 401{"code":14404,"message":"invalid access token"} — 头部名称默认为 Authorization,头部前缀默认为 Basic,演员都不接受这两个值。
  • 一个 n8n 工作流可以在没有错误的情况下进行验证,但在运行时仍可能失败,因为 2.34.4 版本的 Code 节点沙箱不暴露全局 URL 构造函数。
  • 一个具备搜索工具的代理可以在不调用的情况下回答,生成流畅的文本,这些文本从未接触 API;计算工具调用将这种沉默的失误变成失败。
  • 本地包记录返回 place_idgps_coordinatesthumbnail 空,以及 phonetypehours 带有前导空格 — 这两种情况都是文档化的行为,而不是待调试的故障。

scraper.google.search actor 接受查询并返回解析后的 SERP 作为 JSON。这是 Deep SerpApi 的 Google 表面,通常是第一个接入工作流构建器或代理框架的 actor,因为排名结果列表同样有利于排名跟踪和潜在客户研究。

下面的失败并不奇特。它们来自于 被接受 的请求和 可用 的有效负载之间的差距 — 其中每一个都可以从本文使用的四个主机平台(Dify、n8n、Activepieces 和 LangChain)中复制。

请求:端点、actor 和参数

每个调用都是对单个端点的 POST,包含两个字段。 actor 选择了爬虫,input 传递其参数:

bash Copy
curl -sS -X POST https://api.scrapeless.com/api/v1/scraper/request \
  -H 'Content-Type: application/json' \
  -H "x-api-token: $SCRAPELESS_API_KEY" \
  -d '{"actor":"scraper.google.search","input":{"q":"web scraping api"}}'

三个参数涵盖了大多数工作:

参数 目的
q 查询字符串。
tbm 结果类型。 lcl 返回本地包而不是网页结果。
start 用于分页的结果偏移量 — 本地包每页 20 条。

身份验证头为 x-api-token。该名称是无代码平台最可能用其默认值填充的字段。 HTTP 401 响应的语义规范 期待与资源自身身份验证方案相关的挑战,因此假设 Authorization 的平台是合情合理的 — 它只是对这个端点假设了错误的方案。

响应封装

在读取数据之前请先读取封装。成功的 Google Search 调用返回这些顶级键:

json Copy
// illustrative sample — key shape only; values omitted
{
  "search_information": {},
  "organic_results": [],
  "related_searches": [],
  "pagination": {},
  "metadata": {}
}

由该形状可得出两件事。没有 success 标志可供分支,因此 organic_results 的存在和长度是信号。而且在这个封装中没有 People-Also-Ask 块 — 在浏览器中显示相关问题的查询在这里返回 related_searches,因此预期会得到问题数组的工作流得到 None 并写入一个空列。

tbm 设置为 lcl 时,结果移动到 local_results.places[] 而不是 organic_results[]。一个硬编码一个路径的管道在请求另一个时静默地什么也不产生。

在代码中读取响应

请求后的断言是值得复制的部分。此示例引发错误,而不是返回一个空列表,因此故障发生的位置表现出来,而不是在电子表格中三步后:

python Copy
import json
import os
import urllib.request

ENDPOINT = "https://api.scrapeless.com/api/v1/scraper/request"


def search(query: str) -> dict:
    payload = json.dumps({"actor": "scraper.google.search", "input": {"q": query}}).encode()
    request = urllib.request.Request(
        ENDPOINT,
        data=payload,
        headers={
            "Content-Type": "application/json",
            "x-api-token": os.environ["SCRAPELESS_API_KEY"],
        },
    )
    # urlopen raises HTTPError on any 4xx or 5xx, so a rejected call never reaches the parser.
    with urllib.request.urlopen(request, timeout=120) as response:
        return json.loads(response.read())


serp = search("web scraping api")
organic = serp.get("organic_results") or []

if not organic:
    raise SystemExit(f"no organic_results in the response; envelope was {sorted(serp)}")

print(f"organic_results: {len(organic)}")
print(f"first result: {organic[0]['title']}")
print(f"envelope keys: {sorted(serp)}")

缺失和空是不同的状态,JSON 交换格式规范 并未帮助你区分 “键被省略” 与 “值是空字符串”。在你编写第一个插入之前,请决定你的管道将哪种视为错误。

在免费计划上处理这一点足以看到这里描述的每种行为 — 创建一个 Scrapeless 账号 并在以下四个平台上使用同一个密钥。

返回空数据的五个默认设置

你平台预填充的 API 密钥头是错误的

在 Dify 1.16.1 中,将 OpenAPI 架构作为自定义工具导入,并选择 API Key 身份验证会将两个字段留在默认值,actor 会拒绝这些值。头部名称默认为 Authorization,头部前缀默认为 Basic — 即使你更正名称,仍会发送 x-api-token: Basic <key>。两者产生相同的响应:

json Copy
{ "code": 14404, "message": "invalid access token" }

一个错误消息,两个独立的原因,这使得诊断变得昂贵。工作配置命名了所有三个:

字段
鉴权类型 API Key
头名称 x-api-token
头前缀 Custom

Dify 还将嵌套的请求体对象扁平化为一个 字符串 参数,因此 input 字段以文本而不是结构化对象的形式到达。一个对象和一个 JSON 字符串都是可以接受的,这就是为什么这个问题很少被注意,直到下游节点尝试读取 input.q

一个通过验证的工作流仍然可能在运行时失败

在 n8n 代码节点中,静态验证和执行不一致。使用 new URL(link).hostname 按域分组结果的工作流验证时没有错误,但在第一个项目上因 URL is not defined 失败。版本 2.34.4 的沙盒并未公开该全局变量,尽管 WHATWG URL 标准 将其定义为 Web API 构造函数,n8n 自己的 关于未使用 URL 构造函数而导致代码节点失败的报告 记录了该症状。

改为使用字符串操作导出主机名:

javascript Copy
// The Code node sandbox does not expose the global URL constructor,
// so the hostname comes from string operations.
const hostname = (link) =>
  link ? link.replace(/^[a-z]+:\/\//i, '').replace(/^www\./i, '').split(/[/?#]/)[0] : '';

const results = [
  { position: 1, link: 'https://www.scrapeless.com/zh/product/deep-serp-api' },
  { position: 2, link: 'https://docs.scrapeless.com/en/deep-serp-api/quickstart/introduction/' },
];

for (const result of results) {
  console.log(result.position, hostname(result.link));
}

工作流构建器中的验证检查图形,而不是节点内部的代码。因此,绿色勾选并不能说明代码节点是否会执行。

解析为无的步骤引用

在 Activepieces 0.82.0 中,HTTP 步骤解析的 JSON 存在于 body 下。引用是 {{step_1.body.organic_results}},而 {{step_1.organic_results}} 完全解析为无——没有错误,没有警告,仅仅是一个空循环和一条报告成功的运行。将 tbm 设置为 lcl,路径为 {{step_1.body.local_results.places}}

缺失引用的失败看起来与真正的空结果集相同,因此在寻找数据问题之前,请检查引用路径。

不调用工具的代理

给代理一个搜索工具,它可能不会使用它。一个小模型同时拥有搜索工具和获取工具时,通常会先运行搜索,然后在描述“页面内容”时从结果片段中回答——从未检索页面。文笔流畅,引用隐含,因此输出中的任何内容都没有标记答案为毫无根据。

解决方案是一个断言,而不是一个更好的提示。计算工具调用并将零视为失败:

注意:此代码段包装了一个现有代理,因此运行它需要一个构造的 LangChain 代理和一个模型提供者密钥。它依赖的所有内容都是标准 agent.stream(...) 输出。

python Copy
tool_calls = 0
for chunk in agent.stream({"messages": [("human", question)]}, stream_mode="values"):
    message = chunk["messages"][-1]
    tool_calls += len(getattr(message, "tool_calls", None) or [])

if tool_calls == 0:
    raise SystemExit("the model answered without calling a tool; the answer is not grounded")

单工具指令在小模型上是可靠的。链式指令——搜索,然后获取顶部结果——就是工具调用神秘消失的地方,因此请将步骤分开处理,让模型一次处理一个调用。

有意空白的字段

一些空白值是正确的。在 local-pack 结果中,place_idgps_coordinatesthumbnail 返回为空,而 phonetypehours 则带有前导空格。这两者都不是缺陷,并且两者都会破坏天真的代码:尾随空格不匹配会将去重键变为重复,并且将空的 place_id 视为错误会让你调试已按文档正常工作的行为。

在进入时进行规范化:

字段 行为 处理
phonetypehours 前导空格 存储或比较前修剪。
place_idgps_coordinatesthumbnail 本地结果为空 视为可为空;不以此为记录的门槛。
organic_resultslocal_results.places 取决于 tbm 从请求中选择路径,而不是猜测。

相同的原则适用于计数。结果数组可以包含赞助插槽和布局占位符以及列表,因此数组的长度并不是结果数量——在报告统计之前,请先根据记录的类型字段进行过滤,否则每个下游数字都会继承页面恰巧提供的广告负载。

结论

在无代码抓取设置中的代价高昂的失败,以绿色结束,但其中没有任何内容:一个平台预先填充的鉴权头、沙盒省略的 Web API 全局、缺少一个段的引用路径、跳过工具的代理,或一个始终会为空的字段。每个都有一个单行修复而没有指向它的错误消息。
两个习惯捕捉所有五个。先阅读响应信封再看数据,并对你期望的内容进行断言——一个非空数组、一个工具调用、一个记录类型——这样一个静默的遗漏就会在导致它的那一步变成一个响亮的失败。 n8n抓取工作流程指南LangChain集成演练 显示了同一个参与者在这些检查到位后端到端的连接。

准备好构建一个返回文档信封的SERP表面吗?查看 Deep SerpApi文档 以获取完整的参数集,审查 计划和包含的容量,并 开始免费计划

常见问题解答

问:为什么我的Google搜索演员调用返回 200,而organic_results数组为空?

一个空的organic_results数组与200意味着请求已被接受并解析,但对于该查询形状没有产生网页结果。依次检查三件事:tbm是否设置为lcl,这会将结果移动到local_results.places[];查询本身是否具有结果意图;你的平台是否在读取解析的主体而不是信封。响应中没有success标志,因此数组的长度是唯一的信号。

问:当密钥是正确的,是什么导致{"code":14404,"message":"invalid access token"}

该响应意味着密钥以端点期望的形式从未到达。头部必须是x-api-token,携带纯密钥。默认使用Authorization的平台,或在值前加上BasicBearer的,发送的头部是端点无法读取的——而消息在每种情况下都是相同的,因此请分别验证头部名称和任何前缀设置。

问:为什么我的n8n代码节点在工作流程验证时失败并显示URL is not defined

n8n 2.34.4中的代码节点沙箱不公开全局URL构造函数,并且工作流程验证不会执行节点代码,因此图表通过了检查,而运行在第一个项目上失败。使用字符串操作解析主机名,或者将URL处理移入一个提供API的节点。

问:我如何判断代理是否真的使用了搜索工具?

计算流式消息中的工具调用次数,当计数为零时失败。一个模型可以在不调用任何工具的情况下产生完整、自信的答案,文本中没有任何东西将其与有根据的答案区分开。将工具调用计数视为硬性要求,而不是检查文本。

问:空的place_idgps_coordinates值是一个bug吗?

不是。Local-pack记录返回place_idgps_coordinatesthumbnail为空,因此这些字段是可空的设计。保留记录并从现有字段中填充位置,而不是丢弃行或在预期行为周围添加错误处理。

问:为什么我的Activepieces循环在HTTP步骤成功时迭代零次?

解析的响应嵌套在body下,因此{{step_1.organic_results}}解析为空,而{{step_1.body.organic_results}}解析为数组。Activepieces 0.82.0中缺少的引用不会产生错误——循环简单地什么也没有,运行仍然报告成功,这使其在你检查路径之前与空结果集无异。

在Scrapeless,我们仅访问公开可用的数据,并严格遵循适用的法律、法规和网站隐私政策。本博客中的内容仅供演示之用,不涉及任何非法或侵权活动。我们对使用本博客或第三方链接中的信息不做任何保证,并免除所有责任。在进行任何抓取活动之前,请咨询您的法律顾问,并审查目标网站的服务条款或获取必要的许可。

最受欢迎的文章

目录