python3学习

Python3(12) Python 常用内建模块

2018-02-07  本文已影响123人  猿来如痴

本系列主要学习Python的基本使用和语法知识,后续可能会围绕着AI学习展开。
Python3 (1) Python语言的简介
Python3 (2) Python语法基础
Python3 (3) Python函数
Python3 (4) Python高级特性
Python3 (5) Python 函数式编程
Python3 (6) Python 模块
Python3 (7) Python 面向对象编程
Python3 (8) Python 面向对象高级编程
Python3 (9) Python 错误、调试和测试
Python3 (10) Python IO编程
Python3 (11) Python 进程和线程
Python3 (12) Python 常用内建模块
Python 中有一些内置的函数无需额外安装和配置,即可直接使用,这就是python的内置模块。这篇比较简单,主要去熟悉一下有哪些常见的内置模块,具体开发的时候心中有数。

datetime

datetime与时间有关的,是处理日期和时间的标准库。

获取当前日期和时间

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from datetime import datetime

# 获取当前datetime:
now = datetime.now()
print('now =', now)
print('type(now) =', type(now))

输出结果:

now = 2018-02-02 10:47:52.740622
type(now) = <class 'datetime.datetime'>

获取指定日期和时间

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from datetime import datetime

# 设置时间
set_time = datetime(2018,8,18,20,18)
print(set_time)

输出结果:

2018-08-18 20:18:00

datetime转换为timestamp

时间戳这个概念,应该对应上计算机的时间规则:1970年1月1日 00:00:00 UTC+00:00时区的时刻称为epoch time,记为0,当前时间就是相对于epoch time的秒数,称为timestamp:

# 标准时间
timestamp = 0 = 1970-1-1 00:00:00 UTC+0:00
# 相当于 北京时间 (东八区)
timestamp = 0 = 1970-1-1 08:00:00 UTC+8:00
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from datetime import datetime

# 设置时间
set_time = datetime(2018,8,18,20,18)
print(set_time)
# 获取时间戳
s = set_time.timestamp()
print(s)
# 当前时间
current_time = datetime.now()
print(current_time)
# 获取当前时间戳
cs = current_time.timestamp()
print(cs)

输出结果:

2018-08-18 20:18:00
1534594680.0
2018-02-02 13:49:35.115818
1517550575.115818

Python的timestamp是一个浮点数。如果有小数位,小数位表示毫秒数,java 中的timestamp使用整数表示毫秒数,我们只需要把timestamp除以1000就得到Python的浮点表示方法。

timestamp转换为datetime

由时间戳转化成时间的格式 :datetime.fromtimestamp

# 获取设置的时间 (本地时间)
set_t = datetime.fromtimestamp(1534594680.0)
print(set_t)
# 获取当前时间  (本地时间)
cur_t = datetime.fromtimestamp(1517550847.361579)
print(cur_t)
# 获取 标准时间:
print(datetime.utcfromtimestamp(1534594680.0),'\n',datetime.utcfromtimestamp(1517550847.361579))

输出结果:

2018-08-18 20:18:00
2018-02-02 13:54:07.361579
2018-08-18 12:18:00 
 2018-02-02 05:54:07.361579

通过fromtimestamp转化的时间,是针对当前操作系统的时区。utcfromtimestamp转化的是标准时间。

str转换为datetime

str转换为datetime:datetime.strptime()

cday = datetime.strptime('2018-8-18 08:18:18', '%Y-%m-%d %H:%M:%S')
print(cday)
print(type(cday))

输出结果:

2018-08-18 08:18:18
<class 'datetime.datetime'>

转换后的datetime是没有时区信息的,并且参数%Y-%m-%d %H:%M:%S是时间的格式,Python中内置了好多格式,具体参考官方文档

datetime加减运算

在Python 中,对时间的加减运算可以直接通过+-运算,但是不能直接加数字,应该转换成timedelta对象。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from datetime import datetime, timedelta

