Python + 查看个人下载次数小工具 - 记录

目录

前言

一、演示

二、流程简述

1.CSDN网站自动登入

2.登入查询接口网站+获取网页数据

3.处理HTML数据

4.完整业务执行程序

三、主程序

四、UI程序



前言

为了方便查看个人资源下载的数据,通过selenium控制浏览器 + HTML网页源代码数据获取 + 数据分析 三个流程来达到目的。


一、演示


二、流程简述

  1. 进入CSDN网站,登录账号和密码;

  2. 进入个人查询网站,获取网页数据;

  3. 分析网页数据,得到需要的数据;

  4. 将得到的数据,显示在界面;

1.CSDN网站自动登入

通过selenum + 对应浏览器驱动 完成成功登入CSDN网站的操作

代码如下(示例):

        # 浏览器启动选项
        option= webdriver.EdgeOptions()
        #添加启动选项,指定为无界面模式
        option.add_argument('--headless')
        # option = False
        browser=webdriver.Edge(options=option)

        # 访问CSDN首页
        browser.get(r'https://passport.csdn.net/login')
        pson = browser.find_element(by=By.XPATH,value='/html/body/div[2]/div/div[2]/div[2]/div[2]/div/div[1]/span[4]')
        pson.click()

        time.sleep(0.5)

        # 输入用户和密码 
        browser.find_element(by=By.XPATH,value='/html/body/div/div/div/div[2]/div[2]/div/div[2]/div[1]/div[1]/div/input').send_keys(self.msg[0])
        browser.find_element(by=By.XPATH,value='/html/body/div/div/div/div[2]/div[2]/div/div[2]/div[1]/div[2]/div/input').send_keys(self.msg[1])

        browser.find_element(by=By.XPATH,value='/html/body/div/div/div/div[2]/div[2]/div/div[2]/div[2]/div/i').click()

        # 点击登录
        browser.find_element(by=By.XPATH,value='/html/body/div/div/div/div[2]/div[2]/div/div[2]/div[1]/div[4]/button').click()

2.登入查询接口网站+获取网页数据

代码如下(示例):

        # 登入查询网址
        browser.get('https://download-console-api.csdn.net/v1/user/sources/getUploadListByUserName?status=2&pageNum=1&pageSize=100')

        page_sources = browser.page_source

        soup = BeautifulSoup(page_sources, 'html.parser')
        shuju = soup.get_text()
        # shuju = soup
        print(shuju)
        print(type(shuju))

3.处理HTML数据

代码如下(示例):

        try:
            # 提取状态码
            res = r'code(.*?),'
            result = re.findall(res,shuju)
            print(result)
            data = result[0].split(':')[-1]  # 使用空格分割,获取最后一个元素(即数字)
            print(data)
            if data == "200":
                pass
            else:
                self.finished.emit([2,"状态码返回错误!"])
                return -1

            # 提取资源标题
            res = r'title(.*?),'
            result_title = re.findall(res,shuju)
            # print(result_title)
            result_titles = []
            for shujus in result_title:
                result_title = shujus.split(':',1)[-1]
                result_title = str(result_title).replace('"',"")
                result_titles.append(result_title)
            print(result_titles)

            # 提取资源地址
            res = r'sourceUrl(.*?),'
            result_ziyuan = re.findall(res,shuju)
            # print(result_ziyuan)
            result_ziyuans = []
            for shujus in result_ziyuan:
                result_ziyuan = shujus.split(':',1)[-1]
                result_ziyuan = str(result_ziyuan).replace('"',"")
                result_ziyuans.append(result_ziyuan)
            print(result_ziyuans)

            # 提取资源下载次数
            res = r'downloadNum(.*?),'
            result_donum = re.findall(res,shuju)
            # print(result_donum)
            result_donums = []
            for shujus in result_donum:
                result_donum = shujus.split(':',1)[-1]
                result_donum = str(result_donum).replace('"',"")
                result_donums.append(result_donum)
            print(result_donums)
        

            time.sleep(2)


            for i in range(0,len(result_titles)):
                shuju1 = "资源名称{}-- ".format(i+1) + result_titles[i]
                shuju2 =  result_ziyuans[i]
                shuju3 = "资源下载次数-- " + result_donums[i] +"\n"
                shuju4 = "---------------------------------------------\n"
                self.finished.emit([1,shuju1])
                self.finished.emit([3,shuju2])
                self.finished.emit([1,shuju3])
                self.finished.emit([5,""])
                self.finished.emit([2,shuju4])
                self.finished.emit([5,""])


            i = 4
            self.finished.emit([i,"完成查询!"])

            # 关闭浏览器
            browser.quit()
        except Exception as error:
            print(error)
            self.finisheds(2,"{}".format(error))
            self.finished.emit([4,"查询失败!"])
            # 关闭浏览器
            browser.quit()

