文章目录

  • datetime
    • 获取当前日期和时间
    • 获取指定日期和时间
    • datetime转化为timestamp
    • timestamp转化为datetime
    • str转化为datetime
    • datetime转化为str
    • datetime加减
  • collections
    • namedtuple
    • deque
    • defaultdict
    • ChainMap
    • Counter
  • base64
  • hashlib
    • 摘要算法简介
    • MD5摘要算法使用
    • 摘要算法的应用
  • hmac
  • itertools
    • count
    • cycle
    • repeat
    • accumulate
    • chain
    • dropwhile
    • takewhile
    • groupby
    • 计算圆周率的小例子
  • contextlib
    • closing
  • urllib
    • Get
      • 模拟浏览器去访问
    • Post
      • 单独的用户登录
      • 登录 站长啦 网站(https)
      • 添加和查看用户信息
    • urllib总结
  • XML
    • dom
    • sax
    • ElementTree
    • 三种方式的代码运行截图
  • HTMLParser
    • 实战例子
  • 参考网址

datetime

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

获取当前日期和时间

注意下面第一个datetime是包名,第二个datetime是类名

# 获取当前日期和时间
from datetime import datetime
now = datetime.now()
print(now)
print(type(now))

获取指定日期和时间

#获取指定日期和时间
from datetime import datetime
# 注意datetime的初始化方法里 年月日时分秒 都是int类型
dt = datetime(2020, 2, 10, 22, 15, 30)
print(dt)

datetime转化为timestamp

在计算机中,时间实际上是用数字表示的。我们把1970年1月1日 00:00:00 UTC+00:00时区的时刻称为epoch time,记为0(1970年以前的时间timestamp为负数),当前时间就是相对于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

可以看出timestamp的值与时区是无关的。因为一旦timestamp确定,其UTC时间就确定了,转换到任意时区也是可以完全确定的。这也是为什么这计算机存储的当前时间是以timestamp表示的。

from datetime import datetime
dt = datetime(2020, 2, 10, 22, 15, 30)
print(dt)
# 获取datetime对应的timestamp
print(dt.timestamp())

注意Python的timestamp是一个浮点数。如果有小数位,小数位表示毫秒数。
某些编程语言(如Java和JavaScript)的timestamp使用整数表示毫秒数,这种情况下只需要把timestamp除以1000就得到Python的浮点表示方法。

timestamp转化为datetime

我们知道timestamp是和时区没有关系的,但是datetime是和时区有关的。

# timestamp转化为datetime
ts = 1581344130.0
# 转化成本地时间,即北京时间,UTC+8
local_dt = datetime.fromtimestamp(ts)
# 转化成utc时间,即UTC+0
utc_dt = datetime.utcfromtimestamp(ts)
print(local_dt)
print(utc_dt)

我们可以从打印结果也能看出datetime是和时区有关的,北京时间就比UTC时间+8。

str转化为datetime

datetime.strptime(cls, date_string, format),注意是datetime类的类方法。
我们常用的日期格式就是下面的格式,更加的详细可以参考Python官网日期格式

# str转化为datetime
dt = datetime.strptime("2020-05-20 20:05:10", "%Y-%m-%d %H:%M:%S")
print(dt)
print(type(dt))

datetime转化为str

采用datetime.strftime(self, fmt)格式化datetime

# datetime转化为str
s = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(s)

datetime加减

对于日期和时间的加减需要用到datetime包里的timedelta类。
其中timedelta类支持daysweekshoursminutesseconds等等

def __init__(self, days: float = ..., seconds: float = ..., microseconds: float = ...,milliseconds: float = ..., minutes: float = ..., hours: float = ...,weeks: float = ...)
# datetime加减
from datetime import datetime,timedelta
now = datetime.now()
print("当前时间是: ",now)
print("后退5天的时间是: ",now + timedelta(days=5))
print("前进5天的时间是: ",now - timedelta(days=5))

但是时间加减上并没有前几年或者前几个月,这就需要另一个arrow模块去实现,不过这不是内建模块,是第三方模块,具体使用可查看Arrow官网。

# 从timestamp获取arrow
a = arrow.get(1581344130.0)
# 注意这里的format方法和strftime方法的参数格式是不同的
print(a.format("YYYY-MM-DD HH:mm:ss"))
# 从str获取arrow
a = arrow.get("2020-02-10 22:15:30", "YYYY-MM-DD HH:mm:ss")
print(a.format("YYYY-MM-DD HH:mm:ss"))
# 获取前2年 后3月 的时间
print("前2年 后3月 的时间: ",a.shift(years=-2, months=3).format("YYYY-MM-DD HH:mm:ss"))

collections

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

namedtuple

我们知道tuple里的元素是不可变的,但是访问其中元素时是和list一样通过索引下标访问。
例如,我们定义一个平面系坐标的点(x,y),就可以用namedtuple来定义,然后通过名字来访问。如下所示

# namedtuple 通过名字访问tuple元素
from collections import namedtuple
import math
Point = namedtuple("Point",["x", "y"])
p = Point(3,4)
print("点p的横坐标为{}, 纵坐标为{}, 其到原点长度是{}".format(p.x, p.y, math.sqrt(math.pow(p.x,2)+math.pow(p.y,2))))
# 此处Point已经是一个类型
print("p是Point? ", isinstance(p, Point))
print("p是tuple? ",isinstance(p, tuple))

deque

使用list存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了,因为list是线性存储,数据量大的时候,插入和删除效率很低
deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈

from collections import deque
q = deque(['a', 'b', 'c'])
q.append('d')
q.appendleft('1')
print(q)
print("弹出右边的元素: ", q.pop())
print("弹出左边的元素: ", q.popleft())
print(q)

defaultdict

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