# 当前时间
current_time = datetime.now()
print(current_time)

# 加减运算
add_time = current_time + timedelta(days=2, hours=12)
print(add_time)

输出结果:

2018-02-02 16:54:00.977390
2018-02-05 04:54:00.977390

本地时间转换为UTC时间

本地时间是指系统设定时区的时间,如北京时间是UTC+8:00时区的时间,而UTC时间指UTC+0:00时区的时间。datetime类型有一个时区属性tzinfo,但是默认为None,可以强行给datetime设置一个时区,如果没有设置是不知道是哪个时区的。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from datetime import datetime, timedelta, timezone

# 创建时区UTC+8:00
tz_utc_8 = timezone(timedelta(hours=8))
print(tz_utc_8)
now = datetime.now()
print(now)

# 强制设置为UTC+8:00
dt = now.replace(tzinfo=tz_utc_8)
print(dt)

输出结果:

UTC+08:00
2018-02-02 17:06:30.659342
2018-02-02 17:06:30.659342+08:00

时区转换

Python中可以直接获取到当前时间的标准时间(UTC),然后可以通过astimezone切换成其他时区的时间。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

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)
# 切换成东京时间
tokyo_dt = utc_dt.astimezone(timezone(timedelta(hours=9)))
print(tokyo_dt)

# 北京时间切换东京时间
tokyo_dt2 = bj_dt.astimezone(timezone(timedelta(hours=9)))
print(tokyo_dt2)

输出结果:

2018-02-02 09:22:26.540967+00:00
2018-02-02 17:22:26.540967+08:00
2018-02-02 18:22:26.540967+09:00
2018-02-02 18:22:26.540967+09:00

时区转换的关键在于,拿到一个datetime时,要获知其正确的时区,然后强制设置时区,作为基准时间。如果不知道当前时区,就去找 UTC 标准时间,并强制设置时区为00:00,带时区的datetime通过astimezone()方法,可以转换到任意时区,不是必须从UTC+0:00时区转换到其他时区,任何带时区的datetime都可以正确转换。

最后我们要存储时间,一般用datetime转成timestamp进行存储。这里时间的扩展类就写完了,我们可以根据具体的情况来进行选择使用。

collections

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

namedtuple

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

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from collections import namedtuple

# 创建一个Point 表示坐标
Point = namedtuple('Point', ['x', 'y'])
p = Point(2,3)
print(p.x)
print(p.y)
print(isinstance(p,Point))
print(isinstance(p,tuple))

# 创建坐标和半径表示一个圆
Circle = namedtuple('Circle',['x','y','r'])
c = Circle(2,3,2)
print(c.x,c.y,c.r)

输出结果:

2
3
True
True
2 3 2

namedtuple()根据具体的应用场景,通过事物的属性定义不同的tuple对象。使表达的的事物更加的形象具体。

deque

首先双端队列的出现是解决list单向列表结构,对插入、删除操作效率低的问题。双端队列:采用双向列表结构,更高效实现插入和删除操作。适合用于队列和栈。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from collections import deque

q = deque(['a', 'b', 'c'])
q.append('x')
q.appendleft('y')
print(q)

输出结果:

deque(['y', 'a', 'b', 'c', 'x'])

deque除了实现listappend()pop()外,还支持appendleft()popleft(),这样就可以非常高效地往头部添加或删除元素。

defaultdict

使用dict时,如果引用的Key不存在,就会抛出KeyError。如果希望key不存在时,返回一个默认值,defaultdict应用而生。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from collections import defaultdict

dd = defaultdict(lambda: 'N/A')
dd['key1'] = 'abc'
print(dd['key1'])
print(dd['key2'])

输出结果:

abc
N/A

默认值是调用函数返回的,而函数在创建defaultdict对象时传入。除了在Key不存在时返回默认值,defaultdict的其他行为跟dict是完全一样的。

OrderedDict

