datetime是Python处理日期和时间的标准库。
获取当前日期时间
from datetime import datetime
now = datetime.now() # 获取当前datetime
print(now) #2023-09-13 10:28:48.621343
print(type(now))#
datetime是模块
,通过from datetime import datetime
导入的才是datetime这个类。
全名datetime.datetime
获取指定日期和时间
dt = datetime(2023, 9, 13, 12, 20) # 用指定日期时间创建datetime
print(dt)
datetime转换为timestamp
数字
表示的。我们把1970年1月1日 00:00:00 UTC+00:00
时区的时刻称为epoch time
,记为0(
1970年以前的时间timestamp为负数),当前时间就是相对于epoch time的秒数
,称为timestamp。dt = datetime(2023, 9, 13, 12, 20) # 用指定日期时间创建datetime
print(dt.timestamp()) # 把datetime转换为timestamp
#1694578800.0
浮点数,整数位表示秒
。timestamp也可以直接被转换到UTC标准时区的时间:
t = 1429417200.0
print(datetime.fromtimestamp(t)) # 本地时间
#2015-04-19 12:20:00
print(datetime.utcfromtimestamp(t)) # UTC时间
#2015-04-19 04:20:00
str转换为datetime
cday = datetime.strptime('2015-6-1 18:19:59', '%Y-%m-%d %H:%M:%S')
print(cday)
#2015-06-01 18:19:59
datetime转换为str
now = datetime.now()
print(now.strftime('%a, %b %d %H:%M'))
#Mon, May 05 16:28
datetime加减
datetime往后或往前计算
,得到新的datetime。加减可以直接用+和-运算符,不过需要导入timedelta这个类
:from datetime import datetime, timedelta
now = datetime.now()
datetime(2023, 9, 13, 10, 30, 3, 540997)
print(now + timedelta(hours=10))#2023-09-13 20:38:44.709003
datetime(2023, 9, 13, 10, 30, 3, 540997)
print(now - timedelta(days=1))#2023-09-12 10:38:44.709003
datetime(2023, 9, 13, 10, 30, 3, 540997)
print(now + timedelta(days=2, hours=12))#2023-09-12 10:38:44.709003
本地时间转换为UTC时间
from datetime import datetime, timedelta, timezone
tz_utc_8 = timezone(timedelta(hours=8)) # 创建时区UTC+8:00
now = datetime.now()
print(now)
dt = now.replace(tzinfo=tz_utc_8) # 强制设置为UTC+8:00
print(dt)
dt = datetime(2015, 9, 13, 10, 40, 13, 610986, tzinfo=timezone(timedelta(0, 28800)))
print(dt)
时区转换
from datetime import datetime, timedelta, timezone
# 拿到UTC时间,并强制设置时区为UTC+0:00:
utc_dt = datetime.utcnow().replace(tzinfo=timezone.utc)
print(utc_dt)
# astimezone()将转换时区为北京时间:
bj_dt = utc_dt.astimezone(timezone(timedelta(hours=8)))
print(bj_dt)
# astimezone()将转换时区为东京时间:
tokyo_dt = utc_dt.astimezone(timezone(timedelta(hours=9)))
print(tokyo_dt)
# astimezone()将bj_dt转换时区为东京时间:
tokyo_dt2 = bj_dt.astimezone(timezone(timedelta(hours=9)))
print(tokyo_dt2)
小结
datetime表示的时间需要时区信息才能确定一个特定的时间,否则只能视为本地时间。
如果要存储datetime,最佳方法是将其转换为timestamp再存储,因为timestamp的值与时区完全无关
。
Base64是一种任意二进制转换文本字符串的编码方法,常用于在URL、Cookie、网页中传输少量二进制数据
。
Base64的原理很简单,首先,准备一个包含64个字符的数组:
['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']
然后,对二进制数据进行处理,每3个字节一组
,一共是3x8=24bit
,划为4组,每组正好6个bit
:
这样我们得到4个数字作为索引,然后查表
,获得相应的4个字符,就是编码后的字符串。
所以,Base64编码会把3字节的二进制数据编码为4字节的文本数据,长度增加33%
,好处是编码后的文本数据可以在邮件正文、网页等直接显示。
如果要编码的二进制数据不是3的倍数,最后会剩下1个或2个字节怎么办?
\x00字节
在末尾补足后,再在编码的末尾上1个或2个=号,
表示补了多少字节,解码的时候,会自动去掉
。Python内置的base64可以直接进行base64的编解码:
import base64
#`b'str'`可以表示字节,
a = base64.b64encode(b'binary\x00string')
print(a)
b = base64.b64decode(b'YmluYXJ5AHN0cmluZw==')
print(b)
#b'YmluYXJ5AHN0cmluZw=='
#b'binary\x00string'
b'str'
可以表示字节,由于标准Base64编码后可能出现字符+和/
,在URL中就不能直接作为参数,所以又有一种"url safe"
的base64编码,其实就是把字符+和/分别变成-和_
:
#`b'str'`可以表示字节,
c= base64.b64encode(b'i\xb7\x1d\xfb\xef\xff')
print(c)#b'abcd++//'
d = base64.urlsafe_b64encode(b'i\xb7\x1d\xfb\xef\xff')
print(d)#b'abcd++//'
e = base64.urlsafe_b64decode('abcd--__')
print(e)#b'abcd++//'
Python的hashlib提供了常见的摘要算法,如MD5,SHA1
等等。
**什么是摘要算法呢?
**摘要算法又称哈希算法、散列算法
。摘要算法就是通过摘要函数f()
对任意长度的数据data
计算出固定长度的摘要digest
,目的是为了发现原始数据是否被人篡改过。(通常用16进制的字符串表示)。
摘要算法之所以能指出数据是否被篡改过
单向函数
,计算f(data)
很容易,但通过digest反推data却非常困难
。而且,对原始数据做一个bit的修改,都会导致计算出的摘要完全不同。应用场景
2d73d4f15c0db7f5ecb321b6a65e5d6d
’。如果有人篡改了你的文章,并发表为’how to use python hashlib - by Bob
’,你可以一下子指出Bob篡改了你的文章,因为根据’how to use python hashlib - by Bob
’计算出的摘要不同于原始文章的摘要。MD5是最常见的摘要算法,速度很快,生成结果是固定的128 bit/16字节
,通常用一个32位的16进制字符串
表示。如下所示
import hashlib
md5 = hashlib.md5()
md5.update('how to use md5 in python hashlib?'.encode('utf-8'))
print(md5.hexdigest())
#d26a53750bc40b38b65a520292f69306
如果数据量很大,可以分块多次调用update()
,最后计算的结果是一样的:
import hashlib
md5 = hashlib.md5()
md5.update('how to use md5 in '.encode('utf-8'))
md5.update('python hashlib?'.encode('utf-8'))
print(md5.hexdigest())
#d26a53750bc40b38b65a520292f69306
另一种常见的摘要算法是SHA1
,调用SHA1和调用MD5完全类似:SHA1的结果是160 bit/20字节
,通常用一个40位的16进制字符串
表示。
import hashlib
sha1 = hashlib.sha1()
sha1.update('how to use sha1 in '.encode('utf-8'))
sha1.update('python hashlib?'.encode('utf-8'))
print(sha1.hexdigest())
#2c76b57293ce30acef38d98f6046927161b46a44
比SHA1更安全的算法是SHA256和SHA512
,不过越安全的算法不仅越慢,而且摘要长度更长
。
通过哈希算法,我们可以验证一段数据是否有效,方法就是对比该数据的哈希值,例如,判断用户口令是否正确,我们用保存在数据库中的password_md5
对比计算md5(password)
的结果,如果一致,用户输入的口令就是正确的。
为了防止黑客通过彩虹表
根据哈希值
反推原始口令,在计算哈希的时候,不能仅针对原始输入计算,需要增加一个salt
来使得相同的输入也能得到不同的哈希,这样,大大增加了黑客破解的难度。
如果salt是我们自己随机生成的,通常我们计算MD5时采用md5(message + salt)
。但实际上,把salt看做一个“口令”,加salt的哈希就是:计算一段message的哈希时,根据不同口令计算出不同的哈希。要验证哈希值,必须同时提供正确的口令。
这实际上就是Hmac算法
:Keyed-Hashing for Message Authentication。它通过一个标准算法,在计算哈希的过程中,把key混入计算过程中
。
和我们自定义的加salt算法不同,Hmac算法针对所有哈希算法都通用,无论是MD5还是SHA-1
。采用Hmac替代我们自己的salt算法,可以使程序算法更标准化,也更安全。
Python自带的hmac模块实现了标准的Hmac算法。我们来看看如何使用hmac实现带key的哈希。
import hmac
#原始数据
message = b'Hello, world!'
#密钥
key = b'secret'
h = hmac.new(key, message, digestmod='MD5')
# 如果消息很长,可以多次调用h.update(msg)
print(h.hexdigest())
#'fa4ee7d173f2d97ee79022d1a7355bcf'
bytes类型,str类型需要首先编码为bytes
。详见【Python】从入门到上头—网络请求模块urlib和reuests的应用场景(12)
操作XML有两种方法:DOM和SAX
。
DOM会把整个XML读入内存,解析为树,因此占用内存大,解析慢
,优点是可以任意遍历树的节点
。
SAX是流模式
,边读边解析,占用内存小,解析快
,缺点是我们需要自己处理事件
。
正常情况下,优先考虑SAX,因为DOM实在太占内存。
在Python中使用SAX解析XML非常简洁,通常我们关心的事件是start_element,end_element和char_data
,准备好这3个函数,然后就可以解析xml了。
如: 当SAX解析器读到一个节点时:
<a href="/">python</a>
会产生3个事件:
char_data事件,在读取python
时;
end_element事件,在读取时。
from xml.parsers.expat import ParserCreate
class DefaultSaxHandler(object):
def start_element(self, name, attrs):
print('sax:start_element: %s, attrs: %s' % (name, str(attrs)))
def end_element(self, name):
print('sax:end_element: %s' % name)
def char_data(self, text):
print('sax:char_data: %s' % text)
xml = r'''
'''
handler = DefaultSaxHandler()
parser = ParserCreate()
#start_element事件
parser.StartElementHandler = handler.start_element
#end_element事件
parser.EndElementHandler = handler.end_element
#char_data事件
parser.CharacterDataHandler = handler.char_data
#解析
parser.Parse(xml)
执行结果
sax:start_element: ol, attrs: {}
sax:char_data:
sax:char_data:
sax:start_element: li, attrs: {}
sax:start_element: a, attrs: {'href': '/python'}
sax:char_data: Python
sax:end_element: a
sax:end_element: li
sax:char_data:
sax:char_data:
sax:start_element: li, attrs: {}
sax:start_element: a, attrs: {'href': '/ruby'}
sax:char_data: Ruby
sax:end_element: a
sax:end_element: li
sax:char_data:
sax:end_element: ol
CharacterDataHandler
可能被多次调用,所以需要自己保存起来
,在EndElementHandler
里面再合并。除了解析XML外,如何生成XML呢?
99%的情况下需要生成的XML结构都是非常简单的,因此,最简单也是最有效的生成XML的方法是拼接字符串
:
L = []
L.append(r'')
L.append(r'' )
L.append(encode('some & data'))
L.append(r'')
return ''.join(L)
如果我们要编写一个搜索引擎,第一步是用爬虫把目标网站的页面抓下来,第二步就是解析该HTML页面,看看里面的内容到底是新闻、图片还是视频。
HTML本质上是XML的子集
,但是HTML的语法
没有XML那么严格,所以不能用标准的DOM或SAX
来解析HTML。
Python提供了HTMLParser
来非常方便地解析HTML,只需简单几行代码:
from html.parser import HTMLParser
from html.entities import name2codepoint
class MyHTMLParser(HTMLParser):
def handle_starttag(self, tag, attrs):
print('<%s>' % tag)
def handle_endtag(self, tag):
print('%s>' % tag)
def handle_startendtag(self, tag, attrs):
print('<%s/>' % tag)
def handle_data(self, data):
print(data)
def handle_comment(self, data):
print('')
def handle_entityref(self, name):
print('&%s;' % name)
def handle_charref(self, name):
print('%s;' % name)
parser = MyHTMLParser()
parser.feed('''
Some html HTML tutorial...
END
''')
feed()方法可以多次调用
,也就是不一定一次把整个HTML字符串都塞进去,可以一部分一部分塞进去。
特殊字符有两种,一种是英文表示的
,一种是数字表示的Ӓ
,这两种字符都可以通过Parser
解析出来。
Python random 模块主要用于生成随机数。实现了各种分布的伪随机数生成器。
常用方法
andom() 生成一个 [0.0, 1.0) 之间的随机小数
seed(seed) 初始化给定的随机数种子
randint(a, b) 生成一个 [a, b] 之间的随机整数
uniform(a, b) 生成一个 [a, b] 之间的随机小数
choice(seq) 从序列 seq 中随机选择一个元素
shuffle(seq) 将序列 seq 中元素随机排列, 返回打乱后的序列
random.random()
import random
print(random.random())
#0.4784904215869241
**random.seed(seed) **
初始化给定的随机数种子
计算机使用确定性的算法计算出一个随机数序列。计算机产生的随机数并不真正的随机,但具有类似于随机数的统计特征,如均匀性、独立性等
。
计算机根据随机数种子产生随机数序列,如果随机数种子相同,每次产生的随机数序列是相同的
;如果随机数种子不同,产生的随机数序列是不同的。
random.seed(10)
a = random.randint(0, 100)
print(a)
a = random.randint(0, 100)
print(a)
a = random.randint(0, 100)
print(a)
# 73
# 4
# 54
random.seed(10)
a = random.randint(0, 100)
print(a)
a = random.randint(0, 100)
print(a)
a = random.randint(0, 100)
print(a)
# 73
# 4
# 54
第1个random.seed(10)设定种子为 10
产生第 1 个随机数 73
产生第 2 个随机数 4
产生第 3 个随机数 54
第2个random.seed(10)设定种子为 10
产生第 1 个随机数 73
产生第 2 个随机数 4
产生第 3 个随机数 54
可以看出,当种子相同时,产生的随机数序列是相同的
random.randint(a, b)
生成一个 [a, b] 之间的随机整数,示例如下:
a = random.randint(0, 2)
print(a)
a = random.randint(0, 2)
print(a)
a = random.randint(0, 2)
print(a)
# 1
# 2
# 0
random.uniform(a, b)
import random
random.uniform(0, 2)
#0.20000054219225438
random.uniform(0, 2)
#1.4472780206791538
random.uniform(0, 2)
#0.5927807855738692
random.choice(seq)
从序列 seq 中随机选择一个元素
import random
seq = [1, 2, 3, 4]
random.choice(seq)
#3
random.choice(seq)
#1
random.shuffle(seq)
将序列 seq 中元素随机排列, 返回打乱后的序列
import random
seq = [1, 2, 3, 4]
random.shuffle(seq)
#[1, 3, 2, 4]
文本、图像
等解析出来。