模块介绍
一个模块就是一个包含了一组功能的python文件,比如spam.py,模块名为spam,可以通过import spam使用。
模块分4个通用类别:
1 使用python编写的.py文件
2 已被编译为共享库或DLL的C或C++扩展
3 把一系列模块组织到一起的文件夹(注:文件夹下有一个init.py文件,该文件夹称之为包)
4 使用C编写并链接到python解释器的内置模块
模块的意义
从文件级别组织程序,更方便管理。将程序分成一个个的文件,可以把这些文件当做脚本去执行,还可把他们当模块来导入到其他的模块中,实现了功能的重复利用。
提升开发效率。
使用模块之import
import的使用
同一个模块import很多次,仅是对已经加载到内存中的模块对象增加一次引用,不会重新执行模块内的语句,重复导入会直接引用内存中已经加载好的结果:
import spam #只在第一次导入时才执行spam.py内代码,此处的显式效果是只打印一次'from the spam.py',
当然其他的顶级代码也都被执行了,只不过没有显示效果.
import spam
import spam
import spam
'''
执行结果:
from the spam.py
'''
# 从sys.module中可找到当前已经加载的模块
导入模块时执行的内容(以导入spam模块为例)
1.为源文件(spam模块)创建新的名称空间.
2.在新创建的命名空间中执行模块中包含的代码,见初始导入import spam
3.创建名字spam来引用该命名空间.
被导入模块有独立的名称空间
每个模块都是一个独立的名称空间,在编写自定义的模块时,无需担心定义在自己模块中的全局变量会在被导入时,与使用者的全局变量冲突.
为模块名起别名(编写可扩展的代码)
import spam as sm
print(sm.money)
根据用户的输入,选择不同的sql功能.
#定义模块mysql.py
def sqlparse():
print('from mysql sqlparse')
#定义模块oracle.py
def sqlparse():
print('from oracle sqlparse')
#test.py
db_type=input('>>: ')
if db_type == 'mysql':
import mysql as db
elif db_type == 'oracle':
import oracle as db
db.sqlparse()
在一行导入多个模块
import sys,os,re
使用模块之from … import…
from...import...的使用:
from spam import read1,read2
from…import 与import的对比
将spam中的名字直接导入到当前的名称空间中,在当前名称空间中,直接使用名字,无需加前缀:spam.
好处:使用方便
坏处:容易与当前执行文件中的名字冲突
验证
- 验证一
当前位置直接使用read1和read2执行时,仍以spam.py文件全局名称空间:
#测试一:导入的函数read1,执行时仍然回到spam.py中寻找全局变量money
#test.py
from spam import read1
money=1000
read1() # 执行spam模块中read1函数中的money变量
#测试二:导入的函数read2,执行时需要调用read1(),仍然回到spam.py中找read1()
#test.py
from spam import read2
def read1():
print('==========')
read2() # 执行spam模块的read2函数
- 验证二:
#测试三:导入的函数read1,被当前位置定义的read1覆盖掉了
#test.py
from spam import read1
def read1():
print('==========')
read1() #执行本模块中的read1()函数
'''
执行结果:
from the spam.py
- 验证三:
from spam import money,read1
money=100 #将当前位置的名字money绑定到了100
print(money) #打印当前的名字
read1() #读取spam.py中的名字money,仍然为1000
from…import *(不推荐)
所有的不是以下划线(_)开头的名字都导入到当前位置,会覆盖掉之前已定义的名字,且可读性差
模块的重载
性能原因,每个模块只被导入一次,放入字典sys.module中.python不支持重新加载或卸载之前导入的模块.改变了模块的内容,必须重启程序.
# aa.py文件:
def func1():
print('func1')
import time,importlib
import aa
time.sleep(20)
importlib.reload(aa)
aa.func1()
# 在20秒的等待时间里,修改aa.py中func1的内容,结果不变
py文件区分两种用途:模块与脚本
#编写好的一个python文件可以有两种用途:
一:脚本,一个文件就是整个程序,用来被执行
二:模块,文件中存放着一堆功能,用来被导入使用
#python为我们内置了全局变量__name__,
当文件被当做脚本执行时:__name__ 等于'__main__'
当文件被当做模块导入时:__name__等于模块名
#作用:用来控制.py文件在不同的应用场景下执行不同的逻辑
if __name__ == '__main__':
print()
模块搜索路径
模块的查找顺序是:内存中已经加载的模块->内置模块->sys.path路径中包含的模块.
1、在第一次导入某个模块时(比如spam),会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有则直接引用。
ps:python解释器在启动时会自动加载一些模块到内存中,可以使用sys.modules查看。
2、如果没有,解释器则会查找同名的内建模块。
3、如果还没有找到就从sys.path给出的目录列表中依次寻找spam.py文件。
包介绍
包是一种通过使用‘.模块名’来组织python模块名称空间的方式,包就是一个包含有init.py文件的文件夹,所以其实我们创建包的目的就是为了用文件夹将文件/模块组织起来
包本质是文件夹,将文件组织起来,提高程序的结构性和可维护性。
包的导入语句
import #一般用于导入内置模块
from ... import ... #一般用于导入自定义的模块
遵循原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法;
但导入后,使用时没限制,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)
包的使用
import
单独导入包名称时不会导入包中所有包含的所有子模块。
from … import …
from后import导入的模块,必须是明确的一个不能带点,否则会有语法错误,如:from a import b.c是错误语法
from glance.api import *
此处是想从包api中导入所有,实际上该语句只会导入包api下init.py文件中定义的名字
1 #在__init__.py中定义
2 x=10
3
4 def func():
5 print('from api.__init.py')
6
7 __all__=['x','func','policy']
绝对导入和相对导入
- 示范文件
glance/ #Top-level package
├── __init__.py #Initialize the glance package
├── api #Subpackage for api
│ ├── __init__.py
│ ├── policy.py
│ └── versions.py
├── cmd #Subpackage for cmd
│ ├── __init__.py
│ └── manage.py
└── db #Subpackage for db
├── __init__.py
└── models.py
绝对导入:以glance作为起始 。
相对导入:用.或者..的方式最为起始(只能在同一个包中使用,不能用于不同目录内)
例:在glance/api/version.py中想要导入glance/cmd/manage.py
#绝对导入
from glance.cmd import manage
manage.main()
#相对导入
from ..cmd import manage
manage.main()
测试结果:注意一定要在于glance同级的文件中测试
包的执行范围
包以及包所包含的模块都是用来被导入的,而不是被直接执行的。而环境变量都是以执行文件为准的。
- 例如:在glance/api/versions.py中导入glance/api/policy.py
#在version.py中
import policy
policy.get()
#单独运行version.py可以执行,运行version.py的路径搜索是从当前路径开始的,
于是在导入policy时能在当前目录下找到
# 在glance同级下的一个test.py文件中导入version.py
from glance.api import versions
'''
执行结果:
ImportError: No module named 'policy'
'''
'''
分析:
此时导入versions在versions.py中执行
import policy需要先从sys.path也就是从当前目录找policy.py,
这是找不到的
'''
软件开发规范
soft
bin #可执行文件
start.py
conf #配置文件
settions.py
core #程序源文件
src.py
db #数据文件
db.json
lib # 库文件
common.py
log #日志文件
access.lob
Readme #程序说明文件
time与datetime模块
时间类型
print(time.time()) # 时间戳:1487130156.419527
print(time.strftime("%Y-%m-%d %X")) #格式化的时间字符串:'2017-02-15 11:40:53'
# 结构化的时间
print(time.localtime()) #本地时区的struct_time
print(time.gmtime()) #UTC时区的struct_time
时间类型转换关系
- 时间戳与结构化时间的转换
# 将时间戳转换为当前时区的struct_time(结构化时间):
time.localtime()
time.localtime(1473525444.037215)
# 将时间戳转换为UTC时区(0时区)的struct_time:
time.gmtime()
将struct_time转化为时间戳:
print(time.mktime(time.localtime()))
- 结构化时间与格式化字符串时间的转换
# 把struct_time格式化时间(如time.localtime()和 time.gmtime())转化为格式化的时间字符串:
print(time.strftime("%Y-%m-%d %X", time.localtime()))#2016-09-11 00:49:56
# 把格式化时间字符串转化为struct_time:
print(time.strptime('2011-05-05 16:37:06', '%Y-%m-%d %X'))
- 时间格式的其它形式
print(time.asctime()) # 把一个表示时间的元组或者struct_time表示为这种形式:Sun Sep 11 00:43:43 2016
print(time.ctime()) # Sun Sep 11 00:46:38 2016
print(time.ctime(time.time())) # Sun Sep 11 00:46:38 2016
datetime模块
import datetime
# print(datetime.datetime.now()) #返回 2016-08-19 12:47:03.941925
#print(datetime.date.fromtimestamp(time.time()) ) # 时间戳直接转成日期格式 2016-08-19
# print(datetime.datetime.now() + datetime.timedelta(3)) #当前时间+3天
# print(datetime.datetime.now() + datetime.timedelta(-3)) #当前时间-3天
# print(datetime.datetime.now() + datetime.timedelta(hours=3)) #当前时间+3小时
# print(datetime.datetime.now() + datetime.timedelta(minutes=30)) #当前时间+30分
# c_time = datetime.datetime.now()
# print(c_time.replace(minute=3,hour=2)) #时间替换
random模块
import random
print(random.random())#(0,1)----float 大于0且小于1之间的小数
print(random.randint(1,3)) #[1,3] 大于等于1且小于等于3之间的整数
print(random.randrange(1,3)) #[1,3) 大于等于1且小于3之间的整数
print(random.choice([1,'23',[4,5]]))#随机选择1或者23或者[4,5]
print(random.sample([1,'23',[4,5]],2))#对列表元素任意2个组合
print(random.uniform(1,3))#大于1小于3的小数,如1.927109612082716
item=[1,3,5,7,9]
random.shuffle(item) #打乱item的顺序,相当于"洗牌"
print(item)
- 应用示例:
生成随机验证码:
import random
def make_code(n):
res = ''
for i in rang(n):
s1=chr(random.randint(65,90)) # 生成字母
s2 = str(random.randint(0,9)) # 生成数字
res += random.choice([s1, s2])
return res
print(make_code(9))
os模块
os模块是与操作系统交互的一个接口
os.getcwd() 获取当前工作目录,即当前python脚本工作的目录路径
os.chdir("dirname") 改变当前脚本工作目录;相当于shell下cd
os.curdir 返回当前目录: ('.')
os.pardir 获取当前目录的父目录字符串名:('..')
os.makedirs('dirname1/dirname2') 可生成多层递归目录
os.removedirs('dirname1') 若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推
os.mkdir('dirname') 生成单级目录;相当于shell中mkdir dirname
os.rmdir('dirname') 删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir dirname
os.listdir('dirname') 列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印
os.remove() 删除一个文件
os.rename("oldname","newname") 重命名文件/目录
os.stat('path/filename') 获取文件/目录信息
os.sep 输出操作系统特定的路径分隔符,win下为"\\",Linux下为"/"
os.linesep 输出当前平台使用的行终止符,win下为"\t\n",Linux下为"\n"
os.pathsep 输出用于分割文件路径的字符串 win下为;,Linux下为:
os.name 输出字符串指示当前使用平台。win->'nt'; Linux->'posix'
os.system("bash command") 运行shell命令,直接显示
os.environ 获取系统环境变量
os.path.abspath(path) 返回path规范化的绝对路径
os.path.split(path) 将path分割成目录和文件名二元组返回
os.path.dirname(path) 返回path的目录。其实就是os.path.split(path)的第一个元素
os.path.basename(path) 返回path最后的文件名。如何path以/或\结尾,那么就会返回空值。即os.path.split(path)的第二个元素
os.path.exists(path) 如果path存在,返回True;如果path不存在,返回False
os.path.isabs(path) 如果path是绝对路径,返回True
os.path.isfile(path) 如果path是一个存在的文件,返回True。否则返回False
os.path.isdir(path) 如果path是一个存在的目录,则返回True。否则返回False
os.path.join(path1[, path2[, ...]]) 将多个路径组合后返回,第一个绝对路径之前的参数将被忽略
os.path.getatime(path) 返回path所指向的文件或者目录的最后存取时间
os.path.getmtime(path) 返回path所指向的文件或者目录的最后修改时间
os.path.getsize(path) 返回path的大小
系统中的path问题
在Linux和Mac平台上,该函数会原样返回path,
在windows平台上会将路径中所有字符转换为小写,并将所有斜杠转换为反斜杠(\)
>>> os.path.normcase('c:/windows\\system32\\')
'c:\\windows\\system32\\'
规范化路径,如..和/
os.path.normpath('c://windows\\System32\\../Temp/')
'c:\\windows\\Temp' #在windows中的显示样式
>>> a='/Users/jieli/test1/\\\a1/\\\\aa.py/../..'
>>> print(os.path.normpath(a))
/Users/jieli/test1
os路径处理
方式一:推荐使用
import os,sys
possible_topdir = os.path.normpath(os.path.join(
os.path.abspath(__file__), #文件绝对路径
os.pardir, #上一级
os.pardir,
os.pardir
))
sys.path.insert(0,possible_topdir) #添加到sys模块搜索路径列表中的第一位中
方式二:不推荐使用
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys模块
sys.argv 命令行参数List,第一个元素是程序本身路径
sys.exit(n) 退出程序,正常退出时exit(0)
sys.version 获取Python解释程序的版本信息
sys.maxint 最大的Int值
sys.path 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值
sys.platform 返回操作系统平台名称
实现打印进度条函数
import sys
import time
def progress(percent,width=50):
if percent >= 1:
percent=1
show_str=('[%%-%ds]' %width) %(int(width*percent)*'#')
print('\r%s %d%%' %(show_str,int(100*percent)),file=sys.stdout,flush=True,end='')
data_size=1025
recv_size=0
while recv_size < data_size:
time.sleep(0.1) #模拟数据的传输延迟
recv_size+=1024 #每次收1024
percent=recv_size/data_size #接收的比例
progress(percent,width=70) #进度条的宽度70
shutil文件处理模块
高级的 文件、文件夹、压缩包 处理模块..
将文件内容拷贝到另一个文件中.
import shutil
shutil.copyfileobj(open('old.xml','r'), open('new.xml', 'w'))
拷贝文件
shutil.copyfile('f1.log', 'f2.log') #目标文件无需存在,会自己创建新文件
仅拷贝权限,内容、组、用户均不变
shutil.copymode('f1.log', 'f2.log') #目标文件必须存在
仅拷贝状态的信息,包括:mode bits, atime, mtime, flags
shutil.copystat('f1.log', 'f2.log') #目标文件必须存在
拷贝文件和权限
shutil.copy('f1.log', 'f2.log')
递归的去拷贝文件夹
shutil.copytree('folder1', 'folder2', ignore=shutil.ignore_patterns('*.pyc', 'tmp*'))
#目标目录不能存在,注意对folder2目录父级目录要有可写权限,ignore的意思是排除
#拷贝软链接
shutil.copytree('f1', 'f2', symlinks=True, ignore=shutil.ignore_patterns('*.pyc', 'tmp*'))
# 通常的拷贝都把软连接拷贝成硬链接,即对待软连接来说,创建新的文件
递归删除文件
shutil.rmtree('folder1')
递归去移动文件,类似mv命令(重命名)
shutil.move('folder1', 'folder3')
打包文件
import shutil
#将 /data 下的文件打包放置当前程序目录
ret = shutil.make_archive("data_bak", 'gztar', root_dir='/data')
#将 /data下的文件打包放置 /tmp/目录
ret = shutil.make_archive("/tmp/data_bak", 'gztar', root_dir='/data')
shutil.make_archive(base_name, format,...)
base_name: 压缩包的文件名,也可以是压缩包的路径。只是文件名时,则保存至当前目录,否则保存至指定路径,
如 data_bak =>保存至当前路径
如:/tmp/data_bak =>保存至/tmp/
format: 压缩包种类,“zip”, “tar”, “bztar”,“gztar”
root_dir: 要压缩的文件夹路径(默认当前目录)
owner: 用户,默认当前用户
group: 组,默认当前组
logger: 用于记录日志,通常是logging.Logger对象
对压缩包的处理(调用 ZipFile 和 TarFile 两个模块完成)
import zipfile
# 压缩
z = zipfile.ZipFile('laxi.zip', 'w')
z.write('a.log') #需压缩的文件
z.write('data.data')
z.close()
# 解压
z = zipfile.ZipFile('laxi.zip', 'r')
z.extractall(path='.') #提取
z.close()
import tarfile
# 压缩
t=tarfile.open('/tmp/egon.tar','w')
t.add('/test1/a.py',arcname='a.bak')
t.add('/test1/b.py',arcname='b.bak')
t.close()
# 解压
t=tarfile.open('/tmp/egon.tar','r')
t.extractall('/egon')
t.close()
json&pickle模块
eval:将一个字符串转成python对象,执行一个字符串表达式,并返回表达式的值.
import json
x="[null,true,false,1]"
print(eval(x)) #报错,无法解析null类型,而json就可以
print(json.loads(x))
序列化(pickling)
把对象(变量)从内存中变成可存储或传输的过程称之为序列化。
- 序列化意义:
持久保存状态:内存无法永久保存数据,在断电/重启程序之前将内存中数据都保存下来(保存到文件中),便于下次载入之前的数据,然后继续执行,这是序列化。
跨平台数据交互:将序列化后的数据通过网络传输到别的机器上,实现了跨平台数据交互。反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling
json
序列化格式的一种,不仅是标准格式,且比XML更快,
可以直接在Web页面中读取,非常方便。
# 转换过程:
内存中结构化数据---格式JSON---字符串---保存文件中/基于网络传输
# 序列化:
dic={'name':'alvin','age':23,'sex':'male'} #序列化可以单引号,但是反序列化要双引号,json.dumps(dic)
反序列化:(反序列化要双引号)
f=open('序列化对象')
data=json.loads(f.read()) # 等价于data=json.load(f)
# 注意点:
import json
dct="{'1':111}" #json 不认单引号
dct=str({"1":111}) #报错,因为生成的数据还是单引号:{'one': 1}
dct='{"1":"111"}' #反序列化时,需是双引号才能转换成功
print(json.loads(dct))
# 无论数据是怎样创建的,只要满足json格式,就可以json.loads出来,不一定非要dumps的数据才能loads
pickle(转换成字节类型)
# 转换过程:
内在中结构化数据---格式pickle---bytes类型---保存到文件中/基于网络传输
import pickle
dic={'name':'alvin','age':23,'sex':'male'} # 字典格式数据
j=pickle.dumps(dic) #序列化成字节格式数据
print(type(j)) #<class 'bytes'>
f=open('序列化对象_pickle','wb') #注意是w是写入str,wb是写入bytes,j是'bytes'
f.write(j) #等价于pickle.dump(dic,f)
f.close()
#-------------------------反序列化
f=open('序列化对象_pickle','rb')
data=pickle.loads(f.read()) # 等价于data=pickle.load(f)
print(data['age'])
shelve模块
shelve模块比pickle模块简单,只有一个open函数,
返回类似字典的对象,可读可写;
key必须为字符串,而值可以是python所支持的数据类型。
import shelve
f=shelve.open(r'sheve.txt')
# f['stu1_info']={'name':'egon','age':18,'hobby':['piao','smoking','drinking']}
# f['stu2_info']={'name':'gangdan','age':53}
# f['school_info']={'website':'http://www.pypy.org','city':'beijing'}
print(f['stu1_info']['hobby'])
f.close()
xml模块
xml是实现不同语言或程序之间进行数据交换的协议,跟json一样,传统公司如金融行业的很多系统的接口还主要是xml。
格式数据范例
<?xml version="1.0"?>
<data>
<country name="Liechtenstein">
<rank updated="yes">2</rank>
<year>2008</year>
<gdppc>141100</gdppc>
<neighbor name="Austria" direction="E"/>
<neighbor name="Switzerland" direction="W"/>
</country>
<country name="Singapore">
<rank updated="yes">5</rank>
<year>2011</year>
<gdppc>59900</gdppc>
<neighbor name="Malaysia" direction="N"/>
</country>
<country name="Panama">
<rank updated="yes">69</rank>
<year>2011</year>
<gdppc>13600</gdppc>
<neighbor name="Costa Rica" direction="W"/>
<neighbor name="Colombia" direction="E"/>
</country>
</data>
python操作常用模块
root.iter('year') #全文搜索
root.find('country') #在root的子节点找,只找一个
root.findall('country') #在root的子节点找,找所有
操作xml文档
import xml.etree.ElementTree as ET
tree = ET.parse("xmltest.xml")
root = tree.getroot()
print(root.tag)
#遍历xml文档
for child in root:
print('========>',child.tag,child.attrib,child.attrib['name'])
for i in child:
print(i.tag,i.attrib,i.text)
#只遍历year 节点
for node in root.iter('year'):
print(node.tag,node.text)
#---------------------------------------
import xml.etree.ElementTree as ET
tree = ET.parse("xmltest.xml")
root = tree.getroot()
#修改
for node in root.iter('year'):
new_year=int(node.text)+1
node.text=str(new_year)
node.set('updated','yes')
node.set('version','1.0')
tree.write('test.xml')
#删除node
for country in root.findall('country'):
rank = int(country.find('rank').text)
if rank > 50:
root.remove(country)
tree.write('output.xml')
#在country内添加(append)节点year2
import xml.etree.ElementTree as ET
tree = ET.parse("a.xml")
root=tree.getroot()
for country in root.findall('country'):
for year in country.findall('year'):
if int(year.text) > 2000:
year2=ET.Element('year2')
year2.text='新年'
year2.attrib={'update':'yes'}
country.append(year2) #往country节点下添加子节点
tree.write('a.xml.swap')
创建xml文档
import xml.etree.ElementTree as ET
new_xml = ET.Element("namelist")
name = ET.SubElement(new_xml,"name",attrib={"enrolled":"yes"})
age = ET.SubElement(name,"age",attrib={"checked":"no"})
sex = ET.SubElement(name,"sex")
sex.text = '33'
name2 = ET.SubElement(new_xml,"name",attrib={"enrolled":"no"})
age = ET.SubElement(name2,"age")
age.text = '19'
et = ET.ElementTree(new_xml) #生成文档对象
et.write("test.xml", encoding="utf-8",xml_declaration=True)
ET.dump(new_xml) #打印生成的格式
hashlib模块
hash:一种算法 ,3.x里代替了md5模块和sha模块,
主要提供 SHA1, SHA224, SHA256, SHA384, SHA512 ,MD5 算法。
特点
- 内容相同则hash运算结果相同,内容稍微改变则hash值则变。
- 不可逆推。
- 相同算法:无论校验多长的数据,得到的哈希值长度固定。
import hashlib
m=hashlib.md5() # 创建md5对象,m=hashlib.sha256()
m.update('hello'.encode('utf8')) # 加密数据
print(m.hexdigest()) # 取出数据:5d41402abc4b2a76b9719d911017c592
m.update('alvin'.encode('utf8'))
print(m.hexdigest()) #92a7e713c30abbb0319fa07da2a5c4af
m2=hashlib.md5()
m2.update('helloalvin'.encode('utf8'))
print(m2.hexdigest()) #92a7e713c30abbb0319fa07da2a5c4af
'''
注意:把一段很长的数据update多次,与一次update这段长数据,得到的结果一样
但是update多次为校验大文件提供了可能
'''
模拟撞库破解密码
# 加密算法时候存在缺陷,即:通过撞库可以反解。
所以,有必要对加密算法中添加自定义key再来做加密。
import hashlib
passwds=[
'tom3714',
'tom1313',
'tom94139413',
'tom123456',
'123456tom',
'a123tom',
]
def make_passwd_dic(passwds): # 创建库文件
dic={}
for passwd in passwds:
m=hashlib.md5()
m.update(passwd.encode('utf-8'))
dic[passwd]=m.hexdigest()
return dic
def break_code(cryptograph,passwd_dic): # 库文件的md5值与传入值进行匹配,属于暴力破解
for k,v in passwd_dic.items():
if v == cryptograph:
print('密码是===>\033[46m%s\033[0m' %k) # 获得密码
cryptograph='aee949757a2e698417463d47acac93df'
break_code(cryptograph,make_passwd_dic(passwds))
hmac模块
# 内部对我们创建 key 和 内容 进行进一步的处理然后再加密:
import hmac
h = hmac.new('alvin'.encode('utf8'))
h.update('hello'.encode('utf8'))
print (h.hexdigest())#320df9832eab4c038b6c1d7ed73a5940
#要想保证hmac最终结果一致,必须保证:
#1:hmac.new括号内指定的初始key一样时,无论update多少次,校验的内容累加到一起是一样的内容
import hmac
h1=hmac.new(b'egon') # 初始key
h1.update(b'hello')
h1.update(b'world')
print(h1.hexdigest()) # f1bf38d054691688f89dcd34ac3c27f2
h2=hmac.new(b'egon') # 初始key
h2.update(b'helloworld')
print(h2.hexdigest()) # f1bf38d054691688f89dcd34ac3c27f2
h3=hmac.new(b'egonhelloworld')
print(h3.hexdigest()) # bcca84edd9eeb86f30539922b28f3981
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:用户输出的消息
- 应用示例:
#========使用
import logging
logging.basicConfig(filename='access.log',
format='%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S %p',
level=10)
logging.debug('调试debug')
logging.info('消息info')
logging.warning('警告warn')
logging.error('错误error')
logging.critical('严重critical')
#========结果
access.log内容:
2017-07-28 20:32:17 PM - root - DEBUG -test: 调试debug
2017-07-28 20:32:17 PM - root - INFO -test: 消息info
2017-07-28 20:32:17 PM - root - WARNING -test: 警告warn
2017-07-28 20:32:17 PM - root - ERROR -test: 错误error
2017-07-28 20:32:17 PM - root - CRITICAL -test: 严重critical
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')
Logger与Handler的级别
# 验证
import logging
form=logging.Formatter('%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S %p',)
ch=logging.StreamHandler() # 打印到终端
ch.setFormatter(form)
# ch.setLevel(10)
ch.setLevel(20)
l1=logging.getLogger('root')
# l1.setLevel(20)
l1.setLevel(10)
l1.addHandler(ch)
l1.debug('l1 debug')
Logger的继承
import logging
formatter=logging.Formatter('%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S %p',)
ch=logging.StreamHandler() #设置为打印到终端
ch.setFormatter(formatter) #设置日志打印格式
logger1=logging.getLogger('root') #设置Logger的名字
logger2=logging.getLogger('root.child1')
logger3=logging.getLogger('root.child1.child2')
logger1.addHandler(ch) #为logger1对象添加Handler来控制输出
logger2.addHandler(ch)
logger3.addHandler(ch)
logger1.setLevel(10) # 日志级别
logger2.setLevel(10)
logger3.setLevel(10)
logger1.debug('log1 debug')
logger2.debug('log2 debug')
logger3.debug('log3 debug')
'''
2017-07-28 22:22:05 PM - root - DEBUG -test: log1 debug
2017-07-28 22:22:05 PM - root.child1 - DEBUG -test: log2 debug
2017-07-28 22:22:05 PM - root.child1 - DEBUG -test: log2 debug
2017-07-28 22:22:05 PM - root.child1.child2 - DEBUG -test: log3 debug
2017-07-28 22:22:05 PM - root.child1.child2 - DEBUG -test: log3 debug
2017-07-28 22:22:05 PM - root.child1.child2 - DEBUG -test: log3 debug
'''
- 应用
# 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使用测试:
import time
import logging
import my_logging # 导入自定义的logging配置
logger = logging.getLogger(__name__) # 生成logger实例
def demo():
logger.debug("start range... time:{}".format(time.time()))
logger.info("中文测试开始。。。")
for i in range(10):
logger.debug("i:{}".format(i))
time.sleep(0.2)
else:
logger.debug("over range... time:{}".format(time.time()))
logger.info("中文测试结束。。。")
if __name__ == "__main__":
my_logging.load_my_logging_cfg() # 在你程序文件的入口加载自定义的logging配置
demo()
# 如何拿到logger对象
有了上述方式我们的好处是:所有与logging模块有关的配置都写到字典中就可以了,更加清晰,方便管理
需要解决的问题是:
1、从字典加载配置:logging.config.dictConfig(settings.LOGGING_DIC)
2、拿到logger对象来产生日志
logger对象都是配置到字典的loggers 键对应的子字典中的。按照我们对logging模块的理解,要想获取某个东西都是通过名字,也就是key来获取的,于是我们要获取不同的logger对象就是 logger=logging.getLogger('loggers子字典的key名')
但问题是:如果我们想要不同logger名的logger对象都共用一段配置,那么肯定不能在loggers子字典中定义n个key
'loggers': {
'l1': {
'handlers': ['default', 'console'], #
'level': 'DEBUG',
'propagate': True, # 向上(更高level的logger)传递
},
'l2: {
'handlers': ['default', 'console' ],
'level': 'DEBUG',
'propagate': False, # 向上(更高level的logger)传递
},
'l3': {
'handlers': ['default', 'console'], #
'level': 'DEBUG',
'propagate': True, # 向上(更高level的logger)传递
},
}
#我们的解决方式是,定义一个空的key
'loggers': {
'': {
'handlers': ['default', 'console'],
'level': 'DEBUG',
'propagate': True,
},
}
这样我们再取logger对象时
logging.getLogger(__name__),不同的文件__name__不同,这保证了打印日志时标识信息不同,但是拿着该名字去loggers里找key名时却发现找不到,于是默认使用key=''的配置
re正则表达式
正则就是用来描述一类事物的规则,内嵌在Python中,通过 re 模块实现,被编译成一系列的字节码,然后由用 C 编写的匹配引擎执行。
常用匹配模式(元字符)
\w 匹配字母数字及下划线
\W 匹配非字母数字及下划线
\s 匹配任意空白字符,等价于[\t\n\r\f]
\S 匹配任意非空字符
\d 匹配任意数字,等价于[0-9]
\D 匹配任意非数字
\A 匹配字符串开始
\Z 匹配字符串结束,如存在换行,只匹配到换行前的结束字符串
\z 匹配字符串结束
\G 匹配最后匹配完成的位置
\n 匹配一个换行符
\t 匹配一个制表符
^ 匹配字符串的开关
匹配字符串的结尾
. 匹配任意字符。除了换行符,当re.DOTALL标记被指定时,则可匹配包含换行符的任意字符
[……] 用来表示一组字符,单独列出:[amk] 匹配‘a’,'m'或'k'
[^……] 取反,不在[]中的字符:[^abc]匹配除了a,b,c之外的字符
* 匹配0个或多个的表达式
+ 匹配1个或多个的表达式
? 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式
{n} 精确匹配n个前面表达式
{n,m} 匹配n到m次由前面的正则表达式定义的片段,贪婪方式
a|b 匹配a或b
() 匹配括号内的表达式,也表示一个组
re提供的方法
import re
# print(re.findall('e','alex make love') ) #['e', 'e', 'e'],返回所有满足匹配条件的结果,放在列表里
# print(re.search('e','alex make love').group())
#e,只到找到第一个匹配然后返回一个包含匹配信息的对象,该对象可以通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回None。
# print(re.match('e','alex make love')) #None,同search,不过在字符串开始处进行匹配,完全可以用search+^代替match
#为何同样的表达式search与findall却有不同结果:
print(re.search('\(([\+\-\*\/]*\d+\.?\d*)+\)',"1-12*(60+(-40.35/5)-(-4*3))").group()) #(-40.35/5)
print(re.findall('\(([\+\-\*\/]*\d+\.?\d*)+\)',"1-12*(60+(-40.35/5)-(-4*3))")) #['/5', '*3']
#看这个例子:(\d)+相当于(\d)(\d)(\d)(\d)...,是一系列分组
print(re.search('(\d)+','123').group()) # 123 group的作用是将所有组拼接到一起显示出来,
print(re.findall('(\d)+','123')) # ['3'] findall结果是组内的结果,且是最后一个组的结果
-----------------------------------------------------------------------------------------------------
# 分割
# print(re.split('[ab]','abcd')) #['', '', 'cd'],先按'a'分割得到''和'bcd',再对''和'bcd'分别按'b'分割
# 替换
# print('===>',re.sub('a','A','alex make love')) #===> Alex mAke love,不指定n,默认替换所有
# print('===>',re.sub('a','A','alex make love',1)) #===> Alex make love 替换1个
# print('===>',re.sub('a','A','alex make love',2)) #===> Alex mAke love 替换2个
# print('===>',re.sub('^(\w+)(.*?\s)(\w+)(.*?\s)(\w+)(.*?)$',r'\5\2\3\4\1','alex make love')) #===> love make alex
# # alex位于第1位,空格符位于第2位,make位于第三位,再一个空字符位于第4位,love位于第5位
# print('===>',re.subn('a','A','alex make love')) #===> ('Alex mAke love', 2),结果带有总共替换的个数
# 生成正则表达式对象
# obj=re.compile('\d{2}')
# print(obj.search('abc123eeee').group()) #12
# print(obj.findall('abc123eeee')) #['12'],重用了obj
匹配模式
import re
#\w与\W
# print(re.findall('\w','hello egon 123')) #['h', 'e', 'l', 'l', 'o', 'e', 'g', 'o', 'n', '1', '2', '3']
# print(re.findall('\W','hello egon 123')) #[' ', ' ']
#\s与\S
# print(re.findall('\s','hello egon 123')) #[' ', ' ', ' ', ' ']
# print(re.findall('\S','hello egon 123')) #['h', 'e', 'l', 'l', 'o', 'e', 'g', 'o', 'n', '1', '2', '3']
#\n \t都是空,都可以被\s匹配
# print(re.findall('\s','hello \n egon \t 123')) #[' ', '\n', ' ', ' ', '\t', ' ']
#\n与\t
# print(re.findall(r'\n','hello egon \n123')) #['\n']
# print(re.findall(r'\t','hello egon\t123')) #['\n']
#\d与\D
# print(re.findall('\d','hello egon 123')) #['1', '2', '3']
# print(re.findall('\D','hello egon 123')) #['h', 'e', 'l', 'l', 'o', ' ', 'e', 'g', 'o', 'n', ' ']
#\A与\Z
# print(re.findall('\Ahe','hello egon 123')) #['he'],\A==>^
# print(re.findall('123\Z','hello egon 123')) #['he'],\Z==>$
#^与$
# print(re.findall('^h','hello egon 123')) #['h']
# print(re.findall('3$','hello egon 123')) #['3']
# 重复匹配:| . | * | ? | .* | .*? | + | {n,m} |
#.
# print(re.findall('a.b','a1b')) #['a1b']
# print(re.findall('a.b','a1b a*b a b aaab')) #['a1b', 'a*b', 'a b', 'aab']
# print(re.findall('a.b','a\nb')) #[]
# print(re.findall('a.b','a\nb',re.S)) #['a\nb']
# print(re.findall('a.b','a\nb',re.DOTALL)) #['a\nb']同上一条意思一样
#*
# print(re.findall('ab*','bbbbbbb')) #[]
# print(re.findall('ab*','a')) #['a']
# print(re.findall('ab*','abbbb')) #['abbbb']
#?
# print(re.findall('ab?','a')) #['a']
# print(re.findall('ab?','abbb')) #['ab']
#匹配所有包含小数在内的数字
# print(re.findall('\d+\.?\d*',"asdfasdf123as1.13dfa12adsf1asdf3")) #['123', '1.13', '12', '1', '3']
# .*默认为贪婪匹配,匹配多个任意字符
# print(re.findall('a.*b','a1b22222222b')) #['a1b22222222b']
#.*?为非贪婪匹配:推荐使用,要么匹配一个任意字符,要么匹配多个任意字符
# print(re.findall('a.*?b','a1b22222222b')) #['a1b']
#+
# print(re.findall('ab+','a')) #[]
# print(re.findall('ab+','abbb')) #['abbb']
#{n,m} 匹配n到m次由前面的正则表达式定义的片段,贪婪方式
# print(re.findall('ab{2}','abbb')) #['abb']
# print(re.findall('ab{2,4}','abbb')) #['abb']
# print(re.findall('ab{1,}','abbb')) #'ab{1,}' ===> 'ab+'
# print(re.findall('ab{0,}','abbb')) #'ab{0,}' ===> 'ab*'
#[] 用来表示一组字符,单独列出:[amk] 匹配‘a’,'m'或'k'
# print(re.findall('a[1*-]b','a1b a*b a-b')) #[]内的都为普通字符了,且如果-没有被转意的话,应该放到[]的开头或结尾
# print(re.findall('a[^1*-]b','a1b a*b a-b a=b')) #[]内的^代表的意思是取反,所以结果为['a=b']
# print(re.findall('a[0-9]b','a1b a*b a-b a=b')) #[]内代表数字取值范围,所以结果为['a1b']
# print(re.findall('a[a-z]b','a1b a*b a-b a=b aeb')) #[]内代表小定字母取值范围,所以结果为['aeb']
# print(re.findall('a[a-zA-Z]b','a1b a*b a-b a=b aeb aEb')) #[]内代表小写大写的取值范围,所以结果为['a=b']
#\# print(re.findall('a\\c','a\c')) #对于正则来说a\\c确实可以匹配到a\c,但是在python解释器读取a\\c时,会发生转义,然后再交给re去执行,发生2次转义所以抛出异常
# print(re.findall(r'a\\c','a\c')) #r代表告诉解释器使用rawstring,即原生字符串,把我们正则内的所有符号都当普通字符处理,不要转义
# print(re.findall('a\\\\c','a\c')) #同上面的意思一样,和上面的结果一样都是['a\\c']
#():分组 匹配成功后,只保留括号里的结果,括号以外的内容不保留
# print(re.findall('ab+','ababab123')) #['ab', 'ab', 'ab']
# print(re.findall('(ab)+123','ababab123')) #['ab'],匹配到末尾的ab123中的ab
# print(re.findall('(?:ab)+123','ababab123')) # ['ababab123'],findall的结果不是匹配的全部内容,而是组内的内容,?:可以让结果为匹配的全部内容
# print(re.findall('href="(.*?)"','<a href="http://www.baidu.com">点击</a>'))#['http://www.baidu.com']
# print(re.findall('href="(?:.*?)"','<a href="http://www.baidu.com">点击</a>'))#['href="http://www.baidu.com"']
补充
import re
print(re.findall("<(?P<tag_name>\w+)>\w+</(?P=tag_name)>","<h1>hello</h1>")) #['h1']
print(re.search("<(?P<tag_name>\w+)>\w+</(?P=tag_name)>","<h1>hello</h1>").group()) #<h1>hello</h1>
print(re.search("<(?P<tag_name>\w+)>\w+</(?P=tag_name)>","<h1>hello</h1>").groupdict()) #{'tag_name': 'h1'} 获得字典
print(re.search(r"<(\w+)>\w+</(\w+)>","<h1>hello</h1>").group()) # <h1>hello</h1>
print(re.search(r"<(\w+)>\w+</\1>","<h1>hello</h1>").group()) # <h1>hello</h1>
# 使用|(或),先匹配的先生效,|左边是匹配小数,而findall最终结果是查看分组,所有即使匹配成功小数也不会存入结果
#而不是小数时,就去匹配(-?\d+),匹配到的自然就是非小数的数,在此处即整数
print(re.findall(r"-?\d+\.\d*|(-?\d+)","1-2*(60+(-40.35/5)-(-4*3))")) #找出所有整数['1', '-2', '60', '', '5', '-4', '3']
匹配形式(贪婪……)
import re
s='''
http://www.baidu.com
egon@oldboyedu.com
你好
010-3141
'''
#常规匹配
# content='Hello 123 456 World_This is a Regex Demo'
# res=re.match('Hello\s\d\d\d\s\d{3}\s\w{10}.*Demo',content)
# print(res)
# print(res.group())
# print(res.span())
#泛匹配
# content='Hello 123 456 World_This is a Regex Demo'
# res=re.match('^Hello.*Demo',content)
# print(res.group())
#匹配目标,获得指定数据
# content='Hello 123 456 World_This is a Regex Demo'
# res=re.match('^Hello\s(\d+)\s(\d+)\s.*Demo',content)
# print(res.group()) #取所有匹配的内容
# print(res.group(1)) #取匹配的第一个括号内的内容
# print(res.group(2)) #去陪陪的第二个括号内的内容
#贪婪匹配: .* 代表匹配0到无穷个任意字符
# content='Hello 123 456 World_This is a Regex Demo'#
# res=re.match('^He.*(\d+).*Demo$',content)
# print(res)
# print(res.group(1)) #只打印所有数字中的6,因为.*会尽可能多的匹配,然后后面跟至少一个数字,其余数字被其它匹配了
# print(res.group()) # Hello 123 456 World_This is a Regex Demo
#非贪婪匹配:?匹配尽可能少的字符
# content='Hello 123 456 World_This is a Regex Demo'
# res=re.match('^He.*?(\d+).*Demo$',content)
# print(res.group(1)) #打印123
#匹配模式: .不能匹配换行符
# content='''Hello 123456 World_This
# is a Regex Demo
# '''
# res=re.match('He.*?(\d+).*?Demo$',content)
# print(res) #输出None
#
# res=re.match('He.*?(\d+).*?Demo$',content,re.S) #re.S让.可以匹配换行符
# print(res)
# print(res.group(1)) # 123456
#转义:\
# content='price is $5.00'
# res=re.match('price is $5.00',content)
# print(res)
# res=re.match('price is \$5\.00',content)
# print(res.group())
#总结:尽量精简,详细的如下
# 尽量使用泛匹配模式.*
# 尽量使用非贪婪模式: .*?
# 使用括号得到匹配目标:用group(n)去取得结果
# 有换行符就用re.S:修改模式
#re.search:会扫描整个字符串,不会从头开始,找到第一个匹配的结果就会返回
# content='Extra strings Hello 123 456 World_This is a Regex Demo Extra strings'
# res=re.search('Hello.*?(\d+).*?Demo',content) #
# print(res.group(1)) # 输出结果为123
# content='Extra strings Hello 123 456 World_This is a Regex Demo Extra strings'
# res=re.match('Hello.*?(\d+).*?Demo',content)
# print(res) #输出结果为Noneb
匹配演练
import re
content='''
<tbody>
<tr id="4766303201494371851675" class="even "><td><div class="hd"><span class="num">1</span><div class="rk "><span class="u-icn u-icn-75"></span></div></div></td><td class="rank"><div class="f-cb"><div class="tt"><a href="/song?id=476630320"><img class="rpic" src="http://p1.music.126.net/Wl7T1LBRhZFg0O26nnR2iQ==/19217264230385030.jpg?param=50y50&quality=100"></a><span data-res-id="476630320" "
# res=re.search('<a\shref=.*?<b\stitle="(.*?)".*?b>',content)
# print(res.group(1))
#re.findall:找到符合条件的所有结果
# res=re.findall('<a\shref=.*?<b\stitle="(.*?)".*?b>',content)
# for i in res:
# print(i)
#re.sub:字符串替换
import re
content='Extra strings Hello 123 456 World_This is a Regex Demo Extra strings'
# content=re.sub('\d+','',content)
# print(content)
#用\1取得第一个括号的内容
#用法:将123与456换位置
# import re
# content='Extra strings Hello 123 456 World_This is a Regex Demo Extra strings'
#
# # content=re.sub('(Extra.*?)(\d+)(\s)(\d+)(.*?strings)',r'\1\4\3\2\5',content)
# content=re.sub('(\d+)(\s)(\d+)',r'\3\2\1',content)
# print(content)
# import re
# content='Extra strings Hello 123 456 World_This is a Regex Demo Extra strings'
#
# res=re.search('Extra.*?(\d+).*strings',content)
# print(res.group(1))
# import requests,re
# respone=requests.get('https://book.douban.com/').text
# print(respone)
# print('======'*1000)
# print('======'*1000)
# print('======'*1000)
# print('======'*1000)
# res=re.findall('<li.*?cover.*?href="(.*?)".*?title="(.*?)">.*?more-meta.*?author">(.*?)</span.*?year">(.*?)</span.*?publisher">(.*?)</span.*?</li>',respone,re.S)
# # res=re.findall('<li.*?cover.*?href="(.*?)".*?more-meta.*?author">(.*?)</span.*?year">(.*?)</span.*?publisher">(.*?)</span>.*?</li>',respone,re.S)
#
#
# for i in res:
# print('%s %s %s %s' %(i[0].strip(),i[1].strip(),i[2].strip(),i[3].strip()))