大家好,在我们使用 scrapy 进行网站数据采集的时,会遇到多个 spider 同时运行或者单个 spider 运行的情况,一般采取的是 shell 命令去运行,在分析 scrapy 的源码实现时,发现可以定制化启动,本篇文章我将分享启动代码和依靠启动代码分析部分 scrapy 的启动流程,希望能给读者朋友们带来帮助。
特别声明:本公众号文章只作为学术研究,不作为其他不法用途;如有侵权请联系作者删除。
立即加星标
每月看好文
目录
一、前言介绍
二、启动代码实现
三、scrapy启动分析
四、crawl源码分析
五、ExecutionEngine分析
六、downloader源码分析
一、前言介绍
本篇文章阅读完后,读者可以知晓使用 scrapy 时优雅的启动 spider 和了解 scrapy 的启动流程,让采集者在工作中更加的游刃有余,接下来我们进入正文吧。
二、启动代码实现
编写启动代码如下:
思路:传参,可以传 all 或者传 spider 的名字,从而启动爬虫。
使用:可以把这个代码放到对应的位置,名字可以自定义。
这个 name 对应 spider 中的 spider name。
三、scrapy 启动分析
上面的 class 作用:这个类用于在一个进程中运行多个爬虫。
从源码技术角度解读,主要做了如下功能:
启动 reactor 事件监听(start函数)
当接收到关闭信号时时停止 reactor(signal_shutdown 和 signal_kill)从父类 CrawlerRunner
首先初始化方法主要创建了 self._crawlers ,是个 set,里面可以存放多个 Crawler,self._active 也是个 set,里面是正在运行的 crawler。
立刻停止所有的在运行的 Crawler(stop 方法)
等待所有的 Crawler 运行完后再停止(join)
传入一个 spider 的实例类,然后使用 create_crawler 创建一个 crawler,然后调用 _crawl 方法并将加入 self.crawlers,然后调用创建的crawler的crawl进行爬取 (这里不会立即爬,会等到 reactor事件循环开始后)(crawl)
然后分析 crawl,等事件循环开启后,crawl 就开始爬取,那么爬取前做了什么?
总结:Craler 其实算我们的主角了,因为像一些关于爬虫内部相关的的东西都要在这里初始化并创建,他的存在是如果有一个 spider 要被创建那么就会有一个 crawler 去对应。例如初始化时会初始化下方的一些东西:
四、crawl 源码分析
首先会进行 spider 的创建;把自己设置的 setting 和 scrapy 本身的 setting 进行合并(合并的时候自己设置的为优先级高,如果同时拥有重复的,那么自己设置的会将scrapy 默认的进行覆盖);创建 engine,调用引擎的 open_spider 方法;并且调用了 engine的start 方法,这两个要重点观看,代码如下:
总结: 这个类里面还有一些其他方法,获取spider中间件(get_spider_middleware),获得pipline(get_iteme_pipline),获 得扩展 (get_extension),停止 spider(stop),接下来重头戏跑到了引擎里面。
五、ExecutionEngine 分析
分析的过程会具体以爬虫的运行为导向,会牵扯其实一些初始化的代码或者辅助的代码为辅。初始化方法里面会重点的创建 调度器;下载器;Scraper; 源码分析如下:
然后我们分析 open_spider:
做了以下工作,主要对 itempipline 需要进行 open_spider 进行了处理(这里也说明了链接数据库啥的前置性,如果在 itempipline 写的情况下会被引擎主动调用),对 start_requests 进行了调度;统计状态;发送 spider_opened 的信号。源码解读如下:
重点看如何实现的定时在事件循环中调度,通过这个方法可以每五秒主动调用一次,下面的四行代码形成了个逻辑去不间断的调用self._next_request以便达到处理请求的目的。源码解读如下:
通过上面的代码,调用的函数是 _next_request:从 spider 一开始,那么调度器中的 request 肯定为空,那么会从 start_requests 进行取请求,如果调度器中存在 requests,那么会一直使用 _next_request_from_scheduler(),处理请求,如果说这 5s 的是主动去request,那么这 self._next_request_from_scheduler() 调用就是被动去 request。源码如下:
接下来看下 _next_request_from_scheduler,从调度器中获取下一个请求,下载请求并处理下载结果,同时确保在不同阶段(处理下载结果、移除请求、调度下一个调用)记录任何错误,源码解读如下:
总结:拿到请求后,我们接下来分析请求是如何下载和解析的。
六、downloader源码分析
首先会将请求加入 slot 后,使用 downloader.fetch 进行处理 request,如果处理完成后返回的结果是 response,会发送 response_received 信号,然后调用 _on_complete 继续下一个调度,源码解读如下:
接下来我们看download.fetch如何处理的:首先调用中间件处理请求self.middleware.download,然后无论是否成功,都要从activate中移除这个请求,源码如下:
这个 download 函数,会首先调用所有请求中间件的 process_requests 方法,如果 process_requests 返回一个 request 或者返回为空那么接着会进行执行下一个中间件的 process_requests 方法, 如果 process_requests 方法返回一个 response,那么下载器直接就返回 response 了,不会进行请求;如果不返回 response 的情况下,调用 download_dunc 函数进行下载;源码解读如下:
通过分析可以查看到下载函数如下:
接下来分析图片中函数,代码解读如下:
下载完成后,我们回到 _next_request_from_scheduler的d.addBoth(self._handle_downloader_output, request) 处理下载器的结果,我们在上方代码 def download(self, download_func: Callable, request: Request, spider: Spider) 的 process_request 里面已经知道,如果直接返回一个 requqest 或 response 对象,那么会直接返回到现在处理的结果上, 所以处理这个结果的时候,会有两种可能如果是 Request 类型的那么继续抓取,是 response 直接进行解析;代码解读如下:
接下来我们看看 scrapy 是如何处理响应的,代码解读如下:
上面就是其完整流程了,其实还有一个问题?spider 的解析方法 是哪调用的?
答:其实是 scrape2,通过调用 spider 中 spider 中间件,然后通过 call_spider,调用的回调方法,代码解读如下:
本篇分享到这里就结束了,感谢大家的阅读和支持。如果你对爬虫逆向分析、验证码破解及其他技术话题感兴趣,记得关注我的公众号,不错过下一期的更新。我们将继续深入探讨各种技术细节和实用技巧,一起探索数字世界的奥秘。期待与你在下期文章中再见,一起学习,一起进步!☀️☀️