अधिकांश गाइड जो Node.js में प्रॉक्सी के लिए हैं, सलाह देते हैं कि प्रोजेक्ट में https-proxy-agent, socks-proxy-agent और कुछ अन्य पैकेज शामिल करें। वास्तव में, Node.js 18+ के संस्करण से, अंतर्निहित HTTP-क्लाइंट undici पहले से ही ProxyAgent शामिल करता है, जो प्रॉक्सी के माध्यम से API पार्सिंग और परीक्षण के अधिकांश कार्यों को पूरा करता है। इस लेख में हम देखेंगे कि undici के माध्यम से HTTP/HTTPS टनलिंग कैसे सेट करें, अनुरोधों के बीच IP रोटेशन कैसे व्यवस्थित करें और यदि SOCKS5 की आवश्यकता हो तो अतिरिक्त निर्भरता को शामिल किए बिना क्या करें।
Axios और request के बजाय undici क्यों
undici एक HTTP/1.1 और HTTP/2 क्लाइंट है, जिसे Node.js टीम द्वारा लिखा गया है और 18 संस्करण से यह वैश्विक fetch का आधार है। इसमें कोई ट्रांजिटिव निर्भरता नहीं है, यह अधिकांश बेंचमार्क पर कनेक्शन पूलिंग में axios से तेज है, और हमारी विषय के लिए महत्वपूर्ण है - इसमें HTTP CONNECT टनलिंग के माध्यम से प्रॉक्सी के साथ काम करने के लिए एक मूलभूत मॉड्यूल है।
जब आप Wildberries, Ozon या विभिन्न भू-स्थान से विज्ञापन क्रिएटिव का परीक्षण कर रहे होते हैं, तो node_modules में हर अतिरिक्त पैकेज एक संभावित विफलता बिंदु होता है जब Node.js को अपडेट किया जाता है। undici रनटाइम के साथ आता है, इसलिए आपको TLS, HTTP/2 और कीप-एलाइव का वर्तमान कार्यान्वयन मिलता है, बिना इस जोखिम के कि एक परित्यक्त लाइब्रेरी नई प्रमुख संस्करण पर टूट जाएगी।
स्थापना बहुत सरल है:
npm install undici
यदि आपके पास Node.js 20+ है, तो कुछ API वैश्विक स्तर पर बिना आयात के उपलब्ध हैं, लेकिन ProxyAgent के लिए आयात की आवश्यकता है, क्योंकि इसे डिफ़ॉल्ट रूप से वैश्विक fetch में निर्यात नहीं किया गया है।
ProxyAgent की बुनियादी सेटिंग
प्रॉक्सी के माध्यम से अनुरोध का न्यूनतम कार्यशील उदाहरण इस प्रकार है:
import { ProxyAgent, fetch } from 'undici';
const proxyAgent = new ProxyAgent('http://proxy.example.com:8080');
const response = await fetch('https://httpbin.org/ip', {
dispatcher: proxyAgent,
});
const data = await response.json();
console.log(data);
मुख्य बिंदु - dispatcher पैरामीटर। यह agent विकल्प नहीं है, जैसा कि पुराने http मॉड्यूल में है, बल्कि undici का डिस्पैचर है, जो TLS हैंडशेक से पहले कनेक्शन को इंटरसेप्ट करता है और लक्ष्य होस्ट के लिए CONNECT विधि के माध्यम से टनल स्थापित करता है। HTTPS अनुरोधों के लिए प्रॉक्सी के माध्यम से यह महत्वपूर्ण है: सामान्य HTTP प्रॉक्सी एन्क्रिप्टेड ट्रैफ़िक के पीछे की सामग्री को नहीं देखता है, यह केवल TCP टनल के भीतर बाइट्स को पास करता है।
यदि आप fetch के साथ काम नहीं कर रहे हैं, बल्कि निम्न-स्तरीय undici.request के साथ हैं, तो सिंटैक्स समान है:
import { request, ProxyAgent } from 'undici';
const proxyAgent = new ProxyAgent('http://proxy.example.com:8080');
const { statusCode, body } = await request('https://api.example.com/data', {
dispatcher: proxyAgent,
});
console.log(statusCode);
for await (const chunk of body) {
console.log(chunk.toString());
}
प्रोडक्शन पार्सर्स के लिए, जो प्रति मिनट सैकड़ों अनुरोध करते हैं, यह महत्वपूर्ण है कि हर अनुरोध पर नया ProxyAgent न बनाएं - यह कनेक्शन पूलिंग को नष्ट कर देता है। एक बार कनेक्शन पूल पर एजेंट का एक उदाहरण बनाएं और इसे कॉल के बीच पुनः उपयोग करें।
लॉगिन और पासवर्ड द्वारा प्रॉक्सी प्रमाणीकरण
अधिकांश भुगतान किए गए प्रॉक्सी प्रदाता उपयोगकर्ता नाम/पासवर्ड के जोड़े के माध्यम से पहुंच प्रदान करते हैं, न कि IP-व्हाइटलिस्टिंग के माध्यम से। undici में क्रेडेंशियल्स सीधे प्रॉक्सी के URL में पास किए जाते हैं:
import { ProxyAgent, fetch } from 'undici';
const login = 'user123';
const password = 'pass456';
const proxyUrl = `http://${login}:${password}@gate.proxycove.com:9000`;
const proxyAgent = new ProxyAgent(proxyUrl);
const response = await fetch('https://httpbin.org/ip', {
dispatcher: proxyAgent,
});
console.log(await response.json());
ध्यान दें: यदि लॉगिन या पासवर्ड में विशेष वर्ण (जैसे @, :, /) शामिल हैं, तो उन्हें encodeURIComponent के माध्यम से पास करना आवश्यक है, अन्यथा undici के भीतर URL पार्सर कनेक्शन स्ट्रिंग को सही ढंग से पार्स नहीं करेगा और आपको समझने योग्य संदेश के बजाय प्रमाणीकरण त्रुटि 407 प्राप्त होगी।
उन कार्यों के लिए जहां सत्र की स्थिरता महत्वपूर्ण है - जैसे कि एक IP पर सत्र के समय मार्केटप्लेस पर प्रमाणीकरण - रिहायशी प्रॉक्सी लेना अधिक सुविधाजनक है: IP निर्धारित समय के लिए नहीं बदलता है, जिसका अर्थ है कि सेवा "जंप" को नहीं देखती है और हर अनुरोध पर फिर से कैप्चा की आवश्यकता नहीं होती है।
अनुरोधों के बीच प्रॉक्सी रोटेशन
यदि आपके पास N प्रॉक्सी की एक सूची है और कार्य यह है कि अनुरोधों को उनके बीच वितरित किया जाए, तो सबसे सरल विकल्प - बिना किसी तीसरे पक्ष की लाइब्रेरी के लिए राउंड-रॉबिन:
import { ProxyAgent, fetch } from 'undici';
const proxyList = [
'http://user:[email protected]:8000',
'http://user:[email protected]:8000',
'http://user:[email protected]:8000',
];
let currentIndex = 0;
function getNextAgent() {
const proxyUrl = proxyList[currentIndex];
currentIndex = (currentIndex + 1) % proxyList.length;
return new ProxyAgent(proxyUrl);
}
async function fetchWithRotation(url) {
const agent = getNextAgent();
return fetch(url, { dispatcher: agent });
}
for (let i = 0; i < 10; i++) {
const res = await fetchWithRotation('https://httpbin.org/ip');
console.log(await res.json());
}
यह दृष्टिकोण उत्पादों के कार्डों को पार्स करने या कीमतों की निगरानी करने के लिए अच्छी तरह से काम करता है, जब प्रत्येक अगला अनुरोध पिछले से तार्किक रूप से स्वतंत्र होता है। उन कार्यों के लिए जहां प्रत्येक अनुरोध पर IP को तेजी से बदलने की आवश्यकता होती है बिना सूची को मैन्युअल रूप से प्रबंधित किए, प्रॉक्सी का उपयोग करना आसान होता है जो प्रदाता की ओर से स्वचालित रोटेशन प्रदान करता है - तब ऊपर का पूरा कोड एक स्थिर गेटवे URL में संकुचित हो जाता है, और रोटेशन डेटा सेंटर प्रॉक्सी या टाइमर द्वारा रोटेशन के साथ रिहायशी पूल द्वारा लिया जाता है।
यदि आपको केवल गोलाकार रोटेशन की आवश्यकता नहीं है, बल्कि भारित रोटेशन की आवश्यकता है - जैसे कि तेज प्रॉक्सी को प्राथमिकता देना - तो डेटा संरचना में वजन जोड़ें:
const weightedProxies = [
{ url: 'http://user:[email protected]:8000', weight: 5 },
{ url: 'http://user:[email protected]:8000', weight: 1 },
];
function pickWeighted(list) {
const total = list.reduce((sum, p) => sum + p.weight, 0);
let rnd = Math.random() * total;
for (const p of list) {
if (rnd < p.weight) return p.url;
rnd -= p.weight;
}
return list[0].url;
}
प्रॉक्सी की जीवितता की जांच और "मृत" IP को बाहर करना
समय के साथ प्रॉक्सी पूल "सड़ जाता है": कुछ IP लक्षित साइट द्वारा प्रतिबंधित हो जाते हैं, कुछ उत्तर देना बंद कर देते हैं। ताकि रोटेशन मृत पते के माध्यम से अनुरोध न भेजे, एक स्वास्थ्य जांच जोड़ें जिसमें अंतर्निहित AbortController के माध्यम से टाइमआउट हो:
import { ProxyAgent, fetch } from 'undici';
async function isProxyAlive(proxyUrl, timeoutMs = 5000) {
const agent = new ProxyAgent(proxyUrl);
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const res = await fetch('https://httpbin.org/ip', {
dispatcher: agent,
signal: controller.signal,
});
return res.status === 200;
} catch {
return false;
} finally {
clearTimeout(timer);
}
}
async function filterAliveProxies(proxyList) {
const checks = await Promise.all(
proxyList.map(async (url) => ({ url, alive: await isProxyAlive(url) }))
);
return checks.filter((c) => c.alive).map((c) => c.url);
}
इस प्रकार की जांच को हर कुछ मिनटों में बैकग्राउंड टाइमर के माध्यम से चलाएं और सक्रिय पूल को अपडेट करें, न कि पूरे स्थिर सूची को। यह विशेष रूप से महत्वपूर्ण है यदि आप सस्ते डेटा सेंटर IP पर अपने प्रॉक्सी रखते हैं - वहां विफलता का प्रतिशत प्रबंधित प्रदाता पूल की तुलना में अधिक होता है।
बिना किसी तीसरे पक्ष की लाइब्रेरी के SOCKS5
एक महत्वपूर्ण नोट: undici में अंतर्निहित ProxyAgent HTTP CONNECT टनलिंग के माध्यम से काम करता है और "बॉक्स से बाहर" केवल HTTP/HTTPS प्रॉक्सी को समझता है। SOCKS5 प्रोटोकॉल अलग तरीके से काम करता है - यह TCP स्तर पर बाइनरी हैंडशेक है, और undici इसे स्वाभाविक रूप से नहीं समझता है। यदि आपको SOCKS5 की आवश्यकता है बिना socks-proxy-agent स्थापित किए, तो आप net मॉड्यूल और कस्टम Agent के लिए connect विकल्प के माध्यम से न्यूनतम कनेक्टर को मैन्युअल रूप से लागू कर सकते हैं:
import net from 'node:net';
import { Agent, fetch } from 'undici';
function socksHandshake(socket, targetHost, targetPort) {
return new Promise((resolve, reject) => {
// चरण 1: अभिवादन, प्रमाणीकरण के बिना विधि
socket.write(Buffer.from([0x05, 0x01, 0x00]));
socket.once('data', (greeting) => {
if (greeting[1] !== 0x00) {
return reject(new Error('SOCKS5: प्रमाणीकरण विधि समर्थित नहीं है'));
}
const hostBuf = Buffer.from(targetHost, 'utf8');
const request = Buffer.concat([
Buffer.from([0x05, 0x01, 0x00, 0x03, hostBuf.length]),
hostBuf,
Buffer.from([targetPort >> 8, targetPort & 0xff]),
]);
socket.write(request);
socket.once('data', (reply) => {
if (reply[1] !== 0x00) {
return reject(new Error('SOCKS5: कनेक्शन अस्वीकृत'));
}
resolve(socket);
});
});
});
}
const socksAgent = new Agent({
connect: (options, callback) => {
const socket = net.connect(1080, 'socks-proxy.example.com', async () => {
try {
await socksHandshake(socket, options.hostname, 443);
callback(null, socket);
} catch (err) {
callback(err, null);
}
});
socket.on('error', (err) => callback(err, null));
},
});
const response = await fetch('https://httpbin.org/ip', { dispatcher: socksAgent });
console.log(await response.json());
यह प्रमाणीकरण के बिना एक सरल हैंडशेक है (विधि 0x02) और IPv6 का समर्थन नहीं करता है - प्रोडक्शन के लिए प्रमाणीकरण और विभिन्न प्रकार के पते के प्रबंधन के साथ एक शाखा जोड़ना उचित है। लेकिन तंत्र को समझने के लिए - यह पर्याप्त है: SOCKS5 हैंडशेक के चरण पर टनल के भीतर TLS की आवश्यकता नहीं है, HTTP CONNECT के विपरीत, इसलिए कोड पहले दृष्टिकोण में से छोटा है।
व्यावहारिक रूप से, SOCKS5 का उपयोग अक्सर वेब पृष्ठों को पार्स करने के लिए नहीं किया जाता है, बल्कि मोबाइल एप्लिकेशनों और एंटी-डिटेक्ट ब्राउज़रों जैसे Dolphin Anty या AdsPower के ट्रैफ़िक को पास करने के लिए किया जाता है, जहां प्रोटोकॉल को सॉकेट स्तर पर काम करने की आवश्यकता होती है, न कि HTTP अनुरोधों पर। यदि आपका कार्य वास्तव में Node.js में HTTP/HTTPS ट्रैफ़िक है, तो अंतर्निहित ProxyAgent आमतौर पर पर्याप्त होता है, और मैन्युअल SOCKS5 कनेक्टर के साथ निपटना आवश्यक नहीं होता है।
टाइमआउट और पुनः प्रयास लॉजिक का प्रबंधन
प्रॉक्सी कनेक्शन को अक्सर तोड़ते हैं, सीधे कनेक्शन की तुलना में - यह आवासीय नेटवर्क के लिए सामान्य है, जहां नोड किसी भी समय ऑफ़लाइन जा सकता है। एक पुनः प्रयास लपेटने की आवश्यकता है जिसमें एक्सपोनेंशियल डिले हो:
import { ProxyAgent, fetch } from 'undici';
async function fetchWithRetry(url, proxyList, maxRetries = 3) {
let lastError;
for (let attempt = 0; attempt < maxRetries; attempt++) {
const proxyUrl = proxyList[attempt % proxyList.length];
const agent = new ProxyAgent(proxyUrl);
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 8000);
try {
const res = await fetch(url, { dispatcher: agent, signal: controller.signal });
clearTimeout(timer);
if (res.status >= 500) throw new Error(`सर्वर त्रुटि: ${res.status}`);
return res;
} catch (err) {
clearTimeout(timer);
lastError = err;
const delay = 500 * 2 ** attempt;
await new Promise((r) => setTimeout(r, delay));
}
}
throw new Error(`सभी प्रयास समाप्त हो गए: ${lastError?.message}`);
}
यह लपेटना त्रुटियों के मामले में रोटेशन की समस्या को भी हल करता है: यदि वर्तमान प्रॉक्सी विफल हो गई है, तो अगली कोशिश स्वचालित रूप से सूची से दूसरे IP पर जाती है, न कि उसी मृत नोड पर।
undici ProxyAgent, https-proxy-agent और socks-proxy-agent की तुलना
| उपकरण | निर्भरता | SOCKS5 का समर्थन | HTTP/2 | गति |
|---|---|---|---|---|
| undici ProxyAgent | नहीं (Node 18+ में अंतर्निहित) | बॉक्स से बाहर नहीं | हाँ | उच्च |
| https-proxy-agent | 1-2 पैकेज | नहीं | नहीं | मध्यम |
| socks-proxy-agent | 3-4 पैकेज | हाँ | नहीं | मध्यम |
शुद्ध HTTP/HTTPS पार्सिंग के लिए न्यूनतम निर्भरता के साथ undici ProxyAgent अंतर्निहितता और HTTP/2 के साथ संगतता के कारण जीतता है। SOCKS5 के लिए, प्रोडक्शन में प्रमाणित socks-proxy-agent लेना आसान है, बजाय इसके कि स्वयं के हैंडशेक को बनाए रखा जाए - ऊपर के अनुभाग से मैन्युअल कार्यान्वयन शिक्षा और दुर्लभ एज-केस के लिए अच्छा है, लेकिन एक घंटे में हजारों कनेक्शनों के साथ व्यस्त प्रोडक्शन के लिए नहीं।
व्यावहारिक मामले: पार्सिंग और भू- परीक्षण
मार्केटप्लेस पर कीमतों की निगरानी। undici पर एक स्क्रिप्ट जो प्रॉक्सी रोटेशन और पुनः प्रयास लॉजिक के साथ है, Wildberries और Ozon पर दर-सीमा को बायपास करने के लिए आदर्श है: प्रत्येक अगला उत्पाद कार्ड अनुरोध एक नए IP के साथ भेजा जाता है, और स्वास्थ्य जांच स्वचालित रूप से प्रतिबंधित पते को पूल से बाहर कर देती है।
भू-स्थान के अनुसार विज्ञापन क्रिएटिव का परीक्षण। यदि यह जांचना आवश्यक है कि किसी विशेष देश के उपयोगकर्ता को सामग्री कैसे दी जाती है Google Ads या Facebook Ads के लिए, तो ProxyAgent में प्रॉक्सी का URL बदलना पर्याप्त है - किसी ब्राउज़र को उठाने की आवश्यकता नहीं है, यदि जांच API या सर्वर के HTML उत्तर के माध्यम से की जा रही है।
विभिन्न देशों में सामग्री की उपलब्धता की जांच। उन कार्यों के लिए जहां साइट IP के आधार पर विभिन्न सामग्री प्रदान करती है, रिहायशी कनेक्शन लक्षित सर्वर के लिए सामान्य उपयोगकर्ता से अलग नहीं दिखते हैं, जो डेटा सेंटर रेंज की तुलना में ब्लॉक होने के जोखिम को कम करता है। यह परिदृश्य विशेष रूप से मोबाइल प्रॉक्सी के लिए प्रासंगिक है, यदि लक्षित सेवा नेटवर्क के प्रकार की जांच करती है और स्पष्ट डेटा सेंटर उप-नेट्स को ब्लॉक करती है।
API एंडपॉइंट्स की सामूहिक जांच। बाहरी प्रॉक्सी पूल के माध्यम से अपने API के लोड परीक्षण के दौरान कनेक्शन पूलिंग पर नज़र रखना महत्वपूर्ण है - undici डिफ़ॉल्ट रूप से एक ProxyAgent के भीतर TCP कनेक्शनों को पुनः उपयोग करता है, जो एक ही होस्ट पर सैकड़ों अनुक्रमिक अनुरोधों के दौरान TLS हैंडशेक पर समय बचाता है।
निष्कर्ष
अंतर्निहित undici ProxyAgent Node.js में HTTP/HTTPS प्रॉक्सी के साथ अधिकांश कार्यों को बिना किसी तीसरे पक्ष के पैकेज को खींचने की आवश्यकता के बिना पूरा करता है: URL के माध्यम से प्रमाणीकरण, CONNECT के माध्यम से टनलिंग, HTTP/2 के साथ संगतता और कनेक्शन पूलिंग तुरंत काम करते हैं। रोटेशन और प्रॉक्सी की स्वास्थ्य जांच को स्वच्छ प्रॉमिस और टाइमर्स पर आसानी से इकट्ठा किया जा सकता है, और SOCKS5 की आवश्यकता होने पर इसे कस्टम कनेक्ट कनेक्टर के माध्यम से मैन्युअल रूप से लागू किया जा सकता है, हालांकि प्रोडक्शन के लिए SOCKS के लिए एक तैयार लाइब्रेरी लेना अधिक समझदारी है।
यदि आपकी undici स्क्रिप्ट मार्केटप्लेस को पार्स करने या कीमतों की निगरानी के लिए है, तो रिहायशी प्रॉक्सी पर ध्यान दें - वे एंटी-बॉट सिस्टम के प्रति अधिक सहनशील होते हैं, पारंपरिक डेटा सेंटर IP की तुलना में, और Wildberries, Ozon या Avito पर बड़े पैमाने पर अनुरोधों के दौरान अतिरिक्त कैप्चा समाधान की आवश्यकता कम होती है।