|
模块
一、模块简介
1.什么是模块
内部具有一定功能(代码)的py文件
2.python模块的历史
python屈辱史:
python刚出来时被瞧不起因为太简单,写代码都是调用模块(调包侠 贬义).
后来业务扩展很多程序员也需要使用python写代码,发现好用(调包侠 褒义).
python为什么好用?
因为支持python的模块非常多还很全面.
作为python程序员将来接收到业务需求时不要一上来就想自己写,先看看有没有相应的模块已经实现可以调用.
3.模块的表现形式
- py文件
- 含有多个py文件的文件夹
- 已被编译为共享库或DLL的c或c++扩展
- 使用c编写并链接到python解释器的内置模块
二、模块的三大分类
1.内置模块
python解释器中自带的可以直接使用的模块 eg:import time等
2.自定义模块
自己写的模块 eg:登录、注册
3.第三方模块
别人写好的
模块文件 在网上下载
三、导入模块的两种句式
注意:
1.项目中所有py文件名都应该是英文 没有中文和数字
2.导入py文件不需要写后缀
3.同一个py文件中反复导入相同的模块 导入的语句只会执行一次
1.import...句式
- import time
- 1.先产生执行文件的名称空间
- 2.执行模块文件的代码 产生模块名空间2
- 3.在执行文件的名称空间中产生一个模块文件的文件名 通过点的方式就可以使用该模块名称空间中的名字
复制代码- 【a.py】:
- name = 'jason'
- ——————————————————————————————————
- 【md.py】:
- import a
- print(a.name) #执行文件中用a.name即可使用。函数等同理
- #结果为:jason
复制代码 2.from...import...句式
- from a import name
- 1.先产生执行文键的名称空间
- 2.执行模块文件的代码,产生模块名称空间
- 3.在执行文件的名称空间中产生对应的名字绑定模块名称空间中对应的名字,通过点的方式就可以使用该模块名称空间中的名字
复制代码- 【a.py】:
- name = 'jason'
- ——————————————————————————————————
- 【md.py】:
- from a import name
- print(name) #执行文件中可直接使用name。函数等同理
- #结果为:jason
复制代码 import...句式from..import..句式优点可以通过模块名点的方式使用到模块内所有的名字 且不会冲突指名道姓的使用需要的名字 直接就可以使用该名字 不需要模块名去点缺点由于模块名什么都可以点 有时候不想让所有的名字都能够被使用容易与执行文件中的名字产生冲突四、导入模块的句式补充
1.起别名
当两个模块文件中都有相同的变量名name时 在执行文件中打印name 结果就是当前执行文件中的name 如果想要使用被导入文件中的name 就需要as 变量名 给她起一个别名 避免冲突- 【a.py】:
- name = 'jason'
- ——————————————————————————————————
- 【md.py】:
- from a import name
- name = 'torry'
- print(name) #此处打印的name是当前执行文件中的name
- #结果为:torry
- from a import name as n #取别名
- print(n)
- #结果为:jason
复制代码 2.导入多个模块
当有多个模块功能相似时可以在一起导入 不相似的尽量分开导入- import a,b,c
- from a import login,register
- ————————————————————————————————
- import a
- import b
- from a import name
- from a import login
复制代码 3.全导入(仅针对from...import...句式)
- 需要使用模块中多个名字时可以用*号的方式
- 【a.py】:
- name = 'jason'
- age = 18
- job = 'teacher'
- ——————————————————————————————————
- 【md.py】:
- from a import *
- print(name,age,job) #可以使用a模块文件中所有的名字
复制代码 - 针对*号可以用__all__=[变量名]控制可以使用的变量名
- 【a.py】:
- name = 'jason'
- age = 18
- job = 'teacher'
- __all__ = [name,age]
- ——————————————————————————————————
- 【md.py】:
- from a import *
- print(name) # 结果为:jason
- print(job) # 报错
复制代码 五、循环导入问题及解决策略
1.什么是循环导入
循环导入就是两个文件彼此导入彼此 且互相使用对象名称空间中的名字
2.如何解决循环导入问题
- 确保在使用各自名字前就把名字定义好
- 尽量避免写循环导入的代码
六、判断文件类型
所有py文件都可以直接打印__name__判断文件类型- 当文件是'执行文件'时:
- print(__name__)
- #结果为:__main__
- 当文件是'被导入文件'时:
- print(__name__)
- #结果为:被导入的文件名
复制代码 一般__name__主要是用于测试自己代码时使用:当文件是执行文件时才往下走子代码- if __name__=='__main__'
- print('当文件是执行文件时才会执行if的子代码')
-
- #上述脚本一般只出现在整个程序的启动文件中
复制代码 七、模块的查找顺序
- 1.先去内存中查找
- #验证:在执行文件中执行被导入文件中的函数,在执行过程中删除被导入文件发现还可以使用
- import md #导入执行md模块
- import time #导入执行time模块
- time.sleep(15) #让程序暂停15s再执行
- '中途如果把md模块文件删掉 还会继续执行,因为删除的是硬盘中的,内存中的还在'
- print(name)#结果为:jason
- ____________________________________________
- 2.再去内置中查找
- #验证:创建一个和内置模块相同的模块文件名
- 【time.py】:
- name = 'jason'
-
- 【执行文件.py】:
- from time import name
- print(name)#结果会报错
- import time
- print(name)#结果会报错
- _____________________________________________
- 3.然后去执行文件所在的sys.path中查找 # 不是系统环境变量
- 需注意'所有路径都是参照执行文件所在的路径去查找'
- #验证:如果执行文件和被执行文件在不同文件夹下,则要把模块所在的文件路径添加到执行文件的sys.path中
- import sys
- # print(sys.path)#执行sys.path结果为当前执行文件的环境变量,以列表显示
- sys.path.append(r'D:\pythonProject1\day17\aa')#将被导入文件的路径添加
- import md3
- print(md3.name)#此时打印md3中的name就可以找到 不添加路径则找不到
复制代码 八、模块的绝对导入与相对导入
只要涉及到模块导入就以当前执行文件路径为准(站在执行文件路径的视角去看)
解决方式:
1.把bb文件夹路径添加到执行文件sys.path路径中
2.在md1.py文件中把import md2 改为 from bb import md2('这就是绝对导入')
1.绝对导入
- 就是以执行文件所在的sys.path为起始位置一层一层查找
- 当涉及文件嵌套多个文件名时 则要在文件名后加点和内层文件名
2.相对导入
- #点号在相对导入中的作用
- . 在路径中表示当前目录
- .. 在路径中表示上一层目录
- ..\.. 在路径中表示上上一层目录
复制代码
- 相对导入可以不参考执行文件所在的路径 直接以当前模块文件路径为准
- 缺陷:
- 只能在被导入模块文件中使用 不能在执行文件中使用
- 当项目较复杂时容易报错 尽量不用
九、包
1.包是什么
专业角度:内部含有__init__.py文件的文件夹就叫包
大白话:文件内有多个py文件
2.不同解释器如何理解包
python3中:文件夹里有没有__init__.py文件都无所谓 都叫包
python2中:文件夹里必须有__init__.py文件才叫包
3.包的具体使用
虽然python3解释器对包的要求降低了 不需要__init__.py就可以识别 但是为了兼容考虑建议不管什么版本解释器都加上- 1.如果想只用包里的某几个模块
- from aa import md1,md2
- print(md1.name)#可以使用md1中的name
- 2.如果直接导包名则就是包下面'__init__.py文件',该文件中有什么名字就可以通过包名点什么名字
- import aa
- print(aa.name)#可以使用aa包里__init__.py里的名字
复制代码 十、编程思想的转变
阶段一:面条版本(所有代码从上往下依次执行)
阶段二:函数版本(将代码按照功能不同封装成不同函数)
阶段三:模块版本(根据功能不同拆分到不同的py文件)- 1.阶段一可以理解为将所有文件全部存储到c盘且不分类
- eg:'系统、视频、图片'
- 2.阶段二可以理解为将文件分类
- eg:'系统文件、视频文件、图片文件'
- 3.阶段三可以理解为按照不同功能放在不同的盘里
- eg:'C盘放系统文件、D盘放视频文件、E盘放图片文件'
- #这样做就是为了资源的【高效管理】
复制代码 十一、软件开发目录规范
- 1.bin文件夹 'start.py'
- 启动文件
- 2.conf文件 'settings.py'
- 配置文件
- 3.core文件夹 'src.py'
- 核心逻辑
- 4.interface '接口文件'
- 接口文件
- 5.lib文件 'common.py'
- 公共文件
- 6.db文件 'db_hardle'
- 存储信息
- 7.log文件 'log.log'
- 日志文件
- 8.readme.txt文件
- 使用说明书
- 9.requirements.txt文件
- 第三方模块
复制代码 十二、python常用内置模块
1.collection模块
除了基本数据类型(list、dict、tuple、set)collection模块还提供了额外的数据类型:nametuple、deque、orderdict、counter
- nametuple 具名元组
就是可以给一个元组命名 通过名字可以访问到内部自定义的属性- # 表示二维坐标系
- from collections import namedtuple
- zuobiao = namedtuple('二维坐标', ['x', 'y'])
- p1 = zuobiao(1, 2)
- print(p1) # 二维坐标(x=1, y=2)
- print(p1.x) # 1
- print(p1.y) # 2
复制代码- #可以用来做简单扑克牌
- from collections import namedtuple
- puke = namedtuple('扑克牌', ['花色', '点数'])
- res1 = puke('♥', 'A')
- print(res1) # 扑克牌(花色='♥', 点数='A')
复制代码 - deque 双端队列
队列:先进先出 后进后出
堆栈:先进后出 后进先出
队列和堆栈都是:一边只能进 另一边只能出
pop()尾部弹出 popleft()首部弹出
append()尾部追加 appendleft()首部追加
列表里没有首部弹出和首部追加- from collections import deque
- q = deque([1, 2, 3])
- q.append(4) # 尾部追加4
- q.appendleft(0) # 首部追加0
- print(q) # deque([0, 1, 2, 3, 4])
- print(q.pop()) # 4 【队列:取出最后面的】
- print(q.popleft()) # 0 【堆栈:取出最前面的】
复制代码 - OrderedDict 有序字典
使用字典时K键是无序的,当想对字典做迭代时没办法保证K键的顺序。
如果想让字典有顺序则可以用有序字典
补充了解:3.6版本解释器后字典都变成按照插入顺序来的了。- d=dict()
- d['name']='jason'
- d['age']=18
- d['job']='teacher'
- for i in d:
- print(i)
- ______________________________
- from collections import OrderedDict
- d=OrderedDict()
- d['name']='jason'
- d['age']=18
- d['job']='teacher'
- for i in d:
- print(i)
复制代码 - defaultdict 默认字典
普通洗点打印没有的键会报错 defaultdict则会返回空- d1 = dict()
- from collections import defaultdict
- d2 = defaultdict(list)
- print(d1['a']) # 报错
- print(d2['a']) # []
复制代码 - Counter 计数器
主要用来统计出现的次数- #方式一:
- from collections import Counter
- c=Counter('aabbbccc')
- print(c)
- #结果为:Counter({'b':3,'c':3,'a':2})
复制代码- #方式二:
- s1 = 'aabbbccc'
- new_dict = {}
- for i in s1:
- if i not in new_dict:
- new_dict[i] = 1
- else:
- new_dict[i] += 1
- print(new_dict)
- #结果为:{'a':2,'b':3,'c':3}
复制代码 2.time与datatime时间模块
- time 时间没模块
- 1.时间戳 从1970年1月1日0时0分0秒到此时此刻的秒数
- 2.结构化时间 给计算机看的
- 3.格式化时间 给人看的
- time.sleep(3) 让程序停止运行几秒
复制代码
- 时间戳
- #1.时间戳
- import time
- print(time.time())
- #结果为:1666174527.7080512
- """
- 一般用于在某个代码前后加,用来得出该代码执行时间
- """
复制代码 - 结构化时间 localtime()
- #2.结构化时间
- import time
- print(time.localtime())
- #结果为:查看本地时间
- print(time.gmtime())
- #结果为:查看UTC时间 英国伦敦
复制代码 - 格式化时间 strftime()
- #3.格式化时间
- import time
- print(time.strftime('%Y-%m-%d %H:%M:%S'))
- #结果为:2022-10-19 18:25:23
- print(time.strftime('%Y-%m-%d'))
- #结果为:2022-10-19
- """
- %Y 年 | %m 月 | %d 日
- %H 时 | %M 分 | %S 秒
- """
复制代码
- datetime 时间模块
- datetime 年月日时分秒
- data 年月日
- time 时分秒
复制代码- #获取今天【年月日 时分秒】
- import datetime
- res = datetime.datetime.today()
- print(res) # 结果为:2022-10-19 18:48:04.737998
- """
- res.year 年
- res.month 月
- res.isoweekday() 星期几
- """
- ——————————————————————————————————————
- #获取今天【年月日】
- import datetime
- res1 = datetime.date.today()
- print(res1) # 结果为:2022-10-19
复制代码 补充:- #时间间隔
- t1 = datetime.date.today() # 获取当前年月日
- print(t1) # 2022-10-19
- t2 = datetime.timedelta(days=3) # 定义时间间隔为3天
- print(t1 + t2) # 2022-10-22
- #指定日期
- c = datetime.datetime(2022,5,23,12)
- print(c)
- #结果为:2022-05-23 12:00:00
复制代码 3.randon随机数模块
- import random
- """随机打印数字"""
- print(random.random())#产生一个从0~1的小数
- print(random.randint(1,5))#产生一个从1~5的小数
- print(random.randrange(1,10,2))#产生一个从1~10的奇数
- """从数据集中随机打印数据"""
- print(random.choice([1,2,3]))#随机打印一个列表中的数据值
- print(random.choices([1,2,3]))#随机打印一个列表中的数据值并组成列表
- print(random.sample([1,2,3],2))#随机打印两个列表中的数据值并组成列表
- """打乱数据集顺序"""
- l1=[1,2,3,4,5]
- random.shuffle(l1)#把列表中的数据随机打乱顺序
- print(l1)
复制代码- def func(n):
- yzm=''
- for i in range(n):
- #1.先产生随机的大小写字母、数字
- random_upper=chr(random.randint(65,90))
- random_lower=chr(random.randint(97,122))
- random_int=str(random.randint(0,9))#字符串不能和整数相加,所以转换成字符串
- #2.把随机生成的字符三选一
- temp=random.choice([random_upper,random_lower,random_int])
- yzm+=temp
- return yzm
- res=func(4)
- print(res)
复制代码 4.os模块
import os
os 模块主要是当前程序与所在的操作系统打交道
- 创建目录
- 在执行文件所在的路径下创建目录
- mkedirs()创建目录
- 1.创建多级目录
- os.makedirs(r'xxx') #单级目录
- os.makedirs(r'xxx\yyy')#多级目录
- 2.创建单级目录
- os.mkdir(r'xxx')
复制代码 - 删除目录
- 1.os.rmdir(r'xxx') #删除空的单级目录 目录下不能有任何文件
- 2.os.removedirs(r'xxx\yyy') #删除空的多级目录 从内到外删除 直到某目录下有其他文件为止
复制代码 - 列举指定路径下所有文件、目录名(结果会以列表的形式展示)
- print(os.listdir(r'xxx\yyy'))
- #列举路径下所有文件、目录名 不写路径则是执行文件路径下所有文件、目录名
复制代码 - 重命名文件
- os.rename(r'xxx.py',r'yyy.py') #给某文件重命名
- os.rename(r'zzz',r'aaa') #给某目录重命名
复制代码 - 删除文件
- os.remove(r'xxx.py') #删除某文件
复制代码 - 获取、切换当前工作路径
- print(getcwd()) #获取当前工作路径 D:\pythonProject1\day19
- os.chdir('..') #切换到上一级目录
- print(getcwd()) #获取当前工作路径 D:\pythonProject
复制代码 - 动态获取项目路径
- 1.动态获取 项目根路径
- print(os.path.dirname(__file__)) #D:/pythonProject1/day19
- 2.动态获取项目 根路径的上一级路径
- print(os.path.dirname(os.path.dirname(__file__)))
- #D:/pythonProject1
- 3.动态获取执行文件的绝对路径
- print(os.path.abspath(__file__))
- #D:\pythonProject1\day19\run.py
复制代码 - 判断路径是否存在
- exists() 判断路径是否存在
- isdir() 判断路径是否是目录
- isfile() 判断路径是否是文件
- '结果为布尔值'
- 1.判断路径是否存在
- print(os.path.exists(r'路径')) #可以是详细的文件夹或py文件
- 2.判断路径是否是目录
- print(os.path.isdir(r'路径'))
- 3.判断路径是否是文件
- print(os.path.isfile(r'路径'))
复制代码 - 拼接路径
join()- a=r'D:\aa'
- b=r'a.txt'
- new_path=os.path.join(a,b) #将a和b拼接
- print(new_path) # D:\aa\a.txt
复制代码 - 获取文件大小
大小单位是:bytes字节- print(os.path.getsize(r'a.txty'))
复制代码 5.sys模块
import sys
sys模块主要是当前程序与python解释器打交道- (1)获取执行文件的环境变量
- print(sys.path)
- (2)获取最大递归深度 与 修改最大递归深度
- print(sys.getrecursionlimit()) # 1000
- sys.setrecursionlimit(2000) # 修改解释器最大递归深度
- print(sys.getrecursionlimit()) # 2000
- (3)获取当前解释器版本信息
- print(sys.version)
- #3.8.6 (tags/v3.8.6:db45529, Sep 23 2020, 15:52:53) [MSC v.1927 64 bit (AMD64)]
- (4)获取当前平台信息
- print(sys.platform) # win32
- (5)实现从程序外向程序内传递参数
- sys.argv #如下图
复制代码
6.json模块
- json模块也被称为:序列化模块 可以让不同的编程语言之间交互
- 系列化:将字典、列表等内容转换成一个字符串的过程就叫序列化
- json格式数据属于什么类型:由于数据基于网络传输只能用二进制 python中只有字符串可以调用encode编码转二进制 所以json格式的数据也属于字符串
- json格式数据特征:字符串且引号是双引号
- #针对数据
- json.dumps() 将其他数据类型转成json格式字符串
- json.loads() 将json格式字符串转换成对应数据类型
- #针对文件
- json.dump() 将其他数据类型以json格式字符串写入文件
- json.load() 将文件中json格式字符串读取出来并转换成对应的数据类型
复制代码 7.正则表达式
正则表达式是一门独立的技术 所有编程语言都可以使用
- 正则表达式含义:
- 正则表达式就是用一些特殊符号的组合产生特殊含义去字符串中筛选符合条件的数据 也可以直接写需要查找的具体字符
- 主要用于 筛选 匹配数据
- 正则表达式线上测试网站:http://tool.chinaz.com/regex/
复制代码 - 正则表达式前戏:注册手机号校验
案例:京东注册手机号校验 需求:手机号必须是11位 手机号必须以13、14、15、17、18、19开头 却必须是纯数字- """纯python代码实现"""
- while True:
- phone_num = input('输入您的手机号:').strip()
- if len(phone_num) == 11:
- if phone_num.isdigit():
- if phone_num.startswith('13') or phone_num.startswith('14') or phone_num.startswith(
- '15') or phone_num.startswith('17') or phone_num.startswith('18') or phone_num.startswith('19'):
- print('手机号码输入合法')
- else:
- print('手机号码格式错误')
- else:
- print('手机号码必须是纯数字')
- else:
- print('手机号码必须11位')
-
- ————————————————————————————————————————————
- """使用正则表达式实现"""
- import re
- while True:
- phone_num=input('输入您的手机号:').strip()
- if re.match('^(13|14|15|17|18|19)[0-9]{9}$',phone_num):
- print('手机号合法')
- else:
- print('手机号不合法')
复制代码 - 正则表达式 -- 字符组
- 1.字符组默认匹配方式是:一个一个的匹配(一个符号一次匹配一个内容)
- 2.字符组内所有的数据默认都是或的关系
字符组简写字符组全称含义[0-9][0123456789]匹配0-9任意一个数字[A-Z][ABCDE...Z]匹配A-Z任意一个字母[a-z][abcde...z]匹配a-z任意一个字母[0-9a-zA-Z]匹配0-9、a-z、A-Z任意一个字母或数字
- 正则表达式 -- 特殊符号
- 1.字符组默认匹配方式是:一个一个的匹配
特殊符号含义.匹配除换行符外的任意字符\w匹配数字、字母、下划线\W匹配非数字、非字母、非下划线\d匹配数字^匹配字符串的开头$匹配字符串的结尾^数据$两者使用可以精确限制匹配的内容a|b匹配a或b()给正则表达式分组 不影响表达式匹配[]字符组内部填写的内容默认都是获得关系[^]取反操作 匹配除了字符组内填写的其他字符
- 正则表达式 -- 量词
- 正则表达式默认情况下都是贪婪匹配(尽可能多的匹配)
- 量词不能单独使用 必须结合表达式一起 且只能影响左边第一个表达式
量词含义*****匹配0次或者多次 默认是多次+匹配1次或者多次 默认是多次?匹配0次或者是1次 默认是1次重复n次 写几次就是几次重复n次或者更多次 默认是多次重复n到m次 默认是m次
- 正则表达式练习题
- #正则表达式 待匹配字符 结果
- 海. 海燕海娇海东 海燕 海娇 海东
- ^海. 海燕海娇海东 海燕
- 海.$ 海燕海娇海东 海东
- 李.? 李杰和李莲英和李二棍子 李杰 李莲 李二
- 李.* 李杰和李莲英和李二棍子 李杰和李莲英和李二棍子
- 李.+ 李杰和李莲英和李二棍子 李杰和李莲英和李二棍子
- 李.{1,2} 李杰和李莲英和李二棍子 李杰和 李莲英 李二棍
- 李[杰莲英二棍子]* 李杰和李莲英和李二棍子 李杰 李莲英 李二棍子
- 李[^和]* 李杰和李莲英和李二棍子 李杰 李莲英 李二棍子
- [\d] 456bdha3 4 5 6 3
- [\d]+ 456bdha3 456 3
复制代码
- 贪婪匹配与非贪婪匹配
- 1.所有的量词匹配的都是贪婪匹配 非贪婪匹配要在后面加?
- 2.贪婪匹配与非贪婪匹配结束是由左右两边添加的表达式决定的
- 待匹配的文本:
-
- 正则:
- <.*> # 贪婪匹配
- 结果:
-
- ————————————————————————————————————————
- 待匹配的文本:
-
- 正则:
- <.*?> # 非贪婪匹配
- 结果:
-
复制代码
- 转义符
- """斜杠与字母的组合有时候有特殊含义"""
- \n 匹配的是换行符
- \\n 匹配的是文本\n
- \\\\n 匹配的是文本\\n
- #在python中 可以在字符串前加r取消转义
复制代码 - 正则表达式实战建议
- 1.编写校验用户身份证号的正则
- \d{17}[\d|x]|\d{15}
- 2.编写校验邮箱的正则
- \w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}
- 3.编写校验用户手机号的正则
- 0?(13|14|15|17|18|19)[0-9]{9}
- 4.编写校验用户电话号的正则
- [0-9-()()]{7,18}
- 5.编写校验用户qq号的正则
- [1-9]([0-9]{5,11})
- #很多时候有的正则已经有人帮我们做好了,只需要百度查到即可
复制代码 8.re模块
在python中使用正则 re 模块是选择之一
- re模块基本使用
- 1.findall 查找所有符合正则表达式要求的数据 结果是一个列表
- # 在文本中筛选出符合a的所有内容,结果为列表
- import re
- res=re.findall('a','abcabca')
- print(res) # ['a', 'a', 'a']
复制代码 - 2.finditer 查找所有符合正则表达式要求的数据 结果直接是一个迭代器对象
- import re
- res=re.finditer('a','abcabca')
- print(res) # <callable_iterator object at 0x000001FFA8AD7220>
- print(res.__next__()) # <re.Match object; span=(0, 1), match='a'>
- print(res.__next__().group()) # a
复制代码 - 3.search 匹配到一个符合条件的数据就立刻停止
- import re
- res=re.search('a','abcabca')
- print(res) # <re.Match object; span=(0, 1), match='a'>
- print(res.group()) #a
复制代码 - 4.match 从头开始匹配 如果头不符合就结束
- import re
- res=re.match('a','abcabca')
- print(res) # <re.Match object; span=(0, 1), match='a'>
- res1=re.match('b','abcabca')
- print(res1) # None
复制代码 - 5.compile 提前准备好正则 后续可以反复使用减少代码冗余
- import re
- obj=re.compile('a')
- print(re.findall(obj,'abcabca')) # ['a', 'a', 'a']
- print(re.findall(obj,'asssdaada')) # ['a', 'a', 'a', 'a']
- print(re.findall(obj,'ddffee123a')) # ['a']
复制代码 - 6.split 分割
- 1.按照a分割得到''和'bcd'
- res = re.split('[a]','abcd')
- print(res) # ['', 'bcd']
- #res后可以跟索引,索引0为空(不是None),索引1为bcd
- 2.按照a分割得到''和'bcd',再对''和'bcd'按照b分割得到''和'cd'
- res = re.split('[a,b]','abcd')
- print(res) # ['', '', 'cd']
复制代码 - 7.sub 替换
- 1.把数字全部替换成'H'
- res = re.sub('\d','H','abc123')
- print(res) # abcHHH
- 2.把某个数字替换成'H'
- res = re.sub('1','H','abc123123')
- print(res) # abcH23H23
复制代码 - 8.subn 替换
- 1.把数字替换成'H',并返回元组#(替换的结果,替换的次数)
- res = re.subn('\d','H','abc1231')
- print(res) # ('abcHHHH', 4)
复制代码
- re模块补充使用
- 1.分组优先
- findall分组优先展示:优先展示括号内正则表达式匹配到的内容
- res=re.findall('www.(baidu|4399).com','www.4399.com')
- print(res) # ['4399']
复制代码 - 取消分组优先展示(?:)
- res=re.findall('www.(?:baidu|4399).com','www.4399.com')
- print(res) # ['www.4399.com']
复制代码 - search和match针对分组()里的正则表达式不影响
- res=re.search('www.(baidu|4399).com','www.4399.com')
- print(res.group()) # www.4399.com
- res=re.match('www.(baidu|4399).com','www.4399.com')
- print(res.group()) # www.4399.com
复制代码
- 2.分组别名
- res=re.search('www.(?P<mingzi1>baidu|4399)(?P<mingzi2>.com)','www.4399.com')
- print(res) # <re.Match object; span=(0, 12), match='www.4399.com'>
- print(res.group()) # www.4399.com
- print(res.group(0)) # www.4399.com
- print(res.group(1)) # 4399
- print(res.group(2)) # .com
- print(res.group('mingzi1')) # 4399
- print(res.group('mingzi2')) # .com
复制代码
十三、网络爬虫
1.什么是互联网?
将全世界的计算机连接到一起组成的网络
2.互联网发明的目的是什么?
让连接到互联网的计算机数据彼此共享
3.上网的本质是什么?
基于互联网访问其他人计算机上共享数据
4.爬虫的本质是什么?
通过编写代码模拟计算机浏览器朝目标网站发送请求获取数据并筛选出想要的数据
5.有的网页存在防爬机制 数据无法直接拷贝获取(页面会校验是页面发送的请求还是代码发送的)
6.在做爬虫时有可能会被发现导致ip短暂拉黑 不要爬用户数据!
十四、第三方模块
第三方模块简介
第三方模块就是别人写好的模块 一般功能很强大
想用第三方模块必须要下载
第三方模块的下载
- cmd命令行下载
1.下载必须借助pip工具(每个解释器都有)如果电脑里面有多个版本的解释器 要给pip加上版本号 以便区分
python27 pip2.7
python36 pip3.6
python38 pip3.8
2.下载第三方模块的句式- pip install 模块名
- pip3.8 install 模块名
复制代码 3.下载第三方模块临时切换镜像源地址- pip install 模块名 -i 镜像源地址
- pip3.8 install 模块名 -i 镜像源地址
复制代码 4.下载第三方模块指定版本- pip install 模块名==版本号 -i 镜像源地址
- pip3.8 install 模块名==版本号 -i 镜像源地址
复制代码 - pycharm提供快捷下载方式
第三方模块下载报错
- (1)报错并有警告信息
- eg:
- WARNING: You are using pip version 20.2.1;
- #这种是因为pip工具版本过低,拷贝后面的执行命令更新即可:
- python38 -m pip install --upgrade pip
- ——————————————————————————————————————————————
- (2)报错并有Timeout关键字
- #说明当前计算机网络不稳定 只需要换网或者重新执行几次即可
- ——————————————————————————————————————————————
- (3)报错并没有关键字
- #有可能是需要配置特定环境自行百度即可
- ——————————————————————————————————————————————
- (4)下载速度慢
- #pip默认下载的镜像源地址是国外的地址可切换以下地址。
- """
- 清华大学 :https://pypi.tuna.tsinghua.edu.cn/simple/
- 阿里云:http://mirrors.aliyun.com/pypi/simple/
- 中国科学技术大学 :http://pypi.mirrors.ustc.edu.cn/simple/
- 华中科技大学:http://pypi.hustunique.com/
- 豆瓣源:http://pypi.douban.com/simple/
- 腾讯源:http://mirrors.cloud.tencent.com/pypi/simple
- 华为镜像源:https://repo.huaweicloud.com/repository/pypi/simple/
- """
复制代码 1.request模块(网络爬虫模块)
request模块看可以模拟浏览器发送网络请求
超指定网址发送请求获取页面数据(等同于浏览器地址栏输入网址按回车访问)- import request
- res = requests.get('http://www.redbull.com.cn/about/branch')
- # print(res.content) # 获取bytes二进制类型的网页数据
- print(res.text) # 获取字符串类型的网页数据
复制代码 2.网络爬虫实战
爬红牛分公司数据
http://www.redbull.com.cn/about/branch- import re
- import requests
- # 1.朝目标地址发送网络请求
- # res = requests.get('http://www.redbull.com.cn/about/branch')
- # print(res.content)#获取二进制类型数据
- # print(res.text)#获取文本类型数据
- # 2.将二进制数据保存在html页面中
- # with open(r'redbull.html', 'wb')as f:
- # f.write(res.content)
- # 3.获取页面字符串数据
- with open(r'redbull.html', 'r', encoding='utf8')as f:
- data = f.read()
- # 4.用正则筛选出需要的数据
- comp_name_list = re.findall('<h2>(.*?)</h2>', data)
- comp_address_list = re.findall("<p class='mapIco'>(.*?)</p>", data)
- comp_email_list = re.findall("<p class='mailIco'>(.*?)</p>", data)
- comp_phone_list = re.findall("<p class='telIco'>(.*?)</p>", data)
- # 5.导入pandas模块将数据保存在excel表格中
- import pandas
- d1 = {
- '公司名称': comp_name_list,
- '公司地址': comp_address_list,
- '公司邮编': comp_email_list,
- '公司电话': comp_phone_list
- }
- df = pandas.DataFrame(d1) # 将字典转成pandas里面的DataFrame数据结构
- df.to_excel('readbull.xlsx') # 保存为excel文件
复制代码
爬链家二手房数据
https://sh.lianjia.com/ershoufang/pudong/- import re
- import requests
- # 1.朝目标地址发送网络请求
- res = requests.get('https://sh.lianjia.com/ershoufang/pudong/')
- data = res.text
- ljhome_title_list = re.findall('<a href="https://www.cnblogs.com/.*?" target="_blank" data-log_index=".*?" data-el="ershoufang" data-housecode=".*?" data-is_focus="" data-sl="">(.*?)</a>',data)
- ljhome_name_list = re.findall('<a href="https://www.cnblogs.com/.*?" target="_blank" data-log_index=".*?" data-el="region">(.*?) </a>', data)
- ljhome_street_list = re.findall('<a href="https://www.cnblogs.com/.*?" target="_blank" data-log_index=".*?" data-el="region">.*? </a> - <a href="https://www.cnblogs.com/.*?" target="_blank">(.*?)</a> ',data)
- ljhome_info_list = re.findall('(.*?)', data)
- ljhome_watch_list = re.findall('(.*?)', data)
- ljhome_total_price_list = re.findall('<i> </i>(.*?)<i>万</i>', data)
- ljhome_unit_price_list = re.findall('(.*?)', data)
- import pandas
- d1 = {
- '房屋标题': ljhome_title_list,
- '小区名称': ljhome_name_list,
- '街道名称': ljhome_street_list,
- '详细信息': ljhome_info_list,
- '关注程度': ljhome_watch_list,
- '房屋总价/万': ljhome_total_price_list,
- '房屋单价': ljhome_unit_price_list,
- }
- df = pandas.DataFrame(d1)
- df.to_excel('ljhome.xlsx')
复制代码
3.openpyxl模块(自动办公)
主要用于操作excel表格 也是pandas模块底层操作表格的模块- 1.excel文件的后缀问题
- excel2003版本之前后缀名为:**.xls**
- excel2003版本之后后缀名为:**.xlsx**
- 2.可以操作excel表格的第三方模块
- **openpyxl**是近几年比较火的操作excel表格模块
- 但是针对03版本前的excel文件兼容较差
- **xlwt**(往表格中写数据)、**xlrd**(从表格中读数据)
- 兼容所有版本的excel文件,但是使用方法没openpyxl简单
- 还有很多可以操作excel表格的模块:如**pandas**涵盖了上述模块的模块
- 3.创建文件、写入数据、保存数据操作
- 当该excel文件打开时不能任何修改 不要忘记保存文件
复制代码 1.创建excel文件- from openpyxl import Workbook # 导入模块,Workbook是用来创建文件的
- # 创建一个excel文件
- wb = Workbook()
- # 在excel文件中创建工作薄
- wb1 = wb.create_sheet('学生名单')
- # 在excel文件中创建工作薄 并 让该工作薄位置在最前面
- wb2 = wb.create_sheet('老师名单', 0)
- # 修改工作簿名称
- wb2.title = '学生成绩单'
- # 修改工作薄颜色
- wb2.sheet_properties.tabColor = '1072BA'
- """
- 这里放写入数据的操作
- """
- # 保存该excel文件
- wb.save(r'学生信息.xlsx')
复制代码
2.在excel工作簿中写入数据
写入后记得保存- 1)第一种写入方式:
- wb2['A1'] = '张三' # 在A1单元格中写入'张三'
- 2)第二种写入方式:
- wb2.cell(row=2,column=1,value='李四') # 在单元格第2行,第1列,写入'李四'
- 3)第三种写入方式:(批量写入)
- #在单元格中最上方分别写入数据(如果有数据则在数据下一行写入)
- wb2.append(['姓名','年龄','成绩'])
- wb2.append(['王五','18','90'])
复制代码
3.填写数学公式- wb2['A5']='=sum(A1:A4)' # 在A5单元格写入sum公式
- wb2.cell(row=5,column=1,value='=sum(A1:A4)') # 在第5行,第1列写入sum公式
复制代码 4.pandas 模块也可以是实现数据写入
使用pandas模块时需要注意字典里面的v值必须是列表的形式- import pandas
- company_name='腾讯'
- company_address='深圳'
- company_email='123@qq.com'
- data={
- '公司名称':[company_name,],
- '公司地址':[company_address,],
- '公司邮编':[company_email,],
- }
- df = pandas.DataFrame(data)#将字典转成pandas里面的DataFrame数据结构
- df.to_excel('a.xlsx')#保存为excel文件
复制代码
4.读取数据操作
openpyxl时读写分离的(写与读是两个不同的模块 需要在后面在加一个load_workbook)- """
- openpyxl不擅长读数据,所以有些模块优化了读取的方式:pandas模块
- 一般在公司会有专门的人负责读,python程序员只负责写出来
- """
复制代码- #【创建写入数据】
- from openpyxl import Workbook,load_workbook
- # 创建一个excel文件
- wb=Workbook()
- # 创建sheet页
- wb1=wb.create_sheet('第一个sheet页',0)
- wb2=wb.create_sheet('第二个sheet页',1)
- # 批量写入数据
- wb1.append(['姓名','年龄'])
- wb1.append(['jason','18'])
- wb1.append(['torry','20'])
- # 保存文件
- wb.save('ipenpyxl写读练习.xlsx')
- ___________________________________________________________
- #【读取数据】
- # 1.查看文件中所有工作簿名称
- print(wb.sheetnames) # ['第一个sheet页', '第二个sheet页', 'Sheet']
- # 2.1.查看某工作簿中有几行数据,空数据默认为1行
- print(wb1.max_row) # 3
- # 2.2查看某工作簿中有几列数据,空数据默认为1列
- print(wb1.max_column) # 2
- # 3.1.读取wb1中A1单元格的数据
- print(wb1['A1'].value) # 姓名
- # 3.2.读取wb1中第2行第1列的数据
- print(wb1.cell(row=2,column=1).value) # jason
- # 4.读取整行数据并组成列表
- for i in wb1.rows:
- print([j.value for j in i]) # ['姓名', '年龄'] ['jason', '18'] ['torry', '20']
- # 5.读取整列数据并组成列表
- for i in wb1.columns:
- print([j.value for j in i]) # ['姓名', 'jason', 'torry'] ['年龄', '18', '20']
复制代码 4.pandas模块
封装了openyxl模块的模块 主要也是操作表格的 如上面的爬虫爬渠道的数据后用pandas保存到excel表中- import pandas
- d1 = {
- '公司名称': ['腾讯', '飞讯', '跑讯'],
- '公司地址': ['上海', '杭州', '深圳'],
- }
- df = pandas.DataFrame(d1) # 将字典转成pandas里面的DataFrame数据结构
- df.to_excel(r'公司信息.xlsx') # 保存为excel文件
复制代码
5.hashilib加密模块
1.什么是加密?
将明文数据经过处理后变成密文数据的过程就是加密
2.为什么要加密?
不想让敏感数据轻易泄露
3.如何判断当前数据是否已加密?
一般加密的都是遗传没有规则的字母、数字、符号的组合
4.加密算法就是对铭文数据采用的加密策略
不同的加密算法复杂程度也不同 得出的密文长度也不同 一般密文越长说明就越复杂
5.常见的加密算法
md5、sha系列、hmac、base64
6.代码实操- import hashlib
- # 选择加密算法
- md5 = hashlib.md5()
- # 传入明文数据(传入的必须是二进制)
- md5.update(b'hello')
- # 获取加密密文
- res = md5.hexdigest()
- print(res) # 202cb962ac59075b964b07152d234b70
- ————————————————————————————————————————————————————
- import hashlib
- # 获取用户输入密码
- password = input('输入密码:').strip()
- # 选择加密算法
- md5 = hashlib.md5()
- # 传入明文数据(传入的必须是二进制)
- md5.update(password.encode('utf8'))
- # 获取加密密文
- res = md5.hexdigest()
- print(res)
复制代码 7.注意事项
1.相同内容不管分几次传结果都是一样的
加密算法不变 内容相同 结果肯定相同- import hashlib
- # 选择加密算法
- md5 = hashlib.md5()
- # 传入明文数据
- # md5.update(b'aa~bb~')
- md5.update(b'aa~')
- md5.update(b'bb~')
- # 获取加密密文
- res = md5.hexdigest()
- print(res)
- #发现只要是相同的明文,不管是一次性传入还是分多次传入结果都一样
复制代码 2.加密后的结果无法反向解密
只能从铭文到密文正向推导 不能从密文到铭文反向推导
常见的解密其实是提前预测了很多结果去一对一匹配
3.加盐处理
在明文中额外加一些干扰项- import hashlib
- md5 = hashlib.md5()
- md5.update('加盐'.encode('utf8')) # '加盐'为干扰项
- md5.update(b'123456')
- res = md5.hexdigest()
- print(res)
复制代码 4.动态加盐
干扰项可以随机变化的(当前时间、用户名)
5.加密实际应用场景- 1.用户加密
- 注册存储的是密文,'登录校验时也是在对比密文'
-
- 2.文件安全性内容加密校验
- 正规的软件程序写完都会做一个'内容加密',用户下载完软件后会'先对比加密后的密文是否一致',不一致可能被植入了病毒,一致则运行软件
- 3.大文件内容加密
- 当一个文件特别大时,一次性加密效率太低
- 所以会采用'截取一部分来加密'
- #os.path.getsize() 获取文件大小
复制代码 6.subprocess模块
模拟操作系统终端 执行系统命令并获取结果- import subprocess
- cmd = input('输入cmd指令:').strip()
- res = subprocess.Popen(
- cmd, # 获取用户要执行的指令
- shell=True, # 固定配置
- stdin=subprocess.PIPE, # 输入指令
- stdout=subprocess.PIPE, # 输出结果
- )
- # 获取操作系统执行命令后的正确结果
- print('正确结果:', res.stdout.read().decode('gbk'))
- # 获取操作系统执行命令后的错误结果
- print('错误结果:', res.stderr)
复制代码 7.loggin 日志模块
1.如何处理日志
简单的理解就是记录行为举止的操作
2.日志的五种级别- import logging
- logging.debug('debug等级') # 10 默认不显示
- logging.info('info等级') # 20 默认不显示
- logging.warning('警告的') # 30 默认从warning级别开始记录
- logging.error('已经发生的') # 40
- logging.critical('灾难性的') # 50
复制代码 3.日志模块的要求与基本使用
无需掌握 了解怎么用即可- import logging
- # 产生一个日志文件,文件叫x1.log,用a追加模式,编码为utf8
- file_handler = logging.FileHandler(filename='x1.log', mode='a', encoding='utf8',)
- logging.basicConfig(
- # 日志格式
- format='%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s',
- # 年月日 时分秒 上午下午
- datefmt='%Y-%m-%d %H:%M:%S %p',
- handlers=[file_handler,],
- # ERROR级别
- level=logging.ERROR)
- logging.error('你好')
复制代码 4.日志的四个组成部分
- logger对象:产生日志
- fifter对象:过滤日志
- handler对象:输出日志
- format对象:日志格式
- import logging
- # 1.日志的产生(准备原材料) logger对象
- logger = logging.getLogger('购物车记录')
- # 2.日志的过滤(剔除不良品) filter对象>>>:可以忽略 不用使用
- # 3.日志的产出(成品) handler对象
- # hd1~hd3三选一
- hd1 = logging.FileHandler('a1.log', encoding='utf-8') # 输出到a1.log文件中
- hd2 = logging.FileHandler('a2.log', encoding='utf-8') # 输出到a1.log文件中
- hd3 = logging.StreamHandler() # 输出到终端
- # 4.日志的格式(包装) formmat对象
- # fm1与fm2格式复杂度二选一即可
- fm1 = logging.Formatter(
- fmt='%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s',
- datefmt='%Y-%m-%d %H:%M:%S %p',
- )
- fm2 = logging.Formatter(
- fmt='%(asctime)s - %(name)s: %(message)s',
- datefmt='%Y-%m-%d',
- )
- # 5.给logger对象绑定handler对象
- logger.addHandler(hd1)
- logger.addHandler(hd2)
- logger.addHandler(hd3)
- # 6.给handler绑定formate对象
- hd1.setFormatter(fm1)
- hd2.setFormatter(fm2)
- hd3.setFormatter(fm1)
- # 7.设置日志等级
- logger.setLevel(10) # debug
- # 8.记录日志
- logger.debug('写了半天 好累啊 好热啊')
复制代码 5.日志配字典- import logging
- import logging.config
- # 定义日志输出格式 开始
- standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \
- '[%(levelname)s][%(message)s]' # 其中name为getlogger指定的名字
- simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
- # 自定义文件路径
- logfile_path = 'a3.log'
- LOGGING_DIC = {
- 'version': 1,
- 'disable_existing_loggers': False,
- 'formatters': {
- 'standard': {
- 'format': standard_format
- },
- 'simple': {
- 'format': simple_format
- },
- },
- 'filters': {}, # 过滤日志
- 'handlers': {
- # 打印到终端的日志
- 'console': {
- 'level': 'DEBUG',
- 'class': 'logging.StreamHandler', # 打印到屏幕
- 'formatter': 'simple'
- },
- # 打印到文件的日志,收集info及以上的日志
- 'default': {
- 'level': 'DEBUG',
- 'class': 'logging.handlers.RotatingFileHandler', # 保存到文件
- 'formatter': 'standard',
- 'filename': logfile_path, # 日志文件
- 'maxBytes': 1024 * 1024 * 5, # 日志大小 5M
- 'backupCount': 5,
- 'encoding': 'utf-8', # 日志文件的编码,再也不用担心中文log乱码了
- },
- },
- 'loggers': {
- # logging.getLogger(__name__)拿到的logger配置
- '': {
- 'handlers': ['default', 'console'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
- 'level': 'DEBUG',
- 'propagate': True, # 向上(更高level的logger)传递
- }, # 当键不存在的情况下 (key设为空字符串)默认都会使用该k:v配置
- # '购物车记录': {
- # 'handlers': ['default','console'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
- # 'level': 'WARNING',
- # 'propagate': True, # 向上(更高level的logger)传递
- # }, # 当键不存在的情况下 (key设为空字符串)默认都会使用该k:v配置
- },
- }
- logging.config.dictConfig(LOGGING_DIC) # 自动加载字典中的配置
- # logger1 = logging.getLogger('购物车记录')
- # logger1.warning('尊敬的VIP客户 晚上好 您又来啦')
- # logger1 = logging.getLogger('注册记录')
- # logger1.debug('jason注册成功')
- logger1 = logging.getLogger('红浪漫顾客消费记录')
- logger1.debug('慢男 猛男 骚男')
复制代码 6.日志模块实战应用- 1.#【start.py】 先找到根目录路径 添加到sys.path中(为了兼容让任何人打开都可以找到根目录)
- import os
- import sys
- base_dir=os.path.dirname(os.path.dirname(__file__))
- sys.path.append(base_dir)
- if __name__ == '__main__':
- from ATM.core import src
- src.run()
复制代码- 2.#【settings.py】 将日志代码写在配置文件中
- import os
- # 定义日志输出格式 开始
- standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \
- '[%(levelname)s][%(message)s]' # 其中name为getlogger指定的名字
- simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
- # 自定义文件路径
- #logfile_path = 'a3.log'
- BASE_DIR=os.path.dirname(os.path.dirname(__file__))
- LOG_DIR=os.path.join(BASE_DIR,'log')
- if not os.path.exists(LOG_DIR):
- os.makedirs(LOG_DIR)
- logfile_path=os.path.join(LOG_DIR,'log.log') # 【可以起一个日志名字.log】
- LOGGING_DIC = {
- 'version': 1,
- 'disable_existing_loggers': False,
- 'formatters': {
- 'standard': {
- 'format': standard_format
- },
- 'simple': {
- 'format': simple_format
- },
- },
- 'filters': {}, # 过滤日志
- 'handlers': {
- # 打印到终端的日志
- 'console': {
- 'level': 'DEBUG',
- 'class': 'logging.StreamHandler', # 打印到屏幕
- 'formatter': 'simple'
- },
- # 打印到文件的日志,收集info及以上的日志
- 'default': {
- 'level': 'DEBUG',
- 'class': 'logging.handlers.RotatingFileHandler', # 保存到文件
- 'formatter': 'standard',
- 'filename': logfile_path, # 日志文件
- 'maxBytes': 1024 * 1024 * 5, # 日志大小 5M
- 'backupCount': 5, # 最多保存5份5M的文件,个数够了就删第一个
- 'encoding': 'utf-8', # 日志文件的编码,再也不用担心中文log乱码了
- },
- },
- 'loggers': {
- # logging.getLogger(__name__)拿到的logger配置
- '': {
- 'handlers': ['default', 'console'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
- 'level': 'DEBUG',
- 'propagate': True, # 向上(更高level的logger)传递
- }, # 当键不存在的情况下 (key设为空字符串)默认都会使用该k:v配置
- },
- }
复制代码- 3.#【common.py】 公共功能文件中创建一个函数,谁用谁调
- import logging
- import logging.config
- from conf import settings
- def get_my_logger(name):
- logging.config.dictConfig(settings.LOGGING_DIC) # 自动加载字典中的配置
- logger1 = logging.getLogger(name)
- return logger1
复制代码- 4.#【user_interface.py】 接口层调用公共文件里的日志函数
- from ATM.lib import common
- my_log = common.get_my_logger('用户相关记录') # 【给该日志起名】
- def register_interface():
- my_log.info('xxx注册成功') # 【info级别记录日志内容】
- def login_interface():
- my_log.debug('xxx登录成功') # 【debug级别记录日志内容】
复制代码 来源:https://www.cnblogs.com/lzy199911/p/17067534.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|