Python爬虫编程7——多线程爬虫

2023-05-16

目录

一.多线程基本介绍

程序中模拟多任务

二.多线程的创建

三.主线程与子线程的执行关系

四.查看线程数量

五.线程间的通信(多线程共享全局变量)

六.线程间的资源竞争

互斥锁和死锁

互斥锁

死锁

七.Queue线程

八.生产者和消费者

Lock版的生产者和消费者

Condition版的生产者和消费者

九.多线程的应用实例

爬取小米商城使用普通方式爬取

使用多线程爬取


一.多线程基本介绍

有很多场景中的事情是同时进行的,比如开车的时候手和脚来共同驾驶汽车,再比如唱歌跳舞也是同时进行的。

程序中模拟多任务

import time

def sing():
    for i in range(3):
        print("正在唱歌...%d"%i)
        time.sleep(1)

def dance():
    for i in range(3):
        print("正在跳舞...%d"%i)
        time.sleep(1)

if __name__ == '__main__':
    sing()
    dance()

二.多线程的创建

当调用Thread的时候,不会创建线程。

当调用Thread创建出来的实例对象的start方法的时候,才会创建线程以及开始运行这个线程。

(1)通过函数来创建多线程:

import threading
import time


# 通过函数创建多线程
def demo():
    # 线程的函数事件
    print('子线程!')


if __name__ == '__main__':
    for i in range(8):
        t = threading.Thread(target=demo)  # 只是创建还没有启动
        t.start()  # 启动(一个可以启动的状态)

(2)通过类来创建多线程:

import threading
import time

# 通过类来创建多线程
class MyThread(threading.Thread):
    # 重写run方法
    def run(self):
        for i in range(5):
            print("这是一个子线程!")


if __name__ == '__main__':
    m = MyThread()
    # start 启动一个子线程
    m.start()

(3)对线程中传入参数时,需要使用参数 args 注意传入的是一个元组;

    t1 = threading.Thread(target=demo1, args=(100000,))
    t2 = threading.Thread(target=demo2, args=(100000,))

三.主线程与子线程的执行关系

(1)主线程会等待子线程结束之后结束;

(2)join()等待子线程结束之后,主线程继续执行;

(3)setDaemon()守护线程,不会等待子线程结束,即当主线程执行完,无论子线程有没有技术都会结束程序;

import threading
import time

def text():
    for i in range(4):
        print("子线程!")
        # time.sleep(1)


if __name__ == '__main__':
    t = threading.Thread(target=text)
    t.start()
    # 强制等待
    # time.sleep(3)
    # t.setDaemon()   # 当主线程运行完,无论子线程玩没玩都会结束
    t.join()  # 不论子线程运行多久,一定是要等待子线程运行完成之后才运行主线程,有个timeout参数,可以设置等待时间
    print("111")

四.查看线程数量

(1)enumerate() 方法在循环中使用时,会连同索引一起返回:

# enumerate()  连同索引一起返回
text_list = ['xxx', 'yyy', 'zzz']
for index, i in enumerate(text_list):
    # print(type(i), i)
    print(index, i)

(2)threading.enumerate() 查看线程数量的方法:

threading.enumerate()	查看当前线程的数量

使用例子:

import threading
import time


# threading.enumerate()
def demo1():
    for i in range(8):
        time.sleep(1)
        print(f'demo1--{i}')


def demo2():
    for i in range(5):
        time.sleep(1)
        print(f'demo2--{i}')


if __name__ == '__main__':
    t1 = threading.Thread(target=demo1)
    # print(threading.enumerate())
    t2 = threading.Thread(target=demo2)
    t1.start()
    # 在start开始时子线程才会创建成功
    # print(threading.enumerate())
    t2.start()
    # print(threading.enumerate())
    while True:
        time.sleep(1)
        print(threading.enumerate())
        if len(threading.enumerate()) <= 1:
            break

五.线程间的通信(多线程共享全局变量)

在一个函数中,对全局变量进行修改的时候,是否要加 global 要看是否对全局变量的指向进行了修改,如果修改了指向,那么必须使用 global ,仅仅是修改了指向的空间中的数据,此时不用必须使用 global ,线程是共享全局变量。