4.完整业务执行程序

import time,os,shutil,sys
from PyQt5.QtCore import QThread,pyqtSignal
from selenium import webdriver
from selenium.webdriver.common.by import By
from bs4 import BeautifulSoup
import re

class Worker(QThread):
    finished = pyqtSignal(list)
    
    def __init__(self,msg=None):
        super().__init__()
        self.msg = msg
        self.ret = "True"

    def run(self):
        self.get_python_path()
        
        #浏览器启动选项
        option= webdriver.EdgeOptions()
        #添加启动选项,指定为无界面模式
        option.add_argument('--headless')
        # option = False
        browser=webdriver.Edge(options=option)

        # 访问CSDN首页
        browser.get(r'https://passport.csdn.net/login')
        pson = browser.find_element(by=By.XPATH,value='/html/body/div[2]/div/div[2]/div[2]/div[2]/div/div[1]/span[4]')
        pson.click()

        time.sleep(0.5)

        # 输入用户和密码 
        browser.find_element(by=By.XPATH,value='/html/body/div/div/div/div[2]/div[2]/div/div[2]/div[1]/div[1]/div/input').send_keys(self.msg[0])
        browser.find_element(by=By.XPATH,value='/html/body/div/div/div/div[2]/div[2]/div/div[2]/div[1]/div[2]/div/input').send_keys(self.msg[1])

        browser.find_element(by=By.XPATH,value='/html/body/div/div/div/div[2]/div[2]/div/div[2]/div[2]/div/i').click()

        # 点击登录
        browser.find_element(by=By.XPATH,value='/html/body/div/div/div/div[2]/div[2]/div/div[2]/div[1]/div[4]/button').click()

        time.sleep(2)

        # 登入查询网址
        browser.get('https://download-console-api.csdn.net/v1/user/sources/getUploadListByUserName?status=2&pageNum=1&pageSize=100')

        page_sources = browser.page_source

        soup = BeautifulSoup(page_sources, 'html.parser')
        shuju = soup.get_text()
        # shuju = soup
        print(shuju)
        print(type(shuju))

        try:
            # 提取状态码
            res = r'code(.*?),'
            result = re.findall(res,shuju)
            print(result)
            data = result[0].split(':')[-1]  # 使用空格分割,获取最后一个元素(即数字)
            print(data)
            if data == "200":
                pass
            else:
                self.finished.emit([2,"状态码返回错误!"])
                return -1

            # 提取资源标题
            res = r'title(.*?),'
            result_title = re.findall(res,shuju)
            # print(result_title)
            result_titles = []
            for shujus in result_title:
                result_title = shujus.split(':',1)[-1]
                result_title = str(result_title).replace('"',"")
                result_titles.append(result_title)
            print(result_titles)

            # 提取资源地址
            res = r'sourceUrl(.*?),'
            result_ziyuan = re.findall(res,shuju)
            # print(result_ziyuan)
            result_ziyuans = []
            for shujus in result_ziyuan:
                result_ziyuan = shujus.split(':',1)[-1]
                result_ziyuan = str(result_ziyuan).replace('"',"")
                result_ziyuans.append(result_ziyuan)
            print(result_ziyuans)

            # 提取资源下载次数
            res = r'downloadNum(.*?),'
            result_donum = re.findall(res,shuju)
            # print(result_donum)
            result_donums = []
            for shujus in result_donum:
                result_donum = shujus.split(':',1)[-1]
                result_donum = str(result_donum).replace('"',"")
                result_donums.append(result_donum)
            print(result_donums)
        

            time.sleep(2)


            for i in range(0,len(result_titles)):
                shuju1 = "资源名称{}-- ".format(i+1) + result_titles[i]
                shuju2 =  result_ziyuans[i]
                shuju3 = "资源下载次数-- " + result_donums[i] +"\n"
                shuju4 = "---------------------------------------------\n"
                self.finished.emit([1,shuju1])
                self.finished.emit([3,shuju2])
                self.finished.emit([1,shuju3])
                self.finished.emit([5,""])
                self.finished.emit([2,shuju4])
                self.finished.emit([5,""])


            i = 4
            self.finished.emit([i,"完成查询!"])

            # 关闭浏览器
            browser.quit()
        except Exception as error:
            print(error)
            self.finisheds(2,"{}".format(error))
            self.finished.emit([4,"查询失败!"])
            # 关闭浏览器
            browser.quit()


    def finisheds(self,i,h=None):
        self.finished.emit([i,h])


      # 获取python安装路径
    def get_python_path(self):
        python_path = sys.prefix
        # print(f"Python的安装路径: {python_path}")
        python_paths = python_path +"/" + "Scripts\msedgedriver.exe"
        driver_path_file = "./浏览器驱动/msedgedriver.exe"
        # 判断驱动是否存在,不存在直接复制过去
        if os.path.isfile(python_paths):
            pass
        else:
            self.finished.emit([2,"缺少驱动文件...!"])
            shutil.copy(driver_path_file, python_paths)
            time.sleep(1)
            self.finished.emit([1,"驱动文件自动复制成功!"])
            

