常用模块(subprocess/hashlib/configparser/logging/re)

时间:2022-12-15 20:10:56

一、subprocess(用来执行系统命令)

import os

cmd = r'dir D:xxx | findstr "py"'
# res = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
# # 从管道中读取数据   管道就是 两个进程通讯的媒介
# # print(type(res.stdout.read().decode("GBK")))
# print(res.stdout.read().decode("GBK"))
# print(res.stderr.read().decode("GBK"))
subprocess使用当前系统默认编码,得到结果为bytes类型,在windows下需要用gbk解码。

Conclusion

  subprocess 主要用于执行系统指令 (启动子进程) 与os.system的不同在于
  subprocess 可以与这个子进程进行数据交换

二、hashlib(加密)

hash是一种算法 是将一个任意长的数据 根据计算 得到一个固定长度特征码
特征: 不同输入 可能会有相同的结果 几率特别小,相同的输入 必然得到相同结果,由于散列(特征)的性质,从原理来看是不可能 反解。

用来 验证 两个输入的数据是否一致

使用场景:

1.密码验证

2.验证数据是否被篡改 比如游戏安装包 有没有被改过,为了防止别人撞库成功 可用提升密码的复杂度 其次可以为密码加盐 (加点内容进去)

import  hashlib

m = hashlib.md5("aaa".encode("utf-8"))
print(len(m.hexdigest()))   32
# 撞库破解的原理 有人事先 把常见的 明文和密文的对应关系 存到了数据库中 # 运气好就能查询到 pwds = {"aaa":"47bce5c74f589f4867dbd57e9ca9f808"} h1 = hashlib.sha512("123".encode("utf-8")) h2 = hashlib.sha3_512("123".encode("utf-8")) # print(len(h.hexdigest())) print(h1.hexdigest()) print(h2.hexdigest()) # 2b70683ef3fa64572aa50775acc84855 # 加盐 m = hashlib.md5("321".encode("utf-8")) # m.update("abcdefplkjoujhh".encode("utf-8")) print(m.hexdigest()) import hmac # 没啥区别 只是在创建的时候必须加盐 h = hmac.new("abcdefjjjj".encode("utf-8")) h.update("123".encode("utf-8")) print(h.hexdigest())

三、configparser(用于解析配置文件的模块)

何为配置文件?
包含配置程序信息的文件就称为配置文件
什么样的数据应作为配置信息?
需要改 但是不经常改的信息 例如数据文件的路径 DB_PATH

配置文件中 只有两种内容
一种是section 分区
一种是option 选项 就是一个key=value形式

用的最多的就是get功能 用来从配置文件获取一个配置选项

import configparser
# 创建一个解析器
config = configparser.ConfigParser()
# 读取并解析test.cfg
config.read("test.cfg",encoding="utf-8")
# 获取需要的信息
# 获取所有分区
# print(config.sections())
# 获取所有选项
# print(config.options("user"))
# 获取某个选项的值
# print(config.get("path","DB_PATH"))
# print(type(config.get("user","age")))
#
# # get返回的都是字符串类型  如果需要转换类型 直接使用get+对应的类型(bool int float)
# print(type(config.getint("user","age")))
# print(type(config.get("user","age")))

# 是否由某个选项
config.has_option()
# 是否由某个分区
# config.has_section()

# 不太常用的
# 添加
# config.add_section("server")
# config.set("server","url","192.168.1.2")
# 删除
# config.remove_option("user","age")
# 修改
# config.set("server","url","192.168.1.2")

# 写回文件中
# with open("test.cfg", "wt", encoding="utf-8") as f:
#     config.write(f)

练习:
做一个登录 首先查看配置文件 是否又包含 用户名和密码 如果由直接登录 如果没有就进行输入用户名密码登录
登录成功后 询问是否要保存密码 如果是 写入配置文件