六.线程间的资源竞争

一个线程写入,一个线程读取,没问题,如果两个线程都写入呢?

互斥锁和死锁

互斥锁

当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制。

某个线程要更改共享数据时,先将其锁定,此时资源的状态为锁定,其他线程不能改变,只能该线程释放资源,将资源的状态变成非锁定,其它的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

创建锁
mutex = threading.Lock()

锁定
mutex.acquire()

解锁
mutex.release()

死锁

在线程共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方资源,就会造成死锁。

import threading
import time

class MyThread1(threading.Thread):
    def run(self):
        # 对mutexA上锁
        mutexA.acquire()

        # mutexA上锁后,延时1秒,等待另外那个线程 把mutexB上锁
        print(self.name+'----do1---up----')
        time.sleep(1)

        # 此时会堵塞,因为这个mutexB已经被另外的线程抢先上锁了
        mutexB.acquire()
        print(self.name+'----do1---down----')
        mutexB.release()

        # 对mutexA解锁
        mutexA.release()

class MyThread2(threading.Thread):
    def run(self):
        # 对mutexB上锁
        mutexB.acquire()

        # mutexB上锁后,延时1秒,等待另外那个线程 把mutexA上锁
        print(self.name+'----do2---up----')
        time.sleep(1)

        # 此时会堵塞,因为这个mutexA已经被另外的线程抢先上锁了
        mutexA.acquire()
        print(self.name+'----do2---down----')
        mutexA.release()

        # 对mutexB解锁
        mutexB.release()

mutexA = threading.Lock()
mutexB = threading.Lock()

if __name__ == '__main__':
    t1 = MyThread1()
    t2 = MyThread2()
    t1.start()
    t2.start()

实例:

import threading
import time

num = 100
# 线程锁,解决资源竞争问题
lock = threading.Lock()
# RLock 可以上多把锁,上多少解多少
# rlock = threading.RLock()

def demo1(num1):
    global num
    # 上锁
    lock.acquire()
    for i in range(num1):
        num += 1
    # 解锁
    lock.release()
    print(f'demo1--{num}')


def demo2(num1):
    global num
    lock.acquire()
    for i in range(num1):
        num += 1
    lock.release()
    print(f'demo2--{num}')


def main():
    # 使用 args 传参  当传入的参数很大时,资源竞争更加明显,CPU调度问题
    t1 = threading.Thread(target=demo1, args=(100000,))
    t2 = threading.Thread(target=demo2, args=(100000,))
    t1.start()
    t2.start()
    print(f'main--{num}')


if __name__ == '__main__':
    main()

 

七.Queue线程

在线程中,访问一些全局变量,加锁是一个经常的过程。如果你是想把一些数据存储到某个队列中,那么python内置了一个线程安全的模块叫做queue模块。python中的queue模块中提供了同步的,线程安全的队列类,包括FIFO(先进先出)队列Queue,LIFO(后入先出)队列LifoQueue。这些队列都实现了锁原语(可以理解为原子操作,即要么不做,要么都做完),能够在多线程中直接使用。可以使用队列来实现线程间的同步。

初始化Queue(maxsize):创建一个先进先出的队列。
empty():判断队列是否为空。
full():判断队列是否满了。
get():从队列中取最后一个数据。
put():将一个数据放到队列中。

使用实例:


from queue import Queue

'''
四种常用的队列方法
empty():判断队列是否为空
full():判断队列是否满了
get():从队列中取最后一个数据
put():把一个数据放到队列中
'''

q = Queue(3)   # 创建时可以传入队列的大小
# 判断队列是否为空,返回的是布尔值,True表示为空,False表示不为空
print(q.empty())
print(q.full())
q.put(1)
q.put(2)
q.put(3)
# print(q.put_nowait(2))  #立即向队列添加,否则报错

# 先进先出
print(q.get())
print(q.get())
print(q.get())
# print(q.get(timeout=2))
# print(q.get_nowait(2))  #立即向队列取出,否则报错

print('*' * 50)
print(q.empty())
# 判断队列是否为满,返回的是布尔值,True表示满了,False表示不满
print(q.full())

# 当前队列大小
# print(q.qsize())