注意defaultdict里面的参数是一个函数,该函数无参,但是有返回值即默认值

# defaultdict 当key不存在时返回默认值
from collections import defaultdict
d = defaultdict(lambda : 0)
d['patrick']=100
print('patrick的分数是: ', d['patrick'])
print('marry的分数是(默认值): ', d['marry'])

ChainMap

ChainMap可以把一组dict串起来并组成一个逻辑上的dict。ChainMap本身也是一个dict,但是查找的时候,会按照顺序在内部的dict依次查找
这个在向应用程序传入参数时非常适用。向应用程序传入既可以通过命令行传入,也可以通过环境变量,程序也有默认参数。但是我们可以使用ChainMap来按序查找参数的值,即可以先从命令行查找,然后从环境变量查找,最后查默认值。
这个可以结合Hadoop的配置来理解,Hadoop优先支持命令行,然后支持配置文件,再支持默认值。

# ChainMap
from collections import ChainMap
import os
import argparse# 构造缺省参数
defaults = {"dfs_replication": 3, "mapreduce_job_reduces": 0}# 构造命令行参数
parser = argparse.ArgumentParser()
parser.add_argument("-dr", "--dfs_replication")
parser.add_argument("-mrn", "--mapreduce_job_reduces")
namespace = parser.parse_args()
# 获取v不为空的键值对
command_line = {k:v for k,v in vars(namespace).items() if v}combined_maps = ChainMap(command_line, os.environ, defaults)print("dfs_replication=",combined_maps["dfs_replication"])
print("mapreduce_job_reduces=",combined_maps["mapreduce_job_reduces"])

下面分别展示了三种情况下的参数的值。
注意:这里之所以把.换成了_是因为环境变量不支持.
而且需要使用export将变量使得后面的子进程可见,否则os.environ会获取不到设置的环境变量值。

这里由于使用了 argparse 模块,故可以直接输入-h来查看其帮助信息。当然这个帮助信息是自动生成的。关于 argparse 模块后面会有专门的章节去讲,这也是Python内建模块,详细可以直接参考官网argparse模块。

Counter

Counter是一个简单的计数器。
能非常方便的计数和统计前N的字符及其出现次数。

from collections import Counter
d = Counter()
for c in "programming":d[c] = d[c]+1
print(d)
print("统计前2的字符是", d.most_common(2))# 可以传入iterable
print(Counter("programming"))
# 可以通过k=v传入
print(Counter(a=3, b=2))
# 可以通过dict传入
print(Counter({"a":3, "b":2}))

base64

Base64是一种用64个字符来表示任意二进制数据的方法。
由于二进制文件如图片、exe文件等包含很多无法显示和打印的字符,所以,如果要让记事本这样的文本处理软件能处理二进制数据,就需要一个二进制到字符串的转换方法Base64是一种最常见的二进制编码方法

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个字节怎么办?Base64用\x00字节在末尾补足后,再在编码的末尾加上1个或2个=号,表示补了多少字节,解码的时候,会自动去掉。
所以Base64编码后的字符串一定是4的倍数,如果不是4的倍数就需要再后面补相应个数的=号。
这里需要说明下=号只会出现在最后面作为补足位数,前提是表里本身就不包含=

如下所示,需要注意的是 b64encode 和 b64decode 方法的输入参数和返回参数都是bytes类型的字符串

# -*- coding:UTF-8 -*-import base64
# b64encode 和 b64decode 方法的输入参数和返回参数都是bytes类型的字符串# 编码后的是bytes类型字符串
e_str = base64.b64encode("大数据平台yarn".encode())
print(e_str)
# <class 'bytes'>
print(type(e_str))
s = base64.b64decode(e_str)
print(s.decode())# 该方法是用于处理那些尾部已经去掉等号的bytes类型字符串
def safe_base64_decode(s):s = s.decode()left = len(s) % 4list_str = []list_str.append(s)for i in range(left):list_str.append("=")return base64.b64decode("".join(list_str).encode()).decode()assert '大数据平台yarn' == safe_base64_decode(b'5aSn5pWw5o2u5bmz5Y+weWFybg=='), "带等号解码失效"
assert '大数据平台yarn' == safe_base64_decode(b'5aSn5pWw5o2u5bmz5Y+weWFybg'), "不带等号解码失效"
assert '大数据平台' == safe_base64_decode(b'5aSn5pWw5o2u5bmz5Y+w'), "带等号解码失效"
print('ok')


Base64是一种通过查表的编码方法,不能用于加密,即使使用自定义的编码表也不行(一般也不需要自定义表)。

Base64适用于小段内容的编码,比如数字证书签名、Cookie的内容等

hashlib

摘要算法简介

摘要算法又称为哈希算法散列算法。它通过一个函数把任意长度的数据转换为一个固定的数据串(如16位固定长度)。

摘要算法的目的是为了发现原始数据是否被人篡改过,如Apache下的包有的也会附带着摘要,来判断下载的包是否是完整的未经篡改的包,只是平时我们不去校验摘要而已。

摘要算法之所以能够指出原数据是否被人篡改过,主要在于摘要函数是一个单向函数,计算摘要很容易,但是通过摘要反推出原数据就很困难,任意一个bit的修改都会导致计算出摘要完全不同。

Python的hashlib提供了常见的摘要算法,如MD5,SHA1等等。

MD5摘要算法使用

下面是常用的MD5摘要算法的使用方法。
update():传入原始数据(注意必须是bytes类型),可以多次调用
digest():计算原始数据的摘要,返回的类型是bytes
hexdigest():计算原始数据的摘要,返回的类型是32位固定长度的字符串

