Node.js Fetch API: מה כל מדריך מדלג עליו (מדריך 2026)

עודכן לאחרונה ב- May 14, 2026
Node.js Fetch API: מה כל מדריך מדלג עליו (מדריך 2026)
סיכום AI
מדריך מעשי ל־fetch ב־Node.js ב־2026: איך לטפל בשגיאות, להגדיר timeouts, להוסיף retry, למנף Undici ולדעת מתי לעבור ל־Thunderbit לחילוץ נתונים מובנים.

כל מדריך ל־fetch ב־Node.js אומר לכם פשוט await fetch(url) ולקרוא לזה יום. ואז אפליקציית הייצור שלכם בולעת בשקט שגיאת 500, בקשה נתקעת ל־90 שניות בלי timeout, ואתם מוצאים את עצמכם בערב שישי מנַגנים באגים במשהו שהיה אמור להיות מובן מאליו.

בניתי כבר זמן מה כלים פנימיים וצינורות נתונים ב-Thunderbit, ואני יכול לומר לכם: הפער בין "fetch עובד לי במדריך" לבין "fetch עובד בייצור" הוא המקום שבו רוב הכאב נמצא. מפתח אחד ב־Reddit ניסח את זה מצוין: "כשמגיעים לייצור, מבינים שצריך משהו עמיד יותר מ־fetch המקורי."

אחר הודה: "עבדתי 3 שנים כמפתח ווב, ורק היום גיליתי שבלוק ה־catch של ה־fetch API הוא לא לשגיאות HTTP." המדריך הזה מכסה את חמשת הדברים שרוב המדריכים מדלגים עליהם — מלכודת השגיאות, timeouts עם AbortController, לוגיקת retry, שימוש חוזר בחיבורים, ומתי כדאי לעבור מעבר ל־fetch לצורך חילוץ נתונים מובנים. אם אי פעם קרה לכם שקריאת fetch נכשלה בשקט בייצור, זה בשבילכם.

nodejs-fetch-error-handling-flowchart.png

מהו Node.js Fetch API?

