爬虫 — 多线程

news2025/1/10 20:23:16

目录

  • 一、多任务概念
  • 二、实现多任务方式
    • 1、多进程 (Multiprocessing)
    • 2、多线程(Multithreading)
    • 3、协程(Coroutine)
  • 三、多线程执行顺序
  • 四、多线程的方法
    • 1、join()
    • 2、setDaemon()
    • 3、threading.enumerate()
  • 五、继承 Thread 类创建线程
  • 六、线程间的通信(多线程共享全局变量)
  • 七、互斥锁和死锁
    • 1、互斥锁
    • 2、死锁
  • 八、生产者与消费者模式
    • 1、Queue 线程队列
    • 2、生产者和消费者
  • 九、案例
    • 1、单线程实现
    • 2、多线程实现
  • 十、作业

一、多任务概念

多任务(Multitasking)是指在同一时间内执行多个任务或进程的能力。它可以以不同的方式实现,包括多进程、多线程和协程等。

二、实现多任务方式

1、多进程 (Multiprocessing)

多进程是指同时运行多个独立的进程,每个进程有自己的地址空间和系统资源。多进程可以在多个处理器核心上并行执行任务,每个进程拥有独立的执行环境,相互之间不受影响。

进程(Process)

进程是计算机中运行的程序的实例。每个进程都拥有独立的内存空间和系统资源。一个进程可以包含多个线程。

2、多线程(Multithreading)

多线程是指在一个进程中同时执行多个线程的编程模型。线程是进程内的执行单元,每个线程独立执行特定的任务,但共享同一进程的内存空间。多线程编程可以提高程序的并发性和响应性。

线程(Thread)

线程是操作系统能够进行调度的最小单位。它包含了执行代码所需的上下文信息(如程序计数器、栈、寄存器等),可以独立运行和调度。多个线程可以在同一时间内执行不同的任务。

主线程(Main Thread)

主线程是程序启动时默认创建的第一个线程。主线程负责执行程序的入口点,并可以创建其它线程。

3、协程(Coroutine)

协程是一种轻量级的并发编程技术=,它可以在单线程中实现多个独立的执行流程,从而提供高效的并发和协作。与线程相比,协程的切换开销更小,且没有多线程中的锁和同步机制的复杂性。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其它地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。

寄存器上下文(Register Context)

是指存储在处理器寄存器中的一组值,用于保存正在执行的程序的状态信息。寄存器上下文包含了程序计数器、栈指针、通用寄存器等寄存器的值。

并发(Concurrency)

并发是指多个任务同时进行,但不一定同时完成。在多线程编程中,线程可以并发执行,通过时间片轮转等方式实现看似同时执行的效果。(资源够用,比如三个线程,四核的 CPU。)

并行(Parallelism)

并行是指多个任务同时进行且同时完成。在多核处理器上,多个线程可以被映射到不同的核上并行执行。(比如单核 CPU 资源,同时只能运行一个任务,A 运行一段后,让给 B,B 用完继续给 A,交替使用,提高效率。)

三、多线程执行顺序

# 时间模块
import time

def task():
    print("hello python")
    time.sleep(1)
    print("hello world")
for i in range(5):
    task()
    
# hello python
# hello world
# hello python
# hello world
# hello python
# hello world
# hello python
# hello world
# hello python
# hello world
# 时间模块
import time
# 多线程模块
import threading

# 子线程
def task():
    print("hello python")
    time.sleep(1)
    print("hello world")

# 主线程
if __name__ == '__main__':
    for i in range(5): # 循环5次,创建了5个线程对象
        # 创建线程对象,target 是执行任务
        t = threading.Thread(target=task)
        # 多线程为开始工作状态
        t.start()
        
# hello python
# hello python
# hello python
# hello python
# hello python
# hello world
# hello world
# hello world
# hello world
# hello world

四、多线程的方法

1、join()

等待子线程结束之后,主线程继续执行。

