教你控制Python多线程中线程数量
前言
前段时间学习了python的多线程爬虫,当时爬取一个图片网站,开启多线程后,并没有限制线程的数量,也就是说,如果下载1000张图片,会一次性开启1000个子线程同时进行下载
现在希望控制线程数量:例如每次只下载5张,当下载完成后再下载另外5张,直至全部完成
查了一些资料,发现在python中,threading 模块有提供 Semaphore类 和 BoundedSemaphore类来限制线程数
官网给出例子如下:
信号量通常用于保护容量有限的资源,例如数据库服务器。在资源大小固定的任何情况下,都应使用有界信号量。在产生任何工作线程之前,您的主线程将初始化信号量:
maxconnections = 5
# ...
pool_sema = BoundedSemaphore(value=maxconnections)
产生后,工作线程在需要连接到服务器时会调用信号量的获取和释放方法:
改造之前的多线程爬虫
首先贴出原来的代码
# -*- coding:utf-8 -*-
import requests
from requests.exceptions import RequestException
import os, time
import re
from lxml import etree
import threadinglock = threading.Lock()
def get_html(url):"""定义一个方法,用于获取一个url页面的响应内容:param url: 要访问的url:return: 响应内容"""response = requests.get(url, timeout=10)# print(response.status_code)try:if response.status_code == 200:# print(response.text)return response.textelse:return Noneexcept RequestException:print("请求失败")# return Nonedef parse_html(html_text):"""定义一个方法,用于解析页面内容,提取图片url:param html_text::return:一个页面的图片url集合"""html = etree.HTML(html_text)if len(html) > 0:img_src = html.xpath("//img[@class='photothumb lazy']/@data-original") # 元素提取方法# print(img_src)return img_srcelse:print("解析页面元素失败")def get_image_pages(url):"""获取所查询图片结果的所有页码:param url: 查询图片url:return: 总页码数"""html_text = get_html(url) # 获取搜索url响应内容# print(html_text)if html_text is not None:html = etree.HTML(html_text) # 生成XPath解析对象last_page = html.xpath("//div[@class='pages']//a[last()]/@href") # 提取最后一页所在href链接print(last_page)if last_page:max_page = re.compile(r'(\d+)', re.S).search(last_page[0]).group() # 使用正则表达式提取链接中的页码数字print(max_page)print(type(max_page))return int(max_page) # 将字符串页码转为整数并返回else:print("暂无数据")return Noneelse:print("查询结果失败")def get_all_image_url(page_number):"""获取所有图片的下载url:param page_number: 爬取页码:return: 所有图片url的集合"""base_url = 'https://imgbin.com/free-png/naruto/'image_urls = []x = 1 # 定义一个标识,用于给每个图片url编号,从1递增for i in range(1, page_number):url = base_url + str(i) # 根据页码遍历请求urltry:html = get_html(url) # 解析每个页面的内容if html:data = parse_html(html) # 提取页面中的图片url# print(data)# time.sleep(3)if data:for j in data:image_urls.append({'name': x,'value': j})x += 1 # 每提取一个图片url,标识x增加1except RequestException as f:print("遇到错误:", f)continue# print(image_urls)return image_urlsdef get_image_content(url):"""请求图片url,返回二进制内容"""# print("正在下载", url)try:r = requests.get(url, timeout=15)if r.status_code == 200:return r.contentreturn Noneexcept RequestException:return Nonedef main(url, image_name):"""主函数:实现下载图片功能:param url: 图片url:param image_name: 图片名称:return:"""semaphore.acquire() # 加锁,限制线程数print('当前子线程: {}'.format(threading.current_thread().name))save_path = os.path.dirname(os.path.abspath('.')) + '/pics/'try:file_path = '{0}/{1}.jpg'.format(save_path, image_name)if not os.path.exists(file_path): # 判断是否存在文件,不存在则爬取with open(file_path, 'wb') as f:f.write(get_image_content(url))f.close()print('第{}个文件保存成功'.format(image_name))else:print("第{}个文件已存在".format(image_name))semaphore.release() # 解锁imgbin-多线程-重写run方法.pyexcept FileNotFoundError as f:print("第{}个文件下载时遇到错误,url为:{}:".format(image_name, url))print("报错:", f)raiseexcept TypeError as e:print("第{}个文件下载时遇到错误,url为:{}:".format(image_name, url))print("报错:", e)class MyThread(threading.Thread):"""继承Thread类重写run方法创建新进程"""def __init__(self, func, args):""":param func: run方法中要调用的函数名:param args: func函数所需的参数"""threading.Thread.__init__(self)self.func = funcself.args = argsdef run(self):print('当前子线程: {}'.format(threading.current_thread().name))self.func(self.args[0], self.args[1])# 调用func函数# 因为这里的func函数其实是上述的main()函数,它需要2个参数;args传入的是个参数元组,拆解开来传入if __name__ == '__main__':start = time.time()print('这是主线程:{}'.format(threading.current_thread().name))urls = get_all_image_url(5) # 获取所有图片url列表thread_list = [] # 定义一个列表,向里面追加线程semaphore = threading.BoundedSemaphore(5) # 或使用Semaphore方法for t in urls:# print(i)m = MyThread(main, (t["value"], t["name"])) # 调用MyThread类,得到一个实例thread_list.append(m)for m in thread_list:m.start() # 调用start()方法,开始执行for m in thread_list:m.join() # 子线程调用join()方法,使主线程等待子线程运行完毕之后才退出end = time.time()print(end-start)# get_image_pages("https://imgbin.com/free-png/Naruto")
将代码进行改造
1、下面的第8、9行表示调用 threading 的 BoundedSemaphore类,初始化信号量为5,把结果赋给变量 pool_sema
if __name__ == '__main__':start = time.time()print('这是主线程:{}'.format(threading.current_thread().name))urls = get_all_image_url(5) # 获取所有图片url列表thread_list = [] # 定义一个列表,向里面追加线程# 更多Python相关视频、资料加群778463939免费获取max_connections = 5 # 定义最大线程数pool_sema = threading.BoundedSemaphore(max_connections) # 或使用Semaphore方法for t in urls:# print(i)m = MyThread(main, (t["value"], t["name"])) # 调用MyThread类,得到一个实例thread_list.append(m)for m in thread_list:m.start() # 调用start()方法,开始执行for m in thread_list:m.join() # 子线程调用join()方法,使主线程等待子线程运行完毕之后才退出end = time.time()print(end-start)
2、修改main()函数
(1)方法一:通过with语句实现,第9行添加 with pool_sema
使用 with 语句来获得一个锁、条件变量或信号量,相当于调用 acquire();离开 with 块后,会自动调用 release()
def main(url, image_name):"""主函数:实现下载图片功能:param url: 图片url:param image_name: 图片名称:return:"""with pool_sema:print('当前子线程: {}'.format(threading.current_thread().name))save_path = os.path.dirname(os.path.abspath('.')) + '/pics/'try:file_path = '{0}/{1}.jpg'.format(save_path, image_name)if not os.path.exists(file_path): # 判断是否存在文件,不存在则爬取with open(file_path, 'wb') as f:f.write(get_image_content(url))f.close()print('第{}个文件保存成功'.format(image_name))else:print("第{}个文件已存在".format(image_name))except FileNotFoundError as f:print("第{}个文件下载时遇到错误,url为:{}:".format(image_name, url))print("报错:", f)raiseexcept TypeError as e:print("第{}个文件下载时遇到错误,url为:{}:".format(image_name, url))print("报错:", e)
(2)方法二:直接使用 acquire()和 release()
下面的第8行调用 acquire(),第24行调用release()
def main(url, image_name):"""主函数:实现下载图片功能:param url: 图片url:param image_name: 图片名称:return:"""pool_sema.acquire() # 加锁,限制线程数# with pool_sema:print('当前子线程: {}'.format(threading.current_thread().name))save_path = os.path.dirname(os.path.abspath('.')) + '/pics/'try:file_path = '{0}/{1}.jpg'.format(save_path, image_name)if not os.path.exists(file_path): # 判断是否存在文件,不存在则爬取with open(file_path, 'wb') as f:f.write(get_image_content(url))f.close()print('第{}个文件保存成功'.format(image_name))else:print("第{}个文件已存在".format(image_name))pool_sema.release() # 解锁imgbin-多线程-重写run方法.pyexcept FileNotFoundError as f:print("第{}个文件下载时遇到错误,url为:{}:".format(image_name, url))print("报错:", f)raiseexcept TypeError as e:print("第{}个文件下载时遇到错误,url为:{}:".format(image_name, url))print("报错:", e)
最终效果是一样的,每次启用5个线程,完成后再启动下一批
结尾给大家推荐一个非常好的学习教程,希望对你学习Python有帮助!
Python基础入门教程推荐:←点击左边蓝色文字就可以跳转观看了
Python爬虫案例教程推荐:←点击左边蓝色文字就可以跳转观看了
教你控制Python多线程中线程数量相关推荐
- python多线程结束线程_Python线程– Python多线程
python多线程结束线程 Python threading module is used to implement multithreading in python programs. In thi ...
- python多线程守护线程_Python守护程序线程
python多线程守护线程 In this tutorial we will be learning about Python Daemon Thread. In our previous tutor ...
- python多线程结束线程_Python多线程和Office第2部分
python多线程结束线程 This is the second and final part of the series. You can find the first part of the bl ...
- php和python的多线程,Python多线程以及线程锁简单理解(代码)
本篇文章给大家带来的内容是关于Python多线程以及线程锁简单理解(代码),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 多线程threading 模块创建线程创建自己的线程类线程通 ...
- java 多线程池_Java项目中,线程池中线程数量太大会有什么影响?
简单说一下吧!拿我们生活中非常常见的一例子来说:并不是人多就能把事情做好,增加了沟通交流成本.你本来一件事情只需要3个人做,你硬是拉来了6个人,会提升做事效率嘛?我想并不会. 线程数量过多的影响也是和 ...
- python获取当前线程数量_python 线程数
python 多线程 真正的多线程吗? 对于多核处理器,在同一时间确实可以多个线程独立运行,但在Python中确不是这样的了.原因在于,python虚拟机中引入了GIL这一概念.GIL(Global ...
- java闭合数据_java多线程中线程封闭详解
线程封闭的概念 访问共享变量时,通常要使用同步,所以避免使用同步的方法就是减少共享数据的使用,这种技术就是线程封闭. 实现线程封闭的方法 1:ad-hoc线程封闭 这是完全靠实现者控制的线程封闭,他的 ...
- python 多线程及线程间通信
python 多线程 import threading import time def run(n):print("task", n)time.sleep(1)print('2s' ...
- python多线程中join()的理解
在 Python 的多线程编程中,经常碰到 thread.join()这样的代码.那么今天咱们用实际代码来解释一下 join 函数的作用. 第一,当一个进程启动之后,会默认产生一个主线程,因为线程是程 ...
最新文章
- zabbix 小纪录
- UE把环境变量Path改了
- 逆袭“潮牌”后,李宁离安踏还有多远?
- JMS-activMq与spring进行整合
- hadoop2.2.0 分布式存储hdfs完全分布式搭建及功能测试记录(一)----架构及原理介绍...
- $ is not defined 如何解决
- 太空java游戏_JavaSpaceWar 一个基于 语言编写的太空战争游戏,包含所有图像和声音资源,便 习理解使用 Game Program 238万源代码下载- www.pudn.com...
- 在Windows下使用Git+TortoiseGit+码云管理项目代码
- Anormaly_Detection_with_Keras
- Window下MySQL 8.0重新设置密码
- 雷达导论PART-III.8 雷达接收机与数字化
- AutoCAD 百度网盘免费下载
- android高德地图清除marker,删除多个点标记-点标记-示例中心-JS API 示例 | 高德地图API...
- 低代码接口开发平台——YesApi(免费注册)
- PDF 格式的文件编辑难度非常大,相比 .docx格式,它存在的意义是什么?
- 安全的微信群管理工具
- Ubuntu16.04 安装字体教程
- 华硕EeePC: 没有光驱重装系统(用U盘作为电脑启动盘)
- 脑机接口的深度学习算法
- mac电脑安装nvm及使用的详细步骤
热门文章
- 配置百度云盘python客户端bypy上传备份文件
- 前端基础1:HTML常用标签
- 从库存管理系统,体验活字格强大的移动端应用开发能力
- python.re模块
- 隐马尔科夫模型(Hidden Markov Models) 系列之五
- VS Tips]Visual Studio 2008 Toolbox里控件消失(#13119)的问题
- 菜鸟教程之Microsoft Windows Powershell
- 奇怪的方式c语言,C语言中奇怪的C语言特性
- java if在内存中_全面理解Java内存模型
- 【学习笔记】JS进阶语法一事件进阶