当前位置:嗨网首页>书籍在线阅读

11-案例 #2_代码阻塞

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

10.5.2 案例 #2:代码阻塞

症状: 你所观察到的行为无法说通。和期望值相比,系统非常慢,并且奇怪的是,即使当你改变 CONCURRENT_REQUESTS 的值时,速度也没有显著变化(见图10.7)。下载器看起来总是空的(少于 CONCURRENT_REQUESTS ),而抓取程序却有不少响应。

55.png

图10.7 阻塞代码以不可预测的方式使并发无效

示例: 你可以使用两个基准设置: SPEED_SPIDER_BLOCKING_DELAYSPEED_PIPELINE_BLOCKING_DELAY (它们具有相同的效果),对每个响应启用一个100ms的阻塞。在给定并发级别时,我们期望100个URL应当花费2~3秒,但无论 CONCURRENT_REQUESTS 的值是多少,我们总是需要花费大约13秒的时间(见表10.3)。

for concurrent in 16 32 64; do
 time scrapy crawl speed -s SPEED_TOTAL_ITEMS=100 \
 -s CONCURRENT_REQUESTS=$concurrent -s SPEED_SPIDER_BLOCKING_DELAY=0.1
done
表10.3

| CONCURRENT_REQUESTS | 总时间(秒) | | :----- | :----- | :----- | :----- | | 16 | 13.9 | | 32 | 13.2 | | 64 | 12.9 |

讨论: 任何阻塞代码都会立即抵消掉Scrapy的并发性,本质上相当于设置 CONCURRENT_REQUESTS = 1。根据上面的简单公式,100URL · 100ms(阻塞延迟)= 10秒 + tstart/stop,充分解释了我们所看到的延迟。

无论阻塞代码是在管道中还是在爬虫中,你都会发现抓取程序可以被充分利用,但其前后的模块都是空的。这看起来违背了前面讲过的管道的物理现象,不过由于我们已经不再拥有一个并发系统了,所以管道规则不再适用。该错误非常容易发生(比如使用阻塞API),你一定会在某一时刻出现该错误。你会注意到类似的讨论同样适用于复杂代码的计算。你应当为此类代码使用多线程,正如我们在第9章中所看到的;或者是在Scrapy之外进行批量处理,我们将会在第11章中看到一个相关示例。

解决方案: 将假设你继承了基代码,并且不清楚阻塞代码位于何处。如果该系统在没有任何管道的情况下仍然可以工作,那么禁用这些管道,并检查是否仍存在奇怪的行为。如果仍存在,那么阻塞代码位于爬虫中。如果不再存在,那么依次启用管道,观察问题是否开始出现。如果该系统在缺少任何运行中的模块的情况下无法正常运转,那么可以在每个管道阶段的功能之间添加一些日志消息(或插入虚拟管道打印时间戳)。通过检查日志,可以轻松检测出系统在什么地方花费了最多的时间。如果希望有一个更加长期/可复用的解决方案,可以使用虚拟管道跟踪你的请求,在 Requestmeta 字段中为每个阶段添加时间戳。最后,hook到 item_scraped 信号,并记录时间戳日志。一旦你发现阻塞代码,则应将其转换为Twisted/异步代码,或使用Twisted的线程池。如果想要查看该转换的效果,可以将 SPEED_PIPELINE_BLOCKING_DELAY 替换为 SPEED_ PIPELINE_ASYNC_DELAY ,重新运行前面的示例。性能的变化将十分惊人。