谨慎使用,假设子线程当中有一个死循环,子线程不结束,主线程能不能结束。

# 时间模块
import time
# 多线程模块
import threading

# 子线程
def task():
    print("hello python")
    time.sleep(1)
    print("hello world")

# 主线程
if __name__ == '__main__':
    for i in range(5): # 循环5次,创建了5个线程对象
        # 创建线程对象,target 是执行任务
        t = threading.Thread(target=task)
        # 多线程为开始工作状态
        t.start()
        # 子线程结束了才会执行后面的代码
        t.join()

# hello python
# hello world
# hello python
# hello world
# hello python
# hello world
# hello python
# hello world
# hello python
# hello world

2、setDaemon()

守护线程,不会等待子线程结束。

# 时间模块
import time
# 多线程模块
import threading

# 子线程
def task():
    print("hello python")
    time.sleep(1)
    print("hello world")

# 主线程
if __name__ == '__main__':
    for i in range(5): # 循环5次,创建了5个线程对象
        # 创建线程对象,target 是执行任务
        t = threading.Thread(target=task)
        # 守护线程:主线程结束程序就立马结束了,不会影响到主线程的运行
        t.setDaemon(True)
        # 多线程为开始工作状态
        t.start()

# hello python
# hello python
# hello python
# hello python
# hello python

3、threading.enumerate()

查看当前线程的数量。

# 时间模块
import time
# 多线程模块
import threading

# 子线程
def sing():
    for i in range(3):
        print(f'正在唱歌。。。{i}')
        time.sleep(0.5)

# 子线程
def dance():
    for i in range(3):
        print(f'正在跳舞。。。{i}')
        time.sleep(0.5)

# 主线程
if __name__ == '__main__':
    # 创建线程对象
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
    # 开启线程
    t1.start() # start 开启时,子线程才算创建
    t2.start()
    # 查看线程数量
    # 2子1主,共3个线程
    print(threading.enumerate()) # [<_MainThread(MainThread, started 8584)>, <Thread(Thread-1, started 5504)>, <Thread(Thread-2, started 18404)>]

五、继承 Thread 类创建线程

# 时间模块
import time
# 多线程模块
import threading

# 创建的是类,继承线程类,就具备线程的特性
class MyThread1(threading.Thread):
    # 重写父类的 run 方法,start 触发 run 方法
    def run(self):
        for i in range(5):
            print(f'MyThread1---{i}')
            time.sleep(1)

class MyThread2(threading.Thread):
    def run(self):
        for i in range(5):
            print(f'MyThread2---{i}')
            time.sleep(1)

if __name__ == '__main__':
    # 创建对象
    mt = MyThread1()
    mt1 = MyThread2()
    # 开启线程
    mt.start()
    mt1.start()

六、线程间的通信(多线程共享全局变量)

在一个函数中,对全局变量进行修改的时候,如果修改了指向,必须使用 global,仅仅是修改了指向空间中的数据时,不用必须使用 global。

线程是共享全局变量的。

import threading # 导入线程模块

# 定义全局变量 num,初始值为0
num = 0

# 定义函数 task
def task():
    # 在函数内部使用全局变量 num
    global num
    # 循环数据
    for i in range(10000000): # 1千万
        num += 1
    # 打印当前 num 的值
    print("task--num=%d" % num)

# 定义函数 task1
def task1():
    # 在函数内部使用全局变量 num
    global num
    # 循环数据
    for i in range(10000000):  # 1千万
        num += 1
    # 打印当前 num 的值
    print(f"task1 num={num}")

# 主程序
if __name__ == '__main__':
    # 创建一个线程对象 t,执行函数 task
    t = threading.Thread(target=task)
    # 创建一个线程对象 t1,执行函数 task1
    t1 = threading.Thread(target=task1)
    # 启动线程 t
    t.start()
    # 启动线程 t1
    t1.start()
    # 打印当前 num 的值(在两个子线程运行之前打印)
    print(f"main--num={num}")