Node.js Fetch API הוא האופן המובנה, התואם לדפדפן, לשליחת בקשות HTTP ‏(GET, POST, PUT, DELETE וכו') מתוך Node.js — בלי להתקין Axios, ‏node-fetch או כל חבילה אחרת. אם השתמשתם ב־fetch() בדפדפן, אתם כבר מכירים את התחביר. עכשיו אותו API עובד גם בצד השרת.

הנה היסטוריית הגרסאות בקיצור:

ציון דרךגרסת Nodeמה קרה
דגל fetch ניסיוניv17.5.0 / v16.15.0הוספת fetch מאחורי --experimental-fetch
fetch גלובלי כברירת מחדלv18.0.0fetch ניסיוני זמין גלובלית, מופעל על ידי Undici
fetch יציבv21.0.0כבר לא ניסיוני
בסיס ייצור ל־2026v22 LTS / v24 LTSמומלץ לייצור; v20 הוא כעת EOL

מתחת למכסה המנוע, ה־fetch של Node מופעל על ידי Undici — לקוח HTTP עתיר ביצועים שנבנה במיוחד עבור Node.js. הוא לא מסתמך על המודול הוותיק http. היתרון המעשי: מקבלים API מודרני ל־HTTP מבוסס Promise, שעובד אותו דבר בקוד הדפדפן, ב־Express backend, בפונקציות serverless ובסקריפטים של CLI.

למה Node.js Fetch API חשוב לפרויקטים שלכם

לפני Node 18, כל פרויקט חדש התחיל באותו טקס: npm install axios או npm install node-fetch. ב־2026, אם הפרויקט שלכם רץ על Node LTS מתוחזק, בקשות HTTP בסיסיות לא דורשות שום תלות. זה יתרון אמיתי לגודל החבילה, לאבטחת שרשרת האספקה, ולהטמעה של מפתחים חדשים (מפתחי פרונט־אנד ובק־אנד סוף סוף חולקים את אותו API).

כאן ה־fetch המקורי באמת זורח:

תרחישלמה fetch מקורי עובד היטבאזהרת ייצור
backend של Express/Fastify שמתקשר ל־REST APIsasync/await מוכר, בלי תלותלהוסיף timeout ובדיקות response.ok
פונקציות serverless ‏(Lambda, Vercel וכו')שטח cold start קטן, בלי התקנת חבילותלשמור timeout מתחת למקסימום של הפלטפורמה
סקריפטים של CLI ואוטומציותGET/POST פשוט בלי הגדרת פרויקטלהוסיף retry/backoff ל־APIs לא יציבים
מסירת webhook או העברה שלומתודות ו־headers סטנדרטיים של HTTPלא לנסות retry עיוור ל־POST לא idempotent
דוחות ולוחות בקרהטוב לשליפת JSON מ־APIsלהשתמש ב־pagination וב־connection pooling בלולאות
תקשורת בין microservicesעובד לקריאות HTTP פנימיות פשוטותלשקול Got או Undici ישירות ל־retry, hooks או HTTP/2

בפרויקטי Node 22+ חדשים, fetch מקורי הוא ברירת המחדל ההגיונית — אלא אם אתם יודעים שאתם צריכים יכולות שאין בו (interceptors, ‏retry מובנה, ‏HTTP/2 וכו'). מספרי ההורדות ב־npm מספרים את סיפורו של שוק שנמצא במעבר: node-fetch עדיין מושך כ־144.9 מיליון הורדות שבועיות, אבל הרבה מזה הוא תלות ישנה ועקיפה. Axios עומד על כ־108.6 מיליון, Undici על כ־106 מיליון, Got על כ־36 מיליון, ו־Ky על כ־5.6 מיליון. המגמה ברורה: fetch מקורי הוא הבסיס החדש, ולקוחות צד שלישי נועדו לצרכים ספציפיים.

fetch מקורי מול node-fetch מול Axios מול Got מול Ky: מטריצת ההחלטה ל־2026

השאלה הנפוצה ביותר שאני רואה בפורומים למפתחים: "איזה HTTP client כדאי לי להשתמש ב־Node.js?" משתמש Reddit אחד ניסח זאת כך: "למה לייבא ספרייה… כשיש את הפונקציונליות מובנית בשפה/מסגרת?" נקודה הוגנת — אבל התשובה תלויה במה שאתם צריכים.

http-client-libraries-comparison.png

תכונהfetch מקוריnode-fetch v3axiosgot v15ky v2
גרסת Node.js≥18 (מומלץ 22/24 LTS)≥12.20רחב≥22≥22
נדרשת התקנהלאכןכןכןכן
תמיכה ב־ESM + CJSשניהם (גלובלי)ESM בלבד (v3)שניהםESM בלבדESM בלבד
דחייה אוטומטית על 4xx/5xxלאלאכןכןכן
retry מובנהלאלאלאכןכן
interceptors לבקשותלאלאכןכן (hooks)כן (hooks)
תמיכה ב־streamingWeb ReadableStreamכןמוגבלNode streams חזקיםמבוסס fetch
טביעת רגל של התקנה/חבילה0 KB~107 KB, ‏3 תלויות~2.8 MB, ‏4 תלויות~355 KB, ‏12 תלויות~405 KB, ‏0 תלויות
תמיכה ב־HTTP/2דרך Undici dispatcherלאלאכןלא (עטיפת fetch)

הערה קצרה על כאב הראש של ESM/CJS: ‏node-fetch v3 הוא ESM בלבד, וזה שבר הרבה פרויקטים שהשתמשו ב־require(). fetch מקורי הוא גלובלי — הוא עובד גם בקבצי CJS וגם ב־ESM בלי תעלולים של imports. אם אתם תקועים על node-fetch v2 בגלל CommonJS, fetch מקורי פותר את הבעיה הזו לגמרי.

ולגבי חששות יציבות מוקדמים: כן, היו באגים אמיתיים ביישום הראשוני של fetch ב־Node 18. מפתח אחד ב־Reddit ציין: "נתקלתי לאחרונה בבאג רציני ב־fetch המקורי של Node 18 ולכן היינו צריכים להעביר את האפליקציה." זה היה ב־2023. ב־2026, עם Node 22 ו־24 LTS, הבעיות האלה נפתרו. fetch מקורי מוכן לייצור.

מתי להישאר עם fetch מקורי

בחרו ב־fetch מקורי כש:

  • הפרויקט שלכם רץ על Node 22 LTS או Node 24 LTS.
  • הבקשות הן קריאות REST פשוטות (GET, POST, PUT, DELETE).
  • אתם מוכנים להוסיף מעטפת קטנה עבור response.ok, ניתוח JSON, timeouts ו־retry.
  • אתם רוצים אפס תלות ופחות חששות משרשרת האספקה.
  • חשוב לכם שה־API בדפדפן ובשרת יהיה זהה.
  • אתם בסביבות serverless או edge שבהן מעדיפים APIs מובנים.

מתי Axios, Got או Ky הגיוניים יותר

Axios הוא הבחירה הנכונה כשצוות שלכם מסתמך על request/response interceptors (למשל רענון אוטומטי של token לאימות, כותרות tenant, רישום מרכזי), כשאתם רוצים דחייה ברירת מחדל על שגיאות HTTP, או כשאתם צריכים תאימות לאחור עם גרסאות Node ישנות.

Got נבנה עבור שירותי Node עם תפוקה גבוהה שצריכים retries מובנים, hooks, שלבי timeout מתקדמים, streams, עזרי pagination, סוקטים של Unix, workflows של proxy/cache או תמיכה ב־HTTP/2. זהו סכין השווייצרי של עבודת HTTP רק ב־Node.

Ky הוא נקודת האיזון אם אתם אוהבים את הפשטות של fetch אבל רוצים פחות boilerplate — הוא מוסיף retry, timeout, hooks ו־HTTPError בחבילה קטנה בלי תלויות.

איך לבצע בקשות GET עם Node.js Fetch API

בקשת GET עם async/await נראית כך:

const response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
const post = await response.json();
console.log(post.title);
// → "sunt aut facere repellat provident occaecati excepturi optio reprehenderit"

וגרסה עם שרשרת .then(), אם זה הסגנון שלכם:

fetch('https://jsonplaceholder.typicode.com/posts/1')
  .then(response => response.json())
  .then(post => console.log(post.title))
  .catch(error => console.error(error));

שתיהן עובדות. אבל אף אחת מהן עדיין לא בטוחה לייצור (עוד מעט נגיע לזה).

קוראי Response שכדאי להכיר:

מתודהמתי להשתמש
response.json()השרת מחזיר JSON
response.text()השרת מחזיר HTML, טקסט רגיל, CSV, Markdown
response.arrayBuffer()צריך נתונים בינאריים (תמונות, קבצים)
response.bodyצריך עיבוד ב־streaming / chunked

דפוס טוב יותר — כזה שבאמת בודק שגיאות:

async function getPost(id) {
  const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
  if (!response.ok) {
    throw new Error(`HTTP ${response.status} ${response.statusText}`);
  }
  return response.json();
}

const post = await getPost(1);
console.log(post.title);

השורה if (!response.ok) היא ההבדל בין מדריך לקוד ייצור. וזה מביא אותנו למלכודת הגדולה ביותר.

איך לשלוח בקשות POST עם Node.js Fetch API

בקשות POST עוקבות אחרי אותו מבנה — רק מגדירים את method, ה־headers וה־body:

const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    title: 'מדריך ל־Node fetch',
    body: 'fetch בייצור צריך טיפול בשגיאות.',
    userId: 1,
  }),
});

