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

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

一、多线程的基础知识

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

要点主要是:

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

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

2.1 从单线程版本入手

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

import requests
import random
import 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%2F23img.com%2Fi%2F2022%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 requests
import threading
import random
import 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%2F23img.com%2Fi%2F2022%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%2F23img.com%2Fi%2F2022%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 = 0
path = '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网络爬虫程序中使用线程池》

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

  1. 在Python网络爬虫程序中使用线程池

    在Python网络爬虫程序中使用线程池 一.为什么需要使用线程池 二.线程池的使用 2.1 线程池的类与方法 2.2 使用线程池的一般步骤 三.在爬虫程序中使用线程池的实例 一.为什么需要使用线程池 ...

  2. Python大作业-网络爬虫程序

    简介 此程序是本人大三时期的Python大作业,初学Python后所编写的一个程序,是一个网络爬虫程序,可爬取指定网站的信息. 本程序爬取的网站是Bangumi-我看过的动画,Bangumi是一个专注 ...

  3. python编写爬虫的步骤-python学习: 写网络爬虫程序的三个难点

    写爬虫,是一个非常考验综合实力的活儿.有时候,你轻而易举地就抓取到了想要的数据:有时候,你费尽心思却毫无所获. 好多Python爬虫的入门教程都是一行代码就把你骗上了"贼船",等上 ...

  4. 爬虫软件python功能_Python 网络爬虫程序详解

    #!/usr/bin/python #调用python from sys import argv #导入sys是导入python解释器和他环境相关的参数 from os import makedirs ...

  5. python网络爬虫程序_Python写的网络爬虫程序(很简单)

    Python写的网络爬虫程序(很简单) 这是我的一位同学传给我的一个小的网页爬虫程序,觉得挺有意思的,和大家分享一下.不过有一点需要注意,要用python2.3,如果用python3.4会有些问题出现 ...

  6. python从零基础到项目实战怎么样-Python 3.x网络爬虫从零基础到项目实战

    ● 案例完整 本书中的所有案例都是通过"理论讲解 + 环境搭建 + 完整代码及分析 + 运行结果"这种完善的结构进行讲解的.此外,复杂的案例配有项目结构图,有难度的案例还分析了底层 ...

  7. 用python如何写网络爬虫?

    前言 这本书我是真的强烈推荐的. 本书讲解了如何使用 Python 来编写网络爬虫程序,内容包括网络爬虫简介,从页面中抓取数据的三种方法,提取缓存中的数据,使用多个线程和进程来进行并发抓取,如何抓取动 ...

  8. 一篇文章教会你Python网络爬虫程序的基本执行流程

    网络爬虫是指在互联网上自动爬取网站内容信息的程序,也被称作网络蜘蛛或网络机器人.大型的爬虫程序被广泛应用于搜索引擎.数据挖掘等领域,个人用户或企业也可以利用爬虫收集对自身有价值的数据. 一个网络爬虫程 ...

  9. Python网络爬虫过程中,构建网络请求的时候,参数`stream=True`的使用

    点击上方"Python共享之家",进行关注 回复"资源"即可获赠Python学习资料 今 日 鸡 汤 海内存知己,天涯若比邻. 大家好,我是皮皮. 一.前言 前 ...

最新文章

  1. 获得AndroidManifest.xml文件中meta-data的value值
  2. Copycat - 状态
  3. Flagger on ASM·基于Mixerless Telemetry实现渐进式灰度发布系列 1 遥测数据
  4. mysql 优化 修复原理_mysql下表的修复与优化
  5. 玩冒险岛java卸载_如何删除冒险岛安装了,现在不想玩
  6. java d long_java 中long型数据的对比
  7. 从入门到入土:nmap出击:使用nmap扫描某台靶机,给出并解读靶机环境的配置情况
  8. python中所有数值都可以准确比较是否相等_在python里,禁用== = = 以及is和in,如何判断两个数字的值是否相等?...
  9. MATLAB GUI新手备忘录
  10. 打车日记 - 原年人念念不忘的茄汁大虾
  11. gedit c语言,让gedit 成为强大的C语言IDE
  12. java企业级进销存管理系统源码
  13. FPGA下载(kintex-7)
  14. java publisher_Combine -- 常见的 Publisher 及 Operator
  15. 游戏安全资讯精选 2017年第十六期:房卡式棋牌游戏涉赌博风波,抓娃娃火爆市场背后的安全隐患需警惕,Linux内核的Huge Dirty Cow权限提升漏洞...
  16. 运行中的Docker容器添加映射端口
  17. 使用 AccountManager 实现系统内共享账号
  18. 期货十三篇 第七篇 平仓篇
  19. 超越光速:时间旅行不是梦
  20. 以太坊模拟器Ganache v7重磅发布!

热门文章

  1. java之字符串--回文字符串
  2. Kettle连接Clickhouse 自定义插件
  3. python物联网全栈开发实践
  4. python安装库备忘
  5. 为企业上云号脉,听听这些ISV如何说?
  6. Grafana 的介绍和安装
  7. 2021-2027中国军用电子元器件市场现状及未来发展趋势
  8. MapGIS地图导入
  9. 深入理解 DRM (1) --了解Widevine与OEMCrypto
  10. 彻底解决gradle与gradle plugin匹配关系以及gradle下载缓慢的问题