七、互斥锁和死锁

1、互斥锁

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

某个线程要更改共享数据时,先将其锁定,此时,资源的状态为“锁定”,其它线程不能改变,直到该线程释放资源,将资源的状态变成“非锁定”,其它的线程才能再次锁定该资源。

互斥锁保证了每次只有一个线程进入写入操作,从而保证了多线程情况下数据的正确性。

# 创建锁
mutex = threading.Lock()
# 锁定
mutex.acquire()
# 释放
mutex.release()
import threading # 导入线程模块
import time # 导入时间模块

# 定义全局变量 num,初始值为0
num = 0

# 定义函数 task
def task(nums):
    # 在函数内部使用全局变量 num
    global num
    # 获取互斥锁,确保线程安全
    mutex.acquire()
    # 循环数据
    for i in range(nums):
        num += 1
    # 释放互斥锁
    mutex.release()
    # 打印当前 num 的值
    print("task--num=%d" % num)

# 定义函数 task1
def task1(nums):
    # 在函数内部使用全局变量 num
    global num
    # 获取互斥锁,确保线程安全
    mutex.acquire()
    # 循环数据
    for i in range(nums):
        num += 1
    # 释放互斥锁
    mutex.release()
    # 打印当前 num 的值
    print(f"task1 num={num}")

# 主程序
if __name__ == '__main__':
    # 创建互斥锁对象
    mutex = threading.Lock()
    # 定义 nums 的值
    nums = 10000
    # 创建一个线程对象 t,执行函数 task
    t = threading.Thread(target=task, args=(nums,), ) # 传参,数据类型必须是元组
    # 创建一个线程对象 t1,执行函数 task1
    t1 = threading.Thread(target=task1, args=(nums, ))
    # 启动线程 t
    t.start()
    # 启动线程 t1
    t1.start()
    # 主线程等待2秒,确保子线程执行完毕
    time.sleep(2)
    # 打印当前 num 的值
    print(f"main--num={num}")

2、死锁

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

八、生产者与消费者模式

1、Queue 线程队列

Queue(队列)是一个线程安全的数据结构,常用于在多线程编程中实现线程间的通信和数据共享。

Python 中的 queue 模块中提供了同步的、线程安全的队列类,包括 FIFO(先进先出)队列 Queue,LIFO(后入先出)队列 LifoQueue。

这些队列都实现了锁原语(可以理解为原子操作,即要么不做,要么都做完),能够在多线程中直接使用,使用队列可以实现线程间的同步。

队列方法

  • 初始化 Queue(maxsize):创建一个先进先出的队列。
  • empty():判断队列是否为空。
  • full():判断队列是否满了。
  • get():从队列中取最后一个数据。
  • put():将一个数据放到队列中。
from queue import Queue # 导入队列模块中的 Queue 类,用于使用队列数据结构

# 实例化对象,队列充当的是容器
# 初始化 Queue(maxsize)
q = Queue(5) # maxsize 为5,就只能存5组数据,可以存放任何类型的数据
q.put(1) # 往队列当中添加值
q.put({"key":"value"})
q.put([2, 3, 4])
q.put(3.5)
q.put(True)
# q.put(4) # 超出队列大小,程序会出现阻塞
print('----', q.qsize()) # 查看队列的大小

# 取值
print(q.get())
print(q.get())
print('----', q.qsize()) # 查看队列的大小,取出后的值不在队列中了

# 判断队列是否满了
print(q.full()) # False 3
print(q.empty()) # 判断队列是否为空,如果是空返回的是 True

2、生产者和消费者

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

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

在这里插入图片描述

from queue import Queue # 导入 Queue 模块,用于使用队列数据结构
import threading # 导入 threading 模块,用于多线程编程
import time # 导入 time 模块,用于时间相关操作

# 定义函数,用于向队列中存值
def set_value(q):
    num = 0
    while True:
        # 将值放入队列
        q.put(num)
        # 值自增
        num += 1
        # 线程休眠0.5秒
        time.sleep(0.5)

