fix(bikegear): route scraper through residential proxy to bypass Cloudflare 403 on server IPs
Datacenter IPs are blocked by bikegear.in Cloudflare WAF. Switches fetchHtml from native fetch to axios with BIKEGEAR_PROXY_URL support. Set BIKEGEAR_PROXY_URL=http://user:pass@host:port in server .env to enable. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
bf207e48a1
commit
846e467b85
@ -3,13 +3,23 @@
|
|||||||
* 2-phase: listing pages → product URLs, then detail pages → full product data.
|
* 2-phase: listing pages → product URLs, then detail pages → full product data.
|
||||||
*
|
*
|
||||||
* Prices are in INR (Indian Rupees) as scraped from the site.
|
* Prices are in INR (Indian Rupees) as scraped from the site.
|
||||||
|
*
|
||||||
|
* Set BIKEGEAR_PROXY_URL (e.g. http://user:pass@host:port) to route through a
|
||||||
|
* residential proxy — required when running from a datacenter IP that bikegear.in
|
||||||
|
* Cloudflare blocks with 403.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const axios = require("axios");
|
||||||
const { BRANDS } = require("./brands");
|
const { BRANDS } = require("./brands");
|
||||||
|
|
||||||
const BASE_URL = "https://bikegear.in";
|
const BASE_URL = "https://bikegear.in";
|
||||||
const LISTING_CONCURRENCY = Number(process.env.BIKEGEAR_LISTING_CONCURRENCY ?? 3);
|
const LISTING_CONCURRENCY = Number(process.env.BIKEGEAR_LISTING_CONCURRENCY ?? 3);
|
||||||
const DETAIL_CONCURRENCY = Number(process.env.BIKEGEAR_DETAIL_CONCURRENCY ?? 2);
|
const DETAIL_CONCURRENCY = Number(process.env.BIKEGEAR_DETAIL_CONCURRENCY ?? 2);
|
||||||
|
const PROXY_URL = process.env.BIKEGEAR_PROXY_URL || null;
|
||||||
|
|
||||||
|
if (PROXY_URL) {
|
||||||
|
console.log(`[BIKEGEAR] Using proxy: ${PROXY_URL.replace(/:\/\/[^@]+@/, "://<redacted>@")}`);
|
||||||
|
}
|
||||||
|
|
||||||
const FETCH_HEADERS = {
|
const FETCH_HEADERS = {
|
||||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
||||||
@ -31,27 +41,54 @@ function sleep(ms) {
|
|||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Build axios proxy config from a proxy URL string. */
|
||||||
|
function buildProxyConfig(proxyUrl) {
|
||||||
|
const u = new URL(proxyUrl);
|
||||||
|
const cfg = {
|
||||||
|
host: u.hostname,
|
||||||
|
port: parseInt(u.port, 10),
|
||||||
|
protocol: u.protocol.replace(":", ""),
|
||||||
|
};
|
||||||
|
if (u.username) {
|
||||||
|
cfg.auth = { username: decodeURIComponent(u.username), password: decodeURIComponent(u.password) };
|
||||||
|
}
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchHtml(url, attempt = 1) {
|
async function fetchHtml(url, attempt = 1) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(url, { headers: FETCH_HEADERS });
|
const axiosOpts = {
|
||||||
|
headers: FETCH_HEADERS,
|
||||||
if (res.status === 429 || res.status >= 500) {
|
timeout: 30000,
|
||||||
if (attempt <= 4) {
|
responseType: "text",
|
||||||
const wait = attempt * 2000;
|
maxRedirects: 5,
|
||||||
console.log(`[BIKEGEAR] RETRY ${url} HTTP ${res.status} — waiting ${wait}ms (${attempt}/4)`);
|
};
|
||||||
await sleep(wait);
|
if (PROXY_URL) {
|
||||||
return fetchHtml(url, attempt + 1);
|
axiosOpts.proxy = buildProxyConfig(PROXY_URL);
|
||||||
}
|
|
||||||
throw new Error(`HTTP ${res.status} after retries`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
const res = await axios.get(url, axiosOpts);
|
||||||
return res.text();
|
return res.data;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (attempt <= 4 && (err.code === "UND_ERR_CONNECT_TIMEOUT" || err.code === "ECONNRESET" || err.message?.includes("fetch failed"))) {
|
const status = err.response?.status;
|
||||||
|
|
||||||
|
if ((status === 429 || status >= 500) && attempt <= 4) {
|
||||||
|
const wait = attempt * 2000;
|
||||||
|
console.log(`[BIKEGEAR] RETRY ${url} HTTP ${status} — waiting ${wait}ms (${attempt}/4)`);
|
||||||
|
await sleep(wait);
|
||||||
|
return fetchHtml(url, attempt + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status) throw new Error(`HTTP ${status}`);
|
||||||
|
|
||||||
|
const isNetwork = err.code === "ECONNRESET" || err.code === "ECONNREFUSED" ||
|
||||||
|
err.code === "ETIMEDOUT" || err.code === "ENOTFOUND" ||
|
||||||
|
err.message?.includes("timeout") || err.message?.includes("connect");
|
||||||
|
if (isNetwork && attempt <= 4) {
|
||||||
await sleep(attempt * 2000);
|
await sleep(attempt * 2000);
|
||||||
return fetchHtml(url, attempt + 1);
|
return fetchHtml(url, attempt + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user