import hashlib# 获取md5算法
md5 = hashlib.md5()
# 将Bytes类型的字符串传入update方法里, 可以多次传入
md5.update('how to use md5 in python hashlib?'.encode('utf-8'))
md5.update('just use update & hexdigest'.encode('utf-8'))
# 计算到目前位置通过update传入到该md5里所有数据的摘要, 返回bytes类型的字符串
print(md5.digest())
# 返回32位固定长度的16进制字符串, 其类型是str
print(md5.hexdigest())


python的hashlib模块还支持如下算法,但是使用方法都是和md5类似。
越安全的摘要算法长度越长,耗时越高。

摘要算法的应用

最常见的就是在数据库中对用户的密码取md5摘要并代替明文密码存到数据库中,这样可以防止用户密码随意暴露给运维人员。

但是这样也不一定安全。如果用户设置如123456password等简单的密码,黑客完全可以事先算出常用密码的摘要并构造出一个反推表即通过摘要推出简单的密码。

我们可以在程序设计上对简单密码进行加强保护,俗称加盐Salt。即对原始密码添加一个复杂的字符串然后再进行摘要计算。只要Salt没有暴露,那么很难通过摘要计算出明文密码。

如果有两个用户使用同样的密码,那么保存在数据库中的摘要是一样的,如何让相同口令的用户存储不同的摘要呢?可以把用户名作为Salt的一部分,从而实现相同口令的用户存储不同的摘要。

hmac

为了防止黑客通过彩虹表根据哈希值反推出明文口令,根据上面内容可以采用加盐的方式使得相同的输入得到不同的哈希值,大大增加黑客的破解难度。

其实加盐这种方式就是Hmac算法:Keyed-Hashing for Message Authentication。它通过一个标准算法,在计算哈希的过程中,把key混入计算过程中。采用Hmac替代我们自己的salt算法,可以使程序算法更标准化,也更安全。

Python自带的hmac模块实现了标准的Hmac算法。
下面是Hmac算法的例子,和上面的MD5使用方法一样。

import hmackey = b'secret key'
message = "how to use md5 in python hashlib?".encode()
h = hmac.new(key, message, 'md5')
# 可以通过upadte方法传入数据
h.update('just use update & hexdigest'.encode())
print(h.hexdigest())

itertools

itertools模块提供了很多用来创建和使用迭代对象的函数。
下面图展示了itertools模块的源码简介

count

count(start=0, step=1) --> start, start+step, start+2*step, ...会创建一个无限迭代器。
如下所示,打印100以内的自然数,由于是无限迭代器,所以测试代码里设置了退出条件。

import itertools# 打印100以内的自然数
num = itertools.count(1)
for n in num:if n <= 100:print(n)else:break

cycle

cycle(p) --> p0, p1, ... plast, p0, p1, ...会创建一个无限迭代器。
下面代码展示了cycle的使用方法,字符串是可迭代的。为了使测试代码能退出故使用了enumerate计算迭代次数。

iter_c = itertools.cycle("hadoop")
for i,c in enumerate(iter_c):if i< 10:# 此处为了让打印看的更清除,就将sep和end设置成空字符串print(c, sep="", end="")else:break

repeat

repeat(elem [,n]) --> elem, elem, elem, ... endlessly or up to n times负责将一个元素无限重复下去,也可以指定参数来指定重复次数。
如下所示,将字符串hadoop重复3次。

iter_s = itertools.repeat("hadoop", 3)
for s in iter_s:print(s)

accumulate

accumulate(p[, func]) --> p0, p0+p1, p0+p1+p2会创建一个不断累积的无限迭代器,其中累积函数默认是求和。

如下面代码就展示了求自然数前N项和的函数。

def sum_n(n):iter_num = itertools.count(1)iter_sum = itertools.accumulate(iter_num)for i, s in enumerate(iter_sum):if i == 10:breakprint("自然数前{}项和为{}".format((i+1), s))if __name__ == "__main__":sum_n(10)pass

chain

chain(p, q, ...) --> p0, p1, ... plast, q0, q1, ...可以将已有的迭代器串起来形成更大的迭代器。
如下面所示,将两个字符串迭代器串起来形成一个大的迭代器。

iter_chain = itertools.chain("hadoop", "spark")
for c in iter_chain:# 此处为了让打印看的更清除,就将sep和end设置成空字符串print(c, sep="", end="")

dropwhile

dropwhile(pred, seq) --> seq[n], seq[n+1], starting when pred fails也会创建一个子迭代器。

如下面例子展示了从自然数中的11开始前十个自然数。

def print_ten(iter_element):for i, element in enumerate(iter_element):if i == 10:breakprint(element)iter_n = itertools.dropwhile(lambda x:x<11, itertools.count(1))
print_ten(iter_n)

takewhile

takewhile(pred, seq) --> seq[0], seq[1], until pred fails会创建一个迭代器。

如下面代码展示了使用takewhile取奇数项前十个元素

n=10
odd = itertools.count(1, step=2)
ten_odd = itertools.takewhile(lambda v: (v+1)/2 <= n, odd)
print(list(ten_odd))

groupby

groupby()把迭代器中相邻的重复元素挑出来放在一起。可以类比MapReduce任务的Reduce阶段,不过又不同于Reduce,因为这个迭代器中相同的元素并不是全部都是相邻的。
所以请特别注意是相邻的重复元素。

下面展示了groupby()使用的例子及其结果

iter_groups = itertools.groupby('AAAABBCCCCDDD')
for k,sub_iter in iter_groups:print("*******************************")print("k={}".format(k))print(list(sub_iter))

实际上挑选规则是通过函数完成的,只要作用于函数的两个元素返回的值相等,这两个元素就被认为是在一组的,而函数返回值作为组的key。如果我们要忽略大小写分组,就可以让元素’A’和’a’都返回相同的key。

下面的例子就是让相邻的大小写字母归为同一组。