使用dict时,Key是无序的。在对dict做迭代时,我们无法确定Key的顺序如果要保持Key的顺序,可以用OrderedDict:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from collections import OrderedDict

d = dict([('a', 1), ('b', 2), ('c', 3)])
# dict的Key是无序的
print(d)
od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
# OrderedDict的Key是有序的
print(od)

输出结果:

{'a': 1, 'b': 2, 'c': 3}
OrderedDict([('a', 1), ('b', 2), ('c', 3)])

使用OrderedDict的Key会按照插入的顺序排列,不是Key本身排序。 我们可以通过OrderedDict实现一个FIFO(先进先出)的dict。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from collections import OrderedDict

class LastUpdatedOrderedDict(OrderedDict):

    def __init__(self, capacity):
        super(LastUpdatedOrderedDict, self).__init__()
        self._capacity = capacity

    def __setitem__(self, key, value):
        containsKey = 1 if key in self else 0
        if len(self) - containsKey >= self._capacity:
            last = self.popitem(last=False)
            print('remove:', last)
        if containsKey:
            del self[key]
            print('set:', (key, value))
        else:
            print('add:', (key, value))
        OrderedDict.__setitem__(self, key, value)

d = LastUpdatedOrderedDict(3)
d['a'] = 1
d['b'] = 2
d['c'] = 3
d['d'] = 4
print(d)

输出结果:

add: ('a', 1)
add: ('b', 2)
add: ('c', 3)
remove: ('a', 1)
add: ('d', 4)
LastUpdatedOrderedDict([('b', 2), ('c', 3), ('d', 4)])

从输出的数据我们可以清楚的看到数据的插入删除顺序,实现了先进先出的FIFO 队列。

Counter

Counter作为一个简单的计数器,例如:我们来统计字符出现的次数:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Counter

c = Counter()

for ch in 'programming':
    c[ch] = c[ch]+1

print(c)

输出结果:

Counter({'r': 2, 'g': 2, 'm': 2, 'p': 1, 'o': 1, 'a': 1, 'i': 1, 'n': 1})

从输出的结果我们可以看出,Counter 其实是一个dict 子类,通过循环来找到字符的出现次数。

这里集合类的扩展类我们说完了。可以根据实际情况来进行使用。

base64

在开发中我们经常会通过将图片转成Base64作为字符串上传等,在Python中也存在base64。今天我们系统的来学习一下base64的组成。