if (!response.ok) {
  throw new Error(`HTTP ${response.status}`);
}

const created = await response.json();
console.log(created.id); // → 101

שליחת סוגי בקשות אחרים (PUT, DELETE, PATCH)

PUT, PATCH ו־DELETE משתמשים באותו מבנה עם ערך method שונה:

// PUT — החלפה מלאה
await fetch('https://jsonplaceholder.typicode.com/posts/1', {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ id: 1, title: 'הוחלף', body: 'החלפה מלאה', userId: 1 }),
});

// PATCH — עדכון חלקי
await fetch('https://jsonplaceholder.typicode.com/posts/1', {
  method: 'PATCH',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ title: 'עדכון חלקי' }),
});

// DELETE
await fetch('https://jsonplaceholder.typicode.com/posts/1', {
  method: 'DELETE',
});

מלכודת body-parser של Express: אם אתם שולחים JSON לשרת Express ו־req.body חוזר undefined, התיקון הוא כמעט תמיד זה: להשתמש ב־express.json(), לא ב־express.urlencoded(). השרת צריך middleware של express.json() לפני הנתיב כדי לפרש bodies מסוג Content-Type: application/json. זו אחת השאלות הנפוצות ביותר ב-Stack Overflow על Express, והיא תופסת אנשים שוב ושוב.

import express from 'express';
const app = express();
app.use(express.json()); // ← זה מה שצריך עבור JSON POST bodies
app.post('/api/posts', (req, res) => {
  res.json({ received: req.body });
});

מלכודת ה־fetch() ששוברת אפליקציות ייצור

fetch-error-handling-flowchart.png

כאן מגיעים רוב הבאגים של fetch בייצור.

fetch() לא דוחה את ה־Promise שלו על שגיאות HTTP מסוג 4xx או 5xx. הוא דוחה רק על כשלים ברמת הרשת — שגיאות DNS, אין אינטרנט, בקשות שבוטלו. אם השרת מחזיר 403 Forbidden או 500 Internal Server Error, ‏fetch מתייחס לזה כתגובה מוצלחת. בלוק ה־.catch() שלכם לעולם לא ירוץ. ה־try/catch שלכם לעולם לא יתפוס את זה. הקוד שלכם יעבד בשמחה את מה שהשרת החזיר.