iter_groups = itertools.groupby('AAaaABBCccCDdd', lambda v: v.upper())
for k,sub_iter in iter_groups:print("*******************************")print("k={}".format(k))print(list(sub_iter))

计算圆周率的小例子

根据提供的四个步骤去计算圆周率,如果打印出ok则证明算法是正确的。

def pi(n):""" 计算pi的值step 1: 创建一个奇数序列: 1, 3, 5, 7, 9, ...step 2: 取该序列的前N项: 1, 3, 5, 7, 9, ..., 2*N-1.step 3: 添加正负符号并用4除: 4/1, -4/3, 4/5, -4/7, 4/9, ...step 4: 求和:"""# step 1odd = itertools.count(1, step=2)# step 2n_odd = itertools.takewhile(lambda v: (v + 1) / 2 <= n, odd)# step 3  注意这里采用生成式的方式生成迭代对象iter_n = ( -4/i if (i+1)/2%2 == 0 else 4/i for i in n_odd)# step 4from functools import reducereturn reduce(lambda x,y:x+y, iter_n)def check_pi():print(pi(10))print(pi(100))print(pi(1000))print(pi(10000))assert 3.04 < pi(10) < 3.05assert 3.13 < pi(100) < 3.14assert 3.140 < pi(1000) < 3.141assert 3.1414 < pi(10000) < 3.1415print('ok')if __name__ == "__main__":check_pi()pass

contextlib

在Python里我们是通过with语句来自动关闭文件资源的,不需要写try ... finally ...这种繁琐的语句。
并不是只有open()函数返回的文件描述符对象才能使用with语句。实际上,任何对象,只要正确实现了上下文管理,就可以使用with语句关闭相应资源。
实现上下文管理是通过__enter____exit__这两个方法实现的。

下面的代码就展示了如何让自定义类可以使用with语句自动关闭资源。

class Query(object):def __init__(self, name):self.name=namedef __enter__(self):print("Begin")# 注意必须返回当前对象实例 否则使用with语句会报错return selfdef __exit__(self, exc_type, exc_val, exc_tb):if exc_type:print("Error")else:print("End")def query(self):print("Query info about {}".format(self.name))with Query("patrick") as q:q.query()

实际上每个类都要这么去实现两个方法也挺繁琐的。
我们可以使用contextlib模块提供的contextmanager去简化代码。

如下面代码所示。@contextmanager这个装饰器接受一个generator,用yield语句把with ... as var把变量输出出去,然后,with语句就可以正常地工作了。

class Query(object):def __init__(self, name):self.name=namedef query(self):print("Query info about {}".format(self.name))from contextlib import contextmanager@contextmanager
def create_query(name):print("Begin")q = Query(name)yield qprint("End")with create_query("patrick") as q:q.query()

很多时候我们希望在某些代码块前后自动执行特定代码,可以使用@contextmanager来实现。

如当向数据库执行一条查询语句时,可以按照下面的方式去计算查询时间并打印出来。

from contextlib import contextmanager
import time@contextmanager
def count_time(action):print("Begin")start = time.perf_counter()yieldend = time.perf_counter()print("{}共耗时{:.0f}s".format(action, (end-start)))print("End")with count_time("select"):import random# 向数据库执行一条查询语句time.sleep(random.randint(3,7))

closing

contextlib中还包含一个closing对象,这个对象就是一个上下文管理器,它的__exit__函数仅仅调用传入参数的close。其源码如下。

所以closeing上下文管理器仅使用于具有close()方法的资源对象。如我们通过urllib.urlopen打开一个网页,urlopen返回的对象有close方法,所以我们就可以使用closing上下文管理器。

from contextlib import closing
from urllib.request import urlopenwith closing(urlopen("https://www.baidu.com/")) as resp:print(type(resp))for line in resp:print(line)

urllib

urllib模块提供了一系列操作url的功能。

Get

urllibrequest模块可以非常方便地抓取URL内容,也就是发送一个GET请求到指定的页面,然后返回HTTP的响应。

urlopen()函数返回的对象类型是http.client.HTTPResponse。假设下面的resp就表示HTTPResponse对象。
resp.info()resp.getheaders() 返回HTTPResponseheader信息,只不过后者返回的是列表(如下面源码所示)。可以通过resp.info().get('Content-Type')来获取具体的header值,后者的话只能遍历获取了效率没有前者高。

resp.geturl()获取页面真实的url, 通过和原有的url进行比对可发现是否产生了重定向。
resp.getcode()获取响应的返回码, 其实就是返回resp.status
resp.read()获取响应的返回内容。

下面代码展示了通过urlopen()函数来抓取三个网页内容。
其中在抓取https://www.qq.com/时发现该网页的响应header里有Content-Encoding: gzip,表示该网页是通过gzip压缩然后返回给客户端的,客户端需要进行解压缩,所以下面的代码就用了zlib模块去解压缩。
由于resp.read()返回的类型是bytes,所以转换成中文需要解码。一方面可以参考响应header里的Content-Type看其具体是什么编码,一方面可以参考第三方库chardet来检查返回的内容具体是什么编码。
在发现网页编码类型是gb2312时,直接用gbk去解码即可,如果用gb2312反而会报错,具体可看下面代码。

