כל מדריך ל־fetch ב־Node.js אומר לכם פשוט await fetch(url) ולקרוא לזה יום. ואז אפליקציית הייצור שלכם בולעת בשקט שגיאת 500, בקשה נתקעת ל־90 שניות בלי timeout, ואתם מוצאים את עצמכם בערב שישי מנַגנים באגים במשהו שהיה אמור להיות מובן מאליו.
בניתי כבר זמן מה כלים פנימיים וצינורות נתונים ב-Thunderbit, ואני יכול לומר לכם: הפער בין "fetch עובד לי במדריך" לבין "fetch עובד בייצור" הוא המקום שבו רוב הכאב נמצא. מפתח אחד ב־Reddit ניסח את זה מצוין: "כשמגיעים לייצור, מבינים שצריך משהו עמיד יותר מ־fetch המקורי."
אחר הודה: "עבדתי 3 שנים כמפתח ווב, ורק היום גיליתי שבלוק ה־catch של ה־fetch API הוא לא לשגיאות HTTP." המדריך הזה מכסה את חמשת הדברים שרוב המדריכים מדלגים עליהם — מלכודת השגיאות, timeouts עם AbortController, לוגיקת retry, שימוש חוזר בחיבורים, ומתי כדאי לעבור מעבר ל־fetch לצורך חילוץ נתונים מובנים. אם אי פעם קרה לכם שקריאת fetch נכשלה בשקט בייצור, זה בשבילכם.

מהו 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.0 | fetch ניסיוני זמין גלובלית, מופעל על ידי Undici |
| fetch יציב | v21.0.0 | כבר לא ניסיוני |
| בסיס ייצור ל־2026 | v22 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 APIs | async/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 אחד ניסח זאת כך: "למה לייבא ספרייה… כשיש את הפונקציונליות מובנית בשפה/מסגרת?" נקודה הוגנת — אבל התשובה תלויה במה שאתם צריכים.

| תכונה | fetch מקורי | node-fetch v3 | axios | got v15 | ky v2 |
|---|---|---|---|---|---|
| גרסת Node.js | ≥18 (מומלץ 22/24 LTS) | ≥12.20 | רחב | ≥22 | ≥22 |
| נדרשת התקנה | לא | כן | כן | כן | כן |
| תמיכה ב־ESM + CJS | שניהם (גלובלי) | ESM בלבד (v3) | שניהם | ESM בלבד | ESM בלבד |
| דחייה אוטומטית על 4xx/5xx | לא | לא | כן | כן | כן |
| retry מובנה | לא | לא | לא | כן | כן |
| interceptors לבקשות | לא | לא | כן | כן (hooks) | כן (hooks) |
| תמיכה ב־streaming | Web 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 בייצור.
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

בלי 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 מאפשר לטפל בנתונים כשהם מגיעים.

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, ועכשיו מה?"

fetch הוא לקוח HTTP — הוא מביא בתים, טקסט, JSON או HTML. אין לו מושג מהו כרטיס מוצר, מחיר, דירוג או טבלת אנשי קשר. עבור גריפת אתרים מובנית, הסטאק הגולמי הטיפוסי נראה כך:
fetch()להורדת HTML- Cheerio (או דומה) לבחירת אלמנטים עם CSS selectors
- לוגיקת pagination מותאמת
- רינדור JavaScript כשהעמודים בצד הלקוח
- טיפול ב־proxy / anti-bot / CAPTCHA
- תחזוקת 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) }); |
| בדיקת שגיאת HTTP | if (!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 }); |
| זרימת chunks | const reader = res.body.getReader(); loop over await reader.read() |
| חילוץ מובנה | להשתמש ב־Thunderbit Extract API כשהמטרה היא שדות מעמוד אינטרנט, לא HTML גולמי. |
מסקנה ותובנות מרכזיות
fetch מקורי ב־Node.js מוכן לייצור ב־2026 — אין צורך ב־node-fetch לפרויקטים חדשים, ואין צורך בתלות ברירת מחדל ב־Axios. אבל fetch() גולמי לבדו אינו אסטרטגיית HTTP לייצור.
חמשת הדברים שרוב המדריכים מדלגים עליהם — ושמדריך זה כיסה:
- מלכודת השגיאות:
fetch()לא זורק על 4xx/5xx. תמיד בדקוresponse.okאו השתמשו במעטפת כמוfetchJSON(). - Timeouts: השתמשו ב־
AbortSignal.timeout()למקרים פשוטים. AbortSignal.timeout()זורקTimeoutError; controller.abort()ידני זורקAbortError. - Retry logic: לא מובנה. הוסיפו exponential backoff לבקשות idempotent ולכשלי מעבר. או השתמשו ב־Ky ל־retry בסגנון fetch כברירת מחדל.
- שימוש חוזר בחיבורים: ללולאות עתירות תפוקה, השתמשו ב־Agent או Pool של Undici דרך אפשרות
dispatcher. - חילוץ מובנה: כשצריך נתונים מעמודי אינטרנט (ולא רק 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 ישנות יותר. שתי האפשרויות תקפות — ההחלטה תלויה באילו יכולות הפרויקט שלכם באמת משתמש.


