🛠️ Scrapy 框架原理解读
Scrapy 是一个强大的 Python 框架,其设计理念基于事件驱动的异步编程,通过高度模块化的方式实现功能。Scrapy 框架的核心组成包括(Spider)、调度器(Scheduler)、下载器(Downloader)、管道(Pipeline)和中间件(Middleware)。每个组件在数据抓取的过程中扮演不同的角色,从请求的生成、处理到数据的存储,整个流程经过了多个阶段,每个阶段都可以通过中间件进行扩展和修改。
(Spider): 是 Scrapy 的核心,负责定义爬取逻辑,包括请求的生成和响应的解析。它通过回调函数处理响应,并提取数据或生成新的请求。
调度器(Scheduler): 调度器的主要职责是接收请求并将其排队,等待下载器处理。它会按照请求的优先级和调度策略管理请求队列。
下载器(Downloader): 下载器负责发送 HTTP 请求并接收响应。它将响应传递给,再根据业务逻辑进行数据处理。
管道(Pipeline): 管道用于处理提取的数据。它可以进行数据清洗、验证、存储等操作。每个管道的优先级可以配置,以控制数据处理的顺序。
中间件(Middleware): 中间件在请求和响应处理的过程中插入自定义逻辑。它可以在请求发送前修改请求,在响应返回前处理响应,或者处理异常。
Scrapy 的这种设计使得的构建和维护变得更加灵活和可扩展。下面我们将深入解析 Scrapy 的运行原理和底层实现。
🚀 Scrapy 运行原理
Scrapy 的运行过程包括以下主要步骤:
- 请求生成: 定义的初始请求通过调度器(Scheduler)排队,等待下载器(Downloader)处理。
- 请求下载: 下载器从调度器中取出请求,发送 HTTP 请求,并获取响应数据。
- 响应处理: 下载器将响应传递给(Spider),解析响应数据,提取有用信息,并可能生成新的请求。
- 数据处理: 将提取的数据传递给管道(Pipeline),进行清洗、验证和存储操作。
- 数据存储: 数据处理完成后,管道将数据保存到数据库、文件系统或其他存储介质中。
请求生成: 通过定义 start_requests
方法或者 parse
回调函数生成初始请求。这些请求被传递到调度器中。
请求下载: 下载器会从调度器中取出请求,使用异步 I/O 技术(如 Twisted)发送请求,并获取响应。响应会被传递回。
响应处理: 通过回调函数(如 parse
方法)处理响应数据。可以从响应中提取数据,并生成新的请求以继续抓取。
数据处理: 数据处理由管道完成。Scrapy 提供了默认管道(如 JsonItemExporter
)和允许用户自定义管道。管道的优先级决定了数据处理的顺序。
📜 Scrapy 底层源码解析
Scrapy 的底层实现依赖于 Twisted 异步框架,主要通过以下核心类来实现其功能:
CrawlerProcess
CrawlerProcess
是 Scrapy 的一个高层接口,用于启动进程。它封装了的创建、启动和管理过程。下面是一个简单的示例:
from scrapy.crawler import CrawlerProcess from scrapy.utils.project import get_project_settings process = CrawlerProcess(get_project_settings()) process.crawl('my_spider') process.start()
CrawlerRunner
CrawlerRunner
是一个较底层的类,用于在事件循环中管理多个。它允许同时运行多个并进行调度。
from scrapy.crawler import CrawlerRunner from twisted.internet import reactor runner = CrawlerRunner() runner.crawl('my_spider') runner.join().addCallback(lambda _: reactor.stop()) reactor.run()
Crawler
Crawler
是 Scrapy 的类,用于定义抓取逻辑。它包含了请求的生成和响应的处理逻辑。以下是一个自定义的示例:
import scrapy class MySpider(scrapy.Spider): name = 'my_spider' def start_requests(self): urls = ['http://example.com/page1', 'http://example.com/page2'] for url in urls: yield scrapy.Request(url=url, callback=self.parse) def parse(self, response): title = response.css('title::text').get() yield {'title': title}
🛡️ Scrapy 中间件原理解读
Scrapy 的中间件是一种强大的机制,允许用户在请求和响应的处理过程中插入自定义逻辑。中间件可以分为下载中间件和响应中间件,它们分别处理请求和响应的不同阶段。
🌐 下载和响应中间件
- 下载中间件: 负责处理请求的发送和响应的接收。主要方法包括
process_request
和process_response
。process_request
在请求发送之前被调用,process_response
在响应返回之前被调用。 - 响应中间件: 主要负责处理响应内容的进一步操作,如解析响应、处理重定向等。主要方法包括
process_exception
,用于处理请求过程中发生的异常。
以下是一个自定义下载中间件的示例:
middlewares.py
:
class CustomDownloaderMiddleware: def process_request(self, request, spider): # 在请求被发送之前修改请求头 request.headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36' return None def process_response(self, request, response, spider): # 在响应返回之前处理 if response.status == 404: spider.logger.error('Page not found: %s', request.url) return response def process_exception(self, request, exception, spider): # 处理请求异常 spider.logger.error('Request failed: %s', request.url) return None
🔧 中间件优先级别
中间件的优先级通过 DOWNLOADER_MIDDLEWARES
和 SPIDER_MIDDLEWARES
设置。优先级较高的中间件会先处理请求或响应。例如:
settings.py
:
DOWNLOADER_MIDDLEWARES = { 'myproject.middlewares.CustomDownloaderMiddleware': 543, }
🛠️ 自定义下载中间件
自定义下载中间件允许开发者实现特定的功能,例如修改请求头、处理重定向、设置代理等。以下是一个更复杂的自定义下载中间件示例:
middlewares.py
:
class ProxyMiddleware: def process_request(self, request, spider): # 设置代理 request.meta['proxy'] = 'http://myproxy:port' return None def process_response(self, request, response, spider): # 处理响应中的数据 if response.status == 403: spider.logger.warning('Access denied: %s', request.url) return response def process_exception(self, request, exception, spider): # 处理请求异常 spider.logger.error('Request exception: %s', exception) return None
在 settings.py
中启用自定义中间件:
DOWNLOADER_MIDDLEWARES = { 'myproject.middlewares.ProxyMiddleware': 543, }
📥 Scrapy 媒资下载器
Scrapy 媒资下载器用于处理图片、视频等媒体文件的下载。默认情况下,Scrapy 提供了一个 ImagesPipeline
用于下载和处理图片。以下是一个自定义的媒资下载器示例:
pipelines.py
:
import scrapy from scrapy.pipelines.images import ImagesPipeline class MediaPipeline(ImagesPipeline): def get_media_requests(self, item, info): if 'image_urls' in item: for url in item['image_urls']: yield scrapy.Request(url) def file_path(self, request, response=None, info=None, *, item=None): return 'images/%s' % request.url.split('/')[-1]
在 settings.py
中启用媒体管道:
ITEM_PIPELINES = { 'myproject.pipelines.MediaPipeline': 1, }
📧 Scr
apy 邮件监听
Scrapy 允许通过扩展机制发送邮件通知。下面是一个示例,展示如何在结束后通过邮件发送通知:
extensions.py
:
import smtplib from scrapy import signals class EmailNotificationExtension: def __init__(self, smtp_server, smtp_port, from_email, to_email, password): self.smtp_server = smtp_server self.smtp_port = smtp_port self.from_email = from_email self.to_email = to_email self.password = password @classmethod def from_crawler(cls, crawler): settings = crawler.settings return cls( smtp_server=settings.get('SMTP_SERVER'), smtp_port=settings.get('SMTP_PORT'), from_email=settings.get('FROM_EMAIL'), to_email=settings.get('TO_EMAIL'), password=settings.get('EMAIL_PASSWORD') ) def open_spider(self, spider): self.spider = spider def close_spider(self, spider): self.send_email() def send_email(self): with smtplib.SMTP(self.smtp_server, self.smtp_port) as server: server.starttls() server.login(self.from_email, self.password) subject = 'Scrapy Spider Finished' body = 'Your Scrapy spider has finished running successfully!' msg = f'Subject: {subject}\n\n{body}' server.sendmail(self.from_email, self.to_email, msg)
在 settings.py
中启用邮件扩展:
EXTENSIONS = { 'myproject.extensions.EmailNotificationExtension': 500, }
以上内容详细解读了 Scrapy 框架的运行原理、底层源码、中间件和自定义功能,涵盖了从请求生成、响应处理到数据存储的完整流程。希望这些示例和讲解能够帮助你更好地理解和使用 Scrapy 框架,在实际开发中灵活应用。