התיעוד של MDN אומר זאת בבירור, אבל רוב המדריכים מדלגים על זה. התוצאה? קוד כזה נראה תקין אבל בולע שגיאות בשקט:

try {
  const response = await fetch('https://api.example.com/private');
  const data = await response.json(); // ← זה רץ גם על 403
  console.log('נראה מוצלח:', data);
} catch (error) {
  // רק כשלים ברמת הרשת מגיעים לכאן
  console.error('נתפס:', error);
}

פירוק מהיר של מה שכל דפוס באמת תופס:

דפוסתופס שגיאות רשתתופס 4xx/5xxמנתח JSON בבטחהניתן לשימוש חוזר
גישת .then(res => res.json()) גולמיתכן (דרך .catch())לאאין בדיקת content-typeלא
try/catch עם await fetch()כןלאאין בדיקת content-typeלא
if (!res.ok) ידני בכל קריאהכןכןתלוי בכל קריאהחלקית
מעטפת fetchJSON() מותאמתכןכןכןכן

בנו מעטפת fetchJSON() לשימוש חוזר

בנו מעטפת אחת. ייבאו אותה בכל מקום. הפסיקו להעתיק ולהדביק if (!response.ok) לכל קובץ:

export class HTTPError extends Error {
  constructor(message, { status, statusText, url, body }) {
    super(message);
    this.name = 'HTTPError';
    this.status = status;
    this.statusText = statusText;
    this.url = url;
    this.body = body;
  }
}

export async function fetchJSON(url, options = {}) {
  const response = await fetch(url, {
    headers: {
      Accept: 'application/json',
      ...options.headers,
    },
    ...options,
  });

  const contentType = response.headers.get('content-type') || '';
  const isJSON = contentType.includes('application/json');
  const body = isJSON ? await response.json().catch(() => null) : await response.text();

  if (!response.ok) {
    throw new HTTPError(`HTTP ${response.status} ${response.statusText}`, {
      status: response.status,
      statusText: response.statusText,
      url: response.url,
      body,
    });
  }

  return body;
}

עכשיו, כשהשרת מחזיר 403:

try {
  const data = await fetchJSON('https://api.example.com/private');
} catch (error) {
  if (error instanceof HTTPError) {
    console.error(`השרת החזיר ${error.status}:`, error.body);
  } else {
    console.error('כשל רשת או כשל אחר:', error);
  }
}

השגיאה נושאת את קוד הסטטוס, גוף התגובה וה־URL — כל מה שצריך ללוגים, התראות או הודעות למשתמש. ייבאו את זה פעם אחת, השתמשו בזה בכל מקום.

AbortController ו־Timeouts: דפוס הייצור ל־Node.js Fetch API

request-retry-pooling-flowchart.png

בלי timeout, קריאת fetch נתקעת ללא הגבלת זמן כשהשרת המרוחק מפסיק לענות. נתיב ה־Express שלכם נחסם. ה־Lambda שלכם שורף את תקציב הריצה שלו. הסקריפט שלכם פשוט... יושב שם.

בדקתי את תוצאות החיפוש המובילות: אף מדריך fetch ייעודי ל־Node.js לא מכסה ביטול בקשות או timeouts. ובכל זאת, timeouts הם אחת הסיבות המרכזיות לכך שמפתחים נשארים עם Axios או Got. שרשור אחד ב־Reddit אפילו נקרא "Node fetch does not timeout".

שימוש ב־AbortSignal.timeout() ‏(Node 18.11+)

הדרך הפשוטה ביותר — עוד אפשרות אחת:

try {
  const response = await fetch('https://api.example.com/data', {
    signal: AbortSignal.timeout(5000), // 5 שניות
  });

  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  const data = await response.json();
  console.log(data);
} catch (error) {
  if (error.name === 'TimeoutError') {
    console.error('הבקשה פקעה אחרי 5 שניות.');
  } else {
    throw error;
  }
}

שימו לב: AbortSignal.timeout() זורק TimeoutError, לא AbortError. זה פרט שאפילו מפתחים מנוסים לפעמים טועים בו.

Timeout ידני עם AbortController

ליותר שליטה — או אם צריך לבטל בקשה בגלל פעולה של המשתמש, לא רק בגלל טיימר:

const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);

try {
  const response = await fetch('https://api.example.com/data', {
    signal: controller.signal,
  });
  const data = await response.json();
  console.log(data);
} catch (error) {
  if (error.name === 'AbortError') {
    console.error('הבקשה בוטלה ידנית.');
  } else {
    throw error;
  }
} finally {
  clearTimeout(timeout);
}