['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import base64

s = base64.b64encode('在Python中使用BASE 64编码'.encode('utf-8'))
print(s)
d = base64.b64decode(s).decode('utf-8')
print(d)

s = base64.urlsafe_b64encode('在Python中使用BASE 64编码'.encode('utf-8'))
print(s)
d = base64.urlsafe_b64decode(s).decode('utf-8')
print(d)

输出结果:

b'5ZyoUHl0aG9u5Lit5L2/55SoQkFTRSA2NOe8lueggQ=='
在Python中使用BASE 64编码
b'5ZyoUHl0aG9u5Lit5L2_55SoQkFTRSA2NOe8lueggQ=='
在Python中使用BASE 64编码

Python 中提供了一种"url safe"base64编码,其实就是把字符+/分别变成-_使其可以在url中安全拼接。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import base64

def safe_base64_decode(s):
    s1 = s + b'='*( - len(s) % 4)
    print(s1)
    return base64.b64decode(s1)

s = base64.b64encode('在Python中使用BASE 64编码'.encode('utf-8'))
print(s)
d1= base64.b64decode(s).decode('utf-8')
d2 = safe_base64_decode(s).decode('utf-8')
d3 = safe_base64_decode(b'5ZyoUHl0aG9u5Lit5L2/55SoQkFTRSA2NOe8lueggQ').decode('utf-8')
print(d1)
print(d2)
print(d3)

输出结果:

b'5ZyoUHl0aG9u5Lit5L2/55SoQkFTRSA2NOe8lueggQ=='
b'5ZyoUHl0aG9u5Lit5L2/55SoQkFTRSA2NOe8lueggQ=='
b'5ZyoUHl0aG9u5Lit5L2/55SoQkFTRSA2NOe8lueggQ=='
在Python中使用BASE 64编码
在Python中使用BASE 64编码
在Python中使用BASE 64编码

因为=用在URLCookie里面会造成歧义,所以,很多Base64编码后会把=去掉,因为Base64是把3个字节变为4个字节,所以,Base64编码的长度永远是4的倍数,因此,需要加上 =Base64字符串的长度变为4的倍数,我们可以通过上面的方法解析base64

struct

在Python提供了一个struct模块来解决bytes和其他二进制数据类型的转换。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import struct
# 把任意数据类型变成bytes
# >表示字节顺序是big-endian,也就是网络序,I表示4字节无符号整数。
p1 = struct.pack('>I', 10240099)
print(p1)
# 把bytes变成相应的数据类型
p2 = struct.unpack('>I', b'\x00\x9c@c')
print(p2)
# 根据>IH的说明,后面的bytes依次变为I:4字节无符号整数和H:2字节无符号整数。
p3 = struct.unpack('>IH', b'\xf0\xf0\xf0\xf0\x80\x80')
print(p3)

输出结果:

b'\x00\x9c@c'
(10240099,)
(4042322160, 32896)

struct模块定义的数据类型可以参考Python官方文档:https://docs.python.org/3/library/struct.html#format-characters
我们通过一个demo 来练习struct的使用:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import struct


def bmpinfo(str):
    f = open(str, 'rb')
    s = f.read(30)
    h = struct.unpack('<ccIIIIIIHH', s)
    if h[0] == b'B' and (h[1] == b'M' or h[1] == b'A'):
        print(True)
        print('位图大小:', h[6], '*', h[7], '颜色数:', h[9])
    else:
        print(False)

if __name__ == "__main__":
    bmpinfo('F:\python\HelloWord\BMP.bmp')

输出结果:

True
位图大小: 1152 * 648 颜色数: 8

以上验证了一个文件是否是位图文件,如果是,打印出图片大小和颜色数。

hashlib

Pythonhashlib提供了常见的摘要算法,又称哈希算法、散列算法,它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示),常见的有MD5SHA1等。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import hashlib

md5 = hashlib.md5()
md5.update('how to use md5 in python hashlib?'.encode('utf-8'))
print(md5.hexdigest())

md51 = hashlib.md5()
md51.update('how to use md5 in python hashlib'.encode('utf-8'))
print(md51.hexdigest())

输出结果:

d26a53750bc40b38b65a520292f69306
846014c3556d79e878be15fde5426e8a

我们通过 MD5生成的128 bit字节,通常用一个32位的16进制字符串表示,两个我们只是去掉一个符号,就生成差别非常大的两个不同字符串,所以我们经常通过MD5 加密,为了确保密码的更加安全也可以在加密前加上盐,使一些常见的密码更加的安全。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
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算法,生成的是一个160 bit字节,通常用一个40位的16进制字符串表示,比SHA1更安全的算法是SHA256和SHA512,不过越安全的算法不仅越慢,而且摘要长度更长。

hmac

Hmac算法:Keyed-Hashing for Message Authentication通过一个标准算法,在计算哈希的过程中,把key混入计算过程中,适用于所有的哈希算法,如 MD5、SHA-1等。使用如下:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import hmac

message = b'Hello, world!'
key = b'secret'
h = hmac.new(key, message, digestmod='MD5')
print(h.hexdigest())

输出结果:

fa4ee7d173f2d97ee79022d1a7355bcf

其实hmac 算法就是将key混入MD5算法中,得出一个有口令的值,达到防止黑客破解的作用。

itertools

Python的内建模块itertools提供了非常有用的用于操作迭代对象的函数。

“无限”迭代器

itertools中提供了几个无限迭代器,如: count()cycle()repeat()

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import itertools
# 打印出自然数序列,无限打印下去
natuals = itertools.count(1)
for n in natuals:
    print(n)

