

در این راهنما میخواهیم یاد بگیریم چطور هنگام وب اسکریپینگ با Node.js درخواستهایی که شکست میخورند را بهصورت مرتب Retry کنیم تا سیستم پایدارتر و مقاومتری داشته باشیم. بعد از خواندن این مقاله دو رویکرد مرسوم را میدانید:
مثالهای عملی برای کتابخانههای رایج مانند Got، node-fetch، Axios، SuperAgent و request-promise آورده شده و نکات عملکردی و امنیتی نیز پوشش داده میشود.
ایده کلی: retry یک ماشین وضعیت برای تلاش مجدد فراهم میکند؛ شما گزینهها (تعداد تلاشها، backoff، timeout حداقل/حداکثر، تصادفیسازی) را تعریف میکنید و سپس هر بار که درخواست شکست خورد، بسته به خطا تصمیم میگیرد که دوباره تلاش کند یا نه.
نمونه با got:
import got from 'got'
import retry from 'retry'
const retryOptions = {
retries: 5,
factor: 2,
minTimeout: 1000,
maxTimeout: 10000,
randomize: true,
// لیست کدهای HTTP که معمولاً میخواهیم برای آنها Retry کنیم
statusCode: [429, 500, 502, 503, 504]
}
const operation = retry.operation(retryOptions)
const url = 'http://quotes.toscrape.com/'
operation.attempt(async (currentAttempt) => {
try {
const response = await got.get(url)
console.log(response.body) // خروجی موفق
} catch (err) {
// operation.retry تصمیم میگیرد آیا باید دوباره تلاش کند یا نه
if (operation.retry(err)) {
console.log(`Retry attempt: ${currentAttempt}`)
return
}
console.error(`Maximum retries reached. Error: ${err}`)
}
})
توضیح (ورودی/خروجی/نقشها):
فرمول زمانبندی Retry (exponential backoff با امکان randomize):
Math.min(random * minTimeout * Math.pow(factor, attempt), maxTimeout)
نکات عملی:
import fetch from 'node-fetch'
import retry from 'retry'
const options = { retries: 5, factor: 2, minTimeout: 1000, maxTimeout: 10000, randomize: true }
const op = retry.operation(options)
const url = 'http://quotes.toscrape.com/'
op.attempt(async (attempt) => {
try {
const res = await fetch(url)
const text = await res.text()
console.log(text)
} catch (err) {
if (op.retry(err)) {
console.log('Retrying...', attempt)
return
}
console.error('Retries exhausted', err)
}
})
همان منطق برای دیگر کتابخانهها (Axios، SuperAgent، request‑promise) نیز قابل اعمال است. در برخی از آنها (مثلاً Axios) میتوانید نسخههای آماده_retry را هم بیابید اما استفاده از یک کتابخانه عمومی مثل retry کنترل بیشتری به شما میدهد.
ایده کلی: اگر نیاز به کنترول دقیقتری (مثل اعتبارسنجی محتوای HTML، دستهبندی خطاها، اعمال سیاستهای متفاوت بر اساس endpoint یا متد) دارید، یک تابع wrapper بسازید که به شکل عمومی قابل استفاده باشد.
مثال عمومی یک wrapper با backoff و jitter:
// retryWrapper یک تابع عمومی است که یک تابع async (مثلاً fetch/axios) را میگیرد
async function retryWrapper(fn, { retries = 3, minDelay = 500, factor = 2, maxDelay = 10000 } = {}) {
for (let i = 0; i < retries; i++) {
try {
const result = await fn()
return result // بازگشت نتیجه موفق
} catch (err) {
const isLast = i === retries - 1
// تصمیمگیری برای ادامه یا شکستن بسته به نوع خطا
const retriable = isRetriableError(err)
if (!retriable || isLast) throw err
// محاسبه delay با jitter
const backoff = Math.min(maxDelay, minDelay * Math.pow(factor, i))
const jitter = Math.random() * backoff
await new Promise(r => setTimeout(r, backoff + jitter))
}
}
}
function isRetriableError(err) {
// نمونه ساده: خطاهای شبکه یا 5xx یا 429
if (!err.response) return true // خطای شبکه
const status = err.response.status
return status === 429 || (status >= 500 && status < 600)
}
// نمونه استفاده با axios:
// const data = await retryWrapper(() => axios.get(url), { retries: 4 })
توضیح پارامترها و خروجی:
مزیت این رویکرد: قابلیت آزمونپذیری، تزریق سیاستهای متفاوت، اعتبارسنجی پاسخ (برای مثال شناسایی صفحه بن) و ترکیب با معیارگذاری و circuit breaker.
گاهی سرور پاسخ 200 برمیگرداند اما محتوا نشاندهندهٔ صفحه بن یا کپچا است. در این حالت باید محتوا را نیز بررسی کنید و در صورت تشخیص بن، درخواست را Retry کنید یا رفتار متفاوتی اتخاذ نمایید.
// مثال ساده: بررسی تایتل بن
const response = await fetch(url)
const text = await response.text()
if (response.status === 200 && text.includes('Robot or human? ')) {
// این صفحه بن است — میتوانیم Retry کنیم یا proxy/user-agent را تعویض کنیم
}
توجه: بررسی محتوای HTML میتواند بهعنوان یک شرط غیرقابل اعتماد در برخی سایتها باشد (صفحات بن میتوانند متنوع باشند)، بنابراین بهتر است چند امضا (title، متن خاص، وجود فرم کپچا) را بررسی کنید.
برای وب اسکریپینگ با Node.js، داشتن مکانیزم Retry یکی از پایهایترین تکنیکها برای افزایش پایداری است. استفاده از کتابخانههایی مثل retry راهاندازی را ساده میکند، اما اگر نیاز به کنترل دقیق دارید ساختن wrapper اختصاصی با backoff و تشخیص صفحه بن منطقیتر است. همیشه سیاست retry را همراه با محدودیت، jitter، طبقهبندی خطا و مانیتورینگ پیاده کنید تا هم اخلاقیتر و هم کارآمدتر اسکریپ کنید.


