簡潔な回答
Node.jsでSERP APIを使うにはどうすればよいですか?
ヘッダーAuthorization: Bearer <key>を付けてfetch("https://serpcrypto.com/api/v1/search?" + new URLSearchParams({ q }))を呼び出し、await res.json()を読み取ります。Node.js 18+ではパッケージは不要です。SerpCryptoはGoogleまたはBingの検索結果をJSONで返し、成功した検索1回につき1クレジット、$0.30/1K検索から利用でき、エラーは課金されません。暗号資産で支払い、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 typedef。VS Codeとtsc --checkJsが読み取ります |
型付きの小さなクライアントはどう書けばいいですか?
endpoint、header、エラー処理を1つのモジュールにまとめます。JSDocの@typedef行が応答を記述しているので、エディタはビルドステップなしでorganic_resultsを自動補完し、コードを型チェックできます。TypeScriptユーザーは同じ形をinterfaceにコピーできます。
search関数は、HTTPステータスとAPIのエラーメッセージcodeを含むErrorをスローします。そうすればコード側で、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を読み取るので、アカウントが受け付ける数を超えて一度に検索を送ることはありません。
失敗したキーワードは記録され、ワーカーは次へ進みます。あとで失敗分を再実行してください。エラーにはクレジットがかからないので、成功するまで2回目は無料です。
// 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がなくなった時点で停止します。1ページあたり約10件のオーガニック結果です。Googleは2025年9月にnum=100を尊重しなくなって以降、この件数を返しています。追加の各ページにはさらに1クレジットが必要です。1つのキーワードで3ページ分を確認すると3クレジットかかります。結果が1ページしかないキーワードなら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を提供しています。ブラウザページからは不可です。キーがすべての訪問者に見えてしまいます。間に小さなサーバールートを置き、キーを保持して、許可したパラメータだけを転送してください。
同じルールがモバイルアプリにも当てはまります。ユーザーの端末に配布されたものは何でも読み取れるため、キーはサーバー上に置きます。
Node.jsでコストを予測可能にするには?
呼び出し回数を数えます。成功した呼び出し1回が1クレジットで、100,000検索パックでは$0.56/1K検索です。大量のバッチの前にcredits_remainingを確認し、0に近づいたら402の応答を重ねて集めるのではなくプールを停止します。プリペイドの検索クレジットに有効期限はありません。未使用の検索は、使用するまでアカウントに残りますので、使い切らなかった残りは次のジョブに残ります。
Node.jsに関する質問
SerpCrypto用のnpmパッケージはありますか?
いいえ。上記のクライアントはfetchを使った標準JavaScriptで約40行なので、インストールするものはありません。プロジェクトにコピーして調整してください。
CommonJS(require)で動作しますか?
例ではトップレベルのawaitのためにES modulesを使っています。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ならこのページのすべてをカバーできます。