【Python爬虫(55)】Scrapy进阶:深入剖析下载器与下载中间件

发布于:2025-02-24 ⋅ 阅读:(16) ⋅ 点赞:(0)

【Python爬虫】专栏简介:本专栏是 Python 爬虫领域的集大成之作,共 100 章节。从 Python 基础语法、爬虫入门知识讲起,深入探讨反爬虫、多线程、分布式等进阶技术。以大量实例为支撑,覆盖网页、图片、音频等各类数据爬取,还涉及数据处理与分析。无论是新手小白还是进阶开发者,都能从中汲取知识,助力掌握爬虫核心技能,开拓技术视野。


一、Scrapy 下载器:原理与性能优化

1.1 下载器工作原理

在 Scrapy 框架中,下载器(Downloader)扮演着至关重要的角色,它处于整个框架的核心数据流动路径上。当我们启动一个 Scrapy 爬虫任务时,引擎首先会从 Spider 获取初始的请求(Request),这些请求就像是一个个 “抓取任务订单” ,包含了要访问的 URL 以及一些其他的请求信息,比如请求头、请求方法等。引擎将这些请求发送给调度器(Scheduler),调度器会对请求进行管理和排序,按照一定的规则决定下一个要处理的请求。

当调度器将某个请求传递给引擎后,引擎会把这个请求通过下载中间件(Downloader Middlewares)传递给下载器。下载器就像是一个勤劳的 “快递员”,它会根据请求中的 URL,向对应的网站服务器发送 HTTP 请求。在发送请求的过程中,下载器会处理各种网络相关的操作,比如建立 TCP 连接、发送 HTTP 报文等。当服务器返回响应(Response)后,下载器会接收这个响应,并再次通过下载中间件将响应传递回引擎,引擎再将响应发送给 Spider 进行后续的处理,比如解析页面、提取数据等。

1.2 性能优化之并发请求控制

在 Scrapy 中,CONCURRENT_REQUESTS参数是控制并发请求数量的关键。它就像是一个 “交通警察”,决定了同时向网站发送请求的数量。我们可以在settings.py文件中对其进行设置,例如:

CONCURRENT_REQUESTS = 32

上述代码将并发请求数量设置为 32,这意味着 Scrapy 会同时向目标网站发送 32 个请求。如果设置的值过小,比如设为 1,那么爬虫的抓取速度会非常慢,因为它每次只能处理一个请求,就像只有一个工人在工作,效率低下;而如果设置的值过大,比如设为 1000,可能会对目标网站造成过大的压力,导致网站拒绝访问,甚至封禁我们的 IP,就像太多车辆同时涌入一条道路,会造成交通堵塞。所以,合理设置CONCURRENT_REQUESTS的值非常重要,需要根据目标网站的承受能力以及我们自身的网络带宽等因素来综合考虑。

1.3 性能优化之下载延迟与超时设置

DOWNLOAD_DELAY参数用于设置下载延迟,它的作用是在每次请求之间添加一定的时间间隔,单位为秒。比如我们设置:

DOWNLOAD_DELAY = 2

这表示在每次发送请求后,Scrapy 会等待 2 秒再发送下一个请求。设置下载延迟有两个重要的作用,一是可以减轻目标网站的负担,避免过于频繁的请求让网站服务器不堪重负;二是可以避免因为请求过于频繁而被网站识别为恶意爬虫,从而导致 IP 被封。

DOWNLOAD_TIMEOUT参数则用于设置下载超时时间,例如:

DOWNLOAD_TIMEOUT = 15

这意味着如果一个请求在 15 秒内没有完成下载,Scrapy 会认为这个请求超时,并进行相应的处理,比如重新发送请求或者记录错误日志。设置合适的下载超时时间可以避免因为网络延迟过高,导致爬虫长时间等待一个无法完成的请求,从而影响整个抓取效率。

1.4 性能优化之缓存机制

Scrapy 内置的缓存功能就像是一个 “记忆仓库”,它可以将已经抓取过的网页缓存下来。当我们再次请求相同的 URL 时,如果缓存中存在对应的响应,Scrapy 就可以直接从缓存中读取数据,而不必重新下载。这不仅可以节省带宽,减少网络流量的消耗,还能大大提高抓取效率,减少等待时间。

要启用缓存功能,我们可以在settings.py文件中进行如下设置:

HTTPCACHE_ENABLED = True
HTTPCACHE_EXPIRATION_TIME = 3600
HTTPCACHE_DIR = 'httpcache'
HTTPCACHE_IGNORE_HTTP_CODES = [301, 302, 403, 404]
  • HTTPCACHE_ENABLED设置为True表示启用缓存;
  • HTTPCACHE_EXPIRATION_TIME设置缓存的过期时间为 3600 秒,即 1 小时,超过这个时间,缓存的内容将被视为过期,下次请求时会重新下载;
  • HTTPCACHE_DIR指定缓存存储的目录为httpcache;
  • HTTPCACHE_IGNORE_HTTP_CODES则指定了一些需要忽略缓存的 HTTP 状态码,比如 301(永久重定向)、302(临时重定向)、403(禁止访问)、404(未找到)等,因为这些状态码对应的响应可能不适合缓存或者需要重新处理。