# 定义函数,用于从队列中获取值并打印
def get_value(q):
    while True:
        # 从队列中获取值并打印
        print(q.get())

# 主程序
if __name__ == '__main__':
    # 创建一个大小为4的队列对象
    q = Queue(4)
    # 创建一个子线程,调用 set_value 函数,用于存值
    t1 = threading.Thread(target=set_value, args=(q,))
    # 创建一个子线程,调用 get_value 函数,用于取值
    t2 = threading.Thread(target=get_value, args=(q,))
    # 启动线程 t1
    t1.start()
    # 启动线程 t2
    t2.start()

九、案例

目标网站:https://qq.yh31.com/zjbq/List_48.html

需求:爬取表情包图片,并且将图片保存到文件夹中

1、单线程实现

页面分析

1、数据有多页,先获取第一页数据

2、确定 url,判断是静态加载还是动态加载

静态加载 url:https://qq.yh31.com/zjbq/List_48.html

3、解析数据

先获取到所有的 img 标签

循环遍历获取每一组的数据

4、获取翻页数据,观察 url 变化的规律

第一页:https://qq.yh31.com/zjbq/List_48.html

第二页:https://qq.yh31.com/zjbq/List_47.html

第三页:https://qq.yh31.com/zjbq/List_46.html

代码实现

import requests # 导入 requests 模块,用于发送网络请求
from lxml import etree # 导入 lxml 库中的 etree 模块,用于解析 HTML
import re # 导入 re 模块,用于正则表达式匹配

# 定义函数,用于下载图片
def download_img():
    # 请求头
    head = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36'
    }
    # 获取5页数据
    for i in range(48, 43, -1):
        # 目标 url
        url = f'https://qq.yh31.com/zjbq/List_{i}.html'
        # 发送 get 请求,获取响应对象
        res = requests.get(url, headers=head)
        # 设置响应编码为 utf-8
        res.encoding = 'utf-8'
        # 打印响应内容
        # print(res.text)
        # 解析响应内容
        html = etree.HTML(res.text)
        # 获取所有的 img 标签
        images = html.xpath('//div[@class="zj_tp"]/a/img')
        # 遍历循环每一个 img 标签
        for img in images:
            # 获取图片 url
            img_url = img.xpath('@src')[0]
            # 获取图片标题
            img_title = img.xpath('@alt')[0]
            # 使用正则表达式替换标题中的特殊字符
            title = re.sub(r'[<>:?.()/\\]', '', img_title)
            # 打印图片 url 和标题
            # print(img_url, img_title)
            # 发送 get 请求,获取图片响应
            res = requests.get(img_url, headers=head)
            # 打开文件,将图片内容写入到文件中
            with open(f'pictures/{title}.jpg', 'wb') as f:
                f.write(res.content)
            print(f'{title}正在下载')

# 调用下载图片的函数
download_img()

2、多线程实现

页面分析

用生产者与消费者下载表情包

一个是生产数据类,一个是下载数据类

队列只是一个容器

代码实现

import requests # 导入 requests 库,用于发送 HTTP 请求
from lxml import etree # 导入 lxml 库,用于解析 HTML
import re # 导入 re 库,用于正则表达式操作
import threading # 导入 threading 库,用于多线程编程
from queue import Queue # 导入 Queue 类,用于创建队列

# 请求头
head = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36'
}

