几个名词
在分析 Scrapy 如何使用代理之前,我们先明确几个名词(Web 领域):代理(Proxy)、网关(Gateway)和 隧道(Tunnel):
-
代理 - 它位于客户端和服务器之间,接收所有客户端的 HTTP 请求,并将这些请求转发给服务器(它可 能会对请求进行修改之后再转发)。对用户来说,这样的应用程序就是一个代理,它会代表用户访问目标服务器。
-
网关 - 它是一种特殊的服务器,作为其他服务的中间实例使用。通常用于将 HTTP 流量转换成其他的协 议。网关接受请求时就好像自己是资源的源端服务器一样。客户端可能并不知道自己正在与一个网关通信。
-
隧道 - 它会在两条连接之间对原始数据进行盲转发(HTTP CONNECT 方法)的 HTTP 应用程序。HTTP 隧 道通常用来在一条或多条 HTTP 连接上转发非 HTTP 数据,转发时不会窥探数据。
为了方便行文,本文将这三种不同的 Web 组件统一称为代理服务,并将「代理」、「隧道」作为代理服务的 模式使用。
设置请求代理
从 0.8 开始,Scrapy 内建提供了一个 下载中间件 HttpProxyMiddleware
。启用了这个中间件后,用户可
以通过系统环境变量 http_proxy
和 https_proxy
来为所有 Scrapy 发出的 HTTP 请求设置代理服务,也
可通过 Request
的 meta
中添加字段 proxy
的方式为单个请求设置代理服务。
其中,代理服务 URL 格式为: http://username:password@some_proxy_server:port
。
在 Scrapy 爬虫运行过程中,HttpProxyMiddleWare
中间件优先检查 Request.meta["proxy"]
是否为空。
如果该字段不为空,HttpProxyMiddleware
将此字段值作为代理服务 URL 使用,它会根据代理服务 URL 中
的认证信息构造请求包头 Proxy-Authorization
。如果该字段未被设置,Scrapy 使用系统环境变量的设置
为请求选择代理服务:如果是 HTTP 请求,选择环境变量 http_proxy
指定的代理服务;如果是 HTTPS 请
求,选择环境变量 https_proxy
指定的代理服务;当然了,如果环境变量为空,Scrapy 不会为请求使用
任何代理服务。
请求处理流程
scrapy.core.downloader.handlers.DownloadHandlers
负责发起网络请求。它根据请求使用的协议
(即 scheme)从 DOWNLOAD_HANLDERS
配置项中选择一个合适的下载处理类,并使用其实例完成网络请求。
DOWNLOADER = 'scrapy.core.downloader.Downloader'
DOWNLOAD_HANDLERS = {}
DOWNLOAD_HANDLERS_BASE = {
'data': 'scrapy.core.downloader.handlers.datauri.DataURIDownloadHandler',
'file': 'scrapy.core.downloader.handlers.file.FileDownloadHandler',
'http': 'scrapy.core.downloader.handlers.http.HTTPDownloadHandler',
'https': 'scrapy.core.downloader.handlers.http.HTTPDownloadHandler',
's3': 'scrapy.core.downloader.handlers.s3.S3DownloadHandler',
'ftp': 'scrapy.core.downloader.handlers.ftp.FTPDownloadHandler',
}
我们将 Scrapy 处理网络请求的代码流程简略摘录如下(执行顺序由上到下):
## scrapy.core.engine.ExecutionEngine
def __init__(self, crawler, spider_closed_callback):
...
downloader_cls = load_object(self.settings["DOWNLOADER"])
self.downloader = downloader_cls(crawler)
...
def _download(self, request, spider):
...
dwld = self.downloader.fetch(request, spider)
...
## scrapy.core.downloader.Downloader
def __init__(self, crawler):
...
self.handlers = DownloadHandlers(crawler)
def fetch(self, request, spider):
...
dfd = self.middleware.download(self._enqueue_request, request, spider)
...
def _enqueue_request(self, request, spider):
...
self._process_queue(spider, slot):
...
def _process_queue(self, spider, slot):
...
while slot.queue and slot.free_transfer_slots() > 0:
...
dfd = self._download(slot, request, spider)
...
def _download(self, slot, request, spider):
dfd = mustbe_deferred(self.handlers.download_request, request, spider)
...
## scrapy.core.downloader.handlers.DownloadHandlers
def __init__(self, crawler):
...
handlers = without_none_values(
crawler.settings.getwithbase("DOWNLOAD_HANDLERS"))
for scheme, clspath in six.iteritems(handlers):
self._schemes[scheme] = clspath
...
def download_request(self, request, spider):
scheme = urlparse_cached(request).scheme
handler = self._get_handler(scheme)
...
return handler.download_request(request, spider)
由上面代码流程可以看出,请求最终会由 DOWNLOAD_HANDLERS
中配置的各种 scheme 对应的处理类处理。
针对 HTTP/HTTPS,Scrapy 提供了 HTTP10DownloadHandler
和 HTTP11DownloadHandler
两个处理类,分
别实现了 HTTP 1.0 和 HTTP 1.1 协议逻辑。
随着 HTTP 1.0 逐渐淡出互联网,Scrapy 官方文档目前(2018 年)建议使用 HTTP11DownloadHandler
处
理 HTTP 协议。
https://docs.scrapy.org/en/latest/topics/settings.html#downloader-httpclientfactory
HTTP/1.0 is rarely used nowadays so you can safely ignore this setting, unless you use Twisted<11.1, or if you
really want to use HTTP 1.0 and override DOWNLOAD_HANDLDERS_BASE for http(s) scheme accordingly, i.e. to
'scrapy.core.downloader.handlers.http.HTTP10DownloadHandler'.
随后,HTTP11DownloadHandler.download_request
函数将请求委托给 ScrapyAgent
实例的
download_request
方法,这个方法选择使用 Agent
,ProxyAgent
或者 TunnelingAgent
继续处理请求:
## scrapy.core.downloader.handlers.http11.HTTP11DownloadHandler
def download_request(self, request, spider):
agent = ScrapyAgent(contextFactory=self._contextFactory, pool=self._pool,
maxsize=getattr(spider, 'download_maxsize', self._default_maxsize),
warnsize=getattr(spider, 'download_warnsize', self._default_warnsize),
fail_on_dataloss=self._fail_on_dataloss)
return agent.download_request(request)
## scrapy.core.downloader.handlers.http11.ScrapyAgent
def download_reqeust(self, request):
...
agent = self._get_agent(request, timeout)
...
d = agent.request(
method, to_bytes(url, encoding="ascii"), headers, bodyproducer)
...
return d
def _get_agent(self, request, timeout):
bindaddress = request.meta.get('bindaddress') or self._bindAddress
proxy = request.meta.get('proxy')
if proxy:
_, _, proxyHost, proxyPort, proxyParams = _parse(proxy)
scheme = _parse(request.url)[0]
proxyHost = to_unicode(proxyHost)
omitConnectTunnel = b'noconnect' in proxyParams
if scheme == b'https' and not omitConnectTunnel:
proxyConf = (proxyHost, proxyPort,
request.headers.get(b'Proxy-Authorization', None))
return self._TunnelAgent(reactor, proxyConf,
contextFactory=self._contextFactory, connectTimeout=timeout,
bindAddress=bindaddress, pool=self._pool)
else:
endpoint = TCP4ClientEndpoint(reactor, proxyHost, proxyPort,
timeout=timeout, bindAddress=bindaddress)
return self._ProxyAgent(endpoint)
return self._Agent(reactor, contextFactory=self._contextFactory,
connectTimeout=timeout, bindAddress=bindaddress, pool=self._pool)
从上述代码可以看出,对请求设置了代理服务后,Scrapy 会根据条件选择使用「代理」或「隧道」模式来处理该 请求。我们根据代码逻辑将选择条件总结如下:
- 为 HTTPS 请求默认使用 「隧道」 模式;
- 代理服务 URL 中出现
noconnect
参数时,为 HTTPS 请求使用 「代理」 模式; - 为 HTTP 请求全部使用「代理」模式;
几点结论
根据上面的分析,我们得出如下结论:
-
Scrapy 0.8+ 默认启用了
scrapy.downloadmiddlewares.httpproxy.HttpProxyMiddleware
中间件。用户可 以通过环境变量或者Request.meta['proxy']
字段为请求指定代理服务。 -
未显式设置
Request.meta['proxy']
的请求,Scrapy 根据环境变量http_proxy
和https_proxy
的 值选择对请求是否启用代理服务和启用哪个代理服务。如果两个环境变量都被设置的情况下:为 HTTP 请求选用http_proxy
中设置的代理;为 HTTPS 请求选用https_proxy
中设置的代理。 -
Scrapy 默认为 HTTP 请求使用 「代理」 模式代理服务。
-
Scrapy 默认为 HTTPS 请求使用「隧道」模式代理服务。用户可以通过在代理服务 URL 中添加
noconnect
参数关闭此默认行为。 -
Scrapy 并不要求
https_proxy
指定的代理服务支持 HTTPS 协议,但要求其支持「隧道」模式(即支持 HTTP CONNECT 方法)。
Comments
不要轻轻地离开我,请留下点什么...