Không cần KYC — không giấy tờ tùy thân, không selfie, không tài liệu. Thanh toán bằng crypto: BTC, USDT, USDC, ETH, SOL, LTC
Ngôn ngữ: Tiếng Việt
Lấy khóa API

Docs · Node.js 18+ · fetch · ESM

SERP API trong Node.js: fetch, một worker pool và phân trang

Không cần dependency: Node.js có sẵn fetch từ phiên bản 18. Một client nhỏ với kiểu JSDoc cho editor và TypeScript, một pool tôn trọng giới hạn của tài khoản bạn, và một vòng lặp phân trang dừng khi hết kết quả.

Cập nhật lần cuối: · 5 phút đọc

Trả lời nhanh

Làm sao dùng SERP API trong Node.js?

Gọi fetch("https://serpcrypto.com/api/v1/search?" + new URLSearchParams({ q })) với header Authorization: Bearer <key> và đọc await res.json(). Node.js 18+ không cần package nào. SerpCrypto trả về kết quả Google hoặc Bing dưới dạng JSON với 1 credit mỗi lần tìm kiếm thành công, từ 0,30 US$/1K lượt tìm kiếm, và không bao giờ tính phí lỗi. Thanh toán bằng crypto, không cần KYC.

Thiết lập Node.js trong nháy mắt

RuntimeNode.js 18 trở lên (có sẵn fetch, AbortSignal.timeout)
ModulesES modules: tệp .mjs hoặc "type": "module" trong package.json
Khóaprocess.env.SERPCRYPTO_API_KEY
Timeout90 giây với AbortSignal.timeout(90_000)
Tìm kiếm song songMột pool có kích thước bằng concurrency_limit
Kiểu dữ liệuJSDoc typedefs, được VS Code và tsc --checkJs đọc

Làm thế nào để viết một client nhỏ có kiểu dữ liệu?

Đặt endpoint, header và xử lý lỗi trong một module. Các dòng JSDoc @typedef mô tả câu trả lời, nên trình soạn thảo tự động hoàn thành organic_results và kiểm tra kiểu cho mã của bạn mà không cần bước build. Người dùng TypeScript có thể sao chép cùng các cấu trúc đó vào một interface.

Hàm search ném một Error với HTTP status và code lỗi của API trong thông điệp của nó. Mã của bạn sau đó có thể quyết định thử lại 429, 502 và 504 và dừng lại với các trường hợp khác, như trang lỗi giải thích.

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();
}

Làm thế nào để chạy tìm kiếm song song mà không bị 429?

Khởi động số worker tối đa mà tài khoản của bạn cho phép, và để mỗi worker lấy từ khóa tiếp theo từ một danh sách chung. Pool đọc concurrency_limit từ endpoint credit miễn phí, nên nó không bao giờ gửi nhiều tìm kiếm cùng lúc hơn mức tài khoản chấp nhận.

Một từ khóa thất bại được ghi lại và worker chuyển sang từ khóa tiếp theo. Chạy lại các từ khóa thất bại sau đó: lỗi không tốn gì, nên lượt chạy thứ hai miễn phí cho đến khi thành công.

batch.mjs: một pool worker
// 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));

Làm thế nào để lấy nhiều trang kết quả?

Lặp qua page và dừng ngay khi pagination.next_page không còn. Khoảng 10 kết quả organic mỗi trang, đúng như Google đã trả về kể từ khi ngừng áp dụng num=100 vào tháng 9 năm 2025; mỗi trang thêm là thêm một credit. Kiểm tra ba trang cho một từ khóa tốn ba credit; một từ khóa chỉ có một trang kết quả tốn một credit.

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" }));

Tôi có thể gọi API từ trình duyệt hoặc edge function không?

Từ một server hoặc edge function, có: Cloudflare Workers, Vercel và Deno đều cung cấp cùng fetch. Từ một trang trình duyệt, không: khóa sẽ hiển thị với mọi người truy cập. Hãy đặt một route server nhỏ ở giữa để giữ khóa và chỉ chuyển tiếp các tham số bạn cho phép.

Quy tắc tương tự áp dụng cho ứng dụng di động. Bất cứ thứ gì gửi đến thiết bị của người dùng đều có thể bị đọc, nên khóa phải nằm trên server của bạn.

Làm thế nào để giữ chi phí dự đoán được trong Node.js?

Đếm số lần gọi bạn thực hiện: một lần gọi thành công là một credit, với 0,56 US$/1K lượt tìm kiếm trên pack 100.000 tìm kiếm. Kiểm tra credits_remaining trước một batch lớn và dừng pool khi gần về 0 thay vì thu thập một loạt câu trả lời 402. Credit tìm kiếm trả trước không bao giờ hết hạn: các lượt tìm kiếm chưa dùng vẫn nằm trong tài khoản của bạn cho đến khi bạn dùng, nên phần dư chưa dùng được giữ lại cho công việc tiếp theo.

Câu hỏi về Node.js

Có package npm cho SerpCrypto không?

Không. Client ở trên chỉ khoảng 40 dòng JavaScript tiêu chuẩn dùng fetch, nên không có gì của chúng tôi để cài đặt. Sao chép nó vào dự án của bạn và điều chỉnh.

Cái này có hoạt động với CommonJS (require) không?

Các ví dụ dùng ES modules để await ở cấp cao nhất. Trong một tệp CommonJS, hãy bọc các lời gọi trong một hàm async và dùng fetch theo cùng cách; nó là global trong Node.js 18+.

Làm thế nào để dùng với TypeScript?

Sao chép các cấu trúc JSDoc vào một interface SearchResponse và gán kiểu cho kết quả của res.json() với nó. Các khối tùy chọn như organic_results? phải vẫn là tùy chọn, vì một trang có thể không hiển thị chúng.

Tôi cần phiên bản Node.js nào?

Node.js 18 trở lên cho fetch tích hợp sẵn. AbortSignal.timeout có từ Node.js 17.3, nên 18 bao phủ mọi thứ trên trang này.

Từ 0,30 US$/1K lượt tìm kiếm · không cần KYC

Lấy khóa API