# 生产者类,用于获取图片链接
class Producer(threading.Thread):
    # 初始化方法
    def __init__(self, page_queue, img_queue):
        # 必须要执行父类当中的 init 方法完成初始化
        super().__init__()
        # 设置页码队列属性
        self.page_queue = page_queue
        # 设置图片队列属性
        self.img_queue = img_queue

    # 重写 run 方法
    def run(self):
        # 循环取队列里面的数据,直到队列数据为空
        while True:
            # 如果页码队列为空
            if self.page_queue.empty():
                # 退出循环
                break
            # 从页码队列中获取 url
            url = self.page_queue.get()
            # 打印 url
            # print(url)
            # 调用 parse_html 方法解析页面
            self.parse_html(url)

    # 定义解析页面的方法
    def parse_html(self, url):
        # 发送 get 请求,获取响应对象
        res = requests.get(url, headers=head)
        # 设置响应编码为 utf-8
        res.encoding = 'utf-8'
        # 打印响应内容
        # print(res.text)
        # 解析响应内容
        html = etree.HTML(res.text)
        # 获取所有的 img 标签
        images = html.xpath('//div[@class="zj_tp"]/a/img')
        # 遍历循环每一个 img 标签
        for img in images:
            # 获取图片 url
            img_url = img.xpath('@src')[0]
            # 获取图片标题
            img_title = img.xpath('@alt')[0]
            # 使用正则表达式替换标题中的特殊字符
            title = re.sub(r'[<>:?.()/\\]', '', img_title)
            # 将图片 url 和标题作为元组放入图片队列中
            self.img_queue.put((img_url, title))
            # 打印图片队列的大小
            # print(self.img_queue.qsize())

# 消费者类,用于下载图片
class consumer(threading.Thread):
    # 初始化方法
    def __init__(self, img_queue):
        # 必须要执行父类当中的 init 方法完成初始化
        super().__init__()
        # 设置图片队列属性
        self.img_queue = img_queue

    # 重写 run 方法
    def run(self):
        # 循环取队列里面的数据,直到队列数据为空
        while True:
            # 打印图片队列的大小
            print(self.img_queue.qsize())
            # # 如果图片队列为空
            # if self.img_queue.empty():
            #     # 退出循环
            #     break
            # 从图片队列中获取图片数据
            img_data = self.img_queue.get()
            # 将图片数据解包为 url 和标题
            url, title = img_data
            # 发送 get 请求,获取图片响应
            res = requests.get(url, headers=head)
            # 打开文件,将图片内容写入到文件中
            with open(f'pictures/{title}.jpg', 'wb') as f:
                f.write(res.content)
            print(f'{title}正在下载')

# 主程序
if __name__ == '__main__':
    # 存放 url 的队列
    page_queue = Queue()
    # 创建图片队列
    img_queue = Queue()
    # 循环页码
    for i in range(48, 43, -1):
        # 创建 url
        url = f'https://qq.yh31.com/zjbq/List_{i}.html'
        # url 放入页码队列
        page_queue.put(url)

    # 创建空列表
    lst = []

    # 创建生产者
    for i in range(3):
        # 将存放的 url 队列传递给生产者
        t = Producer(page_queue, img_queue)
        # 开启线程
        t.start()
        # 添加线程到列表
        lst.append(t)
    # # join:等子线程结束了才会执行主线程的代码
    # # 加 join 是生产完了再下载,不加是边生产边下载
    # # 如消费者 run 方法里判断图片队列为空,就需要加 join
    # for i in lst:
    #     i.join()

    # 创建消费者
    for i in range(3):
        # 将图片队列传递给消费者
        t1 = consumer(img_queue)
        # 开启线程
        t1.start()

十、作业

目标网站:https://www.fabiaoqing.com/biaoqing/lists/page/1.html

需求:爬取表情包图片,并且将图片保存到文件夹中

import requests # 导入 requests 库,用于发送 HTTP 请求
from lxml import etree # 导入 lxml 库,用于解析 HTML
import re # 导入 re 库,用于正则表达式操作
import threading # 导入 threading 库,用于多线程编程
from queue import Queue # 导入 Queue 类,用于创建队列

# 请求头
head = {
    'Referer':'https://www.fabiaoqing.com/',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36'
}