输出结果:

1
2
3
4
5
...
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import itertools
# 打印出自然数序列,无限打印下去
cs = itertools.cycle('ABCDE')  # 注意字符串也是序列的一种
for n in cs :
    print(n)

输出结果:

A
B
C
D
E
A
B
...
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import itertools
# 打印出自然数序列,无限打印下去
ns = itertools.repeat('A',5)
for n in ns :
    print(n)

输出结果:

A
A
A
A
A

chain()

chain()可以把一组迭代对象串联起来,形成一个更大的迭代器:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import itertools

for c in itertools.chain('ABC', 'XYZ'):
    print(c)

输出结果:

A
B
C
X
Y
Z

groupby()

groupby()把迭代器中相邻的重复元素挑出来放在一起:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import itertools

for key, group in itertools.groupby('AAABBBCCAAA'):
    print(key, list(group))

输出结果:

A ['A', 'A', 'A']
B ['B', 'B', 'B']
C ['C', 'C']
A ['A', 'A', 'A']

Python中的itertools模块还有许多有用的函数,通过这些函数创建出来的是Iterator,而不是list

contextlib

Python中并不是只有open()函数返回的fp对象才能使用with语句。实际上,任何对象,只要正确实现了上下文管理,就可以用于with语句。contextlib模块提供了@contextmanager@closing来快捷的实现上下文管理。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from contextlib import contextmanager

@contextmanager
def tag(name):
    print("<%s>" % name)
    yield
    print("</%s>" % name)

with tag("h1"):
    print("hello")
    print("world")

输出结果:

<h1>
hello
world
</h1>

@contextmanager让我们通过编写generator来简化上下文管理。也可以使用@closing,他其实是经过@contextmanager装饰的generator

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from contextlib import closing
from urllib.request import urlopen

with closing(urlopen('https://www.baidu.com')) as page:
    for line in page:
        print(line)

输出结果:

b'<html>\r\n'
b'<head>\r\n'
b'\t<script>\r\n'
b'\t\tlocation.replace(location.href.replace("https://","http://"));\r\n'
b'\t</script>\r\n'
b'</head>\r\n'
b'<body>\r\n'
b'\t<noscript><meta http-equiv="refresh" content="0;url=http://www.baidu.com/"></noscript>\r\n'
b'</body>\r\n'
b'</html>'

通过@closing快速的解析到每一行代码。

contextlib模块还提供了很多好用的装饰器。

urllib

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

Get

urllib的request模块可以非常方便地抓取URL内容,也就是发送一个GET请求到指定的页面,然后返回HTTP的响应:使得网络抓取在python中非常的简单,下面我们抓取一个网址:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from urllib import request

