唐抉的个人博客

python学习笔记(十三)

字数统计: 3.8k阅读时长: 18 min
2022/10/24

常用内建模块

datetime

datetime是python处理时间和日期的标准库。

获取当前日期和时间

1
2
3
4
5
from datetime import datetime
now=datetime.now()
print(now)
#运行结果如下:
2022-10-13 16:01:58.422125

获取指定日期和时间

1
2
3
4
5
from datetime import datetime
dt=datetime(2022,4,19,12,22)
print(dt)
#运行结果如下:
2022-04-19 12:22:00

datetime转换为timestamp

在计算机中时间是用数字表示的,1970年1月1日 00:00:00 UTC+00:00时区的时刻称为epoch time,记为0,当前时间就是相对于epoch time的秒数称为timestamp。

1
2
3
4
5
from datetime import datetime
dt=datetime(2022,4,19,12,22)
print(dt.timestamp())
#运行结果如下:
1650342120.0

timestamp转换为datetime

1
2
3
4
5
from datetime import datetime
dt=1650342120.0
print(datetime.fromtimestamp(dt))
#运行结果如下:
2022-04-19 12:22:00

str转换为datetime

1
2
3
4
5
from datetime import datetime
dt=datetime.strptime('2022-04-19 12:22:00','%Y-%m-%d %H:%M:%S')
print(dt)
#运行结果如下:
2022-04-19 12:22:00

datetime转换为str

1
2
3
4
5
from datetime import datetime
now=datetime.now()
print(now.strftime('%a, %b %d %H:%M'))
#运行结果如下:
Thu, Oct 13 16:13

datetime加减

1
2
3
4
5
6
7
8
9
10
11
from datetime import datetime,timedelta
now=datetime.now()
print(now)
print(now+timedelta(hours=10))
print(now+timedelta(days=1))
print(now+timedelta(days=2,hours=12))
#运行结果如下:
2022-10-13 16:17:44.344550
2022-10-14 02:17:44.344550
2022-10-14 16:17:44.344550
2022-10-16 04:17:44.344550

本地时间转换为UTC时间

本地时间是指系统设定时区的时间,如北京时间是UTC+8:00,UTC时间是指UTC+0:00时区的时间。

1
2
3
4
5
6
7
8
9
from datetime import datetime,timedelta,timezone
tz_utc_8=timezone(timedelta(hours=8))
now=datetime.now()
print(now)
dt=now.replace(tzinfo=tz_utc_8)
print(dt)
#运行结果如下:
2022-10-13 16:25:07.688476
2022-10-13 16:25:07.688476+08:00

时区转换

1
2
3
4
5
6
7
8
from datetime import datetime,timedelta,timezone
utc_dt=datetime.utcnow().replace(tzinfo=timezone.utc)#拿到utc时间并设置时区为UTC+0:00
print(utc_dt)
bj_dt=utc_dt.astimezone(timezone(timedelta(hours=8)))#将转换时区为北京时间
print(bj_dt)
#运行结果如下:
2022-10-13 08:28:08.363061+00:00
2022-10-13 16:28:08.363061+08:00

collections

collections是Python内建的一个集合模块,提供了许多有用的集合类。

namedtuple

namedtuple是一个函数,用来创建一个自定义的tuple对象,并规定了tuple元素的个数,可以用属性而不是索引来引用tuple的某个元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from collections import namedtuple
Point=namedtuple('Point',['x','y'])
Circle=namedtuple('Circle',['x','y','r'])
p=Point(1,2)
c=Circle(1,2,3)
print(isinstance(p,Point))
print(isinstance(p,tuple))
print(p.x,p.y)
print(c.x,c.y,c.r)
#运行结果如下:
True
True
1 2
1 2 3

deque

deque可以高效实现插入和删除操作的双向列表,适合用于队列和栈:

1
2
3
4
5
6
7
from collections import deque 
q=deque(['a','b','c'])
q.append('x')
q.appendleft('y')
print(q)
#运行结果如下:
deque(['y', 'a', 'b', 'c', 'x'])

除此之外,还支持pop()、popleft()等方法,可以非常高效地往头部添加或删除元素。

defaultdict

使用dict时,若引用的Key不存在,就会抛出KeyError,若希望Key不存在时返回一个默认值,可以用defaultdict:

1
2
3
4
5
6
7
8
from collections import defaultdict
d=defaultdict(lambda:'N/A')
d['key1']='abc'
print(d['key1'])
print(d['key2'])
#运行结果如下:
abc
N/A

默认值是调用函数返回的,而函数在创建defaultdict对象时传入。

OrderedDict

使用dict时,Key是无序的。若要保持Key的顺序,可以用OrderedDict:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from collections import OrderedDict
d=dict([('c',1),('a',2),('b',3)])
print(d)
od=OrderedDict([('b',1),('a',2),('c',3)])
print(od)
odd=OrderedDict()
odd['x']=1
odd['z']=2
odd['y']=3
print(list(odd.keys()))#OrderedDict的Key按照插入的顺序排序,并非是Key自身的顺序,即odd是按插入值的前后排序,而不是根据'x'、'y'、'z'的顺序排

#运行结果如下:
{'c': 1, 'a': 2, 'b': 3}
OrderedDict([('b', 1), ('a', 2), ('c', 3)])
['x', 'z', 'y']

OrderedDict可以实现一个先进先出的dict,当容量超出限制时,先删除最早添加的Key。

ChainMap

ChainMap可以把一组dict串起来组成一个大的dict。但在查找数据时会按照顺序在内部的dict依次查找。

例如,使用ChainMap来实现参数的优先级查找,其顺序为:命令行参数>环境变量>默认参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from collections import ChainMap
from email.policy import default
import os,argparse
# 构造默认参数:
defaults={
'color':'red',
'user':'guest'
}
#构造命令行参数:
parser=argparse.ArgumentParser()
parser.add_argument('-u','--user')
parser.add_argument('-c','--color')
namespace=parser.parse_args()
command_line_args={k :v for k,v in vars(namespace).items() if v}
#组合成ChainMap:
combined=ChainMap(command_line_args,os.environ,defaults)

print('color=%s' % combined['color'])
print('user=%s' % combined['user'])

打开cmd,将路径切换到当前.py文件所在的目录,运行代码:

在没有任何参数传入时,打印出默认参数:

1
2
3
4
5
C:\Users\Administrator\Desktop>python learn.py

#运行结果如下:
color=red
user=guest

当传入命令行参数时,优先使用命令行参数:

1
2
3
4
C:\Users\Administrator\Desktop>python learn.py -u bob
#运行结果如下:
color=red
user=bob

当同时传入命令行参数和环境变量时,也是优先使用命令行参数。

Counter

Counter是一个简单的计数器,也是dict的一个子类,可以统计字符出现的个数:

1
2
3
4
5
6
7
8
9
10
from collections import Counter
c=Counter()
for ch in 'programming':
c[ch]=c[ch]+1
print(c)
c.update('hello')#更新数据
print(c)
#运行结果如下:
Counter({'r': 2, 'g': 2, 'm': 2, 'p': 1, 'o': 1, 'a': 1, 'i': 1, 'n': 1})
Counter({'r': 2, 'o': 2, 'g': 2, 'm': 2, 'l': 2, 'p': 1, 'a': 1, 'i': 1, 'n': 1, 'h': 1, 'e': 1})

urllib

urllib提供了一系列用于操作URL的功能。

Get

urllib的request模块可以非常方便地抓取URL内容,也就是发送一个GET请求到指定的页面,然后返回HTTP的响应。例如,对百度网址进行抓取,并返回响应::

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from urllib import request

with request.urlopen('https://baidu.com/') as f:
data = f.read()
print('Status:', f.status, f.reason)
for k, v in f.getheaders():
print('%s: %s' % (k, v))
print('Data:', data.decode('utf-8'))
#运行结果如下:
Status: 200 OK
Bdpagetype: 1
Bdqid: 0x8dfa7ad90002130f
Content-Type: text/html; charset=utf-8
Date: Mon, 24 Oct 2022 09:01:21 GMT
...
Data: <!DOCTYPE html><!--STATUS OK--><html><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta content="always" name="referrer"><meta name="theme-color" content="#ffffff"><meta name="description" content="全球领先的中文...

若要模拟浏览器发送GET请求,需要使用Request对象,通过往Request对象添加HTTP头,可以把请求伪装成浏览器。例如,模拟iPhone 6去请求豆瓣首页:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from urllib import request