from urllib.request import urlopen
from contextlib import closing# url = "https://lol.qq.com/"  # gb2312编码
url = "https://www.qq.com/"  # gb2312编码 并采用了Content-Encoding: gzip 通过压缩网页内容来减少网络传输数据量, 当然客户端就需要解压缩
# url = "https://www.csdn.net/" # utf-8编码with closing(urlopen(url)) as resp:# 属于http.client.HTTPResponseprint(type(resp))# 获取返回的header信息, 可以通过resp.info() 也可以通过resp.getheaders()print(resp.info())print(resp.getheaders())# 获取页面真实的url, 通过和原有的url进行比对可发现是否产生了重定向# 其实就是返回resp.urlprint(resp.geturl())# 获取响应的返回码 其实就是返回resp.statusprint(resp.getcode())# 获取响应返回的内容data = resp.read()# 获取其'Content-Type'print(resp.info().get('Content-Type'))gzip_val = resp.info().get("Content-Encoding")if gzip_val:print("Content-Encoding: ", gzip_val)# 解压缩import zlibdata = zlib.decompress(data, 16+zlib.MAX_WBITS)# 引用第三方包检查其类型 只作为参考, 也可以使用上面提到的'Content-Type'import chardetdetect_res = chardet.detect(data)print(detect_res)if detect_res.get("encoding").lower().find("utf") != -1:print(data.decode())else:# 例如 https://lol.qq.com/ 网页的编码就是gb2312 但是解码的时候直接用gbk解码就好print(data.decode("gbk"))

模拟浏览器去访问

如果我们要想模拟浏览器发送GET请求,就需要使用Request对象,通过往Request对象添加相应的header信息,我们就可以把请求伪装成浏览器。例如,模拟iPhone 6去请求豆瓣首页

首先通过request.Request(url)获取Request对象,然后通过req.add_header()添加相应的header信息,再使用urlopen(req)请求网页,注意此时urlopen()函数的参数是Request对象。

下面只展示了部分代码,其余内容和上面的代码保持一致。
其中user-agent的值可以通过在浏览器上按F12查看(如下图所示)。

from urllib.request import urlopen
from urllib import request
from contextlib import closing# url = "https://lol.qq.com/"  # gb2312编码
# url = "https://www.qq.com/"  # gb2312编码 并采用了Content-Encoding: gzip 通过压缩网页内容来减少网络传输数据量, 当然客户端就需要解压缩
# url = "https://www.csdn.net/" # utf-8编码
url = "https://www.douban.com/"req = request.Request(url)
# req.add_header("user-agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36")
# 模拟手机发送请求
req.add_header("user-agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Mobile Safari/537.36")
with closing(urlopen(req)) as resp:# 获取返回的header信息, 可以通过resp.info() 也可以通过resp.getheaders()print(resp.info())print(resp.getheaders())

Post

可以采用post的方式去提交请求。譬如登录。

下面的请求中查看所有用户信息和添加用户信息之前都需要先登录。那么这就涉及到一个保存cookie的问题
下面的请求是用SpringBoot写的一个简单的web应用。传递参数都是用的json,所以在发出请求时都需要在请求的header信息里添加content-type: application/json

url 说明
/login 登录
/user/list 查看所有用户信息
/user/add 添加用户信息
单独的用户登录

由于传递的是json信息,需要使用json.dumps(login_d).encode("utf8")将json信息转化成字符串并编码成bytes类型,这样才能传递给urlopen()函数的data参数。

from urllib.request import urlopen
from urllib import request
from contextlib import closing
import jsonurl = "http://localhost:8080/"req = request.Request(url+"login")
# 登录信息
login_d = {"username": 'admin', "password": 'admin'}
# 将个人信息转化成字符串后并编码转化成bytes类型
post_data = json.dumps(login_d).encode("utf8")
# 由于后台服务支持的是 application/json 而不是application/x-www-form-urlencoded 所以请求对象这里必须显示设置
req.add_header("content-type", "application/json")with closing(urlopen(req, data=post_data)) as resp:# 获取响应返回的内容data = resp.read()# 后台返回的是json字符串 通过json.loads转化成json对象res = json.loads(data)print(res)print(res.get("msg"))print(res.get("code"))

登录 站长啦 网站(https)
import ssl
# 如果网站的SSL证书是经过CA认证,就需要单独处理SSL证书,让程序忽略SSL证书验证错误,即可正常访问
context = ssl._create_unverified_context()  # 忽略安全res=urllib.parse.urlencode({"wd": "中文"})
print(res)
wd = urllib.parse.unquote(res)
print(wd)url = "https://top.cnzzla.com/member/?mod=login"
# 采用 urllib.parse.urlencode去编码
# 其中data如果有值就表明是用post请求, data参数必须转化为bytes类型
resp = urlopen(url, data=bytes(urllib.parse.urlencode({"email": "123@qq.com", "pass": "345", "action": "login"}) , encoding="UTF-8"),context=context)print(resp.read().decode("UTF-8"))

添加和查看用户信息

添加用户前,需要登录,如上面所示需要考虑cookie的保存问题。
需要使用下面的代码保存cookie信息,并且不能需要使用opener对象去请求,而不是像以前一样用urlopen()函数。

from urllib import request
from http import cookiejar
from contextlib import closing# 利用cookie保存登录信息
cookie = cookiejar.CookieJar()
handler = request.HTTPCookieProcessor(cookie)
# 后面用opener去请求而不是用urlopen() 这样就能访问cookie里的登录信息
opener = request.build_opener(handler)

下面是完整的代码