with request.urlopen('https://api.douban.com/v2/book/2129650') 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
Date: Wed, 07 Feb 2018 11:28:06 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 2058
Connection: close
Vary: Accept-Encoding
X-Ratelimit-Remaining2: 99
X-Ratelimit-Limit2: 100
Expires: Sun, 1 Jan 2006 01:00:00 GMT
Pragma: no-cache
Cache-Control: must-revalidate, no-cache, private
Set-Cookie: bid=vWIbncKafc0; Expires=Thu, 07-Feb-19 11:28:06 GMT; Domain=.douban.com; Path=/
X-DOUBAN-NEWBID: vWIbncKafc0
X-DAE-Node: dis8
X-DAE-App: book
Server: dae
Data: {"rating":{"max":10,"numRaters":16,"average":"7.4","min":0},"subtitle":"","author":["廖雪峰"],"pubdate":"2007","tags":[{"count":21,"name":"spring","title":"spring"},{"count":13,"name":"Java","title":"Java"},{"count":6,"name":"javaee","title":"javaee"},{"count":5,"name":"j2ee","title":"j2ee"},{"count":4,"name":"计算机","title":"计算机"},{"count":4,"name":"编程","title":"编程"},{"count":3,"name":"藏书","title":"藏书"},{"count":3,"name":"POJO","title":"POJO"}],"origin_title":"","image":"https://img3.doubanio.com\/mpic\/s2552283.jpg","binding":"平装","translator":[],"catalog":"","pages":"509","images":{"small":"https://img3.doubanio.com\/spic\/s2552283.jpg","large":"https://img3.doubanio.com\/lpic\/s2552283.jpg","medium":"https://img3.doubanio.com\/mpic\/s2552283.jpg"},"alt":"https:\/\/book.douban.com\/subject\/2129650\/","id":"2129650","publisher":"电子工业出版社","isbn10":"7121042622","isbn13":"9787121042621","title":"Spring 2.0核心技术与最佳实践","url":"https:\/\/api.douban.com\/v2\/book\/2129650","alt_title":"","author_intro":"","summary":"本书注重实践而又深入理论,由浅入深且详细介绍了Spring 2.0框架的几乎全部的内容,并重点突出2.0版本的新特性。本书将为读者展示如何应用Spring 2.0框架创建灵活高效的JavaEE应用,并提供了一个真正可直接部署的完整的Web应用程序——Live在线书店(http:\/\/www.livebookstore.net)。\n在介绍Spring框架的同时,本书还介绍了与Spring相关的大量第三方框架,涉及领域全面,实用性强。本书另一大特色是实用性强,易于上手,以实际项目为出发点,介绍项目开发中应遵循的最佳开发模式。\n本书还介绍了大量实践性极强的例子,并给出了完整的配置步骤,几乎覆盖了Spring 2.0版本的新特性。\n本书适合有一定Java基础的读者,对JavaEE开发人员特别有帮助。本书既可以作为Spring 2.0的学习指南,也可以作为实际项目开发的参考手册。","price":"59.8"}

可以看到HTTP响应的头和JSON数据。如果我们要模拟浏览器来访问,一般会设计到Request,并在其上添加HTTP头部信息,如下:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
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'))

输出结果:

...
<meta charset="UTF-8">
        <title>豆瓣(手机版)</title>
        <meta name="viewport" content="width=device-width, height=device-height, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0">
        <meta name="format-detection" content="telephone=no">
        <link rel="canonical" href="
...

从截取的部分数据来看,我们模拟的是手机端的首页。

Post

如果要以POST发送一个请求,只需要把参数data以bytes形式传入。我们来模拟一下微博的登录

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
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.6.1
Date: Wed, 07 Feb 2018 11:35:42 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: close
Vary: Accept-Encoding
Cache-Control: no-cache, must-revalidate
Expires: Sat, 26 Jul 1997 05:00:00 GMT
Pragma: no-cache
Access-Control-Allow-Origin: https://passport.weibo.cn
Access-Control-Allow-Credentials: true
Set-Cookie: SUB=_2A253fpTeDeThGeVP4lQU9irKzT-IHXVUgDyWrDV6PUJbkdANLVnFkW1NTTnNOZWZSyoKlZCOotoJpMs1Uoa0SND8; Path=/; Domain=.weibo.cn; Expires=Thu, 07 Feb 2019 11:35:42 GMT; HttpOnly
Set-Cookie: SUHB=0vJYfuFBzMfWcU; expires=Thursday, 07-Feb-2019 11:35:42 GMT; path=/; domain=.weibo.cn
Set-Cookie: SCF=AudxsVEcHHQp3hfmOm6IPPEjejSbj3rJ6m0Z77U_WmWNRu-wr0A2W1OgKqMjpcMiF8-OuoYLLjnk9I4qUXNZqgg.; expires=Saturday, 05-Feb-2028 11:35:42 GMT; path=/; domain=.weibo.cn; httponly
Set-Cookie: SSOLoginState=1518003342; path=/; domain=weibo.cn
Set-Cookie: ALF=1520595342; expires=Friday, 09-Mar-2018 11:35:42 GMT; path=/; domain=.sina.cn
DPOOL_HEADER: lich80
Set-Cookie: login=f34d845f2ab0a4b6999b8de4cd23cb3f; Path=/
Data: {"retcode":20000000,"msg":"","data":{"loginresulturl":"https:\/\/passport.weibo.com\/sso\/crossdomain?entry=mweibo&action=login&proj=1&ticket=ST-MzE5NjU2NDY2Mw%3D%3D-1518003342-aliyun-5F83119A92CC6966117D1D20D46575FE-1&display=0&cdurl=https%3A%2F%2Flogin.sina.com.cn%2Fsso%2Fcrossdomain%3Fentry%3Dmweibo%26action%3Dlogin%26proj%3D1%26ticket%3DST-MzE5NjU2NDY2Mw%253D%253D-1518003342-aliyun-6ED95CC67505BF202311D1BD83F67D6A-1%26display%3D0%26cdurl%3Dhttps%253A%252F%252Fpassport.sina.cn%252Fsso%252Fcrossdomain%253Fentry%253Dmweibo%2526action%253Dlogin%2526display%253D0%2526ticket%253DST-MzE5NjU2NDY2Mw%25253D%25253D-1518003342-aliyun-787B7337EFDF43151FC3B52C1E383597-1","uid":"3196564663"}}

