前言

前段时间学习了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多线程中线程数量相关推荐

  1. python多线程结束线程_Python线程– Python多线程

    python多线程结束线程 Python threading module is used to implement multithreading in python programs. In thi ...

  2. python多线程守护线程_Python守护程序线程

    python多线程守护线程 In this tutorial we will be learning about Python Daemon Thread. In our previous tutor ...

  3. python多线程结束线程_Python多线程和Office第2部分

    python多线程结束线程 This is the second and final part of the series. You can find the first part of the bl ...

  4. php和python的多线程,Python多线程以及线程锁简单理解(代码)

    本篇文章给大家带来的内容是关于Python多线程以及线程锁简单理解(代码),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 多线程threading 模块创建线程创建自己的线程类线程通 ...

  5. java 多线程池_Java项目中,线程池中线程数量太大会有什么影响?

    简单说一下吧!拿我们生活中非常常见的一例子来说:并不是人多就能把事情做好,增加了沟通交流成本.你本来一件事情只需要3个人做,你硬是拉来了6个人,会提升做事效率嘛?我想并不会. 线程数量过多的影响也是和 ...

  6. python获取当前线程数量_python 线程数

    python 多线程 真正的多线程吗? 对于多核处理器,在同一时间确实可以多个线程独立运行,但在Python中确不是这样的了.原因在于,python虚拟机中引入了GIL这一概念.GIL(Global ...

  7. java闭合数据_java多线程中线程封闭详解

    线程封闭的概念 访问共享变量时,通常要使用同步,所以避免使用同步的方法就是减少共享数据的使用,这种技术就是线程封闭. 实现线程封闭的方法 1:ad-hoc线程封闭 这是完全靠实现者控制的线程封闭,他的 ...

  8. python 多线程及线程间通信

    python 多线程 import threading import time def run(n):print("task", n)time.sleep(1)print('2s' ...

  9. python多线程中join()的理解

    在 Python 的多线程编程中,经常碰到 thread.join()这样的代码.那么今天咱们用实际代码来解释一下 join 函数的作用. 第一,当一个进程启动之后,会默认产生一个主线程,因为线程是程 ...

最新文章

  1. zabbix 小纪录
  2. UE把环境变量Path改了
  3. 逆袭“潮牌”后,李宁离安踏还有多远?
  4. JMS-activMq与spring进行整合
  5. hadoop2.2.0 分布式存储hdfs完全分布式搭建及功能测试记录(一)----架构及原理介绍...
  6. $ is not defined 如何解决
  7. 太空java游戏_JavaSpaceWar 一个基于 语言编写的太空战争游戏,包含所有图像和声音资源,便 习理解使用 Game Program 238万源代码下载- www.pudn.com...
  8. 在Windows下使用Git+TortoiseGit+码云管理项目代码
  9. Anormaly_Detection_with_Keras
  10. Window下MySQL 8.0重新设置密码
  11. 雷达导论PART-III.8 雷达接收机与数字化
  12. AutoCAD 百度网盘免费下载
  13. android高德地图清除marker,删除多个点标记-点标记-示例中心-JS API 示例 | 高德地图API...
  14. 低代码接口开发平台——YesApi(免费注册)
  15. PDF 格式的文件编辑难度非常大,相比 .docx格式,它存在的意义是什么?
  16. 安全的微信群管理工具
  17. Ubuntu16.04 安装字体教程
  18. 华硕EeePC: 没有光驱重装系统(用U盘作为电脑启动盘)
  19. 脑机接口的深度学习算法
  20. mac电脑安装nvm及使用的详细步骤

热门文章

  1. 配置百度云盘python客户端bypy上传备份文件
  2. 前端基础1:HTML常用标签
  3. 从库存管理系统,体验活字格强大的移动端应用开发能力
  4. python.re模块
  5. 隐马尔科夫模型(Hidden Markov Models) 系列之五
  6. VS Tips]Visual Studio 2008 Toolbox里控件消失(#13119)的问题
  7. 菜鸟教程之Microsoft Windows Powershell
  8. 奇怪的方式c语言,C语言中奇怪的C语言特性
  9. java if在内存中_全面理解Java内存模型
  10. 【学习笔记】JS进阶语法一事件进阶