AWS Lambda是一个无服务器平台,允许在不管理服务器的情况下运行代码。然而,在进行网站解析、市场API或自动化任务时,常常会遇到一个问题:Lambda函数使用AWS的IP地址,这些地址很容易被检测并被阻止。在本指南中,我们将讨论如何在Lambda中集成代理,设置IP轮换并避免常见错误。
本文面向通过AWS Lambda自动化任务的开发人员:从受保护的网站解析数据、监控竞争对手价格、处理社交网络或市场API。您将获得可立即使用的Python和Node.js代码示例。
为什么在AWS Lambda中需要代理
AWS Lambda默认使用来自Amazon Web Services的IP地址。这些地址在公共列表中,容易被防止机器人攻击的系统识别。以下是代理成为必要的主要场景:
实际案例:开发人员设置Lambda每15分钟监控Wildberries的价格。两天后,市场开始返回403 Forbidden错误——AWS IP被列入黑名单。连接居民代理后,解析已经稳定运行了6个月。
在Lambda中使用代理的主要原因:
- 解析受保护的网站:许多网站阻止来自AWS数据中心的IP请求。代理允许将Lambda伪装成普通用户。
- 地理位置限制:如果您需要从仅在特定国家/地区可用的网站获取数据(例如,Ozon上的区域价格),则具有所需地理位置的代理可以解决此问题。
- 绕过速率限制:许多服务的API限制来自单个IP的请求数量。代理轮换可以分散负载。
- A/B广告测试:检查来自不同地区的广告显示,以分析竞争对手。
- 监控市场:在Wildberries、Ozon、Avito上跟踪商品位置、竞争对手价格而不被阻止。
Lambda函数通常按计划(通过CloudWatch Events)或触发器运行,这使它们成为自动化的理想工具。然而,没有代理,这些任务很快就会遭遇目标资源的阻止。
选择哪种类型的代理用于Lambda
选择代理类型取决于您的Lambda函数要解决的任务。我们将讨论三种主要类型及其在无服务器架构中的应用:
| 代理类型 | 速度 | 匿名性 | 适用于Lambda的最佳场景 |
|---|---|---|---|
| 数据中心代理 | 非常高(50-200毫秒) | 中等 | 解析没有严格保护的API、大规模检查网站可用性、SEO监控 |
| 居民代理 | 中等(300-800毫秒) | 非常高 | 解析受保护的网站(市场、社交网络)、绕过Cloudflare、处理Instagram/Facebook API |
| 移动代理 | 中等(400-1000毫秒) | 最高 | 处理移动API(TikTok、Instagram)、测试移动广告、绕过最严格的保护 |
选择建议:
- 用于解析Wildberries、Ozon、Avito:使用具有俄罗斯地理位置的居民代理。这些平台积极阻止数据中心的IP。
- 用于监控没有严格保护的API:数据中心代理足够,它们更便宜且更快。
- 用于处理Instagram、Facebook、TikTok API:仅使用移动或居民代理——这些平台会检测并禁止数据中心。
- 用于绕过Cloudflare、PerimeterX:使用带轮换的居民代理,最好使用sticky sessions(在5-30分钟内保持IP)。
重要:Lambda函数的执行时间有限制(最多15分钟)。使用慢速代理(居民/移动)时,请考虑延迟——如果通过代理的请求需要2秒,则在15分钟内最多可以进行约450个请求。
在Python中设置Lambda代理(requests,urllib3)
Python是Lambda函数中最流行的语言,特别是在解析和自动化任务中。我们将讨论如何使用库requests设置代理,该库在90%的情况下使用。
HTTP代理的基本设置
连接代理的最简单方法是将参数proxies传递给requests.get()方法:
import requests
import os
def lambda_handler(event, context):
# 从环境变量中获取代理凭证
proxy_host = os.environ['PROXY_HOST'] # 例如:proxy.example.com
proxy_port = os.environ['PROXY_PORT'] # 例如:8080
proxy_user = os.environ['PROXY_USER']
proxy_pass = os.environ['PROXY_PASS']
# 构建带认证的代理URL
proxy_url = f"http://{proxy_user}:{proxy_pass}@{proxy_host}:{proxy_port}"
proxies = {
'http': proxy_url,
'https': proxy_url
}
try:
# 通过代理发起请求
response = requests.get(
'https://api.example.com/data',
proxies=proxies,
timeout=10 # 重要!设置超时
)
return {
'statusCode': 200,
'body': response.text
}
except requests.exceptions.ProxyError as e:
print(f"代理错误: {e}")
return {
'statusCode': 500,
'body': '代理连接失败'
}
except requests.exceptions.Timeout as e:
print(f"超时错误: {e}")
return {
'statusCode': 504,
'body': '请求超时'
}
此代码的关键点:
- 环境变量:绝不要将代理凭证直接存储在代码中!请在Lambda设置中使用环境变量。
- 超时:务必设置超时(10-30秒)。没有它,Lambda可能会在达到最大执行时间之前挂起。
- 错误处理:代理可能不可用或速度慢——始终处理
ProxyError和Timeout异常。 - HTTP和HTTPS:在
proxies字典中指定两种协议,即使您只使用HTTPS。
设置SOCKS5代理
SOCKS5代理提供更高的匿名性,并在TCP层工作,这使它们对某些保护系统来说是不可见的。要在requests中使用SOCKS5,需要库requests[socks]:
import requests
import os
def lambda_handler(event, context):
proxy_host = os.environ['PROXY_HOST']
proxy_port = os.environ['PROXY_PORT']
proxy_user = os.environ['PROXY_USER']
proxy_pass = os.environ['PROXY_PASS']
# 带认证的SOCKS5代理
proxy_url = f"socks5://{proxy_user}:{proxy_pass}@{proxy_host}:{proxy_port}"
proxies = {
'http': proxy_url,
'https': proxy_url
}
try:
response = requests.get(
'https://www.wildberries.ru/catalog/12345/detail.aspx',
proxies=proxies,
timeout=15
)
# 解析数据
return {
'statusCode': 200,
'body': response.text
}
except Exception as e:
print(f"错误: {e}")
return {
'statusCode': 500,
'body': str(e)
}
在Lambda部署时的重要事项:使用SOCKS5时,请在requirements.txt中添加:
requests[socks]
PySocks
通过代理检查IP
在运行主要逻辑之前,检查代理是否工作并返回所需IP是有用的:
def check_proxy_ip(proxies):
"""检查通过代理看到的IP"""
try:
response = requests.get(
'https://api.ipify.org?format=json',
proxies=proxies,
timeout=10
)
ip_data = response.json()
print(f"通过代理的当前IP: {ip_data['ip']}")
return ip_data['ip']
except Exception as e:
print(f"代理检查失败: {e}")
return None
def lambda_handler(event, context):
# ... 设置代理 ...
# 在主要工作之前检查IP
current_ip = check_proxy_ip(proxies)
if not current_ip:
return {
'statusCode': 500,
'body': '代理验证失败'
}
# 主要解析逻辑
# ...
在Node.js中设置Lambda代理(axios,got)
Node.js是Lambda函数中第二受欢迎的语言,特别是在处理API时需要高性能。我们将讨论如何使用库axios和got设置代理。
使用axios设置
Axios是Node.js中最流行的HTTP库。要使用代理,需要额外的包https-proxy-agent:
const axios = require('axios');
const HttpsProxyAgent = require('https-proxy-agent');
exports.handler = async (event) => {
// 从环境变量中获取凭证
const proxyHost = process.env.PROXY_HOST;
const proxyPort = process.env.PROXY_PORT;
const proxyUser = process.env.PROXY_USER;
const proxyPass = process.env.PROXY_PASS;
// 构建代理URL
const proxyUrl = `http://${proxyUser}:${proxyPass}@${proxyHost}:${proxyPort}`;
// 创建代理代理
const agent = new HttpsProxyAgent(proxyUrl);
try {
const response = await axios.get('https://api.example.com/data', {
httpsAgent: agent,
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
})
};
}
};
安装依赖:请在package.json中添加:
{
"dependencies": {
"axios": "^1.6.0",
"https-proxy-agent": "^7.0.0"
}
}
使用axios设置SOCKS5
对于SOCKS5代理,请使用包socks-proxy-agent:
const axios = require('axios');
const { SocksProxyAgent } = require('socks-proxy-agent');
exports.handler = async (event) => {
const proxyUrl = `socks5://${process.env.PROXY_USER}:${process.env.PROXY_PASS}@${process.env.PROXY_HOST}:${process.env.PROXY_PORT}`;
const agent = new SocksProxyAgent(proxyUrl);
try {
const response = await axios.get('https://www.ozon.ru/api/products', {
httpAgent: agent,
httpsAgent: agent,
timeout: 15000
});
return {
statusCode: 200,
body: JSON.stringify(response.data)
};
} catch (error) {
console.error('错误:', error.message);
return {
statusCode: 500,
body: JSON.stringify({ error: error.message })
};
}
};
替代方案:got库
Got是现代HTTP库,原生支持代理(不需要单独的代理):
const got = require('got');
exports.handler = async (event) => {
const proxyUrl = `http://${process.env.PROXY_USER}:${process.env.PROXY_PASS}@${process.env.PROXY_HOST}:${process.env.PROXY_PORT}`;
try {
const response = await got('https://api.example.com/data', {
agent: {
http: new (require('http-proxy-agent'))(proxyUrl),
https: new (require('https-proxy-agent'))(proxyUrl)
},
timeout: {
request: 10000
},
responseType: 'json'
});
return {
statusCode: 200,
body: JSON.stringify(response.body)
};
} catch (error) {
console.error('错误:', error.message);
return {
statusCode: 500,
body: JSON.stringify({ error: error.message })
};
}
};
Lambda中的代理轮换:如何自动更改IP
代理轮换对于需要在不被阻止的情况下进行大量请求的任务至关重要。有两种主要方法:使用具有自动轮换的代理服务或手动管理代理池。
通过提供商自动轮换
大多数居民代理提供商(包括ProxyCove)提供具有自动轮换的端点——每个请求或每N分钟自动更改IP:
import requests
import os
def lambda_handler(event, context):
# 自动轮换的代理
# 格式:rotating.proxy.com:port
# 每个请求 = 新IP
proxy_url = f"http://{os.environ['PROXY_USER']}:{os.environ['PROXY_PASS']}@rotating.proxycove.com:8080"
proxies = {
'http': proxy_url,
'https': proxy_url
}
results = []
# 发起10个请求——每个请求使用新IP
for i in range(10):
try:
response = requests.get(
f'https://api.wildberries.ru/products/{i}',
proxies=proxies,
timeout=10
)
results.append({
'product_id': i,
'status': response.status_code,
'data': response.json()
})
except Exception as e:
results.append({
'product_id': i,
'error': str(e)
})
return {
'statusCode': 200,
'body': json.dumps(results)
}
从代理池手动轮换
如果您有代理列表,可以手动实现轮换。这在需要控制每个请求使用哪个代理时非常有用:
import requests
import random
import json
def lambda_handler(event, context):
# 代理列表(可以存储在DynamoDB或S3中)
proxy_pool = [
{
'host': 'proxy1.example.com',
'port': '8080',
'user': 'user1',
'pass': 'pass1'
},
{
'host': 'proxy2.example.com',
'port': '8080',
'user': 'user2',
'pass': 'pass2'
},
{
'host': 'proxy3.example.com',
'port': '8080',
'user': 'user3',
'pass': 'pass3'
}
]
results = []
for i in range(10):
# 从池中随机选择一个代理
proxy = random.choice(proxy_pool)
proxy_url = f"http://{proxy['user']}:{proxy['pass']}@{proxy['host']}:{proxy['port']}"
proxies = {
'http': proxy_url,
'https': proxy_url
}
try:
response = requests.get(
f'https://api.example.com/item/{i}',
proxies=proxies,
timeout=10
)
results.append({
'item': i,
'proxy_used': proxy['host'],
'status': response.status_code
})
except Exception as e:
results.append({
'item': i,
'proxy_used': proxy['host'],
'error': str(e)
})
return {
'statusCode': 200,
'body': json.dumps(results)
}
保持IP的sticky sessions
某些任务需要在会话期间保持一个IP(例如,网站登录)。代理提供商通过URL参数提供sticky sessions:
import requests
import uuid
def lambda_handler(event, context):
# 生成唯一的session_id
session_id = str(uuid.uuid4())
# 带sticky session的代理(IP保持10分钟)
proxy_url = f"http://{os.environ['PROXY_USER']}-session-{session_id}:{os.environ['PROXY_PASS']}@sticky.proxycove.com:8080"
proxies = {
'http': proxy_url,
'https': proxy_url
}
# 所有在此Lambda中的请求将使用同一IP
# 1. 登录
login_response = requests.post(
'https://example.com/login',
data={'user': 'test', 'pass': 'test'},
proxies=proxies
)
# 2. 获取数据(使用相同的IP)
data_response = requests.get(
'https://example.com/dashboard',
proxies=proxies,
cookies=login_response.cookies
)
return {
'statusCode': 200,
'body': data_response.text
}
通过环境变量存储代理凭证
永远不要将代理凭证(用户名、密码、主机)直接存储在Lambda函数代码中。AWS提供了几种安全存储机密数据的方法:
1. 环境变量(基本方法)
在AWS Lambda控制台 → 配置 → 环境变量中添加:
PROXY_HOST= proxy.example.comPROXY_PORT= 8080PROXY_USER= your_usernamePROXY_PASS= your_password
AWS会自动加密静态环境变量。在代码中访问它们:
# Python
import os
proxy_host = os.environ['PROXY_HOST']
// Node.js
const proxyHost = process.env.PROXY_HOST;
2. AWS Secrets Manager(推荐用于生产)
为了最大限度地提高安全性,请使用AWS Secrets Manager——它提供自动轮换秘密和详细的访问控制:
import boto3
import json
from botocore.exceptions import ClientError
def get_proxy_credentials():
secret_name = "proxy-credentials"
region_name = "us-east-1"
session = boto3.session.Session()
client = session.client(
service_name='secretsmanager',
region_name=region_name
)
try:
get_secret_value_response = client.get_secret_value(
SecretId=secret_name
)
secret = json.loads(get_secret_value_response['SecretString'])
return secret
except ClientError as e:
print(f"检索秘密时出错: {e}")
raise e
def lambda_handler(event, context):
# 从Secrets Manager获取凭证
creds = get_proxy_credentials()
proxy_url = f"http://{creds['user']}:{creds['password']}@{creds['host']}:{creds['port']}"
# 使用代理
# ...
重要:不要忘记为Lambda函数添加IAM权限以访问Secrets Manager:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": "arn:aws:secretsmanager:us-east-1:123456789:secret:proxy-credentials-*"
}
]
}
常见错误及其解决方案
在Lambda中使用代理时,开发人员经常会遇到相同的问题。我们将讨论最常见的问题及其解决方案:
错误:ProxyError / 连接超时
症状: requests.exceptions.ProxyError: HTTPConnectionPool(host='proxy.example.com', port=8080): Max retries exceeded
原因:
- 代理凭证无效(用户名/密码)
- 代理服务器不可用或过载
- 防火墙阻止Lambda的出站连接
- 超时设置过短
解决方案:
# 1. 检查凭证
print(f"使用代理: {proxy_host}:{proxy_port}")
print(f"用户: {proxy_user}")
# 2. 增加超时
response = requests.get(url, proxies=proxies, timeout=30)
# 3. 添加重试逻辑
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
session = requests.Session()
retry = Retry(
total=3,
backoff_factor=1,
status_forcelist=[500, 502, 503, 504]
)
adapter = HTTPAdapter(max_retries=retry)
session.mount('http://', adapter)
session.mount('https://', adapter)
response = session.get(url, proxies=proxies, timeout=30)
错误:SSL证书验证失败
症状: SSLError: [SSL: CERTIFICATE_VERIFY_FAILED]
原因:某些代理(尤其是便宜的)使用自签名的SSL证书。
解决方案(谨慎使用!):
# 禁用SSL验证(仅用于测试!)
response = requests.get(
url,
proxies=proxies,
verify=False # 不要在生产中使用!
)
# 更好:指定CA证书的路径
response = requests.get(
url,
proxies=proxies,
verify='/path/to/ca-bundle.crt'
)
重要:禁用SSL验证(verify=False)使连接易受中间人攻击。仅在开发环境中调试时使用!
错误:Lambda超时(任务在X秒后超时)
症状:Lambda函数因超时错误而终止,未等待代理的响应。
原因:慢速代理(尤其是居民/移动)+大量请求。
解决方案:
- 增加Lambda函数的超时:配置 → 常规配置 → 超时(最多15分钟)
- 减少每次执行的请求数量
- 使用异步请求(Python中的asyncio,Node.js中的Promise.all)
- 对于非关键任务,切换到更快的代理
# Python:异步请求加速
import asyncio
import aiohttp
async def fetch_url(session, url, proxy):
async with session.get(url, proxy=proxy, timeout=10) as response:
return await response.text()
async def lambda_handler_async(event, context):
proxy_url = f"http://{os.environ['PROXY_USER']}:{os.environ['PROXY_PASS']}@{os.environ['PROXY_HOST']}:{os.environ['PROXY_PORT']}"
urls = [f'https://api.example.com/item/{i}' for i in range(50)]
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, url, proxy_url) for url in urls]
results = await asyncio.gather(*tasks)
return {
'statusCode': 200,
'body': json.dumps({'count': len(results)})
}
def lambda_handler(event, context):
return asyncio.run(lambda_handler_async(event, context))
错误:407代理身份验证所需
症状:使用代理时出现HTTP 407错误。
原因:凭证传递格式不正确或代理要求IP身份验证而不是用户名/密码。
解决方案:
# 检查代理URL的格式
# 正确:
proxy_url = f"http://{user}:{password}@{host}:{port}"
# 错误(缺少协议):
proxy_url = f"{user}:{password}@{host}:{port}" # ❌
# 如果代理要求IP身份验证:
# 1. 找到您的Lambda外部IP(可能会变化!)
# 2. 将此IP添加到代理提供商的白名单
# 3. 使用没有user:pass的代理
# 获取Lambda外部IP:
response = requests.get('https://api.ipify.org?format=json')
lambda_ip = response.json()['ip']
print(f"Lambda外部IP: {lambda_ip}")
使用代理优化Lambda性能
使用代理会给每个请求增加延迟。以下是经过验证的减少性能影响的方法:
1. 连接池
重用TCP连接,而不是为每个请求创建新连接:
# Python:使用Session而不是requests.get()
import requests
# 创建session一次(可以放在handler外部)
session = requests.Session()
session.proxies = {
'http': proxy_url,
'https': proxy_url
}
def lambda_handler(event, context):
# 所有请求重用连接
for i in range(100):
response = session.get(f'https://api.example.com/item/{i}')
# 处理响应...
2. 并行请求
如果需要进行许多独立请求,请并行执行它们:
// Node.js:使用Promise.all进行并行请求
const axios = require('axios');
const HttpsProxyAgent = require('https-proxy-agent');
const agent = new HttpsProxyAgent(proxyUrl);
exports.handler = async (event) => {
const urls = Array.from({length: 50}, (_, i) =>
`https://api.example.com/item/${i}`
);
// 所有请求并行执行
const promises = urls.map(url =>
axios.get(url, {
httpsAgent: agent,
timeout: 10000
})
);
try {
const results = await Promise.all(promises);
return {
statusCode: 200,
body: JSON.stringify({
count: results.length,
data: results.map(r => r.data)
})
};
} catch (error) {
console.error('错误:', error.message);
return {
statusCode: 500,
body: JSON.stringify({ error: error.message })
};
}
};
3. 结果缓存
如果数据变化不频繁,请将结果缓存到DynamoDB或S3中:
import boto3
import json
import time
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('proxy-cache')
def get_cached_or_fetch(url, proxies, cache_ttl=3600):
"""从缓存中返回数据或通过代理进行请求"""
# 检查缓存
try:
response = table.get_item(Key={'url': url})
if 'Item' in response:
item = response['Item']
if time.time() - item['timestamp'] < cache_ttl:
print(f"缓存命中: {url}")
return item['data']
except Exception as e:
print(f"缓存错误: {e}")
# 缓存为空或过期——发起请求
print(f"缓存未命中: {url},正在获取...")
response = requests.get(url, proxies=proxies, timeout=10)
data = response.text
# 保存到缓存
try:
table.put_item(Item={
'url': url,
'data': data,
'timestamp': int(time.time())
})
except Exception as e:
print(f"缓存保存错误: {e}")
return data
4. 选择正确的代理类型
在实际条件下比较不同类型代理的速度:
| 代理类型 | 平均延迟 | 每分钟请求(Lambda 1GB RAM) | 推荐 |
|---|---|---|---|
| 数据中心 | 50-200毫秒 | 300-600 | 大规模解析API |
| 居民 | 300-800毫秒 | 100-200 | 受保护的网站 |
| 移动 | 500-1500毫秒 | 50-100 | 移动API |
总结:选择合适的代理类型可以显著提高性能和成功率。根据您的具体需求做出明智的选择。