# -*- coding:UTF-8 -*-from urllib import request
from http import cookiejar
from contextlib import closing
import jsonserver_url = "http://localhost:8080/"# 利用cookie保存登录信息
cookie = cookiejar.CookieJar()
handler = request.HTTPCookieProcessor(cookie)
# 后面用opener去请求而不是用urlopen() 这样就能访问cookie里的登录信息
opener = request.build_opener(handler)def login():req = request.Request(server_url + "login")# 登录信息login_d = {"username": 'admin', "password": 'admin'}# 将个人信息转化成字符串后并编码转化成bytes类型post_data = json.dumps(login_d).encode("utf8")# 由于后台服务支持的是 application/json 而不是application/x-www-form-urlencoded 所以请求对象这里必须显示设置req.add_header("content-type", "application/json")# with closing(urlopen(req, data=post_data)) as resp:with closing(opener.open(req, data=post_data)) as resp:# 获取响应返回的内容data = resp.read()# 后台返回的是json字符串 通过json.loads转化成json对象res = json.loads(data)print(res)def user_list():req = request.Request(server_url + "user/list")with closing(opener.open(req)) as resp:# 获取响应返回的内容data = resp.read()# 后台返回的是json字符串 通过json.loads转化成json对象res = json.loads(data)# 采用pprint模块打印的更加美化import pprintpprint.pprint(res)def user_add(user):req = request.Request(server_url + "user/add")req.add_header("content-type", "application/json")post_data = json.dumps(user).encode()with closing(opener.open(req, data=post_data)) as resp:# 获取响应返回的内容data = resp.read()# 后台返回的是json字符串 通过json.loads转化成json对象res = json.loads(data)print(res)if __name__ == "__main__":login()user_data = {"username": "鹿丸", "password": "admin", "age": 20, "sex": "男", "money": 500.25, "school": "北京大学"}user_add(user_data)user_data = {"username": "丁次", "password": "admin", "age": 20, "sex": "男", "money": 200.25, "school": "火影大学"}user_add(user_data)user_data = {"username": "井野", "password": "admin", "age": 20, "sex": "女", "money": 100.25, "school": "中忍大学"}user_add(user_data)user_list()pass

urllib总结

418 响应码表示是反爬

Cookie 是由服务端生成后通过在响应头里设置set-cookie发送给客户端,Cookie总是保存在客户端。
下次客户端访问服务器时就在请求头里设置cookie一并发送给服务端,然后服务端读取cookie信息。

URL编码也称为 百分号编码 percent-encoding。
编码规则十分简单: %加上两位的字符(代表一个字节的十六进制)
URL编码就是将每个非ASCII码的字符替换为%XX形式, RFC建议使用UTF8编码

urllib.requests 用于打开url
urllib.error 包含urllib可能遇到的异常
urllib.parse 解析url (对应URL的编码和解码)
urllib.robotparser 用于解析robots.txt文件

urllib模块的使用

# get请求
urllib.requests.urlopen(url)
# post请求 注意data是必须是要编码后的bytes类型
urllib.requests.urlopen(url,data=data) # 构建请求对象应对普通的反爬 添加header信息
req=urllib.requests.Request(url,headers=headers)
urllib.requests.urlopen(req)# 高匿IP代理应对反爬
proxy_handler = urllib.request.ProxyHandler({"http": "120.194.55.139:6969"})
opener = urllib.request.build_opener(proxy_handler)
print(opener.open(req).read().decode("utf-8"))# 保存cookie信息
from http import cookiejar
cookie = cookiejar.MozillaCookieJar()
processor = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(processor)
print(opener.open(req).read().decode("utf-8"))

XML

python有三种方式去解析xml文件。个人倾向于第三种方式。

  • 使用dom去解析。缺点是需要将整个xml文件都读入内存,内存占用高,比较慢。
  • 使用sax去解析。sax是采用事件驱动模型,边读入内存边解析,优点是占用内存小,解析快,缺点是需要自己写对应事件的回调函数。
  • 使用ElementTree去解析。ElementTree 相对于 DOM 来说拥有更好的性能,与 SAX 性能差不多,API 使用也很方便。

准备xml文件如下。

<?xml version="1.0" encoding="utf-8"?>
<list>
<student id="stu1" name="stu1_name"><id>1001</id><name>张三</name><age>22</age><gender>男</gender>
</student>
<student id="stu2" name="stu2_name"><id>1002</id><name>李四</name><age>21</age><gender>女</gender>
</student>
</list>

dom

from xml.dom.minidom import parsexml_path = "d:/test.xml"def func_dom():"""使用dom解析xml文件, 由于dom会将整个xml文件读入内存,占用内存高,解析会比较慢"""# 将文件读取成一个dom对象dom = parse(xml_path)# 获取文档元素对象 这里获取的是<list>root = dom.documentElement# 类型是  xml.dom.minidom.Elementprint(type(root))# 根据tag=student获取所有的studentstus = root.getElementsByTagName("student")for stu in stus:# 获取属性值attr_id = stu.getAttribute("id")attr_name = stu.getAttribute("name")# 获取节点值id = stu.getElementsByTagName("id")[0].childNodes[0].dataname = stu.getElementsByTagName("name")[0].childNodes[0].dataage = stu.getElementsByTagName("age")[0].childNodes[0].datagender = stu.getElementsByTagName("gender")[0].childNodes[0].dataprint("attr_id={}\tattr_name={}\tid={}\tname={}\tage={}\tgender={}".format(attr_id, attr_name, id, name, age, gender))

sax

使用sax最麻烦的就是需要自己写回调函数。需要了解xml.sax.handler.ContentHandler类中几个事件的调用时机。

  • characters(content)方法
    从行开始,遇到标签之前,存在字符,content 的值为这些字符串。
    从一个标签,遇到下一个标签之前, 存在字符,content 的值为这些字符串。
    从一个标签,遇到行结束符之前,存在字符,content 的值为这些字符串。
    标签可以是开始标签,也可以是结束标签。

  • startDocument() 方法
    文档启动的时候调用。

  • endDocument() 方法
    解析器到达文档结尾时调用。

  • startElement(name, attrs)方法
    遇到XML开始标签时调用,name是标签的名字,attrs是标签的属性值字典。

  • endElement(name) 方法
    遇到XML结束标签时调用。