三、主程序

import time
from Ui_down import Ui_MainWindow
import sys
from PyQt5.QtGui import QIcon,QKeySequence  # 用于添加图标
from PyQt5.QtWidgets import QMainWindow,QApplication,QLineEdit
from PyQt5.QtCore import QUrl
from thread import Worker


'''
1、进入CSDN网站,登录用户和密码
2、进入个人查询网站,获取数据
3、分析数据和显示数据

'''



class show_window(QMainWindow,Ui_MainWindow):  # 继承至界面文件的主窗口类
    
    def __init__(self):
        super().__init__()  # 使用超类,继承父类的属性及方法
        self.setupUi(self)  # 构造窗体界面
        self.setWindowIcon(QIcon("./IMG/icon/icon.jpg"))
        self.setWindowTitle("测试使用")  # 设置窗体主体
        self.initUI()  # 构造功能函数
    
    def initUI(self):
        self.textBrowser.setOpenLinks(True)
        self.textBrowser.setOpenExternalLinks(True)
        self.pushButton.clicked.connect(self.start_get_data)
        self.lineEdit.setPlaceholderText("手机号/邮箱/用户名")
        self.lineEdit_2.setPlaceholderText("密码")
        # 隐藏输入显示的密码
        self.lineEdit.setEchoMode(QLineEdit.Password) 
        self.lineEdit_2.setEchoMode(QLineEdit.Password) 
        self.pushButton.setShortcut(QKeySequence('Enter'))
        self.pushButton_2.clicked.connect(self.clear)

    def clear(self):
        self.textBrowser.clear()


    def start_get_data(self):
        if self.pushButton.text() == "查询":
            # 获取用户名和密码
            name = self.lineEdit.text()
            paaword = self.lineEdit_2.text()
            
            if name == "" or paaword == "":
                self.receive([2,"用户名或者密码未填写。"])
                return
            data = [name,paaword]
            # 创建工作线程的工作对象
            self.worker = Worker(msg = data)
            # 连接信号与槽
            self.worker.finished.connect(self.receive)
            self.worker.start()
            self.pushButton.setText("查询中")
            self.receive([1,"查询中...\n"])
           
        else:
            self.receive([2,"正在查询中"])
    
    # 接收信息
    def receive(self,text=[]):
        if text[0] == 1:
            self.textBrowser.append("<font color=\"#0000FF\">{}:{}</font> ".format(self.gettime(),text[1]))
        if text[0] == 2:
            self.textBrowser.append("<font color=\"#FF0000\">{}:{}</font> ".format(self.gettime(),text[1]))    
        if text[0] == 3:
            # self.textBrowser.append("<a href=\"%s\">超链接测试</a>" % ("完成下载"))
            self.textBrowser.append("<a href=\"{}\">{}:{}</a>".format(text[1],self.gettime(),text[1]))    
        if text[0] == 4:
            self.textBrowser.append("<font color=\"#00FF00\">{}:{}</font> ".format(self.gettime(),text[1]))
            self.pushButton.setText("查询")
        if text[0] == 5:
            self.textBrowser.append("<font color=\"#000000\">{}</font> ".format(text[1]))

    def gettime(self):
        # 获取当前时间
        time_show = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
        return time_show
    

