700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > python如何在网络爬虫程序中使用多线程(threading.Thread)

python如何在网络爬虫程序中使用多线程(threading.Thread)

时间:2018-10-27 22:16:32

相关推荐

python如何在网络爬虫程序中使用多线程(threading.Thread)

python如何在网络爬虫程序中使用多线程

一、多线程的基础知识二、在网络爬虫中使用多线程2.1 从单线程版本入手2.2 将单线程版本改写为多线程版本2.3 运行多线程版本程序2.4 将多线程应用到爬虫程序中三、考虑使用线程池

一、多线程的基础知识

关于多线程相关的基础知识,已经在另一篇文章中有过详细描述,此处不再赘述。有需要的可以参考:

Python并发编程之threading模块

要点主要是:

使自己的类继承自threading.Thread在自己的类里重写run方法

二、在网络爬虫中使用多线程

2.1 从单线程版本入手

这里,我们仍然以下载单张图片的简单程序为例,首先,我们可以实现一个单线程版的图片下载器,代码范例可以从这篇文章中获取:

python使用requests库下载单张图片的简单示例

import requestsimport randomimport osclass PicDownloader():def __init__(self):self.count = 0self.file_name_prefix = 'img'self.working_dir = '.'def download_img(self, img_url, dir='.'):'''下载url指定的图片到本地dir目录'''self.count += 1file_name = self.file_name_prefix + '_' + str(self.count) + '.jpg'if os.path.isdir(dir):self.working_dir = direlse:try:os.makedirs(dir)self.working_dir = direxcept OSError as e:print(f'create dir: {dir} failed')file_path = os.path.join(self.working_dir, file_name)print(f'开始下载文件:{file_path}')headers = self.get_headers()try:res = requests.get(url=img_url, headers=headers)if 200 == res.status_code:with open(file_path, 'wb') as file:file.write(res.content)except Exception as e:print(f'下载文件失败: {file_path}')print(repr(e))print('文件下载成功')# 本例中实际不需要这么多def get_headers(self):user_agent_list = ["Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1","Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6","Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6","Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1","Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5","Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3","Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3","Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3","Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3","Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3","Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3","Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24","Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"]userAgent = random.choice(user_agent_list)headers = {'User-Agent': userAgent}return headersif __name__== '__main__':downloader = PicDownloader()downloader.download_img('https://images.weserv.nl/?url=https%3A%2F%%2Fi%2F%2F08%2F17%2Fqykvvx.jpg', 'd:/test')

2.2 将单线程版本改写为多线程版本

如何将其改造为多线程版本呢?

首先,导入threading模块,并使PicDownloader继承自threading.Thread, 构造函数中带入一些必要的参数,即可

class PicDownloader(threading.Thread):def __init__(self, name, img_url, file_name, dir='.'):threading.Thread.__init__(self) # 注意,这一步是必须的,先对Thread进行相关初始化self.setName(name)# 线程名是可选的,这里设置只是为了后面观察线程方便self.daemon = True# 按需设置self.url = img_urlself.file_name = file_nameself.working_dir = dir

现在PicDownloader类就可以以线程的方式来运行了,我们还需要重写一下run()方法, 其实就是把原先的主要工作方法download_img()里面的内容挪到了run()方法内,你甚至可以理解为就是把download_img()重命名为run()方法。

def run(self):'''下载url指定的图片到本地dir目录'''if os.path.isdir(self.working_dir):passelse:try:os.makedirs(self.working_dir)except OSError as e:print(f'create dir: {dir} failed')if not self.file_name.endswith('.jpg'):self.file_name = self.file_name + '.jpg'file_path = os.path.join(self.working_dir, self.file_name)print(f'thread:{self.name} -> 开始下载文件:{file_path}')headers = self.get_headers()try:res = requests.get(url=self.url, headers=headers)if 200 == res.status_code:with open(file_path, 'wb') as file:file.write(res.content)except Exception as e:print(f'下载文件失败: {file_path}')print(repr(e))print(f'thread:{self.name} -> 文件下载成功:{file_path}')

