教你控制Python多线程中线程数量_python限制线程数量,剩下的线程怎么办

72 阅读7分钟

前言

前段时间学习了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 threading

lock = 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.text
        else:
             return None
    except RequestException:
        print("请求失败")
        # return None


def 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_src

    else:
        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 None
    else:
        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)  # 根据页码遍历请求url
        try:
            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增加1
        except RequestException as f:
            print("遇到错误:", f)
            continue
    # print(image\_urls)
    return image_urls

def get\_image\_content(url):
    """请求图片url,返回二进制内容"""
    # print("正在下载", url)
    try:
        r = requests.get(url, timeout=15)
        if r.status_code == 200:
            return r.content
        return None
    except RequestException:
        return None

def 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方法.py

    except FileNotFoundError as f:
        print("第{}个文件下载时遇到错误,url为:{}:".format(image_name, url))
        print("报错:", f)
        raise

    except 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 = func
        self.args = args

    def 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:


如果你也是看准了Python,想自学Python,在这里为大家准备了丰厚的免费**学习**大礼包,带大家一起学习,给大家剖析Python兼职、就业行情前景的这些事儿。



### 一、Python所有方向的学习路线

Python所有方向路线就是把Python常用的技术点做整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。



![](https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/2a8d4f88376a4398987c4975cb550cd1~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3NTc5MjMwMTY3MDI=:q75.awebp?rk3s=f64ab15b&x-expires=1771415918&x-signature=8TrTIoX%2FPNDcesaUck2Ev8eYz6c%3D)

### 二、学习软件



工欲善其必先利其器。学习Python常用的开发软件都在这里了,给大家节省了很多时间。



![](https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/02dbb5fc090048c2acd6187fc0ae1c8a~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3NTc5MjMwMTY3MDI=:q75.awebp?rk3s=f64ab15b&x-expires=1771415918&x-signature=OJ%2FAMr6Qaf3MTyg8Y1%2FDU%2BVl6ro%3D)



### 三、全套PDF电子书

书籍的好处就在于权威和体系健全,刚开始学习的时候你可以只看视频或者听某个人讲课,但等你学完之后,你觉得你掌握了,这时候建议还是得去看一下书籍,看权威技术书籍也是每个程序员必经之路。

![](https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/4fa97cf725f5477fb56b32decf3f9b60~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3NTc5MjMwMTY3MDI=:q75.awebp?rk3s=f64ab15b&x-expires=1771415918&x-signature=EGVZCjIyKsChhssJcOidwXE4bAc%3D)



### 四、入门学习视频

我们在看视频学习的时候,不能光动眼动脑不动手,比较科学的学习方法是在理解之后运用它们,这时候练手项目就很适合了。



![](https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/b56bb66f49df4edea70c77de425613de~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3NTc5MjMwMTY3MDI=:q75.awebp?rk3s=f64ab15b&x-expires=1771415918&x-signature=SxXPKbIn1lPPYuVAGfR%2BHShZKc0%3D)  

![](https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/6ab630489c6f4cd1ae845bc32fc5b382~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3NTc5MjMwMTY3MDI=:q75.awebp?rk3s=f64ab15b&x-expires=1771415918&x-signature=fEeW6qJP3zv3kuxKyPCauc5PB2E%3D)



### 四、实战案例

光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。



![](https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/6d9522246cb549298abd77321855ec5b~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3NTc5MjMwMTY3MDI=:q75.awebp?rk3s=f64ab15b&x-expires=1771415918&x-signature=XCPsyVnKKqF0LGNdw2dzwOBoK4E%3D)



### 五、面试资料

我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

![](https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/27b932347cb04d00903de14e60aaa892~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3NTc5MjMwMTY3MDI=:q75.awebp?rk3s=f64ab15b&x-expires=1771415918&x-signature=r%2BQbN0dZgSY3br%2ByLgFukyHNCm0%3D)

成为一个Python程序员专家或许需要花费数年时间,但是打下坚实的基础只要几周就可以,如果你按照我提供的学习路线以及资料有意识地去实践,你就有很大可能成功!
最后祝你好运!!!



**了解详情:https://docs.qq.com/doc/DSnl3ZGlhT1RDaVhV**