if __name__ == "__main__":
    app = QApplication(sys.argv)
    ui2 = show_window()
    ui2.show()
    sys.exit(app.exec_())

四、UI程序

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'd:\pythonitem\获取个人CSDN资源下载次数小工具 - 记录\down.ui'
#
# Created by: PyQt5 UI code generator 5.15.11
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(445, 347)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
        self.gridLayout.setObjectName("gridLayout")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setObjectName("pushButton")
        self.gridLayout.addWidget(self.pushButton, 1, 2, 1, 1)
        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setObjectName("label")
        self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
        self.label_2 = QtWidgets.QLabel(self.centralwidget)
        self.label_2.setObjectName("label_2")
        self.gridLayout.addWidget(self.label_2, 1, 0, 1, 1)
        self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit.setObjectName("lineEdit")
        self.gridLayout.addWidget(self.lineEdit, 0, 1, 1, 1)
        self.lineEdit_2 = QtWidgets.QLineEdit(self.centralwidget)
        self.lineEdit_2.setEchoMode(QtWidgets.QLineEdit.Normal)
        self.lineEdit_2.setObjectName("lineEdit_2")
        self.gridLayout.addWidget(self.lineEdit_2, 1, 1, 1, 1)
        self.textBrowser = QtWidgets.QTextBrowser(self.centralwidget)
        self.textBrowser.setObjectName("textBrowser")
        self.gridLayout.addWidget(self.textBrowser, 2, 0, 1, 3)
        self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton_2.setObjectName("pushButton_2")
        self.gridLayout.addWidget(self.pushButton_2, 3, 2, 1, 1)
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 445, 23))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushButton.setText(_translate("MainWindow", "查询"))
        self.label.setText(_translate("MainWindow", "<html><head/><body><p align=\"center\">手机号:</p></body></html>"))
        self.label_2.setText(_translate("MainWindow", "<html><head/><body><p align=\"center\">密码:</p></body></html>"))
        self.pushButton_2.setText(_translate("MainWindow", "清除"))

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

服务器虚拟化全面教程:从入门到实践

服务器虚拟化全面教程&#xff1a;从入门到实践 引言 在现代 IT 基础设施中&#xff0c;服务器虚拟化已成为一种不可或缺的技术。它不仅能够提高资源利用率&#xff0c;还能降低硬件成本&#xff0c;优化管理流程。本文将深入探讨服务器虚拟化的概念、技术、应用场景及其实现…