from xml.sax import ContentHandler
import xml.sax as saxxml_path = "d:/test.xml"class StudentHandler(ContentHandler):def __init__(self):# 存放所有的学生self.stus = []def startElement(self, name, attrs):# 记录当前的elementself.CurrentData = nameif name == "student":stu = {"attr_id": attrs["id"], "attr_name": attrs["name"]}self.stus.append(stu)# 记录当前学生self.CurrentStu = studef endElement(self, name):if name in ("id", "name", "age", "gender"):# 清空CurrentData 这是由于characters的调用时机会有三次# 所以当遇到介绍元素时就可以清空CurrentData保证属性值不会被覆盖self.CurrentData = ""passdef characters(self, content):if self.CurrentData in ("id", "name", "age", "gender"):self.CurrentStu[self.CurrentData]=contentpassdef func_sax():"""SAX 用事件驱动模型,通过在解析XML的过程中触发一个个的事件并调用用户定义的回调函数来处理XML文件。边读边解析,优点是占用内存小,缺点是需要自己写回调函数"""# 创建一个SAX parsersax_parser = sax.make_parser()# 关闭命名空间sax_parser.setFeature(sax.handler.feature_namespaces, 0)# 重写Handlerstu_handler = StudentHandler()sax_parser.setContentHandler(stu_handler)# 解析xml文件sax_parser.parse(xml_path)import pprintpprint.pprint(stu_handler.stus)

ElementTree

个人比较推荐这种方式去解析xml,主要是api比较友好。

def func_element_tree():"""使用ElementTree解析xml"""# 解析xml文件为ElementTree对象tree = ET.parse(xml_path)# 获取根元素root = tree.getroot()# xml.etree.ElementTree.Element# print(type(root))for stu in root:attrs = stu.attribstu_d = {"attr_id": attrs["id"], "attr_name": attrs["name"]}stu_d["id"]=stu.findtext("id")stu_d["name"]=stu.findtext("name")stu_d["age"]=stu.findtext("age")stu_d["gender"]=stu.findtext("gender")print(stu_d)

三种方式的代码运行截图

将上面三种方式的代码合并在一起后,运行结果截图如下。

HTMLParser

python提供了html.parser.HTMLParser类去解析HTML。需要注意的是该类也是事件驱动型,和用SAX去解析xml文件类似。
下面是该类常用的方法:

  • HTMLParser.feed(data):接收一个字符串类型的HTML内容,并进行解析。
  • HTMLParser.handle_starttag(tag, attrs):对开始标签的处理方法。例如<div id="main">,参数tag指的是div,attrs指的是一个(name,Value)的列表,即列表里面装的数据是元组。
  • HTMLParser.handle_endtag(tag):对结束标签的处理方法。例如</div>,参数tag指的是div。
  • HTMLParser.handle_startendtag(tag, attrs):识别没有结束标签的HTML标签,例如<img />等。
  • HTMLParser.handle_data(data):对标签之间的数据的处理方法。<tag>test</tag>,data指的是“test”。

实战例子

我们获取csdn博客首页右下角的企业博客信息, 包括企业博客的 名字、原创数、粉丝数、获赞数。如下图。

思路:首先通过上面的urlopen()函数去抓取该网页内容,然后通过html.parser.HTMLParser类去解析HTML获取网页上列出的企业博客信息。个人觉得最重要的是需要仔细观看网页内容,然后根据需求定位到所需要的元素,即需要一定的HTML知识。因为html.parser.HTMLParser类就是在扫描一个个标签的时候触发的事件(也就是你写的回调函数)。

下面贴出信息所在的html部分内容。

下面的代码我在上面的思路基础上又写了一个稍微没那么复杂的代码,即通过re模块去匹配到所需要的html内容,这样就会大大地较少解析的内容,同时代码上看起来就会简洁一些。
此处就不细讲代码具体的实现逻辑了。代码关键处有注释。