二、编写下载中间件实现自定义功能

2.1 下载中间件概述

下载中间件(Downloader Middlewares)在 Scrapy 框架中处于引擎(Engine)和下载器(Downloader)之间,就像是一个 “关卡管理员”,负责对请求(Request)和响应(Response)进行拦截和处理。它可以在请求发送到下载器之前,对请求进行各种修改和定制,比如添加代理、修改请求头、设置 Cookies 等;也可以在下载器返回响应之后,对响应进行处理,比如修改响应内容、处理重定向、检查响应状态码等。通过编写自定义的下载中间件,我们能够根据具体的爬虫需求,灵活地扩展和定制 Scrapy 的下载功能 ,增强爬虫的适应性和稳定性。

2.2 编写下载中间件添加代理

在爬虫过程中,使用代理服务器可以隐藏我们的真实 IP 地址,避免因为频繁访问目标网站而导致 IP 被封禁。下面我们来编写一个代理中间件,实现为每个请求设置代理的功能。

首先,在middlewares.py文件中编写代理中间件类,示例代码如下:

import random


class ProxyMiddleware(object):
    def __init__(self, proxy_list):
        self.proxy_list = proxy_list

    @classmethod
    def from_crawler(cls, crawler):
        proxy_list = crawler.settings.get('PROXY_LIST', [])
        return cls(proxy_list)

    def process_request(self, request, spider):
        proxy = random.choice(self.proxy_list)
        request.meta['proxy'] = proxy
        # 如果代理需要验证,添加验证信息
        if 'username' in proxy and 'password' in proxy:
            basic_auth = f"{proxy['username']}:{proxy['password']}"
            request.headers['Proxy-Authorization'] = 'Basic ' + basic_auth.encode('base64').strip()

在上述代码中,ProxyMiddleware类的__init__方法接收一个代理列表proxy_list,from_crawler方法用于从 Scrapy 的设置中获取代理列表。process_request方法则在每个请求经过下载中间件时被调用,它从代理列表中随机选择一个代理,并将其设置到请求的meta属性中。如果代理需要用户名和密码验证,还会添加Proxy - Authorization请求头。

然后,在settings.py文件中配置代理中间件和代理列表:

DOWNLOADER_MIDDLEWARES = {
   'myproject.middlewares.ProxyMiddleware': 543,
}

PROXY_LIST = [
    'http://proxy1.example.com',
    'http://proxy2.example.com',
    'http://username:password@proxy3.example.com',
]

这里将ProxyMiddleware添加到DOWNLOADER_MIDDLEWARES中,并设置其优先级为 543(数字越小,优先级越高)。同时,定义了一个PROXY_LIST列表,包含了多个代理服务器地址,其中第三个代理包含了用户名和密码验证信息。

2.3 编写下载中间件修改请求头

在爬虫过程中,修改请求头是一种常见的反爬虫策略。网站通常会根据请求头中的信息来判断请求是否来自爬虫程序,如果请求头信息过于单一或明显是爬虫特征,就可能会被网站拒绝访问。通过修改请求头,我们可以伪装成不同的浏览器或客户端,增加爬虫的隐蔽性。

一种常见的方法是使用fake_useragent包来随机生成不同的 User - Agent 请求头。首先安装fake_useragent包:

pip install fake_useragent

然后在middlewares.py文件中编写修改请求头的中间件类,示例代码如下:

from fake_useragent import UserAgent


class RandomUserAgentMiddleware(object):
    def __init__(self):
        self.ua = UserAgent()

    def process_request(self, request, spider):
        request.headers['User - Agent'] = self.ua.random

上述代码中,RandomUserAgentMiddleware类的__init__方法初始化了UserAgent对象,process_request方法在每个请求经过下载中间件时,将请求头中的User - Agent设置为随机生成的 User - Agent。

如果不想使用fake_useragent包,也可以自己定义一个 User - Agent 列表,然后随机选择其中一个来设置请求头,示例代码如下:

import random


class CustomUserAgentMiddleware(object):
    USER_AGENTS = [
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
    ]

    def process_request(self, request, spider):
        user_agent = random.choice(self.USER_AGENTS)
        request.headers['User - Agent'] = user_agent

最后,在settings.py文件中启用修改请求头的中间件:

DOWNLOADER_MIDDLEWARES = {
   'myproject.middlewares.RandomUserAgentMiddleware': 544,
    # 或者使用自定义User - Agent中间件
   'myproject.middlewares.CustomUserAgentMiddleware': 544,
}

这样,在每次请求时,请求头中的User - Agent都会被随机修改,从而增加爬虫的伪装性,降低被网站识别为爬虫的风险。

三、处理下载过程中的异常情况

3.1 超时处理

在爬虫过程中,下载超时是一个常见的问题。当网络不稳定、目标服务器响应缓慢或者请求过多导致网络拥堵时,都可能出现下载超时的情况。下载超时会导致请求无法获取到有效的响应,从而影响爬虫的数据抓取效率和完整性。如果不及时处理下载超时,可能会使爬虫任务长时间阻塞在某个请求上,降低整个爬虫的运行速度,甚至导致爬虫任务失败。

