无需 KYC — 无需 ID、无需自拍、无需文件。使用加密货币支付:BTC, USDT, USDC, ETH, SOL, LTC
语言: 简体中文
获取 API 密钥

文档 · Node.js 18+ · fetch · ESM

Node.js 中的 SERP API:fetch、worker pool 和分页

无需依赖:Node.js 自 18 版起就内置了 fetch。一个小型客户端,带 JSDoc 类型供编辑器和 TypeScript 使用;一个遵守账号上限的 pool;以及一个在结果用尽时停止的分页循环。

最后更新: · 3 分钟阅读

快速回答

如何在 Node.js 中使用 SERP API?

带 header Authorization: Bearer <key> 调用 fetch("https://serpcrypto.com/api/v1/search?" + new URLSearchParams({ q })),并读取 await res.json()。Node.js 18+ 无需任何包。SerpCrypto 以 JSON 返回 Google 或 Bing 的结果,每次成功搜索 1 积分起价 US$0.30/千次搜索,错误从不计费。加密货币支付,无需 KYC。

Node.js 配置一览

运行时Node.js 18 或更高版本(内置 fetch、AbortSignal.timeout)
模块ES modules:.mjs 文件或 package.json 中的 "type": "module"
密钥process.env.SERPCRYPTO_API_KEY
超时使用 AbortSignal.timeout(90_000) 时为 90 秒
并行查询一个按 concurrency_limit 调整大小的池
类型JSDoc 类型定义,VS Code 和 tsc --checkJs 可读取

如何编写一个小型的带类型客户端?

把 endpoint、header 和错误处理放在同一个模块里。JSDoc @typedef 行描述返回结果,因此编辑器可以自动补全 organic_results 并对你的代码做类型检查,无需构建步骤。TypeScript 用户可以把同样的结构复制到一个 interface 中。

search 函数会抛出一个 Error,其消息中包含 HTTP 状态码和 API 的错误 code。这样你的代码就可以决定对 429、502 和 504 进行重试,对其他错误则停止,正如错误页面所说明的那样。

serp-client.mjs
// serp-client.mjs — Node.js 18+, no dependency
const API_URL = "https://serpcrypto.com/api/v1/search";
const CREDITS_URL = "https://serpcrypto.com/api/v1/credits";
const headers = { Authorization: `Bearer ${process.env.SERPCRYPTO_API_KEY}` };

/**
 * @typedef {Object} OrganicResult
 * @property {number} position
 * @property {string} title
 * @property {string} link
 * @property {string} [snippet]
 * @property {string} [date]
 */

/**
 * @typedef {Object} SearchResponse
 * @property {Object} search_metadata
 * @property {string} search_metadata.id
 * @property {string} search_metadata.status
 * @property {number} search_metadata.credits_used
 * @property {OrganicResult[]} [organic_results]
 * @property {Object} [pagination]
 * @property {number} pagination.current
 * @property {number} [pagination.next_page]
 */

/**
 * Run one search (1 credit when it succeeds).
 * @param {string} q
 * @param {Record<string, string | number>} [params]
 * @returns {Promise<SearchResponse>}
 */
export async function search(q, params = {}) {
  const query = new URLSearchParams({ q, ...Object.fromEntries(Object.entries(params).map(([k, v]) => [k, String(v)])) });
  const res = await fetch(`${API_URL}?${query}`, { headers, signal: AbortSignal.timeout(90_000) });
  if (!res.ok) {
    const body = await res.json().catch(() => ({}));
    const err = new Error(`${res.status} ${body.error?.code ?? "error"}: ${body.error?.message ?? ""}`);
    err.status = res.status;
    throw err;
  }
  return res.json();
}

/** Free: credits left, used today and the concurrency limit. */
export async function credits() {
  const res = await fetch(CREDITS_URL, { headers });
  if (!res.ok) throw new Error(`credits: HTTP ${res.status}`);
  return res.json();
}

如何在不触发 429 的情况下并行运行查询?