# import configparser
# 
# config = configparser.ConfigParser()
# config.read('login.ini', encoding='utf-8')
# username1 = 'wwl'
# password1 = '123'
# if config.has_option('user','username') and config.has_option('user','password'):
#     print('welcome logging')
#     exit()
# else:
#     username = input('>>>请输入用户名:').strip()
#     password = input('>>>请输入密码:').strip()
#     if username == username1 and password == password1:
#         print('welcome logging')
#         print('保存密码请输入1,退出请输入2')
#         choice = input('请输入:')
#         if choice == '1':
#             with open('login.ini', 'wt', encoding='utf-8') as f:
#                 config.add_section("login")
#                 config.set("login", "Username",username)
#                 config.set("login", "Password", password)
#                 print(config.get('login','Username'))
#                 config.write(f)
#         elif choice == '2':
#             exit()
#     else:
#         print('wrong username or password')
login.ini #产生了新的配置文件

[login] username
= wwl password = 123

四、logging

 一、日志级别:

CRITICAL = 50 #FATAL = CRITICAL
ERROR = 40
WARNING = 30 #WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0 #不设置

二、 默认级别为warning,默认打印到终端

import logging

logging.debug('调试debug')
logging.info('消息info')
logging.warning('警告warn')
logging.error('错误error')
logging.critical('严重critical')