在 Scrapy 中,我们可以在下载中间件的process_exception方法中捕获超时异常,并进行相应的处理。以twisted.internet.error.TimeoutError和twisted.internet.error.TCPTimedOutError为例,这两个异常分别表示请求超时和 TCP 连接超时,通常是由于网络问题或服务器响应过慢导致。我们可以在捕获到这些异常时,重新发送请求,示例代码如下:

from twisted.internet.error import TimeoutError, TCPTimedOutError


class TimeoutMiddleware(object):
    def process_exception(self, request, exception, spider):
        if isinstance(exception, (TimeoutError, TCPTimedOutError)):
            spider.logger.warning(f"Request {request.url} timed out, retrying...")
            return request.replace(dont_filter=True)

在上述代码中,TimeoutMiddleware类的process_exception方法接收request、exception和spider三个参数。当捕获到TimeoutError或TCPTimedOutError异常时,使用spider.logger.warning记录一条警告日志,提示请求超时并正在重试。然后,通过request.replace(dont_filter=True)方法重新发送请求,dont_filter=True参数表示该请求不会被调度器过滤,确保请求能够再次被发送。

3.2 重定向处理

重定向是指当客户端向服务器发送请求时,服务器返回一个特殊的响应状态码,告诉客户端需要将请求发送到另一个 URL。常见的重定向状态码有 301(永久重定向)、302(临时重定向)、303(查看其他位置)、307(临时重定向,要求客户端保持请求方法不变)和 308(永久重定向) 。重定向的原因有很多,比如网站的 URL 结构发生变化,为了保持用户体验和链接的有效性,服务器会将旧 URL 重定向到新的 URL;或者当用户访问一个需要权限的页面时,服务器可能会将用户重定向到登录页面。

在某些情况下,我们可能不希望爬虫自动跟随重定向,比如当我们只想获取重定向前的原始链接信息时。这时,我们可以在请求中添加meta参数来中止重定向。具体方法是在发送请求时,设置meta={‘dont_redirect’: True, ‘handle_httpstatus_list’: [301, 302]},其中dont_redirect设置为True表示禁止重定向,handle_httpstatus_list指定了需要处理的 HTTP 状态码列表,这里将 301 和 302 状态码也视为有效响应,而不是错误。示例代码如下:

import scrapy


class MySpider(scrapy.Spider):
    name ='myspider'
    start_urls = ['http://example.com']

    def start_requests(self):
        for url in self.start_urls:
            yield scrapy.Request(url, meta={'dont_redirect': True, 'handle_httpstatus_list': [301, 302]},
                                 callback=self.parse)

    def parse(self, response):
        # 处理响应,这里可以获取重定向前的原始链接等信息
        pass

如果我们想要获取重定向后的链接,可以在响应中进行处理。当发生重定向时,重定向后的链接会放在响应的headers中的Location字段。我们可以通过response.headers.get(“Location”)来获取这个链接,示例代码如下:

import scrapy


class MySpider(scrapy.Spider):
    name ='myspider'
    start_urls = ['http://example.com']

    def parse(self, response):
        if response.status in [301, 302]:
            redirect_url = response.headers.get("Location").decode('utf-8')
            self.logger.info(f"Redirected to: {redirect_url}")
            # 可以在这里对重定向后的链接进行进一步处理

在上述代码中,首先检查响应的状态码是否为 301 或 302,如果是,则通过response.headers.get(“Location”).decode(‘utf-8’)获取重定向后的链接,并使用self.logger.info记录日志,之后可以根据实际需求对重定向后的链接进行进一步的处理,比如继续发送请求获取页面内容等。

四、总结与展望

Scrapy 的下载器和下载中间件在爬虫开发中扮演着不可或缺的角色。下载器作为核心组件,负责与目标网站进行网络交互,获取响应数据,其性能的优化直接影响到爬虫的效率和稳定性。通过合理配置并发请求、下载延迟、超时时间以及缓存机制,我们能够在保证爬虫正常运行的前提下,最大限度地提高抓取速度,减少资源浪费。

下载中间件则为我们提供了丰富的扩展功能,它像是一个灵活的插件系统,允许我们在请求发送和响应接收的过程中,对数据进行各种自定义处理。从添加代理和修改请求头,到处理下载过程中的异常情况,下载中间件赋予了我们应对复杂网络环境和反爬虫策略的能力。

在实际项目中,我们应当充分运用所学知识,根据不同的爬虫需求,巧妙地配置下载器参数和编写下载中间件。对于新手来说,可以从简单的爬虫任务入手,逐步熟悉下载器和下载中间件的工作原理和使用方法;而对于有一定经验的开发者,则可以尝试更复杂的应用场景,如大规模数据抓取、高并发爬虫等,不断探索和优化爬虫性能。

随着互联网技术的不断发展,反爬虫技术也在日益更新。未来,我们需要持续关注爬虫领域的新技术和新趋势,不断学习和创新,以更好地应对各种挑战,让 Scrapy 爬虫在数据采集的道路上发挥更大的作用。