启动尽可能多的工作进程(数量以账户允许为限),让每个进程从共享列表中取下一个关键词。这个池会从免费积分 endpoint 读取 concurrency_limit,因此它一次发送的查询数量绝不会超过账户可接受的数量。

失败的关键词会被记录下来,工作进程继续处理下一个。之后再重新运行这些失败项:错误不产生费用,所以第二次运行在成功之前都是免费的。

batch.mjs:一个工作进程池
// batch.mjs — parallel searches, never more than the account's limit at once
import { search, credits } from "./serp-client.mjs";

const keywords = ["password manager", "note taking app", "time tracking software", "invoice software"];
const { concurrency_limit: limit } = await credits();

let next = 0;
const results = new Map();

async function worker() {
  while (next < keywords.length) {
    const kw = keywords[next++];
    try {
      const data = await search(kw, { gl: "us", hl: "en" });
      results.set(kw, data.organic_results?.[0]?.link ?? null);
    } catch (err) {
      results.set(kw, `failed: ${err.message}`);
    }
  }
}

await Promise.all(Array.from({ length: Math.min(limit, keywords.length) }, worker));
console.table(Object.fromEntries(results));

如何获取多页结果?

遍历 page,一旦 pagination.next_page 缺失就停止。每页约 10 条自然结果,这是 Google 自 2025 年 9 月起不再支持 num=100 后的返回方式;每多一页就多消耗 1 积分。对一个关键词做三页检查会消耗 3 个积分;只有一页结果的关键词则消耗 1 个。

pages.mjs
// pages.mjs — up to three pages, stop when results end
import { search } from "./serp-client.mjs";

async function topResults(q, pages = 3, params = {}) {
  const rows = [];
  for (let page = 1; page <= pages; page++) {
    const data = await search(q, { ...params, page });
    for (const r of data.organic_results ?? []) rows.push({ page, position: r.position, link: r.link });
    if (!data.pagination?.next_page) break; // nothing more: save the credits
  }
  return rows;
}

console.log(await topResults("headless cms", 3, { gl: "de", hl: "en" }));

我可以在浏览器或边缘函数中调用 API 吗?

从服务器或边缘函数调用可以:Cloudflare Workers、Vercel 和 Deno 都提供相同的 fetch。从浏览器页面调用则不行:API 密钥会暴露给每一位访问者。在中间放一个小的服务器路由,由它持有密钥,并且只转发你允许的参数。

同样的规则适用于移动应用。任何发送到用户设备上的内容都可能被读取,因此密钥要留在你的服务器上。

如何在 Node.js 中让成本可预测?

统计你发起的调用:一次成功的调用就是一个积分,在 100,000 次搜索 套餐上为 US$0.56/千次搜索。在大批量运行前检查 credits_remaining,当余额接近零时停止这个池,而不是收集一连串 402 响应。预付的搜索积分永不过期:未使用的查询会一直留在你的账户中,直到你使用它们,因此未用完的余额会保留到下一次任务。

Node.js 问题

SerpCrypto 有 npm 包吗?

没有。上面的客户端是约 40 行标准 JavaScript,使用 fetch,因此我们没有需要安装的东西。把它复制到你的项目中并加以调整即可。

它能在 CommonJS(require)下使用吗?

示例使用 ES modules,以便在顶层使用 await。在 CommonJS 文件中,把调用包在一个 async 函数里,并同样使用 fetch;它在 Node.js 18+ 中是全局的。

如何在 TypeScript 中使用它?

把 JSDoc 结构复制到一个 interface SearchResponse 中,并用它为 res.json() 的结果标注类型。像 organic_results? 这样的可选块必须保持可选,因为页面可能不显示它们。

我需要哪个 Node.js 版本?

使用内置的 fetch 需要 Node.js 18 或更高版本。AbortSignal.timeout 从 Node.js 17.3 起可用,因此 18 覆盖了本页上的所有内容。

起价 US$0.30/千次搜索 · 无需 KYC

获取 API 密钥