# q.put(4, timeout=2)
# q.put_nowait(4)

八.生产者和消费者

生产者和消费者模式是多线程开发中常见的一种模式。通过生产者和消费者模式,可以让代码达到高内聚低耦合的目标,线程管理更加方便,程序分工更加明确。

生产者的线程专门用来生产一些数据,然后存放到容器中(中间变量)。消费者在从这个中间的容器中取出数据进行消费。

Lock版的生产者和消费者

import threading
import random
gMoney = 0
# 定义一个变量 保存生产的次数 默认是0次
gTimes = 0
# 定义一把锁
gLock = threading.Lock()

# 定义生产者
class Producer(threading.Thread):

    def run(self):
        global gMoney
        global gTimes
        gLock.acquire()  # 上锁
        while True:
            # gLock.acquire() # 上锁
            if gTimes >= 10:
                # gLock.release()
                break
            money = random.randint(0,100)
            gMoney += money
            gTimes += 1
            print("%s生产了%d元钱" % (threading.current_thread().name, money))
        gLock.release() # 解锁
# 定义消费者
class Consumer(threading.Thread)
    def run(self):
        global gMoney
        while True:
            gLock.acquire()  # 上锁
            money = random.randint(0, 100)
            if gMoney >= money:
                gMoney -= money
                print("%s消费了%d元钱" % (threading.current_thread().name, money))
            else:
                if gTimes >= 10:
                    gLock.release()
                    break
                print("%s想消费%d元钱,但是余额只有%d"%(threading.current_thread().name,money,gMoney))
            gLock.release()  # 解锁

def main():
    # 开启5个生产者线程
    for x in range(5):
        th = Producer(name="生产者%d号" % x)
        th.start()
    # 开启5个消费者线程
    for x in range(5):
        th = Consumer(name="消费者%d号" % x)
        th.start()

if __name__ == '__main__':
    main()

Condition版的生产者和消费者

import threading
import random
gMoney = 0
# 定义一个变量 保存生产的次数 默认是0次
gTimes = 0
# 定义一把锁
# gLock = threading.Lock()
gCond = threading.Condition()
# 定义生产者
class Producer(threading.Thread):

    def run(self):
        global gMoney
        global gTimes

        while True:
            gCond.acquire() # 上锁
            if gTimes >= 10:
                gCond.release()
                break
            money = random.randint(0,100)
            gMoney += money
            gTimes += 1
            print("%s生产了%d元钱,剩余%d元钱" % (threading.current_thread().name, money, gMoney))
            gCond.notify_all()
            gCond.release() # 解锁
# 定义消费者
class Consumer(threading.Thread):

    def run(self):
        global gMoney
        while True:
            gCond.acquire()  # 上锁
            money = random.randint(0, 100)

            while gMoney < money:
                if gTimes >= 10:
                    gCond.release()
                    return  # 这里如果用break只能退出外层循环,所以我们直接return
                print("%s想消费%d元钱,但是余额只有%d元钱了,并且生产者已经不再生产了!"%(threading.current_thread().name,money,gMoney))
                gCond.wait()
            # 开始消费
            gMoney -= money
            print("%s消费了%d元钱,剩余%d元钱" % (threading.current_thread().name, money, gMoney))
            gCond.release()

def main():
    # 开启5个生产者线程
    for x in range(5):
        th = Producer(name="生产者%d号" % x)
        th.start()
    # 开启5个消费者线程
    for x in range(5):
        th = Consumer(name="消费者%d号" % x)
        th.start()

if __name__ == '__main__':
    main()

九.多线程的应用实例

爬取小米商城使用普通方式爬取

import requests
import time
import json
import pprint


class XiaomiSpider():
    def __init__(self):
        self.url = 'https://app.mi.com/categotyAllListApi?page={}&categoryId=6&pageSize=30'
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36'
        }

    # 发起请求 获取响应内容
    def get_source(self, url):
        response = requests.get(url, headers=self.headers)
        # 得到json数据方案一 通过python内置的json模块
        # result = json.loads(response.text)
        # 得到json数据方案二 通过requests提供的得到json数据的方式
        result = response.json()
        # pprint.pprint(result)
        return result

    # 解析响应内容
    def parse_html(self, html):
        for app_dict in html['data']:
            item = {}
            # pprint.pprint(app_dict)
            item['app_name'] = app_dict['displayName']
            item['app_type'] = app_dict['level1CategoryName']
            item['app_id'] = 'https://app.mi.com/details?id=' + app_dict['packageName']
            print(item)

    def main(self):
        # 进行翻页处理
        for page in range(3):
            new_url = self.url.format(page)
            html = self.get_source(new_url)
            self.parse_html(html)
            time.sleep(1)