【ECMAScript标准】深入理解ES2023的新特性与应用

&#x1f9d1;‍&#x1f4bc; 一名茫茫大海中沉浮的小小程序员&#x1f36c; &#x1f449; 你的一键四连 (关注 点赞收藏评论)是我更新的最大动力❤️&#xff01; &#x1f4d1; 目录 &#x1f53d; 前言1️⃣ ECMAScript的演变与重要性2️⃣ ES2023的主要新特性概述3️⃣ 记…

[Ansible实践笔记]自动化运维工具Ansible(一):初探ansibleansible的点对点模式

文章目录 Ansible介绍核心组件任务执行方式 实验前的准备更新拓展安装包仓库在ansible主机上配置ip与主机名的对应关系生成密钥对将公钥发送到被管理端&#xff0c;实现免密登录测试一下是否实现免密登录 常用工具ansibleansible—docansible—playbook 主要配置文件 Ansible 模…

安装Maven配置以及构建Maven项目(2023idea)

一、下载Maven绿色软件 地址&#xff1a;http://maven.apache.org/download.cgi 尽量不要选择最高版本的安装&#xff0c;高版本意味着高风险的不兼容问题&#xff0c;选择低版本后续问题就少。你也可以选择尝试。 压缩后&#xff1a; 打开后&#xff1a; 在该目录下新建mvn-…

【算法练习】最小生成树

题意&#xff1a;【模板】最小生成树 方法1&#xff1a;Prim算法(稠密边用优&#xff09; #include <bits/stdc.h> using namespace std; int n,m,u,v,d,ans; bool f[5001]; vector<pair<int,int>> a[5001];//用结构体和重载比直接定义小根堆似乎还快一点点…

局部变量和全局变量(Python)

引入例子拆解 源码 class A:def __init__(self):self.test 0def add(c, k):c.test c.test 1k k 1def main():Count A()k 0for i in range(0, 25):add(Count, k)print("Count.test", Count.test)print("k", k)main() 运行结果如下图 代码解析 这…

使用语音模块的开发智能家居产品(使用雷龙LSYT201B 语音模块)

在这篇博客中&#xff0c;我们将探讨如何使用 LSYT201B 语音模块 进行智能设备的语音交互开发。通过这个模块&#xff0c;我们可以实现智能设备的语音识别和控制功能&#xff0c;为用户带来更为便捷和现代的交互体验。 1. 语音模块介绍 LSYT201B 是一个基于“芯片算法”的语音…

GS-SLAM Dense Visual SLAM with 3D Gaussian Splatt 论文阅读

项目主页 2024 CVPR (highlight) https://gs-slam.github.io/ 摘要 本文提出了一种基于3D Gaussian Splatting方法的视觉同步定位与地图构建方法。 与最近采用神经隐式表达的SLAM方法相比&#xff0c;本文的方法利用实时可微分泼溅渲染管道&#xff0c;显著加速了地图优化和…

一天工作量压缩成半天!5个ChatGPT高效工作法则!

在信息爆炸的时代&#xff0c;高效的生活方式成为了许多人的追求。如何利用科技手段提升效率&#xff0c;成为了一个热门话题。ChatGPT&#xff0c;作为一款强大的语言模型&#xff0c;为我们提供了全新的解决方案。本文将深入探讨如何利用 ChatGPT 改变你的生活&#xff0c;助…

【SSM详细教程】-13-SpringMVC详解

精品专题&#xff1a; 01.《C语言从不挂科到高绩点》课程详细笔记 https://blog.csdn.net/yueyehuguang/category_12753294.html?spm1001.2014.3001.5482 02. 《SpringBoot详细教程》课程详细笔记 https://blog.csdn.net/yueyehuguang/category_12789841.html?spm1001.20…

SQL实战训练之,力扣:1532最近的三笔订单

