Python学习笔记

172 阅读18分钟

Python

贴菜鸟的教程:https://www.runoob.com/python,

注:本资料部分文档来源于菜鸟教程。

数据类型

数据类型描述例子
int整数1
float浮点数1.00
String字符串"test"
bool布尔类型,True /Falset=5>1 # True

查看数据类型

type(数据),用于查看数据类型,python中变量没有类型

例:
print(type(1)) # 查看结果,type(1)具有返回值可以被变量所接受
# 整数

数据类型转换

函数描述例子
int(x)将x转换为整数,可将字符转换为数字num_1 = int("1")
float(x)将x转换为单精度数据float_1 = float(23)
str(x)将x转换为字符串str_1 = str(123)
例:
# 数据类型转换
# int转换 String转换
num_1 = int("1")
print(num_1)
# 双精度装为int精度丢失
num_2 = int(12.34)
print(num_2)
# 数字转字符
str_1 = str(123)
print(type(str_1), str_1)

# 双精度转换
float_1 = float(23)
print(type(float_1), float_1)

操作运算符

操作符描述例子
//整除,得到的结果是整数9//2==4
**指数运算,得到一个数的指数次结果2**10==1024
/除操作,得到的结果是单精度数据类型float9/2==4.5

例:

# 操作运算符
# // 整除符
num_3 = 9 // 2
print(num_3)
# ** 指数操作符
num_4 = 2 ** 10
print(num_4)

# python的/得到的是双精度数据类型
num_5 = 9 / 4
print(num_5)

字符串的定义方式

  • 第一种单引号,'我是字符串1'
  • 第二种双引号,"我是字符串2"
  • 第三种三双引号,也是注释,支持多行,""" 我是字符串3 """
  • 同时单引号和双引号可以互相包裹,不能包裹自身,不过可以通过转义字符实现 \+或者\+"

字符串拼接

通过+拼接,不能与int或者float进行拼接

字符串格式化

# 格式化单个
print("my name is %s"%"唐志峰")
# 格式化多个
print("my name is %s and age is %d"%("唐志峰",20)) # 多个占位符通过()填充数据
# 设置数字的精度,%m.nf 其中f是float,m是宽度,n是小数位数,遵循四舍五入,宽度不够用空格占位
print("my name is %s and age is %.0f" % ("唐志峰", 20))
常用占位符
符号
%s字符串
%d整数
%f单精度浮点数

同时还可以通过如下方式格式化字符串,通过f""标识字符串,然后通过{数据}进行占位

age=20;
print(f"my name is {age}") # {}动态占位

控制语句

python的层级通过缩进实现,一般缩进四个空格,tab键即可

input

这是输入语句,可接受到键盘输入的内容,类型为String,输入的内容在同一行

age=input("输入你的年龄")
print(f"年龄是:{age}")

if-elif-else

# 基本格式
if 18>10:
    print ("hi!")
elif 18>15:
    print ("hi!")
else:
    print ("hi!!!")
# python中,if内部的语句通过缩进实现,缩进四个空格
# elif是else-if的简写


# 与input语句结合
if int(input("输入你的年龄?"))>18:
    print("你已经成年了")
elif str(input("输入你的性别?"))=="男":
    print("虾头男!真虾头") 
elif float(input("输入你的存款?"))>=10000.0:
    print("小有存款")

while

# while
a = 1
while 1:
    print(a)
    a += 1
    if a == 10:
        break
# while-else
a=1
while a<5:
    print(a)
    a+=1
else:
    print("a more than 5!")

for

有点类似于vue中v-for循环, 关键字 in 后面跟的是具体的对象(数组对象),字符串也可以

range(start,end) range()是内置函数,可以给定一定区间的序列,包前不包后,也可以给定一个参数,即参数end位置

len()是内置参数,用户获取字符串或者数组长度

# 简单for循环
arr = [1, 2, 3, 4]
str_arr = "abcdefghijklmnopqrstu"
for a in str_arr: 
    print(f"当前字符是{a}")
    
# for-range循环
# for-range
for idx in range(len(arr)):
    print(arr[idx])

pass