if __name__ == '__main__':
    # 开始时间
    start_time = time.time()
    x = XiaomiSpider()
    x.main()
    # 结束时间
    end_time = time.time()
    print('用时%f' % (end_time - start_time))

使用多线程爬取

import requests
import time
import threading
from queue import Queue

class XiaomiSpider():
    def __init__(self):
        self.url = 'https://app.mi.com/categotyAllListApi?page={}&categoryId=6&pageSize=30'
        # 创建队列
        self.q = Queue()
        # 创建锁
        self.lock = threading.Lock()

    # 把目标url放入队列中
    def put_url(self):
        # range(3)--(0,1,2)
        for page in range(3):
            url = self.url.format(page)
            self.q.put(url)

    # 发请求 获响应 解析数据
    def parse_html(self):
        while True:
            self.lock.acquire()
            if not self.q.empty():
                url = self.q.get()
                self.lock.release()
                headers = {
                    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36'
                }
                html = requests.get(url, headers=headers).json()
                # 解析数据
                for app_dict in html['data']:
                    item = {}
                    # pprint.pprint(app_dict)
                    item['app_name'] = app_dict['displayName']
                    item['app_type'] = app_dict['level1CategoryName']
                    item['app_id'] = 'https://app.mi.com/details?id=' + app_dict['packageName']
                    # print(item)

            else:
                self.lock.release()
                break

    def run(self):
        self.put_url()
        # 线程列表
        t_lst = []
        for i in range(2):
            t = threading.Thread(target=self.parse_html)
            t_lst.append(t)
            t.start()


if __name__ == '__main__':
    # 开始时间
    start_time = time.time()
    x = XiaomiSpider()
    x.run()
    # 结束时间
    end_time = time.time()
    print('用时%f' % (end_time - start_time))

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Python爬虫编程7——多线程爬虫 的相关文章

