几乎所有爬虫框架都能让你设置代理。能替你管理代理的框架则少得多:自动轮换代理、淘汰被封禁的代理、让某个会话保持在同一个 IP 上。如果你使用住宅代理,弄清楚哪部分工作由框架负责、哪部分由网关负责,可以省下大量胶水代码。
本指南从三个问题出发,比较了六种常见工具:如何设置代理、该工具是否会自行轮换代理、以及它是否会把某个会话绑定到一个代理上。然后展示了在一个轮换网关背后,每种工具所需的少量代码。
简短的答案
| 工具 | 语言 | 如何设置代理 | 是否自行轮换 | 是否把会话绑定到代理 |
|---|---|---|---|---|
| Crawlee | Node.js | ProxyConfiguration | 是:轮询、自定义函数、分层 | 是,通过其会话池 |
| Scrapy | Python | request.meta["proxy"] 或环境变量 | 否,需要自己写中间件 | 否 |
| Colly | Go | SetProxy 或 SetProxyFunc | 是:轮询切换器 | 否 |
| Playwright | Node.js、Python、Java、.NET | 启动时或按浏览器上下文设置 | 否 | 按上下文 |
| Puppeteer | Node.js | 启动参数或按浏览器上下文设置 | 否 | 按上下文 |
| Selenium | 多种 | 全浏览器范围的选项 | 否 | 否 |
Crawlee 是唯一一个把代理当作需要管理的资源,而不只是一个设置项来处理的工具。浏览器类工具提供了按上下文隔离的能力,这正是实现会话的基础构件,但轮换逻辑需要你自己实现。
Crawlee:把代理当作受管理的资源
Crawlee 的 ProxyConfiguration 接受一个代理 URL 列表,并”以轮询方式”在其中循环使用。除了列表之外,你也可以传入一个函数,由它为每个请求挑选代理。第三种模式是分层代理,它从最便宜的层级开始(通常根本不用代理),当某个站点封锁爬虫时就升到上一层,当较低层级重新可用时再降回去。
最重要的部分是会话。当爬虫运行其会话池时,每个会话都会保持同一个代理:用文档的话说,之后带有相同 sessionId 的调用”将始终返回相同的 proxyUrl”。当 Crawlee 判定某个会话已被封锁时,它会淘汰该会话,下一个会话会拿到一个不同的代理。Cookie 会跟随会话走,所以 IP 和 Cookie 始终保持一致。
Scrapy:每个请求一个代理,轮换由你负责
Scrapy 的 HttpProxyMiddleware 从 request.meta["proxy"] 读取代理,如果没有则回退读取 http_proxy 和 https_proxy 环境变量。凭证可以放在 URL 中,如 http://username:password@host:port。它所做的仅此而已。为每个请求挑选不同的代理、发现封禁并让失效代理休息,这些都要由你自己完成,或者借助诸如 scrapy-rotating-proxies 这样的扩展,它会读取一个代理列表,并默认把非 200 或空响应视为封禁。
我们的 Scrapy 集成指南介绍了基本设置,而在 Scrapy 中轮换住宅代理这篇文章则更深入地讲解了中间件的写法。
Colly:简单的轮询切换器
Go 语言框架 Colly 提供了 SetProxy 用于设置单个代理,以及 SetProxyFunc 用于传入一个为每个请求选择代理的函数。它的 proxy 包中包含 RoundRobinProxySwitcher,该函数会”在每次请求时轮换 ProxyURL”,并支持 http、https 和 socks5 的 URL。这里没有会话的概念:如果两个请求必须共享同一个 IP,需要你自己在函数里决定。
Playwright 和 Puppeteer:按上下文隔离
浏览器自动化工具本身不做任何轮换,但它们允许每个浏览器上下文使用自己的代理,这正是在同一个浏览器中运行多个独立会话的方式。
- Playwright 接受一个
proxy选项,包含server、username和password,既可以在启动时应用于整个浏览器,也可以在newContext()时应用于单个上下文。完整设置请参阅我们的 Playwright 集成指南。 - Puppeteer 通过
--proxy-server启动参数设置代理,或通过createBrowserContext({ proxyServer })按上下文设置。两者都不支持直接传入凭证:你需要用page.authenticate()来回应代理的登录验证。
为每个上下文分配各自的会话,就能做到每个上下文对应一个 IP 和一个 Cookie 存储区,这正是登录态流程所需要的。
Selenium:全浏览器范围,处理密码时较为笨拙
Selenium 通过浏览器选项设置代理,因此会应用于整个浏览器。在 Chrome 中,常见做法是使用 --proxy-server 开关,但它没有位置填写用户名和密码。常见的变通办法是:写一个小型浏览器扩展来应答登录验证,或者搭建一个不带密码的本地转发代理,由它带着凭证把流量转发给你的服务商。曾经很受欢迎的解决方案 Selenium Wire,已于 2024 年 1 月被归档,不再维护。
轮换网关带来的变化
框架层面的轮换是为代理列表设计的:一百对 IP:port,你在其中循环使用、测试、淘汰。轮换住宅网关的工作方式不同。你只需连接到一个主机和端口,由网关来选择出口 IP。这把大部分工作从你的代码中移走了:
- 轮换发生在网关一侧,所以一个代理 URL 就足够了。只有一个条目的轮询列表不过是个设置项而已。
- 会话通过用户名来请求。在 Shifter 中,你在用户名里加上
sid-<id>来保持同一个出口 IP,再加上ttl-<seconds>来指定保持的时长,详见粘性代理与轮换代理对比。 - 地理位置同样作为用户名的一部分,因此爬虫可以在每个请求中指定国家或城市,而无需维护第二份代理列表。
于是,框架的任务就缩减为一件事:把它自己对会话的理解,映射到代理用户名中的会话 ID 上。
接入方式
Crawlee。 传入一个函数,根据 Crawlee 的会话 ID 构建用户名。会话 ID 可能包含下划线等字符,因此只保留字母和数字:
import { CheerioCrawler, ProxyConfiguration } from 'crawlee';
const proxyConfiguration = new ProxyConfiguration({
newUrlFunction: (sessionId) => {
const sid = sessionId
? `-sid-${String(sessionId).replace(/[^a-zA-Z0-9]/g, '')}-ttl-600`
: '';
return `http://customer-USERNAME${sid}:PASSWORD@p.shifter.io:443`;
},
});
const crawler = new CheerioCrawler({
proxyConfiguration,
useSessionPool: true,
persistCookiesPerSession: true,
async requestHandler({ request, $ }) {
console.log(request.url, $('title').text());
},
});
await crawler.run(['https://example.com/']);
当 Crawlee 淘汰一个被封锁的会话时,下一个会话 ID 会自动生成一个新的粘性 IP。
Scrapy。 一个小型下载器中间件,会在 Scrapy 自身的代理中间件运行之前设置好代理。在 meta 中带有 session 值的请求共享同一个 IP;其余请求则会轮换:
# middlewares.py
class GatewayProxyMiddleware:
def process_request(self, request, spider):
sid = request.meta.get("session")
user = "customer-USERNAME"
if sid:
user += f"-sid-{sid}-ttl-600"
request.meta["proxy"] = f"http://{user}:PASSWORD@p.shifter.io:443"
# settings.py
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.GatewayProxyMiddleware": 350,
}
Playwright。 每个会话对应一个上下文:
const context = await browser.newContext({
proxy: {
server: 'http://p.shifter.io:443',
username: 'customer-USERNAME-sid-cart1-ttl-600',
password: 'PASSWORD',
},
});
如何选择
选择框架应该依据站点和团队的需求,而不是它的代理功能。如果你想在 Node.js 中让封锁处理、会话和代理分层都自动完成,Crawlee 是最佳选择。Scrapy 仍然是用 Python 爬取大量纯 HTML 页面最快的方式,它在代理方面的缺口只需十行中间件代码就能补上。当页面需要真实浏览器时,Playwright 或 Puppeteer 才是正确的工具,上下文能为你提供干净的会话。Selenium 也能用,只是在处理凭证时要多花些力气。
在轮换网关之后,这六种工具都可以归结为同一种模式:一个代理 URL,再加上在请求必须共享 IP 的地方,于用户名中放入一个会话 ID。关于如何将其落地到生产环境的爬虫中,可参阅我们的网页抓取用例。
来源与参考资料
- Crawlee,代理管理指南:crawlee.dev
- Scrapy,下载器中间件参考文档(HttpProxyMiddleware):docs.scrapy.org
- scrapy-rotating-proxies:github.com
- Colly,代理包:pkg.go.dev
- Playwright,网络与 HTTP 代理:playwright.dev
- Puppeteer,BrowserContextOptions:pptr.dev
- Selenium Wire 代码仓库(已于 2024 年 1 月 3 日归档):github.com