טיפול ב־AbortError מול TimeoutError

ההבחנה הזו חשובה ללוגים ולהודעות למשתמש:

נתיב הביטולשם השגיאה בבלוק catch
AbortSignal.timeout(ms)TimeoutError
controller.abort()AbortError
כשל DNS / רשתבדרך כלל TypeError: fetch failed

הנה תרחיש מעשי — נתיב Express שקורא ל־API חיצוני וחייב להגיב בתוך 3 שניות:

app.get('/dashboard', async (req, res, next) => {
  try {
    const data = await fetchJSON('https://api.example.com/report', {
      signal: AbortSignal.timeout(3000),
    });
    res.json(data);
  } catch (error) {
    if (error.name === 'TimeoutError') {
      res.status(504).json({ error: 'ה־API במורד הזרם פקע בזמן' });
      return;
    }
    next(error);
  }
});

בלי הדפוס הזה, API איטי במורד הזרם היה חוסם את כל הנתיב עד שהלקוח מתייאש.

לוגיקת Retry ושימוש חוזר בחיבורים: להפוך את Node.js Fetch API לדרגת ייצור

ל־fetch מקורי אין retry מובנה. תקלה רגעית ברשת או 503 זמני פשוט גורמים לבקשה להיכשל. ברוב פעולות הקריאה בייצור, זה לא מקובל.

מעטפת Retry ניתנת להרכבה עם Exponential Backoff

זה קצר בכוונה — בערך 10 שורות של לוגיקה אמיתית:

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

export async function fetchWithRetry(url, options = {}, retries = 2) {
  for (let attempt = 0; ; attempt++) {
    try {
      const response = await fetch(url, options);
      if (response.ok || ![408, 429, 500, 502, 503, 504].includes(response.status)) {
        return response;
      }
      if (attempt >= retries) return response;
    } catch (error) {
      if (attempt >= retries) throw error;
    }
    await wait(250 * 2 ** attempt); // 250ms, 500ms, 1000ms...
  }
}

מתי כן לנסות שוב, ומתי לא

  • כן לנסות שוב: בקשות GET ו־HEAD idempotent, סטטוסים זמניים (408, 429, 500, 502, 503, 504), תקלות רשת רגעיות.
  • לא לנסות שוב: בקשות POST לא idempotent שיוצרות רשומות, מחייבות כסף, או מפעילות side effects — אלא אם משתמשים ב־idempotency keys.
  • לכבד Retry-After: עבור 429 (הגבלת קצב) ו־503 (שירות לא זמין), בדקו את כותרת Retry-After לפני backoff.

אם אתם מעדיפים לא לבנות retry בעצמכם, Ky היא מעטפת fetch קלת משקל שמוסיפה retry, timeout, hooks ו־HTTPError כבר מהקופסה — בלי תלויות.

שימוש חוזר בחיבורים עם Agent ו־Pool של Undici

בלולאות עתירות תפוקה — גריפת מאות דפים, קריאה ל־API באצווה, polling של שירות — שימוש חוזר בחיבורי TCP חוסך זמן משמעותי. כל חיבור חדש אומר חיפוש DNS חדש, לחיצת יד TCP, ו(ב־HTTPS) משא ומתן TLS.

מכיוון שה־fetch של Node מופעל על ידי Undici, אפשר להעביר dispatcher מותאם:

import { Agent } from 'undici';

const agent = new Agent({
  keepAliveTimeout: 10_000,
  keepAliveMaxTimeout: 60_000,
});

const response = await fetch('https://api.example.com/data', {
  dispatcher: agent,
});

לשליטה גדולה עוד יותר מול origin ספציפי:

import { Pool } from 'undici';

const pool = new Pool('https://api.example.com', { connections: 10 });

const response = await fetch('https://api.example.com/data', {
  dispatcher: pool,
});

// כשמסיימים:
await pool.close();

ה-benchmarks ב־README של Undici מראים ששימוש חוזר בחיבורים ו־pooling יכולים לשפר משמעותית את התפוקה — undici - dispatch הגיע לכ־22,234 בקשות לשנייה לעומת undici - fetch בכ־5,904 בקשות לשנייה בבנצ'מרק המקומי שלהם. המספרים בעולם האמיתי ישתנו, אבל הכיוון ברור: אם אתם שולחים הרבה בקשות לאותו origin, pooling חשוב.

ועוד דבר: תמיד צרכו או בטלו bodies של תגובה. bodies שלא נצרכו עלולים לגרום לדליפות משאבים במנגנוני ה־HTTP הפנימיים של Node.

