快速回答
住宅代理粘性会话和 IP 轮换是如何工作的?
在 CrawlRelay 上,未带会话 ID 的用户名每次新 TCP 连接都会从池中获取一个 IP,通常不同,-session-<id>-ttl-<minutes> 会为该 ID 保持一个 IP,持续 最长支持 30 分钟持续时长,但 IP 也可提前更换。轮换依据连接,而非 HTTP 请求,因此保持连接状态决定您的 IP 何时真正切换。住宅代理流量费用为 US$0.50/GB,起购 50 GB。支持加密货币支付,且无需 KYC。
会话规则一览
| 轮换单位 | 每个新 TCP 连接从池中获取一个通常不同的 IP |
|---|---|
| 粘性会话 | 用户名后附加 -session-<id>-ttl-<minutes> |
| 会话长度 | 最长支持 30 分钟持续时长 |
| 会话 ID | 1–32 个字母或数字;每条流一个 ID |
| 什么情况会提前结束会话 | 背后的住宅设备离线 |
| 共享 ID | 发送相同 ID 的客户端共享相同出口 |
| 费用 | 免费:会话和轮换已包含在每 GB 价格中 |
当我发送会话 ID 到网关时会发生什么?
网关在每次新连接时读取用户名。没有会话部分时,它会为该连接选择符合你目标的住宅代理出口。带有会话部分时,会在你设置的 TTL 时间内为该 ID 保持相同出口。
粘性 IP 是真实的住宅代理连接,因此它可能会在 TTL 结束前消失:设备关机、断开 Wi-Fi 或更换网络。长时间流量需要通过检查出口 IP 并从检查点重启来保证不中断,避免整个任务失败。
每个库如何复用连接?
如果库保持连接活跃,所有请求共用同一出口。你可以利用此特性实现不同的行为。
| 库 | 默认 | 每次请求使用新 IP | 一个流量使用一个 IP |
|---|---|---|---|
| Python requests | Session 保持连接活跃 | 请求头 Connection: close,或每次请求新建 Session | 一个带会话 ID 的 Session |
| httpx | 客户端池化连接 | httpx.Limits(max_keepalive_connections=0) | 每个会话 ID 一个客户端 |
| aiohttp | TCPConnector 池化连接 | TCPConnector(force_close=True) | 代理 URL 用户名中的会话 ID |
| Scrapy | 按代理凭据和主机池化 | 使用我们的 middleware 的 meta={"proxy_session": "new"} | 流量指定一个固定 proxy_session |
| Playwright / Puppeteer | Chrome 按上下文池化 | 每页创建新上下文 | 每个会话 ID 一个上下文 |
| Node.js undici, axios, got | Agents 保持套接字活跃 | 每次请求新建代理或关闭保持连接 | 每个会话 ID 一个代理 |
| curl | 每个命令一个连接 | 已是如此 | 每个命令使用相同会话 ID |
哪种会话策略适合哪类爬取任务?
| 任务 | 策略 | 建议 TTL |
|---|---|---|
| 独立产品、文章或列表页 | 轮换:无会话 ID | — |
| 分页其下一页依赖于 cookie 或游标 | 每个列表一个粘性会话 | 10–30 分钟 |
| 先搜索,再结果,最后详情页 | 每次搜索一个粘性会话 | 10–20 分钟 |
| 无头浏览器渲染同一网站的多个页面 | 每个浏览器上下文一个粘性会话 | 上下文生命周期,最长 30 分钟 |
| 检查多个城市页面展示内容 | 每个城市检查一次短会话 | 5 分钟 |
| 每小时重新检查同一页面 | 轮换 | — |
如何在 Python 中管理粘性会话池?
一个小池会在几个稳定出口之间分担负载,并在请求次数达到设定值或出现502或503错误等出口失效信号后退休此出口。注意它不做的事:不会为绕过 403 或 429 错误而切换 IP。因为这些错误意味着网站要求你降低访问频率,所以池子会将这些错误传递给你的代码。
import random
import uuid
import requests
GATEWAY = "gw.crawlrelay.com:7777"
BASE_USER, PASSWORD = "USERNAME-country-us", "PASSWORD"
class SlowDown(Exception):
"""The target asked us to slow down (403 or 429): back off, do not rotate."""
class StickySession:
def __init__(self, ttl=15, max_requests=50):
self.id = uuid.uuid4().hex[:10]
self.max_requests, self.used = max_requests, 0
user = "%s-session-%s-ttl-%d" % (BASE_USER, self.id, ttl)
proxy = "http://%s:%s@%s" % (user, PASSWORD, GATEWAY)
self.http = requests.Session()
self.http.proxies = {"http": proxy, "https": proxy}
class SessionPool:
EXIT_PROBLEMS = {502, 503, 504}
def __init__(self, size=5, **options):
self.options = options
self.sessions = [StickySession(**options) for _ in range(size)]
def _replace(self, i):
self.sessions[i].http.close()
self.sessions[i] = StickySession(**self.options)
def get(self, url, **kwargs):
i = random.randrange(len(self.sessions))
s = self.sessions[i]
s.used += 1
try:
r = s.http.get(url, timeout=(10, 30), **kwargs)
except requests.exceptions.RequestException:
self._replace(i) # dropped exit or timeout: start a fresh session
raise
if r.status_code in (403, 429):
raise SlowDown("%s returned %d" % (url, r.status_code))
if r.status_code in self.EXIT_PROBLEMS or s.used >= s.max_requests:
self._replace(i)
return r
pool = SessionPool(size=5, ttl=15, max_requests=50)
print(pool.get("https://api.ipify.org").text)如何知道粘性 IP 已更换?
查询。通过同一会话在流程开始和每个检查点请求如 https://api.ipify.org 这样的 IP 回显服务,比较结果。IP 变化意味着会话后端出口已变更:从上次检查点重启流程,或开启新会话。用会话 ID 记录,这样调试时可以区分 IP 变更与网站行为。
哪些会话错误代价最大?
- 多步流程中每次请求都轮换。 网站看到购物车、游标或搜索令牌跨国跳转,会重置状态。
- 每个工作进程只用一个会话 ID。 会话针对单一流程,给每个工作进程和每个流程分配独立 ID。
- 会话存活时间远长于任务时长。 2 分钟流程用 30 分钟 TTL,占用 IP 无用且日志难读。
- 把 429 错误当作 IP 问题。 新 IP 不改变网站限制,必须减缓请求频率。参见 429 Too Many Requests]。
- 假设每请求都轮换。 Keep-alive 保持出口连接;具体情况请参考上表你的库支持情况。
会话和轮换:常见问答
粘性会话持续多久?
粘性会话保持同一 IP,时间由您设置(ttl,单位分钟),最长 30 分钟。IP 可能提前更换,例如当住宅设备离线时。选择略长于所服务流程的 TTL,并在检查点校验出口。
粘性会话比轮换更贵吗?
不。CrawlRelay 按 GB 计费,包含会话、轮换和定向。粘性会话甚至能节省流量,因为保持连接避免每次请求都进行 TLS 握手。
每请求轮换更适合爬虫吗?
对于独立页面,轮换分散负载,避免同一 IP 长时访问单站点。对于带状态的页面(例如依赖 cookie 的分页)则会中断流程。大多数抓取任务混合使用:不同项目间轮换,同一流程内保持粘性。
我能指定获取的具体 IP 吗?
不能。住宅代理网关从不断变化的池中分配出口,匹配用户名内的定向选项。你可以选择国家、城市、美国州、邮编或 ASN,并通过会话保持 IP,但不能预留具体地址。
所有套餐均包含轮换和会话功能
US$1.50,1 GB起,US$0.50/GB从50 GB起。