如何制作一个多线程的爬虫
前言
- 目标: 通过多线程提高爬取图片资源站资源的效率
- 前置知识:
- 使用python发起http请求
- 使用BeautifulSoup或其他库解析请求返回的网站文本
1. 何为生产者消费者模式
生产着消费者问题描述了一个场景, 存在若干生产物品的生产者和若干消费物品的消费者,以及一个容量有限的仓库
生产者不能生产超过仓库容量的物品, 消费者也不能在仓库为空时消费物品
为此, 我们需要在仓库满时挂起生产者的, 在仓库空时挂起消费者以实现两者之间的同步
在我们目标下, 我们需要若干生产者线程生产资源的链接, 若干消费者线程获取链接并下载内容
单线程时, 我们需要获取链接->进行下载
这样的循环操作, 由于下载时候是较费时的IO操作, 获取链接主要是CPU操作, 将它们分离开来显然是可以节省相互等待的时间的
于是我们可以有若干线程同时爬取资源链接, 若干线程同时进行下载操作
所以我们需要一个消费者线程, 一个生产者线程, 以及一个线程安全的连接池
在python中, queue包就是实现了线程安全的一个队列, 我们可以使用他作为连接池
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Producter(threading.Thread):
# 生产者模型:解析页面,将得到的图片地址放入img图片队列中
def __init__(self, page_queue, img_queue, *args, **kwargs):
#初始化
pass
def run(self):
#实现消费者模型的主要业务逻辑
while True:
# 当页面队列为空,生产者停止生产
if self.page_queue.empty():
break
# 获取url队列的对象,进行页面解析
url = self.page_queue.get(True, 300)
self.parse_url(url)
def parse_url(self, url):
# 解析页面并获取链接
self.img_queue.put(img_url)
pass
class Consumer(threading.Thread):
# 消费者模型的主要业务逻辑
def __init__(self, page_queue, img_queue, *args, **kwargs):
# 初始化
pass
def run(self):
# 读取img队列的链接并下载到本地
while True:
# 当解析队列和图片链接队列都为空时退出
if self.page_queue.empty() and self.img_queue.empty():
pass
这样, 一个基本的生产者消费者模式的框架就有了, 我们实现里面的功能, 并在主线程开启若干线程即可
2. 实现过程中的问题
实现爬取页面里的其他页面链接
由于我们不可能只爬取固定页面的资源, 我们需要在爬取图片链接的同时, 将其他的页面抓取到生产者的队列中
实现这个功能并不难, 但网页的链接比我想的多的多, 很快就到达了queue设置的上限
到达上线后, 生产者线程就开始占着cpu划水了, 因为这个问题导致提取图片资源效率极低, 这是后来才发现的, 后面说如何处理
另一个, 由于存在大量链接以及循环引用的存在, 链接极易重复, 如果用遍历来查重我觉得效率太低了, 于是使用了hash实现的python中的set
查重页面以及图片链接, 这是非线程安全的, 但问题不大
解决各种杂症
修修改改实现大致功能运行后, 下载一小段时间就开始卡住, 极慢的下载
排查后发现是页面链接太多到达了queue上限
而且我认为按爬取的顺序来访问页面, 可能效率没有访问的好
于是我对一个页面中的所有链接进行python的切片, 每隔step个链接就加入一个到队列中
并且判断队列的内容长度, 如果太多了就加大step, 反之亦反
这样处理之后一运行, 下载立马全速进行, 之前卡卡嗖嗖的下了几百个图, 改了之后下了一个小时也没见变慢
but我的硬盘会撑不住, 下了快一万张图后停下了, 这次尝试也算是成功了
3. 总结
在实现目标的过程中发现, 对于爬虫来说, 要爬取大量链接时, 链接重复一定是一个问题
我寻思一定有成熟的方案, 我在这里只是做一个初学者的尝试, 对于超大量级来说, em…我可能想不到什么解决方案
还是太菜了, 这个先到这里, 当最后爬虫全速运行时, 感觉真爽