Streaming תגובות עם Node.js Fetch API

הורדות קבצים גדולים, פידים של JSON בחלקים, server-sent events, פלט של LLM — אלה מקרים שבהם המתנה לתגובה מלאה לפני העיבוד מבזבזת זמן וזיכרון. Streaming מאפשר לטפל בנתונים כשהם מגיעים.

streaming-data-chunking-process.png

Node 18+ כולל ReadableStream תואם דפדפן. כך תזרימו תגובת JSON עם שורות מופרדות ותעבדו כל שורה כשהיא מגיעה:

const response = await fetch('https://example.com/large-file.ndjson');
if (!response.ok) throw new Error(`HTTP ${response.status}`);

const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';

while (true) {
  const { value, done } = await reader.read();
  if (done) break;

  buffer += decoder.decode(value, { stream: true });

  let newlineIndex;
  while ((newlineIndex = buffer.indexOf('\n')) >= 0) {
    const line = buffer.slice(0, newlineIndex).trim();
    buffer = buffer.slice(newlineIndex + 1);
    if (line) {
      const item = JSON.parse(line);
      console.log('עובד כעת:', item.id);
    }
  }
}

ל־streaming טקסט פשוט יותר (למשל, העברת פלט של LLM ל־stdout):

const response = await fetch('https://example.com/stream');
const reader = response.body.getReader();
const decoder = new TextDecoder();

for (;;) {
  const { value, done } = await reader.read();
  if (done) break;
  process.stdout.write(decoder.decode(value, { stream: true }));
}

Streaming הוא תחום שבו fetch מקורי ו־Got מצטיינים. התמיכה ב־streaming של Axios מוגבלת יותר.

מתי fetch() מגיע לגבולות שלו: גריפת אתרים מובנית עם APIs

בשלב מסוים, fetch כבר לא מהווה את צוואר הבקבוק. הבעיה האמיתית הופכת ל: "יש לי HTML, ועכשיו מה?"

web-scraping-vs-ai-extraction-api-comparison.png

fetch הוא לקוח HTTP — הוא מביא בתים, טקסט, JSON או HTML. אין לו מושג מהו כרטיס מוצר, מחיר, דירוג או טבלת אנשי קשר. עבור גריפת אתרים מובנית, הסטאק הגולמי הטיפוסי נראה כך:

  1. fetch() להורדת HTML
  2. Cheerio (או דומה) לבחירת אלמנטים עם CSS selectors
  3. לוגיקת pagination מותאמת
  4. רינדור JavaScript כשהעמודים בצד הלקוח
  5. טיפול ב־proxy / anti-bot / CAPTCHA
  6. תחזוקת selectors בכל פעם שמבנה האתר משתנה

הנה דוגמה טיפוסית של fetch + Cheerio — בערך 15 שורות כדי לגרוף כותרות מוצרים:

import * as cheerio from 'cheerio';

const response = await fetch('https://example-store.com/products');
if (!response.ok) throw new Error(`HTTP ${response.status}`);

const html = await response.text();
const $ = cheerio.load(html);

const products = $('.product-card')
  .map((_, el) => ({
    name: $(el).find('.product-title').text().trim(),
    price: $(el).find('.price').text().trim(),
    url: new URL($(el).find('a').attr('href'), response.url).href,
  }))
  .get();

console.log(products);

זה עובד עבור עמודים יציבים עם HTML צפוי. אבל זה נהיה שביר מהר — תוכן שמרונדר ב־JavaScript, שמות מחלקות שמשתנים, מנגנוני anti-bot ו־pagination כולם מוסיפים מורכבות.

ה־Open API של Thunderbit: מ־HTML גולמי לנתונים מובנים בקריאה אחת

כאן נכנס סוג אחר של כלי. ב-Thunderbit, בנינו שכבת API שמטפלת בחלקים המלוכלכים — רינדור JavaScript, הגנה מ־anti-bot, שינויים בפריסה — כדי שתוכלו להתמקד בנתונים שאתם באמת רוצים.

Distill API ‏(POST /distill): ממיר כל URL ל־Markdown נקי. שימושי להזנת LLMs, בניית מאגרי ידע או ניתוח תוכן — בלי צורך ב־HTML parser.

Extract API ‏(POST /extract): מגדירים JSON Schema שמתאר את הנתונים המובנים שאתם רוצים (שם מוצר, מחיר, דירוג), ו־AI מחלץ אותם. בלי CSS selectors, בלי שבירה כשהפריסה משתנה.

הנה אותה משימת גריפת מוצרים באמצעות Thunderbit Extract API — נקרא עם fetch מקורי:

