如果你在 Deno 或 Bun 里直接写 fetch('https://example.com'),请求会从本机 IP 发出,完全忽略 HTTP_PROXY 等环境变量——这与浏览器和 Node.js + undici 的行为并不一致。对于需要 在 Deno 和 Bun 中使用代理 的爬虫、SERP 追踪和自动化场景,你必须显式配置 HTTP 客户端。本文给出可运行的代码、生产级重试逻辑,以及 ProxyHat 住宅代理的接入方式。
Deno 代理基础:createHttpClient 与 fetch client 选项
Deno 从 1.25 起支持通过 Deno.createHttpClient 创建自定义 HTTP 客户端,并在 fetch 的第二个参数中传入 { client }。这是 Deno 代理的标准做法,因为 fetch 本身不接受 proxy 字段。
// deno run --allow-net --unstable proxy_deno.ts
const client = Deno.createHttpClient({
proxy: {
url: "http://gate.proxyhat.com:8080",
basicAuth: {
username: "user-country-US-session-abc123",
password: "YOUR_PASSWORD",
},
},
});
const res = await fetch("https://api.ipify.org?format=json", { client });
const data = await res.json();
console.log(data); // { ip: "住宅 IP" }
用户名中的 country-US 指定出口国家,session-abc123 创建粘性会话——同一会话 ID 在 TTL 内保持同一出口 IP,适合登录态或多页爬取。要切换 IP,只需更换会话 ID。
Bun fetch proxy:一行搞定
Bun 的 fetch 原生支持 proxy 选项,这是 Bun 相对 Deno 的一个便利点。根据 Bun 官方文档,proxy 接受标准 URL 字符串,Bun 会自动处理 CONNECT 隧道。
// bun run proxy_bun.ts
const proxyUrl = "http://user-country-DE-session-s1:YOUR_PASSWORD@gate.proxyhat.com:8080";
const res = await fetch("https://api.ipify.org?format=json", {
proxy: proxyUrl,
});
console.log(await res.json());
Bun 同样支持 SOCKS5,只需把端口改为 1080 并使用 socks5:// 前缀。SOCKS5 适合需要更细粒度 TCP 隧道的场景,比如通过代理连接非 HTTP 协议。
环境变量路径:何时使用 HTTP_PROXY
两个运行时都支持读取 HTTP_PROXY / HTTPS_PROXY 环境变量,但行为有差异。Deno 需要在创建 HttpClient 时显式引用,而 Bun 在较新版本中会自动读取。对于生产环境,建议使用 per-client 配置而非全局环境变量,原因有三:
- 多租户场景下不同任务需要不同地理出口。
- 环境变量容易被其他依赖库意外读取,导致代理泄漏。
- 粘性会话 ID 必须按请求动态生成,环境变量无法满足。
// Deno 环境变量方式(不推荐用于生产)
// HTTPS_PROXY=http://user:pass@gate.proxyhat.com:8080 deno run script.ts
// 推荐:动态构建 proxy URL
function buildProxyUrl(country: string, session: string) {
const u = `user-country-${country}-session-${session}`;
return `http://${encodeURIComponent(u)}:YOUR_PASSWORD@gate.proxyhat.com:8080`;
}
住宅代理与高拦截目标:并发粘性会话池
数据中心 IP 在面对 Cloudflare、Akamai、PerimeterX 等反爬系统时拦截率可达 60%–90%。住宅代理使用真实 ISP 分配的 IP,请求看起来像普通家庭用户,拦截率显著降低。下面是一个在 Deno 中用 Promise.all 并发轮换粘性会话、并用 AbortController 设置超时的完整示例:
// deno run --allow-net --unstable pool.ts
const TARGETS = [
"https://httpbin.org/ip",
"https://api.ipify.org?format=json",
"https://ifconfig.me/all.json",
];
async function fetchViaProxy(target: string, sessionId: string) {
const client = Deno.createHttpClient({
proxy: {
url: "http://gate.proxyhat.com:8080",
basicAuth: {
username: `user-country-US-session-${sessionId}`,
password: "YOUR_PASSWORD",
},
},
});
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 15000);
try {
const res = await fetch(target, { client, signal: controller.signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} finally {
clearTimeout(timeout);
}
}
const tasks = TARGETS.map((url, i) =>
fetchViaProxy(url, `pool-${i}-${Date.now()}`).catch((e) => ({ error: e.message, url }))
);
const results = await Promise.all(tasks);
console.log(results);
每个请求使用独立的会话 ID(pool-0-时间戳),因此每个请求获得不同的住宅出口 IP。如果需要同一站点多页保持同一 IP,则在同一逻辑任务内复用会话 ID。
生产实践:重试退避、自定义 CA 与连接复用
指数退避重试
async function fetchWithRetry(
url: string,
client: Deno.HttpClient,
maxRetries = 3,
baseDelay = 500
): Promise<Response> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const res = await fetch(url, { client });
if (res.ok) return res;
if (res.status === 429 || res.status >= 500) throw new Error(`Retryable: ${res.status}`);
return res; // 4xx 直接返回
} catch (e) {
if (attempt === maxRetries) throw e;
const delay = baseDelay * 2 ** attempt + Math.random() * 100;
await new Promise((r) => setTimeout(r, delay));
}
}
throw new Error("unreachable");
}
自定义 CA 证书
在需要 MITM 代理或企业自签证书的环境中,Deno 允许通过 caCerts 传入 PEM 格式的根证书:
const caCert = await Deno.readTextFile("./custom-ca.pem");
const client = Deno.createHttpClient({
caCerts: [caCert],
proxy: {
url: "http://gate.proxyhat.com:8080",
basicAuth: { username: "user-country-US", password: "YOUR_PASSWORD" },
},
});
Bun 目前不支持在 fetch 级别自定义 CA,但会信任系统证书库。如果遇到 TLS 错误,可设置 NODE_TLS_REJECT_UNAUTHORIZED 相关环境变量或在系统层面安装证书。
Deno vs Bun 代理能力对比
| 特性 | Deno | Bun |
|---|---|---|
| 代理配置方式 | createHttpClient + fetch client | fetch proxy 选项 |
| SOCKS5 支持 | 是(socks5:// URL) | 是(socks5:// URL,端口 1080) |
| 自定义 CA | caCerts 字段 | 系统证书库 |
| 连接复用 | HttpClient 实例复用 | 自动连接池 |
| 环境变量自动读取 | 否(需显式引用) | 较新版本支持 |
| 粘性会话 | 用户名编码 | 用户名编码 |
使用 ProxyHat Node SDK(两个运行时通用)
ProxyHat 提供的 Node SDK 基于 undici,在 Deno 和 Bun 的 Node 兼容层下均可运行。这适合不想手写 proxy URL 的团队:
// bun run sdk_demo.ts 或 deno run --allow-net sdk_demo.ts
import { ProxyClient } from "proxyhat-sdk"; // 假定包名,参考官方文档
const client = new ProxyClient({
gateway: "gate.proxyhat.com",
port: 8080,
username: "user-country-US",
password: "YOUR_PASSWORD",
});
const res = await client.fetch("https://api.ipify.org?format=json");
console.log(await res.json());
SDK 封装了轮换逻辑和错误处理,适合快速上手。查看 ProxyHat 官方文档获取最新 API。完整定价参见 ProxyHat 定价页,可用地理位置见 代理位置列表。
伦理与合规:只抓公开数据
爬虫不是法外之地。在美国,CFAA(计算机欺诈和滥用法)可能适用于绕过认证壁垒的访问;在欧盟,GDPR 对个人数据的处理有严格要求。实践原则:
- 优先使用目标平台提供的官方 API。
- 遵守
robots.txt和服务条款。 - 只采集公开可访问的数据,不绕过付费墙或登录。
- 控制请求频率,避免对目标服务器造成负担。
关键要点
Deno 用
createHttpClient({ proxy })+fetch(url, { client });Bun 用fetch(url, { proxy })。两者都通过用户名编码地理定位和粘性会话。生产环境务必加超时、重试和并发控制。住宅代理在高拦截目标上成功率远超数据中心 IP。
- Deno
fetch不自动读取代理——必须传client。 - Bun
fetch原生支持proxy选项,更简洁。 - 用户名格式
user-country-US-session-abc123同时控制地理和会话。 - SOCKS5 使用端口
1080,HTTP 使用8080。 - 住宅代理在高拦截目标上成功率可提升 3–5 倍。