用于在函数定义时,未想好函数具体的实现时,用作占位使用

# python2.x:
def function():
    # 空函数在Python2.x版本中pass是必须的
    pass

# python3.x:
def function():
    # 在Python3.x的时候pass可以写或不写
    pass
# 或者是其他需要具体内容地方
if a>1:
    pass

函数定义

基本定义格式

def functionName(参数名):
函数体
   return

参数

给定默认参数

在python函数定义中,参数值可以给定一个默认值,当该参数未传入时,择使用默认参数

def defaultParams(a,b=1):# 给定b一个默认参数
    return
关键字参数匹配

在python函数中,传入参数时,可以根据参数名称匹配,如下,顺序此时可不遵循,python底层会进行匹配,不过需要再

def keyWordParams(a,b,c):
    return 

# 调用函数
keyWordParams(b=1,c=3,a=2) # 此时的abc的值与函数的参数列表的参数根据名称一一对应,如果未对应,程序运行会出错
不定长参数

python的函数定义中,如果参数的个数无法确定,可以使用不定长参数,通过*params定义不定长参数列表

def add5(*args):
    print(args)
    return sum(args)


d = add5(1, 2, 3, 4, 5)
print(d)

return返回值

默认返回为None,可返回任意类型

数据容器

序列切片,像list,tuple,string等支持索引的序列都满足

list=[1,2,3,4,5,6,7,8,9,10]
print(list[::1]) # 正向输出,步长为1
print(list[::-1]) # 逆向输出,步长为1
  • 第一个:是起始索引,默认为0
  • 第二个:是结束索引位置,默认为len(list)-1
  • 第三个:是索引的步长

list

