(一)、 datetime-日期和时间模块
Python处理日期和时间的标准库
获取当前日期和时间,获取指定日期和时间:
from datetime import datetime
# 获取当前日期和时间
now_date = datetime.now()
print(now_date)
print(type(now_date))
# 获取指定日期和时间
d_time = datetime(2020,3,6,12,18)
print(d_time)
2020-03-06 14:17:14.286800
<class 'datetime.datetime'>
2020-03-06 12:18:00
1、 datetime\timestamp互转
在计算机中,时间实际上是用数字表示的。我们把1970年1月1日 00:00:00 UTC+00:00时区的时刻称为epoch time,记为 0
(1970年以前的时间timestamp为负数),当前时间就是相对于epoch time的秒数,称为timestamp。Python的timestamp是一个浮点数。如果有小数位,小数位表示毫秒数。
d_time = datetime(2020,3,6,12,18)
print(d_time.timestamp())
'''
1583468280.0
'''
t = 1583462280.0
print(datetime.fromtimestamp(t))
'''
2020-03-06 10:38:00
'''
2、str/datetime互转
# str-datetime
obj_time = datetime.strptime('2020-09-11 14:36:01', '%Y-%m-%d %H:%M:%S')
print(obj_time)
print(type(obj_time))
now_time = datetime.now()
str_time = now_time.strftime('%Y-%m-%d %H:%M:%S')
print(str_time)
'''
2020-09-11 14:36:01
<class 'datetime.datetime'>
2020-03-06 14:48:42
'''
3、 datetime加减
对日期和时间进行加减实际上就是把datetime
后或往前计算,得到新的datetime
。加减可以直接用+和-
运算符。
from datetime import datetime, timedelta
now_a = datetime.now()
print(now_a)
print(now_a+timedelta(days=1))
print(now_a+timedelta(days=1,hours=10,minutes=20))
2020-03-09 09:31:52.615500
2020-03-10 09:31:52.615500
2020-03-10 19:51:52.615500
4、 本地时间转换为UTC时间
本地时间是指系统设定时区的时间,例如北京时间是UTC+8:00时区的时间,而UTC时间指UTC+0:00时区的时间。
一个datetime
类型有一个时区属性tzinfo
,但是默认为None
,所以无法区分这个datetime
到底是哪个时区,除非强行给datetime
设置一个时区。·
from datetime import datetime, timedelta, timezone
t_utc8 = timezone(timedelta(hours=8)) # 时区UTC+8:00
now_b = datetime.now()
print(now_b)
dt = now_b.replace(tzinfo=t_utc8)
print(dt)
2020-03-09 09:31:52.615500
2020-03-09 09:31:52.615500+08:00
5、 时区转换
先通过utcnow()
拿到当前的UTC时间,再转换为任意时区的时间:
from datetime import datetime, timedelta, timezone
# 获取UTC时间,强制设置时区为UTC+0:00
utc_dt = datetime.utcnow().replace(tzinfo=timezone.utc)
print(utc_dt)
# 转换时区为北京时间
bj_dt = utc_dt.astimezone(timezone(timedelta(hours=8)))
print(bj_dt)
# 转换时区为东京时间
dj_dt = utc_dt.astimezone(timezone(timedelta(hours=9)))
print(dj_dt)
2020-03-09 01:47:44.125500+00:00
2020-03-09 09:47:44.125500+08:00
2020-03-09 10:47:44.125500+09:00
from datetime import datetime, timedelta, timezone
import re
# 获取了用户输入的日期和时间如2020-3-18 9:01:30,以及一个时区信息如UTC+5:00,均是str,请编写一个函数将其转换为timestamp
def to_timestamp(dt_str, tz_str):
# 获取时间
r_time = datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S')
# 获取时区
utc = re.match(r'^UTC([\+\-])(\d{1,2})\:00$', tz_str)
if utc[1] == '+':
t_utc = int(utc[2])
else:
t_utc = -int(utc[2])
# 强制设置时区
utc_n = r_time.replace(tzinfo=timezone(timedelta(hours=t_utc))).astimezone(timezone.utc)
# 返回时间戳
return utc_n.timestamp()
print(to_timestamp('2020-3-18 9:01:30', 'UTC+5:00'))
(二)、collections-集合模块
collections
是Python内建的一个集合模块,提供了许多有用的集合类。
1、namedtuple
用来创建一个自定义的tuple
对象,并且规定了tuple
元素的个数,并可以用属性而不是索引来引用tuple
的某个元素。
from collections import namedtuple
Pit = namedtuple('Pit',['x','y','z'])
p = Pit(1,2,3)
print(p)
print(p.x,p.y,p.z)
2、deque
使用list
存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了,因为list是线性存储,数据量大的时候,插入和删除效率很低。deque
是为了高效实现插入和删除操作的双向列表,适合用于队列和栈:
from collections import deque
q = deque(['a', 'b', 'c'])
q.append('d')
q.appendleft('x')
print(q)
3、defaultdict
使用dict
时,如果引用的Key
不存在,就会抛出KeyError
。如果希望key
不存在时,返回一个默认值,就可以用defaultdict
:
from collections import defaultdict
dd = defaultdict(lambda :'N/A')
dd['key1'] = 'abc'
print(dd['key1'])
print(dd['key2'])
4、Counter
Counter
是一个简单的计数器,统计字符出现的个数:
from collections import Counter
c = Counter('dasdasdf asda')
print(c)
c = Counter()
c.update('asffwerr fdsfs')
print(c)
Counter({'d': 4, 'a': 4, 's': 3, 'f': 1, ' ': 1})
Counter({'f': 4, 's': 3, 'r': 2, 'a': 1, 'w': 1, 'e': 1, ' ': 1, 'd': 1})
(三)、base64
Python内置的base64可以直接进行base64的编解码,Base64是一种任意二进制到文本字符串的编码方法,常用于在URL、Cookie、网页中传输少量二进制数据
import base64
# 编码
s = base64.b64encode(b'binary\x00string')
print(s)
# 解码
b = base64.b64decode(b'YmluYXJ5AHN0cmluZw==')
print(b)
标准的Base64
编码后可能出现字符+
和/
,在URL
中就不能直接作为参数,所以又有一种"url safe"的base64
编码,其实就是把字符+
和/
分别变成-
和_
:
d = base64.b64encode(b'i\xb7\x1d\xfb\xef\xff')
print(d)
f = base64.urlsafe_b64encode(b'i\xb7\x1d\xfb\xef\xff')
print(f)
rf = base64.urlsafe_b64decode('abcd--__')
print(rf)
Base64
是一种通过查表的编码方法,不能用于加密,即使使用自定义的编码表也不行。
Base64
适用于小段内容的编码,比如数字证书签名、Cookie
的内容等。
由于=
字符也可能出现在Base64
编码中,但=
用在URL
、Cookie
里面会造成歧义,所以,很多Base64
编码后会把=
去掉,Base64
编码的长度永远是4
的倍数,因此,需要加上=
把Base64
字符串的长度变为4
的倍数,就可以正常解码
(四)、struct
Python提供了一个struct
模块来解决bytes
和其他二进制数据类型的转换。struct
的pack
函数把任意数据类型变成bytes
import struct
i = struct.pack('>I',10240099)
print(i)
'''
b'\x00\x9c@c'
'''
>I
的意思是:>
表示字节顺序是big-endian
,也就是网络序,I
表示4
字节无符号整数。后面的参数个数要和处理指令一致。
unpack
把bytes
变成相应的数据类型:
import struct
s = struct.unpack('>IH',b'\xf0\xf0\xf0\xf0\x80\x80')
print(s)
'''
(4042322160, 32896)
'''
I
:4
字节无符号整数 H
:2
字节无符号整数
(五)、hashlib
hashlib
提供了常见的摘要算法,如MD5
,SHA1
等等。摘要算法就是通过摘要函数f()
对任意长度的数据data
计算出固定长度的摘要digest
,目的是为了发现原始数据是否被人篡改过。
摘要算法MD5
:
import hashlib
md5 = hashlib.md5()
md5.update('how to use md5 in python hashlib?'.encode('utf-8'))
print(md5.hexdigest())
'''
d26a53750bc40b38b65a520292f69306
'''
摘要算法是SHA1
:
import hashlib
sha1 = hashlib.sha1()
sha1.update('hhhhhhsdadw'.encode('utf-8'))
sha1.update('dawrqr qrqr?'.encode('utf-8'))
print(sha1.hexdigest())
'''
f14475f43f4cb8d5f86b1f0ce89d9ec7369ab600
'''
练习1:
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import hashlib
db = {
'michael': 'e10adc3949ba59abbe56e057f20f883e',
'bob': '878ef96e86145580c38c87f0410ad153',
'alice': '99b1c2188db85afee403b1536010c2c9'
}
def login(user, password):
return db[user] == hashlib.md5(password.encode('utf-8')).hexdigest()
# 测试
assert login('michael', '123456')
assert login('bob', 'abc999')
assert login('alice', 'alice2008')
assert not login('michael', '1234567')
assert not login('bob', '123456')
assert not login('alice', 'Alice2008')
print('作业一ok')
练习2:
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import hashlib, random
def get_md5(pwd):
return hashlib.md5(pwd.encode('utf-8')).hexdigest()
class User():
def __init__(self, uname, pwd):
self.uname = uname
self.salt = ''.join([chr(random.randint(48, 122)) for i in range(20)])
self.pwd = get_md5(pwd + self.salt)
db = {
'michael': User('michael', '123456'),
'bob': User('bob', 'abc999'),
'alice': User('alice', 'alice2008')
}
def login(uname, pwd):
user = db[uname]
return user.pwd == get_md5(pwd + user.salt)
# 测试:
assert login('michael', '123456')
assert login('bob', 'abc999')
assert login('alice', 'alice2008')
assert not login('michael', '1234567')
assert not login('bob', '123456')
assert not login('alice', 'Alice2008')
print('ok')
(六)、hmac
Hmac
算法:Keyed-Hashing for Message Authentication。它通过一个标准算法,在计算哈希的过程中,把key
混入计算过程中。Python自带的hmac
模块实现了标准的Hmac
算法。
import hmac
message = b'Hello,Python!'
key = b'secret'
h = hmac.new(key, message, digestmod='MD5')
print(h.hexdigest())
'''
e18f0514cec1694534724ea2eff6d401
'''
使用hmac
和普通hash
算法非常类似。hmac
输出的长度和原始哈希算法的长度一致。需要注意传入的key
和message
都是bytes
类型,str
类型需要首先编码为bytes
。
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import hmac, random
def hmac_md5(key, s):
return hmac.new(key.encode('utf-8'), s.encode('utf-8'), 'MD5').hexdigest()
class User():
def __init__(self, uname, pwd):
self.uname = uname
self.key = ''.join([chr(random.randint(48, 122)) for i in range(20)])
self.pwd = hmac_md5(self.key, pwd)
db = {
'michael': User('michael', '123456'),
'bob': User('bob', 'abc999'),
'alice': User('alice', 'alice2008')
}
def login(uname, pwd):
user = db[uname]
return user.pwd == hmac_md5(user.key, pwd)
# 测试:
assert login('michael', '123456')
assert login('bob', 'abc999')
assert login('alice', 'alice2008')
assert not login('michael', '1234567')
assert not login('bob', '123456')
assert not login('alice', 'Alice2008')
print('ok')
(七)、itertools
模块itertools
提供了非常有用的用于操作迭代对象的函数
count()
会创建一个无限的迭代器
repeat()
负责把一个元素无限重复下去,第二个参数可以限定重复次数
takewhile()
等函数根据条件判断来截取出一个有限的序列
import itertools
# count()会创建一个无限的迭代器
'''
ns = itertools.count(1)
for n in ns:
print(n)
'''
# cycle()会把传入的一个序列无限重复下去
'''
cs = itertools.cycle('ABC')
for c in cs:
print(c)
'''
# repeat()负责把一个元素无限重复下去,不提供第二个参数就可以限定重复次数
rs = itertools.repeat('A', 4)
for r in rs:
print(r)
# takewhile()函数根据条件判断来截取出一个有限的序列
ns = itertools.count(1)
n = itertools.takewhile(lambda x: x <= 10, ns)
print(list(n))
import itertools
# count()会创建一个无限的迭代器
'''
ns = itertools.count(1)
for n in ns:
print(n)
'''
# cycle()会把传入的一个序列无限重复下去
'''
cs = itertools.cycle('ABC')
for c in cs:
print(c)
'''
# repeat()负责把一个元素无限重复下去,不提供第二个参数就可以限定重复次数
rs = itertools.repeat('A', 4)
for r in rs:
print(r)
'''
A
A
A
A
'''
# takewhile()函数根据条件判断来截取出一个有限的序列
ns = itertools.count(1)
n = itertools.takewhile(lambda x: x <= 10, ns)
print(list(n))
'''
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
'''
# chain()可以把一组迭代对象串联起来,形成一个更大的迭代器
for c in itertools.chain('abc', 'xyz'):
print(c)
'''
a
b
c
x
y
z
'''
# groupby()把迭代器中相邻的重复元素挑出来放在一起
for key, group in itertools.groupby('aaabbbcccaaassAaBbb',lambda c: c.upper()):
print(key, list(group))
'''
A ['a', 'a', 'a']
B ['b', 'b', 'b']
C ['c', 'c', 'c']
A ['a', 'a', 'a']
S ['s', 's']
A ['A', 'a']
B ['B', 'b', 'b']
'''
练习:
# -*- coding: utf-8 -*-
import itertools
from functools import reduce
def pi(N):
'计算pi值'
# 1、创建奇数序列
odd = itertools.count(1, 2)
# 2、取前N项
lodd = itertools.takewhile(lambda x: x <= 2 * N - 1, odd)
# 3、添加正负号 并用4除
nums = itertools.cycle((4, -4))
s = [nums.__next__() / i for i in lodd]
# 4、求和
sum = reduce(lambda x, y: x + y, s)
return sum
# 测试:
print(pi(10))
print(pi(100))
print(pi(1000))
print(pi(10000))
assert 3.04 < pi(10) < 3.05
assert 3.13 < pi(100) < 3.14
assert 3.140 < pi(1000) < 3.141
assert 3.1414 < pi(10000) < 3.1415
print('ok')
itertools
模块提供的全部是处理迭代功能的函数,它们的返回值不是list
,而是Iterator
,只有用for
循环迭代的时候才真正计算
(八)、contextlib
Python的with
语句允许我们非常方便地使用资源,而不必担心资源没有关闭。@contextmanager
这个decorator
接受一个generator
,用yield
语句把with ... as var
把变量输出出去,然后with
语句就可以正常地工作。@contextmanager
让我们通过编写generator
来简化上下文管理。
from contextlib import contextmanager
class Query():
def __init__(self, name):
self.name = name
def query(self):
print('Query info about %s...' % self.name)
@contextmanager
def create_query(name):
print('开始')
q = Query(name)
yield q
print('结束')
with create_query('bob') as q:
q.query()
'''
开始
Query info about bob...
结束
'''
在某段代码执行前后自动执行特定代码,也可以用@contextmanager
实现
@contextmanager
def tag(name):
print('<%s>' % name)
yield
print('</%s>' % name)
with tag('h1'):
print('hello')
print('python')
'''
<h1>
hello
python
</h1>
'''
执行顺序是:
with
语句首先执行yield
前的语句,因此打印出<h1>
;yield
调用会执行with
语句内部的所有语句,因此打印出hello和world;- 最后执行
yield
之后的语句,打印出</h1>
。
如果一个对象没有实现上下文,就不能把它用于with
语句。可以用closing()
来把该对象变为上下文对象。例如,用with
语句使用urlopen()
。
from contextlib import closing
from urllib.request import urlopen
with closing(urlopen('https://www.python.org')) as page:
for line in page:
print(line)
closing
是一个经过@contextmanager
装饰的generator
,这个generator
编写起来其实非常简单:
@contextmanager
def closing(thing):
try:
yield thing
finally:
thing.close()
(九)、urllib
urllib
提供了一系列用于操作URL
的功能
- get
urllib
的request
模块可以非常方便地抓取URL
内容,也就是发送一个GET
请求到指定的页面,然后返回HTTP
的响应:
例如,对知乎的一个http://news-at.zhihu.com/api/4/news/latest
进行抓取,并返回响应
from urllib import request
with request.urlopen('http://news-at.zhihu.com/api/4/news/latest') as f:
datas = f.read()
print('状态:', f.status, f.reason)
for k, v in f.getheaders():
print('%s:%s' % (k, v))
print('数据:', datas.decode('utf-8'))
状态: 200 OK
Server:Tengine
Content-Type:application/json; charset=UTF-8
Content-Length:2682
Connection:close
Date:Tue, 10 Mar 2020 07:12:49 GMT
Set-Cookie:tgw_l7_route=116a747939468d99065d12a386ab1c5f; Expires=Tue, 10-Mar-2020 07:27:49 GMT; Path=/
Vary:Accept-Encoding
Etag:"1a79ee3d3f912b41a4e4b189da6c2e88edd0df1a"
X-Backend:zhihu-daily-web--1-e2c01739-645cb8d756-kpkh5
X-Backend-Response:0.014
X-SecNG-Response:0.026000022888184
Set-Cookie:_xsrf=BmhAxiCPgLLtxu8ltktSmOfGN9p8OsMX; path=/; domain=zhihu.com; expires=Sat, 27-Aug-22 07:12:49 GMT
x-lb-timing:0.025
x-idc-id:1
Via:cache33.l2cn1828[280,0], vcache11.cn2450[285,0]
Timing-Allow-Origin:*
EagleId:75bb159f15838243689427639e
数据: {"date":"20200310","stories":[{"image_hue":"0x4d3b36","title":"贪腐、失控、倒逼工作室,顶流团队真能管好粉圈吗?","url":"https:\/\/daily.zhihu.com\/story\/9721372","hint":"娱理工作室 · 3 分钟阅读","ga_prefix":"031011","images":["https:\/\/pic2.zhimg.com\/v2-5c7068cccefbbfdd00b27d2667f26e95.jpg"],"type":0,"id":9721372},{"image_hue":"0x5c928e","title":"如果你是天美的运营官,你会如何优化《王者荣耀》?","url":"https:\/\/daily.zhihu.com\/story\/9721362","hint":"Joker · 4 分钟阅读","ga_prefix":"031009","images":["https:\/\/pic4.zhimg.com\/v2-d49cfd2d89adef0cc587f6602c2f11b3.jpg"],"type":0,"id":9721362},....}]}
模拟浏览器发送GET
请求,就需要使用Request
对象,通过往Request
对象添加HTTP
头,我们就可以把请求伪装成浏览器。例如,模拟iPhone 6去请求baidu首页
from urllib import request
req = request.Request('https://www.baidu.com/')
req.add_header('User-Agent',
'Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25')
with request.urlopen(req) as f:
print('Status:', f.status, f.reason)
for k, v in f.getheaders():
print('%s:%s' % (k, v))
print('Data:', f.read().decode('utf-8'))
- Post
如果要以POST
发送一个请求,只需要把参数data
以bytes
形式传入。
我们模拟一个微博登录,先读取登录的邮箱和口令,然后按照weibo.cn的登录页的格式以username=xxx&password=xxx的编码传入:
from urllib import request, parse
print('Login to weibo.cn...')
email = input('Email: ')
passwd = input('Password: ')
login_data = parse.urlencode([
('username', email),
('password', passwd),
('entry', 'mweibo'),
('client_id', ''),
('savestate', '1'),
('ec', ''),
('pagerefer', 'https://passport.weibo.cn/signin/welcome?entry=mweibo&r=http%3A%2F%2Fm.weibo.cn%2F')
])
req = request.Request('https://passport.weibo.cn/sso/login')
req.add_header('Origin', 'https://passport.weibo.cn')
req.add_header('User-Agent', 'Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25')
req.add_header('Referer', 'https://passport.weibo.cn/signin/login?entry=mweibo&res=wel&wm=3349&r=http%3A%2F%2Fm.weibo.cn%2F')
with request.urlopen(req, data=login_data.encode('utf-8')) as f:
print('Status:', f.status, f.reason)
for k, v in f.getheaders():
print('%s: %s' % (k, v))
print('Data:', f.read().decode('utf-8'))
登录成功:
Status: 200 OK
Server: nginx/1.2.0
...
Set-Cookie: SSOLoginState=1432620126; path=/; domain=weibo.cn
...
Data: {"retcode":20000000,"msg":"","data":{...,"uid":"1658384301"}}
登录失败:
...
Data: {"retcode":50011015,"msg":"\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef","data":{"username":"example@python.org","errline":536}}
(十)、HTMLParser
from html.parser import HTMLParser
# from html.entities import name2codepoint
from urllib import request
import re
class MyHTMLParser(HTMLParser):
def __init__(self):
super(MyHTMLParser,self).__init__()
self.__parsedata='' # 设置一个空的标志位
def handle_starttag(self, tag, attrs):
if ('class', 'event-title') in attrs:
self.__parsedata = 'name' # 通过属性判断如果该标签是我们要找的标签,设置标志位
if tag == 'time':
self.__parsedata = 'time'
if ('class', 'say-no-more') in attrs:
self.__parsedata = 'year'
if ('class', 'event-location') in attrs:
self.__parsedata = 'location'
def handle_endtag(self, tag):
self.__parsedata = ''# 在HTML 标签结束时,把标志位清空
def handle_data(self, data):
if self.__parsedata == 'name':
print('会议名称:%s' % data) # 通过标志位判断,输出打印标签内容
if self.__parsedata == 'time':
print('会议时间:%s' % data)
if self.__parsedata == 'year':
if re.match(r'\s\d{4}', data): # 因为后面还有两组 say-no-more 后面的data却不是年份信息,所以用正则检测一下
print('会议年份:%s' % data.strip())
if self.__parsedata == 'location':
print('会议地点:%s' % data)
print('----------------------------------')
parser = MyHTMLParser()
URL = 'https://www.python.org/events/python-events/'
with request.urlopen(URL, timeout=15) as f: # 打开网页并取到数据
data = f.read()
parser.feed(data.decode('utf-8'))