python学习笔记(十二)常用内建模块

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

获取当前日期和时间

>>> from datetime import datetime
>>> now = datetime.now() # 获取当前datetime
>>> print(now)
2015-05-18 16:28:07.198690
>>> print(type(now))
<class 'datetime.datetime'>

datetime是模块,datetime模块还包含一个datetime类

获取指定日期和时间
要指定某个日期和时间,直接用参数构造一个datetime:

>>> from datetime import datetime
>>> dt = datetime(2015, 4, 19, 12, 20) # 用指定日期时间创建datetime
>>> print(dt)
2015-04-19 12:20:00

datetime抓换位timestamp
在计算机中,时间实际上是用数字表示的。
把1970.1.1的00:00:00 UTC+00:00时区的时刻称为epoch time,记为0(1970以前的时间timestamp为负数)当前时间就是对于epochtime的秒数,称为timestamp

timestamp的值与时区毫无关系,因为timestamp一旦确定,其UTC时间就确定了,转换到任意时区的时间也是完全确定的。
全球各地的计算机在任意时刻的timestamp都是完全相同的

把一个datetime类型转换为timestamp只需要调用timestamp()方法

>>> from datetime import datetime
>>> dt = datetime(2015, 4, 19, 12, 20) # 用指定日期时间创建datetime
>>> dt.timestamp() # 把datetime转换为timestamp
1429417200.0

整数位表示秒

timestamp转换位datetime
要把timestamp转换为datetime,使用datetime提供的fromtimestamp()方法:

>>> from datetime import datetime
>>> t = 1429417200.0
>>> print(datetime.fromtimestamp(t))
2015-04-19 12:20:00

timestamp是一个浮点数,没有时区的概念。
datetime是有时区的。

timestamp可以直接被转换到UTC标准时区的时间:

>>> from datetime import datetime
>>> 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

>>> from datetime import datetime
>>> cday = datetime.strptime('2015-6-1 18:19:59', '%Y-%m-%d %H:%M:%S')
>>> print(cday)
2015-06-01 18:19:59

%Y-%m-%d %H:%M:%S规定了日期和时间部分的格式。
转换后的datetime是没有时区信息的

datetime转换为str
如果有了datetime对象,要把它格式化位字符串显示给用户,就需要转换为str,转换方法是通过strftime()实现:

>>> from datetime import datetime
>>> now = datetime.now()
>>> print(now.strftime('%a, %b %d %H:%M'))
Mon, May 05 16:28

datetime加减
对日期和时间进行加减实际上就是把datetime往后或往前计算,得到新的datetime。
加减可以直接用±运算符,需要导入timedalta类:

>>> from datetime import datetime, timedelta
>>> now = datetime.now()
>>> now
datetime.datetime(2015, 5, 18, 16, 57, 3, 540997)
>>> now + timedelta(hours=10)
datetime.datetime(2015, 5, 19, 2, 57, 3, 540997)
>>> now - timedelta(days=1)
datetime.datetime(2015, 5, 17, 16, 57, 3, 540997)
>>> now + timedelta(days=2, hours=12)
datetime.datetime(2015, 5, 21, 4, 57, 3, 540997)

本地时间转换为UTC时间
本地时间是指系统设定时区的时间

一个datetime类型有一个时区属性tzinfo,但是默认为None,所以无法区别这个datetime到底是哪个时区,只能强行给它设置一个时区:

>>> from datetime import datetime, timedelta, timezone
>>> tz_utc_8 = timezone(timedelta(hours=8)) # 创建时区UTC+8:00
>>> now = datetime.now()
>>> now
datetime.datetime(2015, 5, 18, 17, 2, 10, 871012)
>>> dt = now.replace(tzinfo=tz_utc_8) # 强制设置为UTC+8:00
>>> dt
datetime.datetime(2015, 5, 18, 17, 2, 10, 871012, tzinfo=datetime.timezone(datetime.timedelta(0, 28800)))