# 生产者类,用于获取图片链接
class Producer(threading.Thread):
    # 初始化方法
    def __init__(self, page_queue, img_queue):
        # 必须要执行父类当中的 init 方法完成初始化
        super().__init__()
        # 设置页码队列属性
        self.page_queue = page_queue
        # 设置图片队列属性
        self.img_queue = img_queue

    # 重写 run 方法
    def run(self):
        # 循环取队列里面的数据,直到队列数据为空
        while True:
            # 如果页码队列为空
            if self.page_queue.empty():
                # 退出循环
                break
            # 从页码队列中获取 url
            url = self.page_queue.get()
            # 打印 url
            # print(url)
            # 调用 parse_html 方法解析页面
            self.parse_html(url)

    # 定义解析页面的方法
    def parse_html(self, url):
        # 发送 get 请求,获取响应对象
        res = requests.get(url, headers=head)
        # 设置响应编码为 utf-8
        res.encoding = 'utf-8'
        # 打印响应内容
        # print(res.text)
        # 解析响应内容
        html = etree.HTML(res.text)
        # 获取所有的 img 标签
        images = html.xpath('//div[@class="tagbqppdiv"]/a/img')
        # 遍历循环每一个 img 标签
        for img in images:
            # 获取图片 url
            img_url = img.xpath('@data-original')[0]
            # 获取图片标题
            img_title = img.xpath('@alt')[0]
            # 使用正则表达式替换标题中的特殊字符
            title = re.sub(r'[<>:?.()/\\]', '', img_title)
            # 将图片 url 和标题作为元组放入图片队列中
            self.img_queue.put((img_url, title))
            # 打印图片队列的大小
            # print(self.img_queue.qsize())

# 消费者类,用于下载图片
class consumer(threading.Thread):
    # 初始化方法
    def __init__(self, img_queue):
        # 必须要执行父类当中的 init 方法完成初始化
        super().__init__()
        # 设置图片队列属性
        self.img_queue = img_queue

    # 重写 run 方法
    def run(self):
        # 循环取队列里面的数据,直到队列数据为空
        while True:
            # 打印图片队列的大小
            print(self.img_queue.qsize())
            # # 如果图片队列为空
            # if self.img_queue.empty():
            #     # 退出循环
            #     break
            # 从图片队列中获取图片数据
            img_data = self.img_queue.get()
            # 将图片数据解包为 url 和标题
            url, title = img_data
            # 发送 get 请求,获取图片响应
            res = requests.get(url, headers=head)
            # 打开文件,将图片内容写入到文件中
            with open(f'pictures/{title}.jpg', 'wb') as f:
                f.write(res.content)
            print(f'{title}正在下载')

# 主程序
if __name__ == '__main__':
    # 存放 url 的队列
    page_queue = Queue()
    # 创建图片队列
    img_queue = Queue()
    # 循环页码
    for i in range(1, 5, 1):
        # 创建 url
        url = f'https://www.fabiaoqing.com/biaoqing/lists/page/{i}.html'
        # url 放入页码队列
        page_queue.put(url)

    # 创建空列表
    lst = []

    # 创建生产者
    for i in range(3):
        # 将存放的 url 队列传递给生产者
        t = Producer(page_queue, img_queue)
        # 开启线程
        t.start()
        # 添加线程到列表
        lst.append(t)
    # # join:等子线程结束了才会执行主线程的代码
    # # 加 join 是生产完了再下载,不加是边生产边下载
    # # 如消费者 run 方法里判断图片队列为空,就需要加 join
    # for i in lst:
    #     i.join()

    # 创建消费者
    for i in range(3):
        # 将图片队列传递给消费者
        t1 = consumer(img_queue)
        # 开启线程
        t1.start()

记录学习过程,欢迎讨论交流,尊重原创,转载请注明出处~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1020342.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

npm发布vue3自定义组件库--方法一

npm发布vue3自定义组件库 创建项目 vue create test-ui自定义组件 创建自定义组件&#xff0c;组件名称根据你的需求来&#xff0c;最好一个组件一个文件夹&#xff0c;下图是我的示例。 src/components 组件和你写页面一样&#xff0c;所谓组件就是方便实用&#xff0c;不…