'''
WARNING:root:警告warn
ERROR:root:错误error
CRITICAL:root:严重critical

 三、为logging模块指定全局配置,针对所有logger有效,控制打印到文件中

可在logging.basicConfig()函数中通过具体参数来更改logging模块默认行为,可用参数有
filename:用指定的文件名创建FiledHandler(后边会具体讲解handler的概念),这样日志会被存储在指定的文件中。
filemode:文件打开方式,在指定了filename时使用这个参数,默认值为“a”还可指定为“w”。
format:指定handler使用的日志显示格式。 
datefmt:指定日期时间格式。 
level:设置rootlogger(后边会讲解具体概念)的日志级别 
stream:用指定的stream创建StreamHandler。可以指定输出到sys.stderr,sys.stdout或者文件,默认为sys.stderr。若同时列出了filename和stream两个参数,则stream参数会被忽略

#格式
%(name)s:Logger的名字,并非用户名,详细查看

%(levelno)s:数字形式的日志级别

%(levelname)s:文本形式的日志级别

%(pathname)s:调用日志输出函数的模块的完整路径名,可能没有

%(filename)s:调用日志输出函数的模块的文件名

%(module)s:调用日志输出函数的模块名

%(funcName)s:调用日志输出函数的函数名

%(lineno)d:调用日志输出函数的语句所在的代码行

%(created)f:当前时间,用UNIX标准的表示时间的浮 点数表示

%(relativeCreated)d:输出日志信息时的,自Logger创建以 来的毫秒数

%(asctime)s:字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒

%(thread)d:线程ID。可能没有

%(threadName)s:线程名。可能没有

%(process)d:进程ID。可能没有

%(message)s:用户输出的消息

logging.basicConfig()

 四、logging模块的Formatter,Handler,Logger,Filter对象

#logger:产生日志的对象

#Filter:过滤日志的对象

#Handler:接收日志然后控制打印到不同的地方,FileHandler用来打印到文件中,StreamHandler用来打印到终端

#Formatter对象:可以定制不同的日志格式对象,然后绑定给不同的Handler对象使用,以此来控制不同的Handler的日志格式
'''
critical=50
error =40
warning =30
info = 20
debug =10
'''


import logging

#1、logger对象:负责产生日志,然后交给Filter过滤,然后交给不同的Handler输出
logger=logging.getLogger(__file__)

#2、Filter对象:不常用,略

#3、Handler对象:接收logger传来的日志,然后控制输出
h1=logging.FileHandler('t1.log') #打印到文件
h2=logging.FileHandler('t2.log') #打印到文件
h3=logging.StreamHandler() #打印到终端

#4、Formatter对象:日志格式
formmater1=logging.Formatter('%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s',
                    datefmt='%Y-%m-%d %H:%M:%S %p',)

formmater2=logging.Formatter('%(asctime)s :  %(message)s',
                    datefmt='%Y-%m-%d %H:%M:%S %p',)

formmater3=logging.Formatter('%(name)s %(message)s',)


#5、为Handler对象绑定格式
h1.setFormatter(formmater1)
h2.setFormatter(formmater2)
h3.setFormatter(formmater3)

#6、将Handler添加给logger并设置日志级别
logger.addHandler(h1)
logger.addHandler(h2)
logger.addHandler(h3)
logger.setLevel(10)

#7、测试
logger.debug('debug')
logger.info('info')
logger.warning('warning')
logger.error('error')
logger.critical('critical'

五、应用

"""
logging配置
"""

import os
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'

id_simple_format = '[%(levelname)s][%(asctime)s] %(message)s'

# 定义日志输出格式 结束

logfile_dir = os.path.dirname(os.path.abspath(__file__))  # log文件的目录

logfile_name = 'all2.log'  # log文件名

# 如果不存在定义的日志目录就创建一个
if not os.path.isdir(logfile_dir):
    os.mkdir(logfile_dir)

# log文件的全路径
logfile_path = os.path.join(logfile_dir, logfile_name)

# 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)传递
        },
    },
}


def load_my_logging_cfg():
    logging.config.dictConfig(LOGGING_DIC)  # 导入上面定义的logging配置
    logger = logging.getLogger(__name__)  # 生成一个log实例
    logger.info('It works!')  # 记录该文件的运行状态

if __name__ == '__main__':
    load_my_logging_cfg()

logging配置文件

五、re(正则表达式相关)

什么是正则表达式?

一堆带有特殊意义的符号组成式子

它的作用,处理(匹配 查找 替换 )字符串。
1.在爬虫中大量使用 其实有框架帮你封装了这些复杂的正则
2.在网站和手机app的注册功能中大量使用 例如判断你的邮箱地址是否正确

 
import re

# =========单个字符匹配=========
print(re.findall("\n","1\n"))  # 匹配换行符
print(re.findall("\t","1asasas121   \t"))  # 匹配制表符
 
# ==========范围匹配===========
print(re.findall("\w","1aA_*")) # 匹配数字字母下划线
print(re.findall("\W","1aA_*,")) # 匹配非数字字母下划线
print(re.findall("\s","   \n\r\t\f")) # 匹配任意空白字符
print(re.findall("\S","   \n\r\t\f")) # 匹配任意非空白字符
print(re.findall("\d","123abc1*")) # 匹配任意非空白字符
print(re.findall("\D","123abc1*")) # 匹配任意非空白字符
# print(re.findall("[abc]","AaBbCc")) # 匹配 a b c都行
# print(re.findall("[^abc]","AaBbCc")) # 除了 a b c都行
# print(re.findall("[0-9]","AaBbCc12349")) # 除了 a b c都行
print(re.findall("[a-z]","AaBbCc12349")) # a-z 英文字母
print(re.findall("[A-z]","AaBbC:c????2349[]")) # A-z 匹配原理 是按照ascII码表
 
# ===========匹配位置======
print(re.findall("\A\d","123abc1*")) # 从字符串的开始处匹配
print(re.findall("\d\Z","123abc1*9\n")) # 从字符串的结束处匹配 注意把\Z写在表达式的右边
print(re.findall("\d$","123abc1*9"))  # 从字符串的结束处匹配  如果末尾有换行 换行不会参与匹配
print(re.findall("^\d","s1asasas121   \t"))  # 从字符开始匹配数字
 
import re


# [] 范围匹配  中间 用-来连接
# re.findall("[a-zA-Z0-9]","a ab abc abcd a123c")
# 如果要匹配 符号-  要写表达式的左边或右边
# print(re.findall("[-ab]","a ab abc abcd a123c a--"))

# 重复匹配 表达式的匹配次数
# * 表示 任意次数  所以0次也满足
print(re.findall("[a-zA-Z]*","a ab abc abcdssdsjad a123c"))
#                               [a-zA-Z]*
# +    一次或多次
print(re.findall("[a-zA-Z]+","a ab abc abcdssdsjad a123c"))
#                                                  [a-zA-Z]+
# ?  0次或1次
print(re.findall("[a-zA-Z]?","a ab abc abcdssdsjad a123c"))

# {1,2} 自定义匹配次数  {1,} 1到无穷 {,1} 0到1次
print(re.findall("[a-zA-Z]{1,2}","a ab abc abcdsdssjad a123c"))

一般用非贪婪匹配的情况多一些:

# + * 贪婪匹配  表达式匹配的情况下 尽可能的多拿     (一直匹配 直到不满足为止)

# print(re.findall("\w*","jjsahdjshdjssadsa dssddsads"))
# print(re.findall("\w+","jjsahdjshdjssadsa dssddsads"))
# 非贪婪匹配 在表达式的后面加上?
# print(re.findall("\w?","jjsahdjshdjssadsa dssddsads")) # 非贪婪匹配

  分组

# 分组 加上分组 不会改变原来的规则 仅仅是将括号中的内容单独拿出来了
print(re.findall("([a-zA-Z]+)_dsb","aigen_dsb cxx_dsb alex_dsb zxx_xsb _dsb"))

 模块中常用的函数

# re模块中常用的函数
# match 从字符串开始处匹配  只找一个
print(re.match("\w*","abc").group(0)) # 获取匹配成功的内容
# group 用来获取某个分组的内容 默认获取第0组 就是整个表达式本身
print(re.match("([a-zA-Z]+)(_dsb)","aigen_dsb cxx_dsb alex_dsb zxx_xsb _dsb").group(2))
print(re.match("\w*","abc").span()) # 获取匹配成功的内容的索引
print(re.search("\w*","abc").group())
# 从全文范围取一个
print(re.search("([a-zA-Z]+)(_dsb)","xxx aigen_dsb cxx_dsb alex_dsb zxx_xsb _dsb"))
# 从开始的位置开始匹配
# print(re.match("([a-zA-Z]+)(_dsb)","xxx aigen_dsb cxx_dsb alex_dsb zxx_xsb _dsb").group())
# 将正则表达式 编译成一个对象 往后可以不用在写表达式 直接开始匹配
# print(re.compile("\w*").findall("abcd"))
# print(re.split("\|_*\|","python|____|js|____|java"))
# 替换
print(re.sub("python","PYTHON","js|python|java"))
# 用正则表达式来交换位置
text = "java|C++|js|C|python"
# text1 = "java|C++|js|C|python"
# 将整个内容分为三块 java     |C++xxxxxx|      python
partten = "(.+?)(\|.+\|)(.+)"
".+?ahgshags"
# ?:用于取消分组  就和没写括号一样
# partten = "(?:.+?)(\|.+\|)(.+)"
# print(re.search(partten,text).group(0))
print(re.sub(partten,r"\2\3\1",text))


# 当要匹配的内容包含\时
text = "a\p"
"\p"

print(text)
print(re.findall(r"a\\p",text))
练习题:
# qq密码  长度6--16   数字字母特殊    不包含^
# 如果包含^ 不匹配任何内容
# 除了^ 别的都能匹配上
"[^\^]{6,16}"
import re
# print(re.search("[^^]{6,16}","1234567^as^"))
# print(re.search("[^[\^]+.{6,16}","1234567as"))

# print(re.match("[^@]{6,16}","1234567@"))
#
# print(re.match("[a-z]{6,16}","abasadsasasa^"))
# 长度必须为6 不能包含@
print(re.match("^[^^]{6,8}$","1111111^56781111"))

# print(re.match("[0-9]{6,7}","1234567"))
# print(re.match("^\"[^@]{6,16}\"$", '"1234567io1u"'))

# ^$ 整体匹配   将字符串内容看作一个整体  而不是像之前的逐个匹配
print(re.match("^[^^]{3,6}$","1234567"))

# 手机号码验证 长度11 以1开头 全都是数字
print(re.match("^1(89|80|32)\d{8}$","18921999093"))

# 邮箱地址验证 字母数字下划线(至少6个)@字母数字下划线(最少一个).(cn com org edu任意一个)   可以有18921999093@189.cn
partten = "^\w{6,}@\w+\.(cn|com|org|edu)$"
# 只接受  qq  sina 163

print(re.match(partten,"18921999as dsadsadsadsasdad093@189.cn"))

# 身份证号码  要么18 要么15位数字  最后一个可能是X
# partten = "^\d{17}(X|\d)$"
partten2 = "(^\d{15}$)|(^\d{17}(X|\d)$)"
print(re.match(partten2,"123321200010100"))