时区转换
通过utcnow()拿到当前的UTC时间,再转换为任意时区的时间:

# 拿到UTC时间,并强制设置时区为UTC+0:00:
>>> utc_dt = datetime.utcnow().replace(tzinfo=timezone.utc)
>>> print(utc_dt)
2015-05-18 09:05:12.377316+00:00
# astimezone()将转换时区为北京时间:
>>> bj_dt = utc_dt.astimezone(timezone(timedelta(hours=8)))
>>> print(bj_dt)
2015-05-18 17:05:12.377316+08:00
# astimezone()将转换时区为东京时间:
>>> tokyo_dt = utc_dt.astimezone(timezone(timedelta(hours=9)))
>>> print(tokyo_dt)
2015-05-18 18:05:12.377316+09:00
# astimezone()将bj_dt转换时区为东京时间:
>>> tokyo_dt2 = bj_dt.astimezone(timezone(timedelta(hours=9)))
>>> print(tokyo_dt2)
2015-05-18 18:05:12.377316+09:00

时区转换的关键在于拿到一个datetime时,要获知其正确的时区,然后强制设置时区,作为基准时间。
利用带失去的datetime,通过astimezone()方法可以转换到任意时区

collections
collection是一个集合模块,提供了许多有用的集合类

namedtuple
tuple可以表示不变集合
定义一个二维坐标:

>>> from collections import namedtuple
>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(1, 2)
>>> p.x
1
>>> p.y
2

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

要用坐标和半径表示一个源,可以用namedtuple定义:

# namedtuple('名称', [属性list]):
Circle = namedtuple('Circle', ['x', 'y', 'r'])

deque
使用list存储数据时,按索引访问元素很快,但是插入和删除元素很慢,因为list是线性存储,数据量大的时候,插入和删除效率很低

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

>>> from collections import deque
>>> q = deque(['a', 'b', 'c'])
>>> q.append('x')
>>> q.appendleft('y')
>>> q
deque(['y', 'a', 'b', 'c', 'x'])

deque除了实现list的append()和pop()外,还支持apendleft()和popleft()

defaultdict
使用dict时,如果引用的Key不存在,就会抛出keyError。
defaultdict,当key不存在时,返回一个默认值

>>> from collections import defaultdict
>>> dd = defaultdict(lambda: 'N/A')
>>> dd['key1'] = 'abc'
>>> dd['key1'] # key1存在
'abc'
>>> dd['key2'] # key2不存在,返回默认值
'N/A'

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

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

>>> from collections import OrderedDict
>>> d = dict([('a', 1), ('b', 2), ('c', 3)])
>>> d # dict的Key是无序的
{'a': 1, 'c': 3, 'b': 2}
>>> od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
>>> od # OrderedDict的Key是有序的
OrderedDict([('a', 1), ('b', 2), ('c', 3)])

OrderedDict的key会按照插入的顺序排列,而不是key本身排序:

>>> od = OrderedDict()
>>> od['z'] = 1
>>> od['y'] = 2
>>> od['x'] = 3
>>> list(od.keys()) # 按照插入的Key的顺序返回
['z', 'y', 'x']

OrderedDict可以实现一个FIFO(先进先出)的dict,当容量超出限制时,先删除最早添加的key:

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)

ChainMap
可以把一组dict串起来并组成一个逻辑上的dict
ChainMap本身也是一个dict,但是查找的时候,会按照顺序在内部的dict依次查找

应用程序往往都需要传入参数,参数可以通过命令行传入,可以通过环境变量传入,还可以有默认参数。可以用ChainMap实现参数的优先级查找,即先查命令行参数,如果没有传入,再查环境变量,如果没有就是用默认参数

from collections import ChainMap
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'])

输出:

#没有任何参数时
$ python3 use_chainmap.py 
color=red
user=guest
#传入命令行参数时,优先使用命令行参数
$ python3 use_chainmap.py -u bob
color=red
user=bob
#同时传入命令行参数和环境变量,命令行参数的优先级较高
$ user=admin color=green python3 use_chainmap.py -u bob
color=green
user=bob

Counter
Counter是一个简单的计数器,

>>> from collections import Counter
>>> c = Counter()
>>> for ch in 'programming':
...     c[ch] = c[ch] + 1
...
>>> c
Counter({'g': 2, 'm': 2, 'r': 2, 'a': 1, 'i': 1, 'o': 1, 'n': 1, 'p': 1})
>>> c.update('hello') # 也可以一次性update
>>> c
Counter({'r': 2, 'o': 2, 'g': 2, 'm': 2, 'l': 2, 'p': 1, 'a': 1, 'i': 1, 'n': 1, 'h': 1, 'e': 1})

Counter实际上也是dict的一个子类

base64
base64是一种用64个字符来表示任意二进制数据的方法

base64编码会把3字节的二进制数据编码为4字节的文本数据,长度增加33%,编码后的文本数据可以在邮件正文,网页等直接显示。

base64用\x00字节在末尾补足后,再在编码的末尾加上一个或两个=号,表示补了多少字节,解码的时候会自动去掉。

>>> import base64
>>> base64.b64encode(b'binary\x00string')
b'YmluYXJ5AHN0cmluZw=='
>>> base64.b64decode(b'YmluYXJ5AHN0cmluZw==')
b'binary\x00string'

由于标准的base64编码后可能出现字符+和/,在url中就不能直接作为参数,所有又有一种“urlsafe”的base64编码,其实就是把字符+和/分别编程-和_

>>> base64.b64encode(b'i\xb7\x1d\xfb\xef\xff')
b'abcd++//'
>>> base64.urlsafe_b64encode(b'i\xb7\x1d\xfb\xef\xff')
b'abcd--__'
>>> base64.urlsafe_b64decode('abcd--__')
b'i\xb7\x1d\xfb\xef\xff'

base64是一种任意二进制到文本字符串的编码方法,常用于在URL,cookie,网页传输少量二进制数据。

struct
python没有专门处理字节的数据类型。
b’str’可以表示字节,所以字节数组=二进制str

要把一个32位无符号整数变成字节,就是4个长度的bytes:

>>> n = 10240099
>>> b1 = (n & 0xff000000) >> 24
>>> b2 = (n & 0xff0000) >> 16
>>> b3 = (n & 0xff00) >> 8
>>> b4 = n & 0xff
>>> bs = bytes([b1, b2, b3, b4])
>>> bs
b'\x00\x9c@c'

struct模块解决bytes和其他二进制数据类型的转换
struct的pack函数把任意数据类型变成bytes:

>>> import struct
>>> struct.pack('>I', 10240099)
b'\x00\x9c@c'

pack的第一个参数 是处理指令,’>I’的意思是:>表示字节顺序是big-endian,也就是网络序,I表示4字节无符号整数。
后面的参数个数要和处理指令一致。

unpack把bytes变成相应的数据类型:

>>> struct.unpack('>IH', b'\xf0\xf0\xf0\xf0\x80\x80')
(4042322160, 32896)

根据>IH的说明,后面的bytes依次变为I:4字节无符号整数和H:2字节无符号整数
所以,尽管python不适合编写底层操作字节流的代码,但在对性能要求不高的地方,利用struct就方便多了。

windows的位图文件(.bmp)是一种非常简单的文件格式,用struct分析
找一个bmp文件,读入前30个字节来分析
bmp格式采用小端方式存储数据,文件头的结构按顺序:
两个字节,‘BM’表示windows位图,‘BA’表示os/2位图,一个4字节整数表示位图大小;一个4字节整数表示保留位,始终为0;一个4字节整数表示实际图像的偏移量;一个4字节整数表示hesder的字节数;一个4字节整数表示图像宽度;一个4字节整数表示图像高度;一个2字节整数始终为1;一个2字节整数表示颜色数。
组合起来用unpack读取:

