无服务器架构已成为现代 Web 应用程序的标准,但开发人员经常面临一个问题:来自 Lambda 函数或 Edge Functions 的所有请求都来自云服务提供商数据中心的 IP 地址。这导致在访问外部 API、解析数据或自动化任务时出现阻止。在本指南中,我们将讨论如何将代理集成到无服务器函数中,以绕过限制、速率限制和地理封锁。
为什么无服务器函数需要代理
无服务器平台(AWS Lambda、Google Cloud Functions、Vercel、Cloudflare Workers)在云基础设施中执行代码,使用数据中心的 IP 地址。这为开发人员创造了几个关键问题:
问题 1: 数据中心 IP 阻止。 许多服务自动阻止来自已知 AWS、Google Cloud 或 Azure IP 地址的请求。例如,在解析电子商务网站(亚马逊、eBay、Wildberries)或社交网络(Instagram API、TikTok API)时,您的 Lambda 函数在第一次请求时就会收到 HTTP 403 或验证码。防止机器人系统(Cloudflare、Akamai、DataDome)会立即识别来自云数据中心的流量。
问题 2: IP 级别的速率限制。 如果您部署了一个具有数千个并发调用的无服务器应用程序,所有请求可能来自一个或多个 AWS IP 地址。外部 API 很快就会达到限制(例如,GitHub API — 每个 IP 每小时 60 个请求,Google Maps API — 每秒 100 个请求)。即使您已支付 API 的扩展计划,IP 限制仍然会生效。
问题 3: 地理封锁。 在 us-east-1 区域的无服务器函数无法访问仅在俄罗斯、欧洲或亚洲可用的内容。这在解析区域市场(Ozon、Yandex.Market)、检查来自不同国家的广告或测试网站本地化时至关重要。
问题 4: 与其他用户共享 IP。 在无服务器环境中,您的函数可能会获得一个已经被其他云服务提供商客户使用过的 IP 地址。如果有人之前滥用过这个 IP(垃圾邮件、DDoS、解析),它可能会被列入黑名单。您将会在没有任何过错的情况下被阻止。
解决这些问题的方法是集成代理服务器。代理允许您的无服务器函数通过看起来像普通用户的住宅或移动 IP 地址发送请求。这可以解除阻止、绕过速率限制并访问地理封锁的内容。
哪些类型的代理适合无服务器
选择代理类型取决于您无服务器应用程序的任务。我们来看看三种主要选项及其使用场景:
| 代理类型 | 速度 | 匿名性 | 使用场景 |
|---|---|---|---|
| 数据中心代理 | 非常高(10-50 毫秒) | 低 | 无严格限制的 API 访问、服务可用性检查、正常运行时间监控 |
| 住宅代理 | 中等(100-500 毫秒) | 高 | 电子商务解析、社交网络操作、绕过 Cloudflare、访问地理封锁内容 |
| 移动代理 | 中等(150-600 毫秒) | 非常高 | 与移动 API(Instagram、TikTok)交互、移动应用测试、绕过最严格的保护 |
对于大多数无服务器应用程序,建议使用住宅代理。 它们在速度和匿名性之间提供了最佳平衡。住宅 IP 看起来像普通家庭用户,这使得绕过防止机器人和速率限制成为可能,而不会显著增加延迟。
数据中心代理仅适用于简单任务(检查 HTTP 状态、与没有限制的公共 API 交互)。移动代理在特定情况下需要使用——当您与移动 API 交互或对最大匿名性有严格要求时。
在 AWS Lambda 中设置代理
AWS Lambda 是最流行的无服务器平台,在这里集成代理需要正确配置 HTTP 客户端。Lambda 函数可以使用多种编程语言(Node.js、Python、Go),我们将讨论最常用的示例。
Node.js (axios)
Axios 是 Node.js 中最流行的 HTTP 请求库。要设置代理,请在配置中使用 proxy 参数:
const axios = require('axios');
exports.handler = async (event) => {
const proxyConfig = {
host: 'proxy.example.com',
port: 8080,
auth: {
username: 'your_username',
password: 'your_password'
},
protocol: 'http'
};
try {
const response = await axios.get('https://api.example.com/data', {
proxy: proxyConfig,
timeout: 10000 // 10 秒
});
return {
statusCode: 200,
body: JSON.stringify(response.data)
};
} catch (error) {
console.error('代理错误:', error.message);
return {
statusCode: 500,
body: JSON.stringify({ error: error.message })
};
}
};
重要提示: 将代理凭据存储在 AWS Systems Manager Parameter Store 或 AWS Secrets Manager 中,而不是代码中。这将确保安全性,并允许您在不重新构建函数的情况下轻松更改代理。
Python (requests)
在 Python 中,使用带有 proxies 参数的 requests 库来处理代理:
import requests
import json
def lambda_handler(event, context):
proxies = {
'http': 'http://username:password@proxy.example.com:8080',
'https': 'http://username:password@proxy.example.com:8080'
}
try:
response = requests.get(
'https://api.example.com/data',
proxies=proxies,
timeout=10
)
return {
'statusCode': 200,
'body': json.dumps(response.json())
}
except requests.exceptions.RequestException as e:
print(f'代理错误: {str(e)}')
return {
'statusCode': 500,
'body': json.dumps({'error': str(e)})
}
对于 SOCKS5 代理(更安全的协议),在 Python 中需要安装额外的库 requests[socks] 并更改 URL 格式:
proxies = {
'http': 'socks5://username:password@proxy.example.com:1080',
'https': 'socks5://username:password@proxy.example.com:1080'
}
针对冷启动的优化
Lambda 函数存在冷启动问题——在闲置后第一次请求需要 1-3 秒。使用代理时,这段时间会增加。为了最小化延迟,请在处理程序函数外创建 HTTP 客户端:
const axios = require('axios');
// 在初始化容器时创建客户端一次
const httpClient = axios.create({
proxy: {
host: 'proxy.example.com',
port: 8080,
auth: {
username: process.env.PROXY_USER,
password: process.env.PROXY_PASS
}
},
timeout: 10000
});
exports.handler = async (event) => {
// 在每次调用时重用客户端
const response = await httpClient.get('https://api.example.com/data');
return {
statusCode: 200,
body: JSON.stringify(response.data)
};
};
这种方法将冷启动时间缩短了 200-500 毫秒,因为代理配置仅在创建 Lambda 容器时执行一次。
将代理集成到 Vercel Edge Functions 中
Vercel 提供两种类型的无服务器函数:Node.js Functions(类似于 Lambda)和 Edge Functions(在 CDN 上执行)。Edge Functions 在接近 Cloudflare Workers 的运行时中工作,并对 Node.js API 的使用有限制。我们来看看这两种选择。
Vercel Node.js Functions
对于普通的 Vercel Functions,请使用与 AWS Lambda 相同的方法。创建文件 api/fetch-data.js:
import axios from 'axios';
export default async function handler(req, res) {
const proxyConfig = {
host: process.env.PROXY_HOST,
port: parseInt(process.env.PROXY_PORT),
auth: {
username: process.env.PROXY_USER,
password: process.env.PROXY_PASS
}
};
try {
const response = await axios.get(req.query.url, {
proxy: proxyConfig,
timeout: 8000
});
res.status(200).json(response.data);
} catch (error) {
res.status(500).json({ error: error.message });
}
}
在 Vercel Dashboard 中添加环境变量(设置 → 环境变量): PROXY_HOST、PROXY_PORT、PROXY_USER、PROXY_PASS。
Vercel Edge Functions
Edge Functions 使用 Web Fetch API,而不是 Node.js 库。代理通过自定义头或中间件进行设置:
export const config = {
runtime: 'edge',
};
export default async function handler(req) {
const proxyUrl = `http://${process.env.PROXY_USER}:${process.env.PROXY_PASS}@${process.env.PROXY_HOST}:${process.env.PROXY_PORT}`;
// 对于 Edge Runtime,需要通过代理使用 fetch(需要填充)
// 替代方案:直接使用代理 API
const targetUrl = new URL(req.url).searchParams.get('target');
const response = await fetch(targetUrl, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
});
return new Response(await response.text(), {
status: response.status,
headers: response.headers
});
}
重要限制: Edge Runtime 不支持标准的 Node.js 代理代理。为了充分利用代理,建议使用 Node.js Functions 或在单独的服务器上创建中间代理服务器,该服务器将接受来自 Edge Functions 的请求。
Cloudflare Workers 中的代理
Cloudflare Workers 在 V8 isolates 中运行,并且比 Vercel Edge Functions 有更严格的限制。通过 Node.js 库连接代理的标准方法在这里不起作用。有两种有效的方法:
方法 1: HTTP CONNECT 隧道
使用支持 HTTP CONNECT 方法的代理。通过代理服务器创建隧道:
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const proxyUrl = 'http://proxy.example.com:8080';
const targetUrl = 'https://api.example.com/data';
const proxyAuth = btoa(`${PROXY_USER}:${PROXY_PASS}`);
const response = await fetch(proxyUrl, {
method: 'CONNECT',
headers: {
'Host': new URL(targetUrl).host,
'Proxy-Authorization': `Basic ${proxyAuth}`
}
});
if (response.status === 200) {
// 隧道已建立,执行主要请求
const finalResponse = await fetch(targetUrl, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
}
});
return finalResponse;
}
return new Response('代理连接失败', { status: 502 });
}
此方法仅适用于支持 CONNECT 的 HTTP 代理。大多数住宅代理提供商提供此功能。
方法 2: 代理网关(推荐)
更可靠的方法是在单独的服务器(例如,VPS 或 AWS EC2)上部署中间代理网关。Cloudflare Worker 将请求发送到您的网关,然后网关通过代理转发请求:
// Cloudflare Worker
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const targetUrl = new URL(request.url).searchParams.get('url');
const gatewayUrl = 'https://your-proxy-gateway.com/fetch';
const response = await fetch(gatewayUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': API_KEY // 保护您的网关
},
body: JSON.stringify({
url: targetUrl,
method: 'GET'
})
});
return response;
}
在代理网关(Node.js 服务器)一侧:
const express = require('express');
const axios = require('axios');
const app = express();
app.use(express.json());
const proxyConfig = {
host: 'proxy.example.com',
port: 8080,
auth: {
username: process.env.PROXY_USER,
password: process.env.PROXY_PASS
}
};
app.post('/fetch', async (req, res) => {
if (req.headers['x-api-key'] !== process.env.API_KEY) {
return res.status(401).json({ error: '未授权' });
}
try {
const response = await axios({
url: req.body.url,
method: req.body.method || 'GET',
proxy: proxyConfig,
timeout: 10000
});
res.json(response.data);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(3000);
这种方法增加了额外的跳跃(增加 50-100 毫秒的延迟),但提供了对代理连接的完全兼容性和控制。
无服务器环境中的 IP 地址轮换
使用代理的主要原因之一是将请求分配到多个 IP 地址,以绕过速率限制。在无服务器架构中,有两种轮换方法:
代理提供商端的自动轮换
大多数住宅代理提供商提供旋转代理——您连接到一个端点,IP 会在每次请求时或通过指定间隔(例如,每 5 分钟)自动更改。这是无服务器的最简单选项:
// 一个端点,IP 自动更改
const proxyConfig = {
host: 'rotating.proxy.example.com',
port: 8080,
auth: {
username: 'user-session-' + Date.now(), // 唯一会话
password: 'password'
}
};
一些提供商允许通过用户名中的参数管理轮换: user-session-random(每次请求时更改 IP), user-session-sticky-300(在 300 秒内保持一个 IP)。
通过代理池手动轮换
如果您有静态代理列表(例如,您购买了专用代理),可以在应用程序级别实现轮换。在无服务器环境中,使用 DynamoDB(AWS)或 KV 存储(Cloudflare)来存储状态:
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();
const PROXY_POOL = [
{ host: 'proxy1.example.com', port: 8080 },
{ host: 'proxy2.example.com', port: 8080 },
{ host: 'proxy3.example.com', port: 8080 }
];
async function getNextProxy() {
// 从 DynamoDB 获取当前索引
const result = await dynamodb.get({
TableName: 'ProxyRotation',
Key: { id: 'current_index' }
}).promise();
const currentIndex = result.Item?.index || 0;
const nextIndex = (currentIndex + 1) % PROXY_POOL.length;
// 更新索引
await dynamodb.put({
TableName: 'ProxyRotation',
Item: { id: 'current_index', index: nextIndex }
}).promise();
return PROXY_POOL[currentIndex];
}
exports.handler = async (event) => {
const proxy = await getNextProxy();
const response = await axios.get('https://api.example.com/data', {
proxy: {
...proxy,
auth: {
username: process.env.PROXY_USER,
password: process.env.PROXY_PASS
}
}
});
return { statusCode: 200, body: JSON.stringify(response.data) };
};
这种方法提供了对轮换的完全控制,但需要额外的 DynamoDB 请求(增加 10-30 毫秒的延迟)。对于高负载应用程序,建议在 Lambda 容器内缓存索引,并每 100-1000 个请求更新一次。
错误和超时处理
代理为您的无服务器应用程序增加了额外的故障点。正确处理错误至关重要,以免丢失用户请求。
使用代理时的常见错误
| 错误 | 原因 | 解决方案 |
|---|---|---|
| ETIMEDOUT | 代理没有响应或响应缓慢 | 将超时减少到 5-8 秒,添加重试以使用其他代理 |
| ECONNREFUSED | 代理服务器不可用 | 检查代理的可用性,使用备用代理 |
| 407 需要代理身份验证 | 凭据无效 | 检查用户名/密码,确保 Lambda 的 IP 被列入代理的白名单 |
| 502 错误网关 | 代理无法连接到目标网站 | 网站可能会阻止代理,尝试使用其他 IP 或代理类型 |
实现带有备用的重试逻辑
添加自动重试,错误时切换到备用代理:
const axios = require('axios');
const PRIMARY_PROXY = {
host: 'primary.proxy.com',
port: 8080,
auth: { username: 'user', password: 'pass' }
};
const FALLBACK_PROXY = {
host: 'fallback.proxy.com',
port: 8080,
auth: { username: 'user', password: 'pass' }
};
async function fetchWithRetry(url, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const proxy = attempt === 0 ? PRIMARY_PROXY : FALLBACK_PROXY;
try {
const response = await axios.get(url, {
proxy,
timeout: 8000
});
return response.data;
} catch (error) {
console.log(`尝试 ${attempt + 1} 失败:`, error.message);
// 不对客户端错误(4xx)进行重试
if (error.response && error.response.status < 500) {
throw error;
}
// 最后一次尝试——抛出错误
if (attempt === maxRetries - 1) {
throw error;
}
// 在重试前进行指数延迟
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
}
}
}
exports.handler = async (event) => {
try {
const data = await fetchWithRetry('https://api.example.com/data');
return { statusCode: 200, body: JSON.stringify(data) };
} catch (error) {
return { statusCode: 500, body: JSON.stringify({ error: error.message }) };
}
};
该实现提供了三次请求尝试:第一次通过主要代理,后两次通过备用代理。在尝试之间添加了指数延迟(1 秒、2 秒、4 秒),以避免过度负载。
监控和警报
通过 CloudWatch(AWS)、Vercel Analytics 或 Sentry 设置代理错误监控。跟踪指标:
- 通过代理的成功请求百分比(应大于 95%)
- 请求的平均延迟(增长可能表明代理存在问题)
- 超时错误的数量(如果 >5% — 代理过载或缓慢)
- 按代码分布的错误(407、502、ETIMEDOUT 等)
在超过阈值时设置警报——这将使您能够快速切换到备用代理提供商或更改配置。
结论
将代理集成到无服务器应用程序中解决了关键问题:数据中心 IP 阻止、速率限制和地理封锁。我们讨论了在 AWS Lambda(Node.js 和 Python)、Vercel Functions 和 Cloudflare Workers 中设置代理,以及实现 IP 地址轮换和错误处理。
关键建议:对于需要高匿名性的任务(解析、社交网络 API 操作),使用住宅代理,将凭据存储在安全存储中(AWS Secrets Manager、Vercel 环境变量),实现带有备用代理的重试逻辑,并设置错误监控。
对于对稳定性要求较高的无服务器应用程序,我们建议使用 住宅代理,并进行自动轮换——它们在速度、匿名性和可靠性之间提供了最佳平衡,最大限度地减少了在与外部 API 和服务交互时被阻止的风险。