随机推荐

  • 空间转录组学(Spatial Transcriptomics)

    01 空间转录组技术的发展 近年来单细胞转录组测序技术的应用大大拓宽了人们的视野 xff0c 使人们能够深入了解组织中细胞的构成的多样性和基因表达状态 众所周知 xff0c 基因表达具有时间和空间的特异性 xff0c 通过对不同时间点的样本
  • 修改R语言安装包的默认路径 r包安装位置

    修改R语言安装包的默认路径 r包安装位置 一次性 xff08 每次都需要设置 xff09 xff1a 运行命令 xff1a libPaths 查看已经安装的r包位置 libPaths 34 yourpath 34 设置新的r包位置 或者 i
  • 如何在ubuntu系统安装libboost-dev) and openmp libraries libboost-dev libomp-dev

    https blog csdn net qq 41854911 article details 119454212 You need to have boost e g sudo apt get install libboost dev a
  • Docker删除容器命令

    更多优质内容欢迎看看我的号 xff1a 生信小博士 删除容器 之前要先docker stop 容器 span class token number 1 span 删除指定容器 docker rm span class token opera
  • 生存分析原理简明教程 单因素生存分析 Kaplan-Meier、LogRank 只能针对单一的变量进行 多因素cox回归分析

    一 生存分析 狭义上来说 xff0c 生存分析用来分析病人的生存和死亡情况 广义上讲的是事件是否发生 在这里就用是否死亡来代替 一般来说 xff0c 生存的数据一般有两个变量 xff0c 一个事件是否发生 xff0c 病人是否死亡 xff0
  • R语言|4. 轻松绘制临床基线表Table 1 临床三线表绘制

    R语言 4 轻松绘制临床基线表Table 1 regular table regular 欢迎关注公众号 第四次考核 Jimmy 学徒考核 Linux安装软件 rnaseq上游分析 2 ascp kingfisher数据下载ena qq c
  • tinyarray 相关性cor.full

    R Documentation cor test for one variable with all variables Description cor test for all variables each two columns Usa
  • Mac升级node和npm

    直接下载安装覆盖 xff1a https nodejs org zh cn download
  • NanoStringQCPro: Quality metrics and data processing methods for NanoString mRNA gene expression dat

    addCodesetAnnotationAdd NanoString codeset annotation to an RccSetaddQCFlagsAdd sample QC flags to an rccSetallSumPlotal
  • 颜色 免疫荧光通道颜色色彩对应激发光发射光 488 546 647

    DAPI xff08 ex 350nm em 470nm xff09 检测通道 xff0c 荧光色 xff1a 蓝色 488绿色 546黄色 647红色 Fluorescence SpectraViewer Use the new inte
  • 【Git】gitee与github同步

    gitee与github同步 前言一 克隆远程库二 与github同步1 建立第二个远程库2 修改配置文件3 查看仓库权限 总结 前言 本章所讲内容有克隆远程库 xff08 填上章的坑 xff09 和两个远程库在本地同步上传的方法 接着我们
  • [imx6ull]Linux下的SocketCAN通信

    文章目录 一 CAN总线协议1 简介2 电气属性3 通信原理 数据帧的帧格式 xff1a 总线同步 总线竞争 数据保护 二 Linux下CAN的操作1 硬件连接 CAN电平转换器 扩展板使用CAN 2 查询 can 信息3 开启 关闭 ca
  • FreeRTOS-ARM架构、TCB结构体,调度机制

    ARM架构 对于ARM架构来说 xff0c 主要有3部分构成 xff1a CPU xff0c RAM xff0c FLASH CPU内部主要是运算单元和寄存器单元 xff0c 可以读写RAM xff0c 修改内存 xff0c 也可以读取FL
  • FreeRTOS--队列

    在讲队列前 xff0c 先思考一下这个问题 xff1a 下面这个程序 xff0c 如果用RTOS实现会出问题吗 xff1f c span class token punctuation span span class token keywo
  • 新手村——如何改变CSDN代码块背景样式

    1 在电脑上登录自己CSDN账号 xff1b 2 按如下指示操作 xff1a 鼠标滑动至头像处 xff0c 点击下方的内容管理 xff1b 3 在左方中一直下滑 xff0c 找到设置中的博客设置 xff1b 4 在代码样式片中选择自己喜欢的
  • antd的Form实例控制表格,表格可收集/回显数据

    大概就是如图的效果 xff0c 但是需要控制数据的收集和回显 首先看到这个页面 xff0c 第一眼就想到table表格 但是我翻阅了下antd的Table组件 xff0c 发现只能展示数据 也有可编辑单元格 但是我需要这里的表格 xff0c
  • 网络字节序、大小端模式

    多字节数据的存储顺序称为字节序 分为大 小端模式 xff1a 大端 xff1a 数据高位对应低地址 小端 xff1a 数据高位对应高地址 可以通过下面的程序 测试自己的机器是大端字节序还是小端字节序 用共用体的方式来测试 xff1a spa
  • C++运算符重载详解

    目录 什么是运算符重载 运算符重载的意义 运算符重载的语法格式 简单例子 43 运算符的重载 代码分析 运算符重载的两种方式 1 重载为类的成员函数 1 双目运算符 2 单目运算符 2 重载为类的友元函数 1 重载格式 2 调用格式 两种重
  • 微信登录老是间歇式失败

    在微信小程的开发中 xff0c 登录问题 xff0c 一定要按照这样的顺序 1 小程序请求login xff0c 拿到code 然后传给服务端 xff1b 2 服务端拿到code 到微信服务器拿到sessionKey xff1b 3 然后小
  • Python爬虫编程7——多线程爬虫

    目录 一 多线程基本介绍 程序中模拟多任务 二 多线程的创建 三 主线程与子线程的执行关系 四 查看线程数量 五 线程间的通信 xff08 多线程共享全局变量 xff09 六 线程间的资源竞争 互斥锁和死锁 互斥锁 死锁 七 Queue线程