>>> struct.unpack('<ccIIIIIIHH', s)
(b'B', b'M', 691256, 0, 54, 40, 640, 360, 1, 24)

b’B’,b’M’说明是windows位图,位图大小是640x360

hashlib
hashlib提供常见的摘要算法
摘要算法:哈希算法,散列算法。通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)
通过摘要函数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

如果数据量很大,可以分块多次调用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())

计算结果一样

md5是最常见的摘要算法,速度很快,生成结果是固定的128bit字节,通常用一个32位的16进制字符串表示。

另一种常见的摘要算法是sha1,调用sha1和调用md5完全类似

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())

sha1的结果是160bit字节,通常用一个40位的16进制字符串表示。
比sha1更安全的算法是sha256和sha512,但越安全的算法不仅越慢,摘要长度更长

摘要算法应用
任何允许用户登录的网站都会存储用户登录的用户名和口令,将这些内容存储到数据库表中
保存口令的方式不是存储用户的铭文口令,而是存储用户口令的摘要,比如md5
当用户登录时,首先计算用户输入的明文口令的md5,然后和数据库存储的md5对比。

由于常用口令的md5值很容易被计算出来,所以,要确保存储的用户口令不是已经被计算出来的常用口令的md5,这一方法通过对原始口令加一个复杂字符串来实现,俗称‘加盐’:

def calc_md5(password):
    return get_md5(password + 'the-Salt')

hmac
python内置的hmac模块实现了标准的hmac算法,利用一个key对message计算‘杂凑后的hash,使用hamc算法比标准的hash算法更安全。

intertools
itertools提供了非常有用的用于操作迭代对象的函数
’无限‘迭代器:

>>> import itertools
>>> natuals = itertools.count(1)
>>> for n in natuals:
...     print(n)
...
1
2
3
...

count()会创建一个无限的迭代器
cycle()会把传入的一个序列无限重复下去:

>>> import itertools
>>> cs = itertools.cycle('ABC') # 注意字符串也是序列的一种
>>> for c in cs:
...     print(c)
...
'A'
'B'
'C'
'A'
'B'
'C'
...

repeat()负责把一个元素无限重复下去,如果提供第二个参数就可以限定重复次数:

>>> ns = itertools.repeat('A', 3)
>>> for n in ns:
...     print(n)
...
A
A
A

无限序列只有在for迭代时才会无限的迭代下去,如果只是创建了一个迭代对象,她不会事先把无限个元素生成出来,事实上也不可能在内存中创建无限多个元素

无限序列可以无限迭代下去,但是通常会通过takewhile()等函数根据条件判断来截取出一个有限的序列:

>>> natuals = itertools.count(1)
>>> ns = itertools.takewhile(lambda x: x <= 10, natuals)
>>> list(ns)
[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('AAABBBCCAAA'):
...     print(key, list(group))
...
A ['A', 'A', 'A']
B ['B', 'B', 'B']
C ['C', 'C']
A ['A', 'A', 'A']

挑选规则是通过函数完成的,只要作用于函数的两个元素返回值相等,这两个元素就被认为是在一组的,而函数返回值作为组的key。
忽略大小写:

>>> for key, group in itertools.groupby('AaaBBbcCAAa', lambda c: c.upper()):
...     print(key, list(group))
...
A ['A', 'a', 'a']
B ['B', 'B', 'b']
C ['c', 'C']
A ['A', 'A', 'a']

contextlib
在python中,读写文件这样的资源必须在使用完毕后正确关闭
正确关闭文件资源的一个方法是使用try…finally:

try:
    f = open('/path/to/file', 'r')
    f.read()
finally:
    if f:
        f.close()

with语句也可以关闭:

with open('/path/to/file', 'r') as f:
    f.read()

并不是只要open()函数返回的fp对象才能使用with语句。
任何对象,只要正确实现了上下文管理,就可以用于with语句

实现上下文管理是通过__enter__ __exit__这两个方法实现的:

class Query(object):

    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print('Begin')
        return self
    
    def __exit__(self, exc_type, exc_value, traceback):
        if exc_type:
            print('Error')
        else:
            print('End')
    
    def query(self):
        print('Query info about %s...' % self.name)

可以把自己写的资源对象用于with语句:

with Query('Bob') as q:
    q.query()

@contextmanager

from contextlib import contextmanager

class Query(object):

    def __init__(self, name):
        self.name = name

    def query(self):
        print('Query info about %s...' % self.name)

@contextmanager
def create_query(name):
    print('Begin')
    q = Query(name)
    yield q
    print('End')

@contextmanager这个decorator接受一个generator,用yield语句把with…as var把变量输出出去,然后,with语句就可以正常的工作

with create_query('Bob') as q:
    q.query()

希望在某段代码执行前后自动执行特定代码:

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

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

执行结果
<h1>
hello
world
</h1>

代码的执行顺序:
1 with语句首先执行yield之前的语句
2 yield调用会执行with语句内部的所有语句
3 最后执行yield之后的语句

@contextmanager通过编写generator来简化上下文管理

@closing
如果一个对象没有实现上下文,就不能用于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

@contextmanager
def closing(thing):
    try:
        yield thing
    finally:
        thing.close()

作用就是把任意对象变为上下文对象,并支持with语句

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

Get
urllib的request模块可以非常方便的抓取URL内容,就是发送一个get请求到指定的页面,然后返回http的响应:
对豆瓣的一个url进行抓取,并返回相应:

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'))

可以看到http响应的头和json数据:

Status: 200 OK
Server: nginx
Date: Tue, 26 May 2015 10:02:27 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 2049
Connection: close
Expires: Sun, 1 Jan 2006 01:00:00 GMT
Pragma: no-cache
Cache-Control: must-revalidate, no-cache, private
X-DAE-Node: pidl1
Data: {"rating":{"max":10,"numRaters":16,"average":"7.4","min":0},"subtitle":"","author":["廖雪峰编著"],"pubdate":"2007-6",...}

如果要模拟浏览器发送get请求,就需要request对象,通过往request对象添加http头,就可以把请求伪装成浏览器。
模拟iPhone6请求豆瓣首页:

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'))

豆瓣会返回适合iPhone的移动版网页:

...
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0">
    <meta name="format-detection" content="telephone=no">
    <link rel="apple-touch-icon" sizes="57x57" href="http://img4.douban.com/pics/cardkit/launcher/57.png" />
...

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}}

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请求。
如果要模拟浏览器完成特定功能,需要把请求伪装成浏览器。
伪装的方法是先监控浏览器发出的请求,再根据浏览器的请求头来伪装
User-Agent头就是用来标识浏览器的。

XML
XML比JSON复杂

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

python中使用sax解析xml非常简洁,通常关心的事件是start_element,end_element,char_data,准备好这三个函数,然后就可以解析xml了
当sax解析器读到一个节点时:

<a href="/">python</a>

会产生三个事件:
1 start_element事件,在读取<a href="/">
2 char_data事件,在读取python时
3 end_element事件,在读取'<a/>

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)

读取一大段字符串时,CharacterDataHandler可能多次被调用,所以需要自己保存起来,在EndElementHandler里面再合并

生成XML最简单有效的方法是拼接字符:

L = []
L.append(r'<?xml version="1.0"?>')
L.append(r'<root>')
L.append(encode('some & data'))
L.append(r'</root>')
return ''.join(L)

(习题没做)

HTMLParser
如果要编写一个搜索引擎,第一步是用爬虫把目标网站的的页面抓下来,第二步就是解析该HTML页面,看看里面的内容到底是新闻图片还是视频

解析HTML
html实质上是xml的子集

使用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>''')

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

(习题没做)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值