python多线程2

本文探讨了Python多线程的使用,包括线程数量的查看、线程的启动与资源竞争。详细介绍了线程间共享全局变量可能导致的问题及解决方案,如time.sleep()和join()方法。此外,还讲解了线程锁、死锁和队列操作,最后讨论了多线程在爬虫中的应用,提升爬取速度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

查看线程数量——enumerate()

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

通常用index代表索引

在这里插入图片描述

enumerate()在threading线程库的作用——threading.enumerate()

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

加入time.sleep()观察

import threading
# threading.enumerate()

def demo1():
    for i in range(5):
        print('我是demo1---%s' % i)
        time.sleep(1)

def demo2():
    for i in range(8):
        print('我是demo2---%s' % i)
        time.sleep(1)
def main():
    t1 =threading.Thread(target=demo1,name='demo1')
    t2 = threading.Thread(target=demo2,name='demo2')
    t1.start()
    t2.start()
    while True:
        print(threading.enumerate())
        if len(threading.enumerate())<=1:
            break
        time.sleep(1)


if __name__ == '__main__':
    main()

在这里插入图片描述

start()的作用——开启线程创建线程

在这里插入图片描述
在这里插入图片描述

global——修改值

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

函数与方法

在这里插入图片描述

线程资源竞争

线程间共享全局变量

import threading

num = 100


def demo1():
    global num
    num += 1
    print('demo1-num-%d' % num)


def demo2():
    # global num
    # num +=1
    print('demo2-num-%d' % num)


def main():
    t1 = threading.Thread(target=demo1, name='demo1')
    t2 = threading.Thread(target=demo2, name='demo2')
    t1.start()  # 开起线程和创建执行线程
    t2.start()
    # 线程间共享全局变量
    print('main-num-%d' % num)


if __name__ == '__main__':
    main()

在这里插入图片描述

当数量变大

import threading

num = 0

# num外部是0,demo1先加1,加到最后是100
def demo1(nums):
    global num
    for i in range(nums):  #nums接受args(100,)这个赋值100,就是循环100次
        num += 1
    print('demo1-num-%d' % num)

# demo2接受demo1的num值,最后结果应该是200
def demo2(nums):
    global num
    for i in range(nums):
        num += 1
    print('demo2-num-%d' % num)


def main():
    t1 = threading.Thread(target=demo1, args=(100,))
    t2 = threading.Thread(target=demo2, args=(100,))
    t1.start()  # 开起线程和创建执行线程
    t2.start()
    # 线程间共享全局变量
    # 最后结果应该同demo2一致为200
    print('main-num-%d' % num)


if __name__ == '__main__':
    main()

在这里插入图片描述

当数量变得更大时——200w时

在这里插入图片描述

线程间相互争抢资源,有的时候线程1对num进行运算还没赋值,线程2也对没有重新复制的num赋值,导致结果不同

在这里插入图片描述

解决方法:time.sleep()

在这里插入图片描述

解决方法2:join()——主线程一直等待全部的子线程结束之后,主线程自身才结束,程序退出

在这里插入图片描述
在这里插入图片描述

线程锁

互斥锁

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

死锁

在这里插入图片描述
在这里插入图片描述

两把锁——会程序堵塞(即使解锁两次也会堵塞)

在这里插入图片描述
在这里插入图片描述

threading.RLock()可以上多次锁

在这里插入图片描述

queue线程

在这里插入图片描述
在这里插入图片描述

判断队列是否为空——Queue().empty()——True为空,False为非空

在这里插入图片描述

判断是否队列满—— Queue().full()——False 为不满,True为满

在这里插入图片描述

put()将一个数据添加到队列中

在这里插入图片描述
在这里插入图片描述

注意——当指定大小后,在队列已满的情况再添加数据会导致程序堵塞,解决方式1为添加参数——timeout

在这里插入图片描述

解决方法2:putnowait()

在这里插入图片描述

取数据 get() 一次取一个

在这里插入图片描述

一次取一个,取完再取会堵塞,解决方式也是timeout()或者getnowait()

在这里插入图片描述

用多线程爬取

发现应用程序是ajax动态加载,可以用selenuim或者分析页面来源,可以在network找XHR或者在ALL找

在这里插入图片描述
在这里插入图片描述

打开第一个url

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

普通(非多线程下的爬虫案例+csv保存)

# 发现应用程序是ajax动态加载,可以用selenuim或者分析页面来源
# 可以再network找XHR或者在ALL找

# 点开一个,url https://app.mi.com/details?id=com.kuaiyin.live
# XHR 里的packageName是这个                   com.kuaiyin.live
# 所以url为‘https://app.mi.com/details?id=’ 拼接+ packageName
# 通过 XHR找到的url为
# 第一页 https://app.mi.com/categotyAllListApi?page=0
# 第二页 https://app.mi.com/categotyAllListApi?page=1