const response = await fetch('https://openapi.thunderbit.com/openapi/v1/extract', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.THUNDERBIT_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    url: 'https://example-store.com/products',
    renderMode: 'basic',
    schema: {
      type: 'object',
      properties: {
        products: {
          type: 'array',
          items: {
            type: 'object',
            properties: {
              name: { type: 'string', description: 'שם המוצר' },
              price: { type: 'string', description: 'מחיר המוצר כפי שמוצג' },
              rating: { type: 'number', description: 'דירוג ממוצע של לקוחות' },
            },
            required: ['name', 'price'],
          },
        },
      },
      required: ['products'],
    },
  }),
});

if (!response.ok) throw new Error(`Thunderbit API: ${response.status}`);
const result = await response.json();
console.log(result.data);

ההשוואה: כ־15 שורות של fetch + Cheerio (ועם selectors שבירים) לעומת קריאת API אחת שמחזירה JSON נקי. עבור jobs באצווה, Thunderbit תומך בעד 50 כתובות URL לכל קריאת batch extract ועד 100 כתובות URL לכל קריאת batch distill.

Thunderbit הוא לא תחליף ל־fetch — fetch הוא שכבת התעבורה. Thunderbit הוא שכבת החילוץ שאליה פונים כשה־HTML הגולמי הופך לבעיה האמיתית. אם אתם סקרנים לגבי המחיר, התוכנית החינמית נותנת לכם 600 יחידות API להתנסות, ותוכניות בתשלום מתחילות ב־$6 לחודש. אפשר גם לבדוק את תוסף Chrome של Thunderbit לחילוץ ללא קוד ישירות בדפדפן.

למידע נוסף על גישות לגריפה מובנית, המדריכים שלנו על כלי חילוץ הנתונים הטובים ביותר, איך ליצור web scraper, ו-גריפת נתונים מאתר ל־Excel מכסים תהליכי עבודה ספציפיים בפירוט.

Reference מהיר: גיליון עזר ל־Node.js Fetch API

החלק הזה נועד לסימנייה. חזרו אליו כשאתם צריכים דפוס להעתקה והדבקה.

דפוסקטע קוד
GET בסיסיconst res = await fetch(url); const data = await res.json();
POST בסיסיawait fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
בדיקת שגיאת HTTPif (!res.ok) throw new Error(\\HTTP ${res.status}\);
timeout (פשוט)await fetch(url, { signal: AbortSignal.timeout(5000) });
ביטול ידניconst c = new AbortController(); setTimeout(() => c.abort(), 5000); await fetch(url, { signal: c.signal });
סטטוסים ל־retryלנסות שוב על 408, 429, 500, 502, 503, 504. לא לנסות עיוור על POST.
מעטפת JSONלהשתמש ב־fetchJSON() כדי לבדוק ok, לנתח content type, ולהשליך HTTPError.
Pool של חיבוריםimport { Pool } from 'undici'; const pool = new Pool(origin, { connections: 10 }); fetch(url, { dispatcher: pool });
זרימת chunksconst reader = res.body.getReader(); loop over await reader.read()
חילוץ מובנהלהשתמש ב־Thunderbit Extract API כשהמטרה היא שדות מעמוד אינטרנט, לא HTML גולמי.

מסקנה ותובנות מרכזיות

fetch מקורי ב־Node.js מוכן לייצור ב־2026 — אין צורך ב־node-fetch לפרויקטים חדשים, ואין צורך בתלות ברירת מחדל ב־Axios. אבל fetch() גולמי לבדו אינו אסטרטגיית HTTP לייצור.

חמשת הדברים שרוב המדריכים מדלגים עליהם — ושמדריך זה כיסה:

  1. מלכודת השגיאות:fetch() לא זורק על 4xx/5xx. תמיד בדקו response.ok או השתמשו במעטפת כמו fetchJSON().
  2. Timeouts: השתמשו ב־AbortSignal.timeout() למקרים פשוטים. ‏AbortSignal.timeout() זורק TimeoutError; ‏controller.abort() ידני זורק AbortError.
  3. Retry logic: לא מובנה. הוסיפו exponential backoff לבקשות idempotent ולכשלי מעבר. או השתמשו ב־Ky ל־retry בסגנון fetch כברירת מחדל.
  4. שימוש חוזר בחיבורים: ללולאות עתירות תפוקה, השתמשו ב־Agent או Pool של Undici דרך אפשרות dispatcher.
  5. חילוץ מובנה: כשצריך נתונים מעמודי אינטרנט (ולא רק HTML גולמי), שקלו API לחילוץ כמו Thunderbit במקום לתחזק CSS selectors שבירים.