至此,多线程的改造基本结束。

完整的代码:

# coding=utf-8import requestsimport threadingimport randomimport osclass PicDownloader(threading.Thread):def __init__(self, name, img_url, file_name, dir='.'):threading.Thread.__init__(self)self.setName(name)self.daemon = Trueself.url = img_urlself.file_name = file_nameself.working_dir = dirdef run(self):'''下载url指定的图片到本地dir目录'''if os.path.isdir(self.working_dir):passelse:try:os.makedirs(self.working_dir)except OSError as e:print(f'create dir: {dir} failed')if not self.file_name.endswith('.jpg'):self.file_name = self.file_name + '.jpg'file_path = os.path.join(self.working_dir, self.file_name)print(f'thread:{self.name} -> 开始下载文件:{file_path}')headers = self.get_headers()try:res = requests.get(url=self.url, headers=headers)if 200 == res.status_code:with open(file_path, 'wb') as file:file.write(res.content)except Exception as e:print(f'下载文件失败: {file_path}')print(repr(e))print(f'thread:{self.name} -> 文件下载成功:{file_path}')def get_headers(self):user_agent_list = ["Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1","Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6","Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6","Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1","Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5","Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3","Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3","Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3","Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3","Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3","Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3","Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24","Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"]userAgent = random.choice(user_agent_list)headers = {'User-Agent': userAgent}return headersif __name__== '__main__':downloader = PicDownloader(img_url='https://images.weserv.nl/?url=https%3A%2F%%2Fi%2F%2F08%2F17%2Fqykvvx.jpg', file_name='1', dir='d:/test')downloader.start()downloader.join()

2.3 运行多线程版本程序

多线程版本的类class PicDownloader(threading.Thread)改造完成后,我们只需要调用start()方法就可以启动线程。例如:

if __name__== '__main__':downloader = PicDownloader(img_url='https://images.weserv.nl/?url=https%3A%2F%%2Fi%2F%2F08%2F17%2Fqykvvx.jpg', file_name='1', dir='d:/test')downloader.start()downloader.join()

start()

此方法会在一个单独的控制线程中调用run()方法,启动线程。此方法只能调用一次。

join([timeout])

等待直到线程终止或者出现超时为止。timeout是一个浮点数,用于指定以秒为单位的超时时间。必须在start()方法之后调用,如果在线程启动之前就连接它将出现错误。

2.4 将多线程应用到爬虫程序中

以上程序单独执行,即下载单张图片,还不能体现多线程的优势。结合爬虫程序,我们看看如何运用多线程版本的图片下载器。

假设一个最简单的场景,就是我们想从一个页面上,爬取该页面上所有的图片。

当然了,这前期有一些工作要做,比如将所有的图片url都解析出来(本例中存到了列表img_url_list中),那么之后就可以发挥出多线程版本图片下载器的威力了。示例代码如下:

count = 0path = 'd:/test'pic_download_threads = []for img_url in img_url_list:count += 1file_name = 'img_' + str(count)pic_download_threads.append(PicDownloader(str(count), img_url, file_name, path))for working_thread in pic_download_threads:working_thread.start()for working_thread in pic_download_threads:working_thread.join(5)

即使img_url_list里只有十几张图片的url,多线程版本也比单线程版本要快的多。如果图片url数目更多的话,速度优势就更明显了。

三、考虑使用线程池

上面是常规的多线程处理方式,如果img_url_list里只有十几张图片的url,多线程版本将比单线程版本要快的多。

但我们回过头来考虑以上程序,假如img_url_list里面的成员数非常多的话,比如该页面可能有数百张图片需要下载时,此时会发生什么?如果像上面这么做,我们将启动数百个工作线程!!!而系统启动一个新线程的成本是比较高的,因为它涉及与操作系统的交互。并且,当系统中包含有大量的并发线程时,会导致系统性能急剧下降,甚至导致 Python 解释器崩溃。

在这种情况下,使用线程池就可以很好地提升性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。

关于线程池的使用,请移步我的另一篇博文:《在Python网络爬虫程序中使用线程池》

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。