# -*- coding:UTF-8 -*-from contextlib import closing
from urllib.request import urlopen
from html.parser import HTMLParser"""
获取csdn博客首页右下角的企业博客信息, 包括企业博客的 名字、原创数、粉丝数、获赞数。
"""def get_page(url):data = ""with closing(urlopen(url)) as resp:# 获取响应返回的内容data = resp.read()return data.decode()class CsdnHtmlParser(HTMLParser):def __init__(self):# 必须对父类进行初始化 要不然运行会报错HTMLParser.__init__(self)# 记录一些信息以便后面事件驱动时使用self.enterprises = []self.is_tick = Falseself.is_tick_data = Falseself.CurrentEnterprise = Noneself.name_ok = Falsedef handle_starttag(self, tag, attrs):if tag == "div" and (("class", "enterprise_r") in attrs):enterprise = {"msg": "ok"}self.CurrentEnterprise = enterpriseself.enterprises.append(enterprise)elif tag == "a" and (("target", "_blank") in attrs):if self.CurrentEnterprise:href = list(filter(lambda x: x[0] == "href", attrs))[0][1]self.CurrentEnterprise["地址"] = hrefself.name_ok = Trueelif tag == "span" and (("class", "name") in attrs):if self.CurrentEnterprise:self.is_tick = Trueself.attr_name = Noneelif tag == "span" and (("class", "number") in attrs):if self.CurrentEnterprise and self.attr_name:self.is_tick_data = Truepassdef handle_startendtag(self, tag, attrs):# print("tag: ", tag)passdef handle_endtag(self, tag):if tag == "div" and self.CurrentEnterprise:self.CurrentEnterprise = Nonepassdef handle_data(self, data):if self.CurrentEnterprise and self.name_ok:self.CurrentEnterprise["名字"] = dataself.name_ok = Falseif self.is_tick:old_attr_name = self.attr_nameself.attr_name = dataif self.is_tick_data:self.CurrentEnterprise[old_attr_name] = dataself.is_tick = Falseself.is_tick_data = Falsepasspassdef parse_page(htmldata):"""获取企业博客的 名字、原创数、粉丝数、获赞数"""csdn_parser = CsdnHtmlParser()csdn_parser.feed(htmldata)import pprintpprint.pprint(csdn_parser.enterprises)def cut_html(htmldata):import re# 记住.*?这样就不是贪婪匹配了pattern = r'.*?<div class="enterprise_r">(.*?)</p>\n\s+</div>.*?'# 这里使用re.DOTALL让.可以表示任意字符(包括回车换行符) 不加的话.不能表示回车换行符的# 使用findall找出所有匹配到的内容matchs = re.findall(pattern, htmldata, re.DOTALL)for text in matchs:# print(text)cut_parser = CutCsdnHtmlParser()cut_parser.feed(text)cut_parser.blog_info()class CutCsdnHtmlParser(HTMLParser):def __init__(self):HTMLParser.__init__(self)# 记录一些信息以便后面事件驱动时使用self.href=""self.name=""self.blog = {}self.name_ok = Falseself.is_tick = Falseself.attr_name = Noneself.is_tick_data = Nonedef handle_starttag(self, tag, attrs):if tag == "a" and (("target", "_blank") in attrs):self.href=list(filter(lambda x: x[0] == "href", attrs))[0][1]self.name_ok=Trueelif tag == "span" and (("class", "name") in attrs):self.is_tick = Trueelif tag == "span" and (("class", "number") in attrs):self.is_tick_data = Truepassdef handle_startendtag(self, tag, attrs):passdef handle_endtag(self, tag):passdef handle_data(self, data):if self.name_ok:self.name=data# 避免下次进来替换掉正确的值self.name_ok=Falseelif self.is_tick:self.attr_name=dataself.is_tick=Falseelif self.is_tick_data:self.blog[self.attr_name]=dataself.is_tick_data=Falsepassdef blog_info(self):self.blog["href"]=self.hrefself.blog["name"]=self.nameimport pprintpprint.pprint(self.blog)passif __name__ == "__main__":url = "https://www.csdn.net/"# 第一种方式是直接整个HTML文件 由于HTMLParser是事件驱动类型 代码写的会比较凌乱htmldata = get_page(url)parse_page(htmldata)# 第二种方式是首先用re模块去匹配出所需要的html内容,然后再通过HTMLParser去解析# 由于已经通过re模块找出我们所需要的内容,所以代码上相较于第一种方式会简单一点htmldata = get_page(url)cut_html(htmldata)

参考网址

廖雪峰老师Python教程之常用内建模块
argparse简述
python菜鸟教程之xml解析
python解析xml
python正则中换行符的匹配

Python常用内建模块(内含实例)相关推荐

  1. Python常用内建模块——学习笔记

    1.datetime:Python处理日期和时间的标准库 引入方法: from datetime import datetime. 第一个datetime是模块,第二个datetime是类. 如果仅导 ...

  2. python标准类型内建模块_Python内建模块struct实例详解

    本文研究的主要是Python内建模块struct的相关内容,具体如下. Python中变量的类型只有列表.元祖.字典.集合等高级抽象类型,并没有像c中定义了位.字节.整型等底层初级类型.因为Pytho ...

  3. Python学习笔记:常用内建模块7XML

    前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...

  4. Python学习笔记:常用内建模块6 (urllib)

    前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...

  5. Python学习笔记:常用内建模块5

    前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...

  6. Python学习笔记:常用内建模块4:hmac

    前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...

  7. Python学习笔记:常用内建模块3:struct

    前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...

  8. Python学习笔记:常用内建模块2:collections

    前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...

  9. Python学习笔记:常用内建模块1

    前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...

最新文章

  1. 七、使用栈实现综合计算器(中缀表达式)
  2. SAP标准成本估算删除
  3. 反病毒引擎设计全解(一)
  4. ES报错:Connection reset by peer 解决经历
  5. RDD(弹性分布式数据集)
  6. Oracle性能优化
  7. Linux 文件系统相关的命令
  8. 杭电1879继续畅通工程
  9. springboot文件上传下载实战 —— 登录功能、展示所有文件
  10. QT-提示“database not open”
  11. 主从脉冲触发器和边沿触发器区别 一次翻转
  12. 关于网站中Logo部分的写法
  13. JavaMail使用阿里云企业版邮箱发送邮件
  14. java 判断手机运营商_JS正则表达式判断手机号所属运营商
  15. 在iOS应用中跳转到淘宝或天猫客户端商品详情页
  16. 爷爷:啥是佩奇?佩奇:Python 10 秒做出来,你看像不像?
  17. 【2022 CCF BDCI 文心大模型创意项目】乐享词话—诗词意境辅助记忆工具
  18. java在文件中输出换行符
  19. javascript常用知识点集
  20. (C语言)写一个函数insert,用来向一个动态链表插入节点

热门文章

  1. python 直接退出程序_python 退出程序 Python程序运行后直接退出
  2. 计算机运行程序时声音特别大,为什么我的电脑在运行程序的时候机箱的声音非常大?...
  3. php+mysql decimal字段处理金额遇到误差处理思路
  4. Mimikatz2.2 如何抓取Win11登录明文密码
  5. XMETA受邀参加迪拜MetaWeek2022,共探元界的发展与未来
  6. python 列表、元组 1.2 苏州大学运动会100米决赛8名队员按照成绩从小到大排序
  7. Android5 喜刷刷
  8. idea java 热部署_Intellij IDEA 热部署处理方法(图解)
  9. 2020年,网红KOL营销到底要怎么做才算成功?我来谈谈
  10. 微信收费持续发酵 或演变为运营商与腾讯的博弈