Handler

如果还需要更复杂的控制,比如通过一个Proxy去访问网站,我们需要利用ProxyHandler来处理,示例代码如下:

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

urllib提供的功能就是利用程序去执行各种HTTP请求。如果要模拟浏览器完成特定功能,需要把请求伪装成浏览器。伪装的方法是先监控浏览器发出的请求,再根据浏览器的请求头来伪装。

XML

json没有流行起来是,Web中应用中还采用的XML格式,XML 的解析有两种方式DOMSAXPython中提供了专门的解析方法。

DOM vs SAX

操作XML有两种方法:DOM和SAX。DOM会把整个XML读入内存,解析为树,因此占用内存大,解析慢,优点是可以任意遍历树的节点。SAX是流模式,边读边解析,占用内存小,解析快,缺点是我们需要自己处理事件,正常情况下,优先考虑SAX,因为DOM实在太占内存。

在Python中使用SAX解析XML非常简洁,通常我们关心的事件是start_element,end_element和char_data,准备好这3个函数,然后就可以解析xml了。如下:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
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'''<?xml version="1.0"?>
<ol>
    <li><a href="/python">Python</a></li>
    <li><a href="/ruby">Ruby</a></li>
</ol>
'''

handler = DefaultSaxHandler()
parser = ParserCreate()
parser.StartElementHandler = handler.start_element
parser.EndElementHandler = handler.end_element
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

HTMLParser

如何解析该HTML页面,我们用爬虫把目标网站的页面抓下来,然后就是解析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('<!--', data, '-->')

    def handle_entityref(self, name):
        print('&%s;' % name)

    def handle_charref(self, name):
        print('&#%s;' % name)

parser = MyHTMLParser()
parser.feed('''<html>
<head></head>
<body>
<!-- test html parser -->
    <p>Some <a href=\"#\">html</a> HTML&nbsp;tutorial...<br>END</p>
</body></html>''')

输出结果:

<html>


<head>
</head>


<body>


<!--  test html parser  -->

    
<p>
Some 
<a>
html
</a>
 HTML tutorial...
<br>
END
</p>


</body>
</html>

解析过程中,feed()方法可以多次调用,也就是不一定一次把整个HTML字符串都塞进去,可以一部分一部分塞进去。特殊字符有两种,一种是英文表示的 ,一种是数字表示的Ӓ,这两种字符都可以通过Parser解析出来。

在这里我们基本上把python中内置模块的部分重要的方法和用法讲解了,但是还有好多都没有提到,在实践中可以通过官方文档,采用最优的方法解决具体问题。

上一篇下一篇

猜你喜欢

热点阅读