מטריצת ההחלטה במשפט אחד: השתמשו ב־fetch מקורי לרוב הפרויקטים, ב־Axios ל־interceptors, ב־Got ל־retry מובנה ו־HTTP/2, ב־Ky ל־fetch עם ברירות מחדל טובות יותר, וב־API של Thunderbit כשהסקריפטים שלכם לגריפת נתונים מבוססי fetch הופכים מורכבים מדי לתחזוקה.

נסו את Thunderbit לחילוץ נתונים מובנים

נסו את הדפוסים שבמדריך הזה. ואם אתם רוצים לראות איך Thunderbit מטפל בחילוץ מובנה, התוכנית החינמית היא מקום טוב להתחיל — או לצפות בהדרכה בערוץ Thunderbit YouTube.

נסו את Thunderbit לגריפת אתרים עם AI Get Started Free

שאלות נפוצות

1. האם fetch מובנה ב־Node.js או שצריך להתקין אותו?

fetch מובנה ב־Node.js 18 ומעלה — אין צורך בהתקנה. הוא הפך ליציב ב־Node 21 ונתמך במלואו ב־Node 22 LTS וב־Node 24 LTS. עבור גרסאות Node ישנות יותר, אפשר להשתמש בחבילת npm ‏node-fetch, אבל פרויקטים חדשים צריכים לכוון לגרסת LTS מתוחזקת.

2. האם fetch זורק שגיאה על תגובות 404 או 500?

לא. ‏fetch דוחה את ה־Promise רק על כשלים ברמת הרשת (שגיאות DNS, אין קישוריות, בקשות שבוטלו). תגובות HTTP כמו 404, ‏403 ו־500 נפתרות כרגיל עם response.ok === false. צריך לבדוק את response.ok או response.status במפורש — או להשתמש במעטפת כמו פונקציית fetchJSON() שמופיעה במדריך הזה.

3. איך מוסיפים timeout ל־fetch ב־Node.js?

הדרך הפשוטה ביותר היא AbortSignal.timeout(ms), הזמינה ב־Node 18.11+: await fetch(url, { signal: AbortSignal.timeout(5000) }). זה זורק TimeoutError אם הבקשה עוברת את 5 השניות. לשליטה רבה יותר, יוצרים AbortController ידני וקוראים controller.abort() מתוך setTimeout. תפסו AbortError בדפוס הידני ו־TimeoutError עבור AbortSignal.timeout().

4. אפשר להשתמש ב־fetch לגריפת אתרים ב־Node.js?

כן, אבל fetch מחזיר רק HTML גולמי. תצטרכו parser כמו Cheerio כדי לחלץ אלמנטים ספציפיים, יחד עם לוגיקה מותאמת ל־pagination, עמודים שמרונדרים ב־JavaScript ואמצעי anti-bot. לחילוץ נתונים מובנים בקנה מידה גדול — כשאתם רוצים JSON נקי עם שמות מוצרים, מחירים או פרטי קשר — שקלו את Thunderbit Extract API, שמשתמש ב־AI כדי להחזיר נתונים מובנים בלי CSS selectors או קוד שתלוי בפריסה.

5. האם כדאי לעבור מ־Axios ל־fetch מקורי ב־2026?

עבור פרויקטים חדשים ב־Node 22+, fetch מקורי הוא ברירת מחדל חזקה. הוא בלי תלות, מבוסס Promise, ומשתף את אותו API כמו fetch בדפדפן. השאירו את Axios אם אתם מסתמכים על request/response interceptors, על דחיית שגיאות HTTP כברירת מחדל, או אם אתם צריכים תאימות לאחור עם גרסאות Node ישנות יותר. שתי האפשרויות תקפות — ההחלטה תלויה באילו יכולות הפרויקט שלכם באמת משתמש.

Fawad Khan
Fawad Khan
פוואד כותב למחייתו, ובכנות — הוא די אוהב את זה. הוא השקיע שנים בלגלות מה גורם לשורת קופי להיתקע בזיכרון, ומה גורם לקוראים פשוט לדפדף הלאה. תשאלו אותו על שיווק, והוא יוכל לדבר שעות. תשאלו אותו על קרבונרה, והוא ידבר עוד יותר.
תוכן עניינים

גרדו דף אינטרנט פשוט על ידי בקשה

תגידו מה צריך באנגלית פשוטה. או אפילו לא צריך להגיד כלום.

נסו את Thunderbit חינם
חילוץ נתונים באמצעות AI
העבירו נתונים בקלות ל-Google Sheets, Airtable או Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week