req = request.Request('http://www.douban.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'))
#运行结果如下:
<!DOCTYPE html>
<html itemscope itemtype="http://schema.org/WebPage" class="ua-safari ua-mobile ">
<head>
<meta charset="UTF-8">
<title>豆瓣(手机版)</title>
<meta name="google-site-verification" content="ok0wCgT20tBBgo9_zat2iAcimtN4Ftf5ccsh092Xeyw" />...

Post

若要以POST发送一个请求,只需要把参数data以bytes形式传入。

例如模拟一个微博登录,先读取登录的邮箱和口令,然后按照weibo.cn的登录页的格式以username=xxx&password=xxx的编码传入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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'))

若登录成功,获得的响应如下:

1
2
3
4
5
6
Status: 200 OK
Server: nginx/1.2.0
...
Set-Cookie: SSOLoginState=1432620126; path=/; domain=weibo.cn
...
Data: {"retcode":20000000,"msg":"","data":{...,"uid":"1658384301"}}

若登录失败,获得的响应如下:

1
2
...
Data: {"retcode":50011002,"msg":"\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef","data":{"username":"123456@qq.com","errline":15}}

Handler

若需要更复杂的控制,比如通过一个Proxy去访问网站,需要利用ProxyHandler来处理,例如:

1
2
3
4
5
6
proxy_handler = urllib.request.ProxyHandler({'http': 'http://www.example.com:3128/'})
proxy_auth_handler = urllib.request.ProxyBasicAuthHandler()
proxy_auth_handler.add_password('realm', 'host', 'username', 'password')
opener = urllib.request.build_opener(proxy_handler, proxy_auth_handler)
with opener.open('http://www.example.com/login.html') as f:
pass

常用第三方模块

requests

Python内置的urllib模块,用于访问网络资源。但是,它用起来比较麻烦,而且,缺少很多实用的高级功能。

更好的方案是使用requests。它是一个Python第三方库,处理URL资源特别方便。

安装request:pip install requests

requests的使用

通过GET访问一个页面:

1
2
3
4
5
6
7
8
import requests 
r=requests.get('https://www.baidu.com/')
print(r.status_code)#访问状态码
print(r.text)#网页详情
#运行结果如下:
200
'''<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css ...'''

对于带参数的URL,传入一个dict作为params参数:

1
2
3
4
5
6
7
8
9
import requests 
r=requests.get('https://www.baidu.com/',params={'q':'python','cat':'1001'})
print(r.url)#实际请求的URL
print(r.encoding)#自动检测编码
print(r.content)#获得bytes对象
#运行结果如下:
https://www.baidu.com/?q=python&cat=1001
ISO-8859-1
'''b'<!DOCTYPE html>\r\n<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css ...'''

对于特定类型的响应,例如JSON,可以直接获取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests 
#获取JSON
r1=requests.get('https://p.3.cn/prices/mgets?skuIds=J_10026711061553')
#需要传入HTTP Header时,传入一个dict
r2=requests.get('https://douban.com/',headers={'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit'})
print(r1.json())#必须为JSON类型的网址
print(r2.text)

#运行结果如下:
[{'exception': '该接口即将下线,请联系(erp)wangjianyu1,liuhuimin9,liteng36;p.3.cn,null'}]


'''<!DOCTYPE html>
<html itemscope itemtype="http://schema.org/WebPage" class="ua-mobile ">
<head>
<meta charset="UTF-8">
<title>豆瓣(手机版)</title>
<meta name="google-site-verification" content="ok0wCgT20tBBgo9_zat2iAcimtN4Ftf5ccsh092Xeyw" /> ...'''

需要发送POST请求,只需要把get()方法变成post(),然后传入data参数作为POST请求的数据:

1
r=requests.post('https://accounts.douban.com/login', data={'form_email': 'abc@example.com', 'form_password': '123456'})

requests默认使用application/x-www-form-urlencoded对POST数据编码。如果要传递JSON数据,可以直接传入json参数:

1
2
params={'key': 'value'}
r=requests.post(url,json=params) # 内部自动序列化为JSON

类似的,上传文件需要更复杂的编码格式,但是requests把它简化成files参数:

1
2
upload_files={'file':open('report.xls','rb')}
r=requests.post(url,files=upload_files)