Unity Bolt 实现UI拖拽功能

最近在学习使用Bolt插件实现五代码对UGUI Image元素实现拖拽。先看效果 录制_2023_09_15_17_50_45_29 下面是实现方式介绍&#xff1a; 1&#xff1a;注册RectTransformUtility 在使用Bolt插件实现UI拖拽的功能&#xff0c;需要使用 RectTransformUtility.ScreenPointToLoca…

网络安全深入学习第五课——热门框架漏洞(RCE— Apache Shiro 1.2.4反序列化漏洞)

文章目录 一、序列化和反序列化二、反序列化漏洞原理三、Apache Shiro 1.2.4反序列化漏洞1、漏洞描述&#xff1a;2、漏洞影响的版本3、Shiro反序列化漏洞原理4、工作原理&#xff1a;5、shiro反序列化的特征&#xff1a; 四、Apache Shiro 1.2.4反序列化漏洞手工复现1、使用DN…

做好制造项目管理的5个技巧

制造过程通常由不同的要素组成&#xff0c;如采购材料、与供应商合作、优化生产线效率等。制造商还需要处理库存、物流和分销。 为了确保制造项目在预算范围内按时完成&#xff0c;并且不遗漏任何环节&#xff0c;企业必须建立项目管理流程&#xff0c;以帮助改善组织流程和效…

社区版MyApps低代码平台,免费即刻拥有!

编者按&#xff1a;本文主要介绍了MyApps推出的免费社区版的优势&#xff0c;为企业数字化转型提供了解决方案。立即登录MyApps低代码平台&#xff0c;就能获取永久免费的低代码平台。 1.MyApps社区版的优势 1.1不受限制&#xff0c;畅享自由 无用户限制、无安装限制、全面应用…

使用 Charles 去修改响应信息(真实工作使用场景1)

目录 背景 理论 Breakpoint功能 Map功能 实践 原理 背景 测试过程中&#xff0c;遇到接口透传数据&#xff0c;修改请求中的值可以使用Postman来进行&#xff0c;当业务场景遇到修改响应里的值的时候&#xff0c;就需要借助Charles来进行。 以下将会阐述具体的步…

七天学会C语言-第三天(循环语句)

1. 用 while 语句循环做数学运算&#xff1a; 使用while语句&#xff0c;您可以创建一个循环&#xff0c;它会重复执行一段代码&#xff0c;直到指定的条件不再满足。 例 1&#xff1a; 求 246100。 #include <stdio.h>int main() {int n 1, sum 0, a 2;while (n &…

第六章 关系数据库理论

第六章 关系数据库理论 6.1 问题的提出 关系模式的表示 关系模式由五部分组成&#xff0c;是一个五元组&#xff1a;R&#xff08;U&#xff0c;D&#xff0c;DOM&#xff0c;F&#xff09;。&#xff08;1 关系名R是符号化的元组语义。U为一组属性。D为属性组U中的属性所来自…

K8s的网络——Underlay和Overlay网络

0. 基础知识 1&#xff09;网络7层基础知识 在网络7层协议基础里&#xff0c; 第一层物理链路&#xff1b;第二层是数据链路层&#xff0c;在第一层的基础上引入MAC地址做数据转发。MAC地址在局域网内具有唯一性&#xff0c;主机A发送数据时&#xff0c;会向局域网内进行广播…

idea显示git分支信息(GitToolBox插件)

效果图 说明 本身idea在右下角会有git分支信息&#xff0c;但是显示的当前打开文件的分支信息&#xff0c;并且不够显眼 解决 1、安装插件(GitToolBox插件) 2、修改idea.properties project.tree.structure.show.urlfalse ide.tree.horizontal.default.autoscrollingfalse将…

【数据结构】堆的应用+TOP-K问题+二叉树遍历

