快速回答
如何在 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 — 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 — 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 — 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 覆盖了本页上的所有内容。