在读取文件时,注意务必使用'rb'即二进制模式读取,这样获取的bytes长度才是文件的长度。

requests对获取HTTP响应的其他信息也非常简单。例如,获取响应头:

1
2
3
4
5
print(r.headers)
print(r.headers['Content-Type'])
#运行结果如下:
{Content-Type': 'text/html; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Content-Encoding': 'gzip', ...}
'text/html; charset=utf-8'

requests对Cookie做了特殊处理,使得不必解析Cookie就可以轻松获取指定的Cookie:

1
2
3
print(r.cookies['ts'])
#运行结果如下:
'example_cookie_12345'

要在请求中传入Cookie,只需准备一个dict传入cookies参数:

1
2
cs={'token':'12345','status':'working'}
r=requests.get(url,cookies=cs)

最后,要指定超时,传入以秒为单位的timeout参数:

1
r=requests.get(url,timeout=2.5) # 2.5秒后超时

练习题

参考《OA统一待办接口协议》,使用requests访问统一待办接口,给自己生成一个待办,确认生成后,调用删除接口删除待办。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import requests 
import json
#基础数据
headers={'Content-Type':'application/json'}
url='GetToken的URL地址'#URL地址以http://开头,后接ip形式的url
url_create='CreateTask的URL地址'
url_del='DeleteTask的URL地址'
data={"app_id":"app_id的数值","app_secret":"app_secret的数值"}

#获取调用凭证access_token
r=requests.post(url,json=data)
token=json.loads(r.text)['access_token']

#生成待办事项
data_create={"access_token":token,"title":"20221025测试","task_segment":"报名",\
"task_url":"http://tangmenjue.top/","staff_id":"staff_id的数值"}
r_create=requests.post(url_create,json=data_create)
print(r_create.text)

r_del=requests.post(url,json=data)
task_id=json.loads(r_create.text)['task_id']
# task_id='17837ed5-48e4-4120-964a-477d258aaeb3'

#确认待办事项已生成后,调用删除接口删除待办。
data_del={"access_token":token,'task_id':task_id}
r_del=requests.post(url_del,json=data_del)
print(r_del.text)

chardet

字符串编码一直是令人非常头疼的问题,尤其是在处理一些不规范的第三方网页的时候。虽然Python提供了Unicode表示的strbytes两种数据类型,并且可以通过encode()decode()方法转换,但是,在不知道编码的情况下,对bytesdecode()不好做。

对于未知编码的bytes,要把它转换成str,需要先“猜测”编码。猜测的方式是先收集各种编码的特征字符,根据特征字符判断,就能有很大概率“猜对”,此时可以用chardet来检测编码。

venv

在开发Python应用程序的时候,系统安装的Python3只有一个版本:3.10。所有第三方的包都会被pip安装到Python3的site-packages目录下。

如果要同时开发多个应用程序,那这些应用程序都会共用一个Python,就是安装在系统的Python 3。如果应用A需要jinja 2.7,而应用B需要jinja 2.6怎么办?

这种情况下,每个应用可能需要各自拥有一套“独立”的Python运行环境。venv就是用来为一个应用创建一套“隔离”的Python运行环境。

CATALOG
  1. 1. 常用内建模块
    1. 1.1. datetime
      1. 1.1.1. 获取当前日期和时间
      2. 1.1.2. 获取指定日期和时间
      3. 1.1.3. datetime转换为timestamp
      4. 1.1.4. timestamp转换为datetime
      5. 1.1.5. str转换为datetime
      6. 1.1.6. datetime转换为str
      7. 1.1.7. datetime加减
      8. 1.1.8. 本地时间转换为UTC时间
      9. 1.1.9. 时区转换
    2. 1.2. collections
      1. 1.2.1. namedtuple
      2. 1.2.2. deque
      3. 1.2.3. defaultdict
      4. 1.2.4. OrderedDict
      5. 1.2.5. ChainMap
      6. 1.2.6. Counter
    3. 1.3. urllib
      1. 1.3.1. Get
      2. 1.3.2. Post
      3. 1.3.3. Handler
  2. 2. 常用第三方模块
    1. 2.1. requests
      1. 2.1.1. requests的使用
      2. 2.1.2. 练习题
    2. 2.2. chardet
  3. 3. venv