更多在[Python 列表(List) | 菜鸟教程 (runoob.com)](https://www.runoob.com/python/python-lists.html),或者到官网学习

  • 基本的定义格式为list=[1,2,3,4,5],在java或者其他的高级语言中通常不会这么随意,同时元素包裹符号为[]
  • list,可以存放不同类型的元素,可以近似的看成Object类型的数组
  • list,有常见的添加元素方法和删除方法等,
# 对list初始化 *后面跟的是初始化数组的长度
list=[0]*10;
# 定义list
list1 = [1, 2, 3, 4, 5]
for i in list1:
    print(i)

# # list可以存放不同类型的数据
list2 = [1, 2, 3, "hello", True, None]
for i in list2:
    print(i)
# list可以嵌套存放list
list3 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
for i in list3:
    print(i)
    for j in i:
        print(j)

# 追加元素至list
list4 = []
list4.append(1)
list4.append(2)
list4.append(3)
print(list4)
# 删除元素
list4.remove(2)
print(list4)
# list4.clear()
min = min(list4)
max = max(list4)

print("最小值是:%d" % min)
print("最大值是:%d" % max)
print(list4.count(3))
list4.insert(1, 4) # 第一个参数是插入的索引的位置,第二个参数是元素值
print(list4)
# 排序
# 对list4进行升序排序
list4.sort()
print(list4)
# 对list4进行降序排序
list4.sort(reverse=True)
print(list4)

元组

Python 元组 | 菜鸟教程 (runoob.com)

t=(a,b,c,d) # define tuple

集合set

集合特点,无序,无重复,可修改,不支持索引

# 定义空集合
myset = set()
# 添加元素
myset.add(1)
myset.add(2)
myset.add(3)
myset.add(3)
myset.add(4)
myset.add(5)
# 输出
print(myset)

# 删除元素3
myset.remove(3)
# 输出
print(myset)

# 弹出元素,数字有序
myset.pop()
print(myset)
# 清空
myset.clear()
print(myset)

set1 = {1, 2, 3, 4, 5}
set2 = {4, 5, 6, 7, 8}
# 取两集合的差集,可用-或者difference方法
print(set1 - set2)
print(set1.difference(set2))

# 取并集合,使用|或者union方法
print(set1 | set2)
print(set1.union(set2))

# 遍历
for i in set1.union(set2):
    print(i)

字典

Python 字典(Dictionary) | 菜鸟教程 (runoob.com)

# 定义字典
dict1 = {'name': '小明', 'age': 18, 'gender': '男', 'height': 175, 'weight': 55, 78: 78}
# 根据键名获取值
print(dict1['age'])
# 修改值
dict1['age'] = 20
print(dict1['age'])
# 直接添加key:value
dict1['address'] = '上海'
print(f"添加地址元素后{dict1}")

# 常用api
# 获取所有键名
print(f"获取所有键名{dict1.keys()}")
# 获取所有键值
print(f"获取所有键値{dict1.values()}")
# 获取元祖数组
print(f"获取元祖数组{dict1.items()}")

# 根据键名获取键值
print(f"标准键名{dict1.get('name', '暂无')}")  # 第二个参数是默认参数,当键名不存在时

# 清空字典
dict1.clear()
print(dict1)
# 删除字典,释放内存
del dict1

日期和时间

Python 日期和时间 | 菜鸟教程 (runoob.com)

time模块

python中时间日期格式化符号:

  • %y 两位数的年份表示(00-99)
  • %Y 四位数的年份表示(000-9999)
  • %m 月份(01-12)
  • %d 月内中的一天(0-31)
  • %H 24小时制小时数(0-23)
  • %I 12小时制小时数(01-12)
  • %M 分钟数(00-59)
  • %S 秒(00-59)
  • %a 本地简化星期名称
  • %A 本地完整星期名称
  • %b 本地简化的月份名称
  • %B 本地完整的月份名称
  • %c 本地相应的日期表示和时间表示
  • %j 年内的一天(001-366)
  • %p 本地A.M.或P.M.的等价符
  • %U 一年中的星期数(00-53)星期天为星期的开始
  • %w 星期(0-6),星期天为星期的开始
  • %W 一年中的星期数(00-53)星期一为星期的开始
  • %x 本地相应的日期表示
  • %X 本地相应的时间表示
  • %Z 当前时区的名称
  • %% %号本身
import time  # 导入time模块

# 获取时间戳
now = time.time()
# 打印上面的now时间,时间戳
print(now)
# 时间戽数表示的时间
print(time.localtime(now))
# 时间元组表示的时间
# 格式化日期
print(time.strftime("%Y-%m-%d %H:%M:%S"))
# 标准样式
print(time.asctime())

calendar

# 导入日历模块
import calendar

# 获取指定日期的日历
print(calendar.month(2023, 8))

# 判断年份是否是闰年
print(calendar.isleap(2023))

模块

Python 模块 | 菜鸟教程 (runoob.com)

搜索路径

当你导入一个模块,Python 解析器对模块位置的搜索顺序是:

  • 1、当前目录
  • 2、如果不在当前目录,Python 则搜索在 shell 变量 PYTHONPATH 下的每个目录。
  • 3、如果都找不到,Python会察看默认路径。UNIX下,默认路径一般为/usr/local/lib/python/。

模块搜索路径存储在 system 模块的 sys.path 变量中。变量里包含当前目录,PYTHONPATH和由安装过程决定的默认目录。

包是一个分层次的文件目录结构,它定义了一个由模块及子包,和子包下的子包等组成的 Python 的应用环境。

简单来说,包就是文件夹,但该文件夹下必须存在 init.py 文件, 该文件的内容可以为空。init.py 用于标识当前文件夹是一个包。

考虑一个在 package_runoob 目录下的 runoob1.py、runoob2.py、init.py 文件,test.py 为测试调用包的代码,目录结构如下:

test.py
package_runoob
|-- __init__.py
|-- runoob1.py
|-- runoob2.py
# 导入整个模块
import BrainySearch

list = [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57,
        59, 61, 63, 65, 67, 69, ]
res = BrainySearch.binary_search(list, 69)
print(res)

# 导入模块中单一的函数

from BrainySearch import binary_search

res2 = binary_search(list, 35)
print(res2)

# 导入模块中全部的函数
from BrainySearch import *

res3 = binary_search(list, 37)
print(res3)

文件IO

Python 文件I/O | 菜鸟教程 (runoob.com)

open()函数的编码格式通过encoding指定,一般使用utf-8

方法解释
open(mode,Path,encoding)用于打开文件,mode是打开模式,具体模式有如下表所示,path是文件路径,encoding是编码格式,一般使用utf-8
read(count)读取文件内容,按字节数读取
readline()读取一行
readlines()读取全部内容,以list形式存储,可遍历
write()写入内容,这里要求文件打开模式为可写入,个人推荐使用a+模式,可读可写.
writelines()写入一行,可自己添加换行符,writelines("hello world\n")
flush()将内容从缓存写入到硬盘中
close()关闭文件
closed()判断文件是否已经关闭
tell()返回文件当前的位置,即处于第几个字符的索引位置
seek(start,offset)设置文件的指针位置,start是参照位置的索引,offset是偏移的索引值,默认是0,0即文件开头的位置
with open() as f:是一种打开文件的语法,读取结束后,会自动关闭文件流,f是整个文本内容(readlines()),可遍历f[本质还是list]。

不同模式打开文件的完全列表:

模式描述
t文本模式 (默认)。
x写模式,新建一个文件,如果该文件已存在则会报错。
b二进制模式。
+打开一个文件进行更新(可读可写)。
U通用换行模式(不推荐)。
r以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。
rb以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。一般用于非文本文件如图片等。
r+打开一个文件用于读写。文件指针将会放在文件的开头。
rb+以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。一般用于非文本文件如图片等。
w打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
wb以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。
w+打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
wb+以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。
a打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
ab以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
a+打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。
ab+以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。
fileObj = open("test.txt", "a+",encoding="utf-8")  # opens the file in read mode

print(fileObj.name)  # prints the name of the file
print(fileObj.mode)  # prints the mode in which the file was opened
position = fileObj.tell()  # prints the current position of the file pointer
print(position)
fileObj.seek(0)  # moves the file pointer to the beginning of the file
ContentStr = fileObj.read(10)
print(ContentStr)  # prints the content of the file
fileObj.close()  # closes the file
print(fileObj.closed)  # prints whether the file is closed or not

# 导入os模块
import os

os.rename("test.txt", "test1.txt")  # 重命名文件
os.remove("test1.txt")  # 删除文件
os.mkdir("test")  # 当前目录下创建新的文件夹
os.rmdir("test")  # 删除文件外目录

Exception异常

Python 异常处理 | 菜鸟教程 (runoob.com)

基本语法

try:
   
except: # 捕获所有异常或者采用except Exception:
# 捕获单个异常,except NameError: 
# 捕获多个时,用()括上
# except Exception as e:
# 	print(e) 打印异常信息
# 可选有else和finally

except是发生异常时执行

else是异常未发生时执行

finally是异常无论发生都会执行

个人感觉else很奇怪

try:
    fh = open("testfile", "w")
    fh.write("这是一个测试文件,用于测试异常!!")
except IOError as e:
    print "Error: 没有找到文件或读取文件失败"
    print(e) # 打印异常信息
else:
    print "内容写入文件成功"
finally:
    fh.close()

面向对象

Python 面向对象 | 菜鸟教程 (runoob.com)

class parent:
    # 公共变量
    name = "tzf"
    age = 20
    # 私有变量
    __sex = "男"

    def __init__(self, name, age):
        self.name = name
        self.age = age
        print("parent init")

    # 定义公共方法
    def getSex(self):
        return self.__sex

    # 定义私有方法 __methods
    def __privateMethods(self):
        return f"用户信息:用户名{self.name}\n年龄:{self.age}\n性别:{self.getSex()}"

    # 重写str方法
    def __str__(self):
        return self.__privateMethods()

    # 重写add方法
    def __add__(self, other):
        return f"用户年龄之和:{self.age + other.age}"


# 创建对象
p1 = parent("tzf", 20)
print(p1.getSex())
print(p1)

p2 = parent("ymj", 19)
print(p2.getSex())
print(p2)

数据注解&形参注解&方法注解

相当于提示,对数据类型的提示,多的不说,上代码

UNION

# 数据类型的
a: int = 10
b: dict[str, int] = {"age": 10}
c: list[int] = [1, 2, 3]
d: tuple[int, int] = (1, 2)
# 或者注释
a=10 # type:int

# 形参的
def add(a:int):
    pass
# 方法的
def str_substr(string: str, start: int, end: int) -> str:
    # 判断参数str是否是合法字符中
    if not isinstance(string, str):
        raise TypeError('str_reverse() only accepts string')
    return string[start:end]

mysql连接

Python3 MySQL 数据库连接 – PyMySQL 驱动 | 菜鸟教程 (runoob.com)

import pymysql

db = pymysql.connect(host='localhost', user='root', password='root', port=3306, db='reggie')
# 使用 cursor() 方法创建一个游标对象 cursor
cursor = db.cursor()

# 使用 execute()  方法执行 SQL 查询
cursor.execute('select * from orders')

# 使用 fetchone() 方法获取单条数据.
data = cursor.fetchone()

print(data)

多线程

Python3 多线程 | 菜鸟教程 (runoob.com)

# 下面的例子是基于threading中Thread模块的
# 引入线程
from threading import Thread


def runStart():
    for t in range(1000):
        print(f"子线程:{t}")


if __name__ == '__main__':
    # 开启子线程
    Thread(target=runStart).start()
    for i in range(1000):
        print(f"主线程:{i}")

协程

[协程与任务 — Python 3.11.4 文档](https://docs.python.org/zh-cn/3/library/asyncio-task.html)

# 导入协程模块,开启异步任务
import asyncio
import time


async def f1():
    await asyncio.sleep(3)
    print("3s")


async def f2():
    await asyncio.sleep(4)
    print("4s")


async def f3():
    await asyncio.sleep(5)
    print("5s")


async def main():
    # 创建两个任务
    task1 = asyncio.create_task(f1())
    task2 = asyncio.create_task(f2())
    print(f"started at {time.strftime('%X')}")
    # 开启挂载
    await task1
    await task2
    print(f"started at {time.strftime('%X')}")


if __name__ == '__main__':
    asyncio.run(main())

爬虫

需要用到的库有requests,发送网络请求

菜鸟:Python requests 模块 | 菜鸟教程 (runoob.com)

BeautifulSoup库,用于解析html css等网页内容

练习的网站:

demo1,解析<p>

import requests
from bs4 import BeautifulSoup

res = requests.get("http://books.toscrape.com/").text
soup = BeautifulSoup(res, "html.parser")
all_price = soup.findAll("p", {"class": "price_color"})

print("书单价格:")
for price in all_price:
    print(price.string[1:])

demo2,爬取豆瓣

import requests
from bs4 import BeautifulSoup

# 添加请求头,模拟网站发送请求
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36 Edg/88.0.705.74"
}
i = 1;
for start in range(0, 250, 25):
    content = requests.get(f"https://movie.douban.com/top250?start={start}", headers=headers).text
    soup = BeautifulSoup(content, "html.parser")
    for item in soup.findAll("span", attrs={"class": "title"}):
        if "/" not in item.string:
            print(f"排名第{i}的电影是--->{item.string}")
            i += 1

demo3,爬取知乎,需要cookie

也可以使用request.session(),保存会话状态,保存自动获取cookie

import requests
import os
from bs4 import BeautifulSoup
import time

# 解析cookie
cookies = "cookies"
# 模拟网站发送请求
# 添加请求头,模拟网站发送请求
# h2-->HotItem-title
headers = {
    "cookie": cookies,
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
}
url = "https://www.zhihu.com/hot"
res = requests.get(url, headers=headers).text
soup = BeautifulSoup(res, "html.parser")
context = soup.findAll("h2", attrs={"class": "HotItem-title"})
hot = soup.findAll("div", attrs={"class": "HotItem-content"})
idx = 1
createFileUrl = "D:\\pythonProject\\zhihuHot.txt"
zhihuHotFile = open(createFileUrl, "a+", encoding="utf-8")
title = f"{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}的知乎热榜:"
print(title)
zhihuHotFile.writelines(title + "\n")
for i in context:
    zhihuHotFile.writelines(f"热度第{idx}的标题是<<" + i.string + f">>,热度:{hot[idx - 1].select('div')[0].text}\n")
    idx += 1
# 写入文件
zhihuHotFile.close()
print(f"{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}的知乎热榜文件保存至{createFileUrl}")


demo4,梨视频

主要防反爬的措施是,防盗链,和地址的url的改变

import requests
# 获取session
session = requests.session()
# 具体的请求的资源路径,即referer
referer = "https://www.pearvideo.com/video_1497209"
# 获取contId
contId = referer.split("_")[1]
# 发起登录请求,保存会话状态
url = f"https://www.pearvideo.com/videoStatus.jsp?contId={contId}&mrd=0.9779566895867053"
# 请求头
headers = {
    "Referer": referer,
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"
}
# 请求参数使用字典存放
data = {

}
result = session.get(url, headers=headers, data=data)
# 设置编码
result.encoding = "utf-8"
dt = result.json()
# 获取视频资源链接
srcUrl = dt["videoInfo"]["videos"]["srcUrl"]
# 获取时间戳
systemTime = dt["systemTime"]
# 对srcUrl进行替换
srcUrl = str(srcUrl)
srcUrl = srcUrl.replace(systemTime, f"cont-{contId}")
print(f"{referer}的视频资源路径为:{srcUrl}")

# 对视频资源进行下载
# 地址
address = f"D:/Temp/{contId}.mp4"
with open(address, mode="wb") as f:
    f.write(session.get(srcUrl).content)

print(f"梨视频站点的{referer}的视频资源已下载完毕!,保存地址:{address}")

demo5,爬取西游记,异步

asyncio.wait(),从3.11开始,需要通过asyncio.create_task创建task

import requests
import asyncio
import aiohttp
import json
import aiofiles


async def Download(bookId, cid, title):
    data = {
        "book_id": bookId,
        "cid": f"{bookId}|{cid}",
        "need_bookinfo": 1
    }
    url = f"https://dushu.baidu.com/api/pc/getChapterContent?data={json.dumps(data)}"
    async with aiohttp.ClientSession() as s:
        async with s.get(url) as res:
            dit = await res.json()
            content = dit['data']['novel']['content']
            async with aiofiles.open(title, mode="w", encoding='utf-8') as f:
                await f.write(content)


async def getBookInfo():
    # 书籍id
    bookId = "4306063500"
    # 下载路径
    baseUrl = f"C:/book/"
    # cookie
    cookies = "BAIDUID=635FE5371F9F52EC90558D9B6E49B2F5:FG=1; BAIDUID_BFESS=635FE5371F9F52EC90558D9B6E49B2F5:FG=1"
    # header
    headers = {
        "Sec-Ch-Ua": "Chromium';v = '116', 'Not)A;Brand';v = '24','Microsoft Edge';v = '116'",
        "Sec-Ch-Ua-Mobile": "?0",
        "Sec-Ch-Ua-Platform": "Windows",
        "Sec-Fetch-Dest": "empty",
        "Sec-Fetch-Mode": "cors",
        "Sec-Fetch-Site": "same-origin",
        "cookie": cookies,
        "Host": "dushu.baidu.com",
        "If-None-Match": 'W/"2b7d-ZsELp86aJawrzgjq9ti2Mw"',
        "Referer": f"https://dushu.baidu.com/pc/detail?gid={bookId}",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.0.0"
    }
    # 请求书籍基本内容的url
    url = 'https://dushu.baidu.com/api/pc/getCatalog?data={"book_id":' + bookId + '}'
    resp = requests.get(url, headers=headers)
    resp.encoding = 'utf-8'
    dit = resp.json()
    task = []
    for item in dit['data']['novel']['items']:
        task.append(
            asyncio.create_task(Download(bookId=bookId, cid=item['cid'], title=baseUrl + item['title'] + ".txt")))
    await asyncio.wait(task)


if __name__ == '__main__':
    # 下载图书
    asyncio.run(getBookInfo())

技巧

解析cookies

cookies="cookies"
cookies = {i.split("=")[0]: i.split("=")[1] for i in cookies.split(";")}