import requests
import time
import random
from fake_useragent import UserAgent
import csv
class XiaomiSpider():
    def __init__(self):
        # {}为占位 在后面的来一个format占位页数
        self.url ='https://app.mi.com/categotyAllListApi?page={}&categoryId=2&pageSize=30'
        # headers
        # self.headers ={
        #     'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36'
        # }

    # 获取响应内容 获得的内容使json数据
    def get_html(self,url):
        lst =[]
        # 写一个假的headers
        headers ={'User-Agent':UserAgent().random}
        # 之前的方法 把json变成python字典类型
        # response =requests.get(html) response.text
        # data =json.loads(requests.text)
        # 方法2
        # data =requests.json()
        # html =requests.get(url,headers=self.headers).json()
        html = requests.get(url, headers=headers).json()
        # 调用parse_html函数
        lst += self.parse_html(html)
        # print(item,type(item))
        return lst

    # 解析响应内容
    def parse_html(self,html):
        lst =[]
        # 现在是字典类型了
        # item ={}
        for app_dict in html['data']:
            item = {}
            # 将每个html(app_dict)的应用名赋值给空字典item,并设下key和对应value值
            item['app_name']=app_dict['displayName']
            item['app_category'] = app_dict['level1CategoryName']
            # 这个是应用程序的url
            item['app_link'] = 'https://app.mi.com/details?id='+app_dict['packageName']
            lst.append(item)
        return lst
    def save_html(self,lst):
        with open('xiaomi.csv','w',encoding='utf-8',newline='')as f:
            headers = ('app_name', 'app_category', 'app_link')
            dwriter = csv.DictWriter(f, headers)
            dwriter.writeheader()
            dwriter.writerows(lst)

    def main(self):
        # 一共67页 因为时0为第一页,67页为66,但是range最后一个取不到,要加1 所以开始range(67)
        lst =[]
        for page in range(67):
            url =self.url.format(page)
            # 调用get_html函数
            lst += self.get_html(url=url)
            # self.save_html(item)
            # 控制抓取频率
            # n =random.randint(1,2)等于是1=<n<=2
            # time.sleep(random.randint(1,2))
        self.save_html(lst)


if __name__ == '__main__':
    xiaomi =XiaomiSpider()
    xiaomi.main()

结果

在这里插入图片描述

用多线程方式爬取同一个网站——速度更快

import requests
import time
import random
from fake_useragent import UserAgent
import csv
import threading
from queue import Queue


class XiaomiSpider():
    def __init__(self):
        # {}为占位 在后面的来一个format占位页数
        self.url = 'https://app.mi.com/categotyAllListApi?page={}&categoryId=2&pageSize=30'
        # 创建队列
        self.q = Queue()
        self.lock = threading.Lock()

    def url_in(self):
        # 将目标url放进队列
        for page in range(67):
            url = self.url.format(page)
            # 将网页数据添加到队列
            self.q.put(url)

    # 线程事件函数 获取url 请求解析处理
    def parse_html(self):
        # 线程中不可能只让线程抓取一页,所以来个死循环while true,不停抓取
        lst = []
        while True:
            # 上锁
            self.lock.acquire()
            if not self.q.empty():
                # 获取 url
                url = self.q.get()
                # 解锁
                self.lock.release()
                headers = {'User-Agent': UserAgent().random}
                # 将json类型转化为字典类型
                html =requests.get(url,headers=headers).json()
                # item = {}
                # lst =[]
                for app_dict in html['data']:
                    item = {}
                # 将每个html(app_dict)的应用名赋值给空字典item,并设下key和对应value值
                    item['app_name'] = app_dict['displayName']
                    item['app_category'] = app_dict['level1CategoryName']
                    # 这个是应用程序的url
                    item['app_link'] = 'https://app.mi.com/details?id=' + app_dict['packageName']
                    lst.append(item)
                # print(lst)
                self.save(lst)
            else:
                self.lock.release()
                break

    def save(self,lst):
        with open('xiaomi1.csv','w',encoding='utf-8',newline='')as f:
            headers =('app_name','app_category','app_link')
            dwriter =csv.DictWriter(f,headers)
            dwriter.writeheader()
            dwriter.writerows(lst)
    def run(self):
        # 调用方法,添加数据到队列,然后放在线程
        self.url_in()
        t_lst =[]
        for i in range(2):
            # 循环两次,创建两个线程
            t =threading.Thread(target=self.parse_html)
            # 这个写法跟创建t1 t2 一样
            t_lst.append(t)
            t.start()

if __name__ == '__main__':
    spider =XiaomiSpider()
    spider.run()

# JSONDecodeError 抓取频率太快了,json数据页面没加载完美及时返回

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值