目录 一、力扣原题链接 二、题目描述 三、建表语句 四、题目分析 五、SQL解答 六、最终答案 七、验证 八、知识点 一、力扣原题链接 1532. 最近的三笔订单 二、题目描述 客户表&#xff1a;Customers ------------------------ | Column Name | Type | --------…

Redis进阶:Spring框架中利用Redis实现对象的序列化存储

前言 由于Redis只能提供基于字符串型的操作&#xff0c;而Java中使用的却以类对象为主&#xff0c;所以需要Redis存储的字符串和Java对象相互转换。如果我们自己编写这些规则&#xff0c;工作量是比较大的&#xff0c;因此本文介绍如何使用Spring框架快速实现Java数据类型在Red…

Flask-SocketIO 简单示例

用于服务端和客户端通信&#xff0c;服务端主动给客户端发送消息 前提&#xff1a; 确保安装了socket库&#xff1a; pip install flask-socketio python-socketio服务端代码 from flask import Flask from flask_socketio import SocketIO import threading import timeap…

计算机网络:网络层 —— IPv4 地址的应用规划

文章目录 IPv4地址的应用规划定长的子网掩码变长的子网掩码 IPv4地址的应用规划 IPv4地址的应用规划是指将给定的 IPv4地址块 (或分类网络)划分成若干个更小的地址块(或子网)&#xff0c;并将这些地址块(或子网)分配给互联网中的不同网络&#xff0c;进而可以给各网络中的主机…

2023IKCEST第五届“一带一路”国际大数据竞赛--社交网络中多模态虚假 媒体内容核查top11

比赛链接&#xff1a;https://aistudio.baidu.com/competition/detail/1030/0/introduction PPT链接&#xff1a;https://www.ikcest.org/bigdata2024/zlxz/list/page.html 赛题 社交网络中多模态虚假媒体内容核查 背景 随着新媒体时代信息媒介的多元化发展&#xff0c;各种内容…

Handler、Looper、message进阶知识

Android Handler、Looper、Message的进阶知识 在Android开发中&#xff0c;Handler、Looper和Message机制是多线程通信的核心。为了深入理解并优化它们的使用&#xff0c;尤其是在高并发和UI性能优化中&#xff0c;可以利用一些高级特性。 1. Handler的高阶知识 Handler在基本…

基于SpringBoot的“心灵治愈交流平台”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“心灵治愈交流平台”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统功能界面图 登录、用户注册界面图 心灵专…

从“摸黑”到“透视”:AORO A23热成像防爆手机如何改变工业检测?

在工业检测领域&#xff0c;传统的检测手段常因效率低下、精度不足和潜在的安全风险而受到诟病。随着科技的不断进步&#xff0c;一种新兴的检测技术——红外热成像技术&#xff0c;正逐渐在该领域崭露头角。近期&#xff0c;小编对一款集成红外热成像技术的AORO A23防爆手机进…

FineReport 分栏报表

将报表中的数据根据所需要的展示的样式将数据进行分栏展示列分栏 报表中数据是横向扩展的,超过一页的数据会显示在下一页,而每页下面会有很大的一片空白区域,不美观且浪费纸张。希望在一页中第一行扩展满后自动到下一行继续扩展 1、新建数据集 SELECT * FROM 公司股票2、内…

C++游戏开发中的多线程处理是否真的能够显著提高游戏性能?如果多个线程同时访问同一资源,会发生什么?如何避免数据竞争?|多线程|游戏开发|性能优化

目录 1. 多线程处理的基本概念 1.1 多线程的定义 1.2 线程的创建与管理 2. 多线程在游戏开发中的应用 2.1 渲染与物理计算 3. 多线程处理的性能提升 3.1 性能评估 3.2 任务分配策略 4. 多线程中的数据竞争 4.1 数据竞争的定义 4.2 多线程访问同一资源的后果 4.3 避…