欢迎来到我的&#xff1a;世界 希望作者的文章对你有所帮助&#xff0c;有不足的地方还请指正&#xff0c;大家一起学习交流 ! 目录 前言堆的时间复杂度向下调整算法的时间复杂度向上调整算法的时间复杂度 堆的应用堆排序TOP—K问题链式二叉树二叉树的节点&#xff1a;初始化节…

ThreadLocal线程局部变量

1.原理 ThreadLocal是用来保存当前线程数据的&#xff0c;每一个线程的内部都有一个ThreadLocalMap&#xff0c;当前这个map中存储了以当前ThreadLocal作键&#xff0c;具体的数据作值的一个个Entry对象。 为什么非得以ThreadLocal对象作键呢&#xff1f;因为一个线程可能使用了…

手游联运平台是什么?

手游联运平台是一种服务于手游联运的专业平台&#xff0c;旨在为游戏开发商、发行商和代理商提供联运合作所需的技术、工具和资源。这些平台通常提供以下功能和服务&#xff1a; 游戏接入和管理&#xff1a;允许游戏开发商将他们的游戏接入联运平台&#xff0c;以便发行到不同的…

linux 环境变量详解/etc/proflie

Linux 环境变量是可以在多个文件中进行配置的&#xff0c;如/etc/proflie&#xff0c;/etc/profile.d/*.sh&#xff0c;~/.bashrc&#xff0c;~/.bash_profile等但是这些之间有什么区别呢 bash的运行模式可以分为 login shell 和 non-login shell。 例如&#xff1a;通过终端&a…

【送书活动】强势挑战Java,Kotlin杀回TIOBE榜单Top 20!学Kotlin看哪些书?

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 「推荐专栏」&#xff1a; ★java一站式服务 ★ ★ React从入门到精通★ ★前端炫酷代码分享 ★ ★ 从0到英雄&#xff0c;vue成神之路★ ★ uniapp-从构建到提升★ ★ 从0到英雄&#xff…

王道考研操作系统

王道考研操作系统 计算机系统概述操作系统的概念操作系统的特征操作系统的发展历程操作系统内核中断和异常![在这里插入图片描述](https://img-blog.csdnimg.cn/162452b4c60144e0bd500e180127c447.png)系统调用操作系统结构虚拟机错题 进程与线程进程控制进程通信线程和多线程模…

Python+Selenium定位不到元素常见原因及解决办法(报:NoSuchElementException)

这篇文章主要介绍了PythonSelenium定位不到元素常见原因及解决办法(报&#xff1a;NoSuchElementException),文中通过示例代码介绍的非常详细&#xff0c;对大家的学习或者工作具有一定的参考学习价值&#xff0c;需要的朋友们下面随着小编来一起学习学习吧 在做web应用的自动…

elasticsearch17-自动补全

个人名片&#xff1a; 博主&#xff1a;酒徒ᝰ. 个人简介&#xff1a;沉醉在酒中&#xff0c;借着一股酒劲&#xff0c;去拼搏一个未来。 本篇励志&#xff1a;三人行&#xff0c;必有我师焉。 本项目基于B站黑马程序员Java《SpringCloud微服务技术栈》&#xff0c;SpringCloud…

2023/9/17总结

Vue defineOptions 为什么要使用defineOptions 在有<script setup> 之前 如果需要定义props emit 可以很容易的添加一个与setup 平级的属性 但是用了 <script setup> 后 就不能这样做了 setup 属性也就没有了&#xff0c;就不能添加 与其平级 的属性 为了解…

基于springboot+vue的网上商城系统

一、选题背景意义 &#x1f30a;项目背景&#xff1a;该电子商城系统旨在为商家和消费者提供一个直观、易用的购物平台&#xff0c;通过该平台销售商品和宣传品牌&#xff0c;消费者通过该平台购买商品&#xff0c;享受更便捷的购物体验。在该电子商城系统中&#xff0c;商家可…