# SERP API ใน Node.js: fetch, worker pool และ pagination

> เรียก SerpCrypto SERP API จาก Node.js 18+ ด้วย fetch ที่มีมาให้ในตัว: ไคลเอนต์แบบมี type, worker pool ภายใต้ขีดจำกัด concurrency ของคุณ และลูป pagination

- URL: https://serpcrypto.com/th/docs/node/
- อัปเดตล่าสุด: 2026-10-22
- ผู้เผยแพร่: SerpCrypto (https://serpcrypto.com)

ไม่ต้องมี dependency: Node.js มี `fetch` ตั้งแต่เวอร์ชัน 18 เป็นต้นมา ไคลเอนต์เล็ก ๆ พร้อม JSDoc type สำหรับ editor และ TypeScript, pool ที่เคารพขีดจำกัดของบัญชีคุณ และลูป pagination ที่หยุดเมื่อผลลัพธ์หมด

**คำตอบสั้น — ฉันใช้ SERP API ใน Node.js ได้อย่างไร?**

เรียก `fetch("https://serpcrypto.com/api/v1/search?" + new URLSearchParams({ q }))` พร้อม header `Authorization: Bearer <key>` แล้วอ่าน `await res.json()` Node.js 18+ ไม่ต้องใช้แพ็กเกจ **SerpCrypto** คืนผลการค้นหา Google หรือ Bing เป็น JSON ในราคา 1 เครดิตต่อการค้นหาที่สำเร็จ เริ่มต้นที่ US$0.30/1K การค้นหา และไม่เรียกเก็บเงินเมื่อเกิดข้อผิดพลาด จ่ายด้วยคริปโต ไม่ต้อง KYC

## การตั้งค่า Node.js โดยสรุป

| คุณลักษณะ | ค่า |
| --- | --- |
| Runtime | Node.js 18 หรือใหม่กว่า (มี `fetch`, `AbortSignal.timeout` ในตัว) |
| โมดูล | ES modules: ไฟล์ `.mjs` หรือ `"type": "module"` ใน package.json |
| คีย์ | `process.env.SERPCRYPTO_API_KEY` |
| Timeout | 90 วินาทีด้วย `AbortSignal.timeout(90_000)` |
| การค้นหาแบบขนาน | พูลที่ปรับขนาดให้เท่ากับ `concurrency_limit` |
| Types | JSDoc typedefs ที่ VS Code และ `tsc --checkJs` อ่านได้ |

## จะเขียนไคลเอนต์แบบ typed ขนาดเล็กได้อย่างไร?

รวม endpoint, header และการจัดการข้อผิดพลาดไว้ในโมดูลเดียว บรรทัด JSDoc `@typedef` อธิบายคำตอบ ดังนั้นเอดิเตอร์จะเติม `organic_results` อัตโนมัติและตรวจ type ของโค้ดคุณได้โดยไม่ต้องมีขั้นตอน build ผู้ใช้ TypeScript สามารถคัดลอก shape เดียวกันไปไว้ใน interface ได้

ฟังก์ชัน `search` จะ throw `Error` พร้อม HTTP status และ `code` ข้อผิดพลาดของ API ไว้ในข้อความ จากนั้นโค้ดของคุณจะตัดสินใจได้ว่าจะ retry `429`, `502` และ `504` และหยุดเมื่อเจอข้อผิดพลาดอื่น ตามที่อธิบายไว้ใน [หน้าข้อผิดพลาด](https://serpcrypto.com/th/docs/errors/)

*serp-client.mjs*

```javascript
// 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 ได้อย่างไร?

เริ่มจำนวน worker เท่าที่บัญชีของคุณอนุญาต และให้แต่ละตัวดึงคำค้นหาถัดไปจากลิสต์ที่แชร์กัน พูลจะอ่าน `concurrency_limit` จาก endpoint เครดิตฟรี จึงไม่ส่งการค้นหาพร้อมกันมากกว่าที่บัญชีรับได้

คำค้นหาที่ล้มเหลวจะถูกบันทึกไว้ แล้ว worker ก็ไปต่อ รันคำที่ล้มเหลวอีกครั้งภายหลัง: ข้อผิดพลาดไม่มีค่าใช้จ่าย ดังนั้นรอบที่สองจึงฟรีจนกว่าจะสำเร็จ

*batch.mjs: พูลของ worker*

```javascript
// 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 ให้บริการตั้งแต่เลิกใช้ num=100 ในเดือนกันยายน 2025; แต่ละหน้าเพิ่มคือหนึ่งเครดิตเพิ่มเติม การตรวจหนึ่งคำค้นหาสามหน้าเสียสามเครดิต คำค้นหาที่มีผลลัพธ์เพียงหน้าเดียวเสียหนึ่งเครดิต

*pages.mjs*

```javascript
// 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 จากเบราว์เซอร์หรือ edge function ได้ไหม?

จากเซิร์ฟเวอร์หรือ edge function ได้: Cloudflare Workers, Vercel และ Deno ให้ `fetch` แบบเดียวกันทั้งหมด จากหน้าเบราว์เซอร์ ทำไม่ได้: key จะมองเห็นได้โดยผู้เข้าชมทุกคน ให้วาง route เล็กๆ บนเซิร์ฟเวอร์คั่นกลางที่เก็บ key ไว้และส่งต่อเฉพาะพารามิเตอร์ที่คุณอนุญาต

กฎเดียวกันใช้กับแอปมือถือ ทุกอย่างที่ส่งไปยังอุปกรณ์ของผู้ใช้สามารถถูกอ่านได้ ดังนั้น key จึงต้องอยู่บนเซิร์ฟเวอร์ของคุณ

## จะควบคุมค่าใช้จ่ายใน Node.js ให้คาดเดาได้อย่างไร?

นับจำนวนการเรียกที่คุณทำ: การเรียกที่สำเร็จหนึ่งครั้งคือหนึ่งเครดิต ที่ US$0.56/1K การค้นหา บนแพ็ก 100,000 การค้นหา ตรวจ `credits_remaining` ก่อนเริ่มแบตช์ใหญ่ และหยุดพูลเมื่อใกล้ศูนย์แทนที่จะเก็บคำตอบ `402` ติดกันหลายอัน เครดิตการค้นหาแบบเติมเงินไม่มีวันหมดอายุ: คำค้นหาที่ไม่ได้ใช้จะคงอยู่ในบัญชีของคุณจนกว่าคุณจะใช้ ส่วนที่เหลือที่ยังไม่ได้ใช้จึงเก็บไว้สำหรับงานถัดไป

## คำถามเกี่ยวกับ Node.js

### มีแพ็กเกจ npm สำหรับ SerpCrypto ไหม?

ไม่มี ไคลเอนต์ด้านบนมีขนาดประมาณ 40 บรรทัดของ JavaScript มาตรฐานที่ใช้ `fetch` จึงไม่มีอะไรของเราที่ต้องติดตั้ง คัดลอกลงในโปรเจกต์ของคุณแล้วปรับใช้ได้เลย

### ใช้กับ CommonJS (require) ได้ไหม?

ตัวอย่างใช้ ES modules สำหรับ `await` ระดับบนสุด ในไฟล์ CommonJS ให้ห่อการเรียกด้วยฟังก์ชัน `async` และใช้ `fetch` แบบเดียวกัน; มันเป็น global ใน Node.js 18+

### จะใช้กับ TypeScript ได้อย่างไร?

คัดลอก JSDoc shape ไปไว้ใน `interface SearchResponse` แล้วกำหนด type ของผลลัพธ์จาก `res.json()` ด้วย shape นั้น บล็อกที่เป็น optional เช่น `organic_results?` ต้องคงเป็น optional ไว้ เพราะหน้าเว็บอาจไม่แสดงบล็อกเหล่านั้น

### ต้องใช้ Node.js เวอร์ชันไหน?

Node.js 18 ขึ้นไปสำหรับ `fetch` ที่มีมาในตัว `AbortSignal.timeout` ใช้ได้ตั้งแต่ Node.js 17.3 ดังนั้น 18 ครอบคลุมทุกอย่างในหน้านี้

---

SerpCrypto: SerpCrypto คือ SERP API สำหรับนักพัฒนาและ AI agent ที่ส่งคืนผลการค้นหา Google และ Bing เป็น JSON จ่ายด้วยคริปโตโดยไม่ต้องอัปโหลดบัตรประชาชน เครดิตการค้นหาแบบเติมเงินจาก US$0.30/1K การค้นหา ไม่มีวันหมดอายุ ไม่ต้อง KYC — ไม่ต้องใช้บัตรประชาชน ไม่ต้องเซลฟี่ ไม่ต้องใช้เอกสาร จ่ายด้วยคริปโต
