Python 标准库非常庞大,所提供的组件涉及范围十分广泛。这个库包含了多个内置模块 (以 C 编写),Python 程序员必须依靠它们来实现系统级功能,例如文件 I/O,此外还有大量以 Python 编写的模块,提供了日常编程中许多问题的标准解决方案。其中有些模块经过专门设计,通过将特定平台功能抽象化为平台中立的 API 来鼓励和加强 Python 程序的可移植性。
Windows 版本的 Python 安装程序通常包含整个标准库,往往还包含许多额外组件。对于类 Unix 操作系统,Python 通常会分成一系列的软件包,因此可能需要使用操作系统所提供的包管理工具来获取部分或全部可选组件。
1.官方入门教程
链接:https://docs.python.org/zh-cn/3/tutorial/index.html Python 教程 — Python 3.9.6 文档
这个教程非正式地介绍了 Python 语言和系统的基本概念和功能。最好在阅读的时候准备一个 Python 解释器进行练习。所有的例子都是相互独立的,所以这个教程也可以离线阅读
2.urllib——URL处理模块
urllib标准库是python内置的HTTP请求库,无需额外安装。这个模块的功能大多都能被第三方库requests库代替,而且requests库功能更强大,用法更方便,所以这个部分主要进行过渡,了解大概内容就好——了解库的功能,知道库的常用函数以及函数的功能。
urllib库主要包含4个模块:
request——HTTP请求模块,打开读取URL;
error——异常处理模块,对抛出的异常进行捕获、处理;
parse——工具模块,可以对URL进行解析、拆分、合并等操作;
robotparser——识别、解析网站的robots.txt文件。
很明显,urllib库可以完成的功能:1.通过request模块发送请求;2.通过error模块处理异常;3.通过parse模块对链接进行解析;4.通过robotparser模块解析网站的robots.txt文件。
2.1 发送请求
利用urllib库发送请求,首先要将urllib.request模块导入,接着确定要请求的网址,然后查看返回的类型,以便进一步处理。以对常用测试HTTP的网站 httpbin为例,对这个网站进行请求。
import urllib.request # 导入urllib.request模块
url = 'http://httpbin.org/' # 设置网址
response = urllib.request.urlopen(url) # 发出请求,并将返回的对象赋给response
print(type(response)) #查看返回的对象类型
-------------------------------------------
<class 'http.client.HTTPResponse'> #返回的类型为HTTPResponse
我们可以看到response的类型是HTTPResponse类型,那么HTTPResonse类型是什么鬼呢?我第一次接触这个,不要问我啊,我也不知道啊喂。
2.1.1 HTTPResponse
针对这种事件,第一想到的应该是查找官方文档——标准且全面,所以我们可以在Python官网——http.client — HTTP protocol client — Python 3.9.6 documentation上查到HTTPResponse Objects的内容:
1.HTTPResponse 对象来自服务器的 HTTP 响应。 它提供对请求标头和实体正文的访问。 响应是一个可迭代对象,可以在 with 语句中使用。
2.HTTPResopnse包含5种方法:
read(num)——读取并以字节流的形式返回响应主体;num参数可以不传;当传入num数值时,响应体只显示前num个字节
readinto(b)——将响应正文的下一个 len(b) 字节读入缓冲区 b并返回读取的字节数;
getheader(name)——获取单个Headers请求头中name所对应的value;
getheaders()——返回 (header, value) 元组的列表;
fileno()——返回底层socket的文件编号
我们在程序中具体实践以上方法,得到如下响应。
import urllib.request # 导入urllib.request模块
url = 'http://httpbin.org/' # 设置网址
response = urllib.request.urlopen(url) # 发出请求,并将返回的对象赋给response
print(response.read()) # 返回响应体
print("______________________________________________________________________") # 分隔符
print(response.readinto('20')) # len(20")=2,返回读取的字节数
print("______________________________________________________________________")
print(response.getheader('Date')) # 返回headers中Date的value值
print("______________________________________________________________________")
print(response.getheaders()) # 返回headers列表
print("______________________________________________________________________")
print(response.fileno()) #单独运行才行,否则会报错
——————————————————————————————————————————————————————————————————————————————
由于第一个read()返回的响应体内容太多,所以就截取了开头和结尾的字节
b'<!DOCTYPE html>\n<html lang="en">\n\n<head>\n <meta charset="UTF-8">\n <title>httpbin.org</title>\n
......
</div>\n</div>\n</body>\n\n</html>'
_____________________________________________________________________________
0
_____________________________________________________________________________
Tue, 03 Aug 2021 10:56:47 GMT
_____________________________________________________________________________
[('Date', 'Tue, 03 Aug 2021 10:56:47 GMT'), ('Content-Type', 'text/html; charset=utf-8'), ('Content-Length', '9593'), ('Connection', 'close'), ('Server', 'gunicorn/19.9.0'), ('Access-Control-Allow-Origin', '*'), ('Access-Control-Allow-Credentials', 'true')]
_____________________________________________________________________________
484
3.HTTPResponse包含8种属性:
msg——包含响应标头的 http.client.HTTPMessage 实例,目前我还不知道这个是什么意思 ;
version——服务器使用的 HTTP 协议版本,10 表示 HTTP/1.0,11 表示 HTTP/1.1;
url——检索到的资源的 URL,通常用于确定是否遵循重定向;
headers——某种特定格式的响应标头;
status—— 服务器返回的状态代码;
reason——服务器返回的原因短语;
debuglevel——如果
调试
大于零,消息将打印到粗壮的响应读取和解析,否则返回0;closed——如果流被关闭,则返回
True;
我们在程序中具体实践以获得response属性,得到如下响应:
import urllib.request # 导入urllib.request模块
url = 'http://httpbin.org/' # 设置网址
response = urllib.request.urlopen(url) # 发出请求,并将返回的对象赋给response
print(response.msg)
print("__________________________________________________________")
print(response.version)
print("__________________________________________________________")
print(response.url)
print("__________________________________________________________")
print(response.headers)
print("__________________________________________________________")
print(response.status)
print(response.reason)
print(response.debuglevel)
print(response.closed)
----------------------------------------------------------
OK
__________________________________________________________
11
__________________________________________________________
http://httpbin.org/
__________________________________________________________
Date: Tue, 03 Aug 2021 11:14:24 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 9593
Connection: close
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
__________________________________________________________
200
OK
0
False
2.1.2 urlopen()方法
官方文档给出的urlopen()的API:
urllib.request.
urlopen
(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)
我们可以看到除了第一个参数传递url网址外,还可以传递其它的参数,如data,timeout等。
- data参数说明
data参数是可选的,默认为None。不传data参数时,urlopen()方法其实是属于HTTP请求方法中的GET方法。若传递data参数,则urlopen()是属于POST方法。
data必须是指定要发送到服务器的其他数据的对象,或者如果不需要此类数据。目前 HTTP 请求是唯一使用数据的请求。 支持的对象类型包括字节、类文件对象和类字节对象的可迭代对象。
根据官方文档,对于 HTTP POST 请求方法,数据应该是标准 application/x-www-form-urlencoded 格式的缓冲区。 urllib.parse.urlencode() 函数采用 2 元组的映射或序列,并以这种格式返回 ASCII 字符串。 在用作数据参数之前,应将其编码为字节bytes。application/x-www-form-urlencoded 格式的参数格式为key=value&key=value,具体可以传入字典形式如{'word’:’ hello'}。
如何将ASCII码编码为字节呢?python3.x 提供了内置函数bytes()函数,bytes
([source[, encoding[, errors]]]),返回一个新的 bytes 对象。
字节文字中只允许使用 ASCII 字符(无论声明的源代码编码如何)。 任何超过 127 的二进制值都必须使用适当的转义序列输入到字节文字中。
- 如果 source 为整数,则返回一个长度为 source 的初始化数组;
- 如果 source 为字符串,则按照指定的 encoding 将字符串转换为字节序列;
- 如果 source 为可迭代类型,则元素必须为[0 ,255] 中的整数;
- 如果 source 为与 buffer 接口一致的对象,则此对象也可以被用于初始化 bytearray。
- 如果没有输入任何参数,默认就是初始化数组为0个元素
通过前面的学习我们知道POST请求方法需要向服务器提交表单或上传文件,这个时候就需要一个测试网站来提供HTTP 请求测试。http://httpbin.org/post这个链接可以进行post测试,它可以传输一些请求的信息。依据上文提到的post请求方法,对要传递的data参数先用urllib.parse.urlencode()进行格式化处理,然后将返回的字符串以’utf-8'编码为字节,接着将data参数post给服务器。
import urllib.request # 导入urllib.request模块
import urllib.parse #导入urllib.parse模块
url = 'http://httpbin.org/post' # 设置网址
data = bytes(urllib.parse.urlencode({'world':' hello'}), encoding='utf-8')
response = urllib.request.urlopen(url, data=data) # 发出Post请求,并将返回的对象赋给response
print(response.read().decode('utf-8'))
我们可以在如下图所示返回的响应体中的form字段中看到post提交的内容。
{
"args": {},
"data": "",
"files": {},
"form": {
"world": " hello"
},
"headers": {
"Accept-Encoding": "identity",
"Content-Length": "11",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "Python-urllib/3.9",
"X-Amzn-Trace-Id": "Root=1-610946a0-68e3a3182124f6c947e7043f"
},
"json": null,
"origin": "123.124.23.253",
"url": "http://httpbin.org/post"
}
这事就完了吗?我觉得还可以探讨一下:关于程序中encoding是如何确定的,以及decode()又是做什么的?
encoding指☞编码形式,在本例子中是对经过处理的、已经是ASCII 字符串形式的{'world':' hello'}以'utf-8'编码。可能你会问为什么不将它编码为其它类型的呢,如GBK?说实话,我找了不少资料仍没有很清楚,在这里点出这个问题,提醒自己并希望能获得更适合的解释,但是有以下几点可能需要注意:
1.UTF-8 是一种国际化的编码方式,包含了世界上大部分的语种文字(简体中文字、繁体中文字、英文、日文、韩文等语言),也兼容 ASCII 码。
2.网页编码通常是’utf-8',如本例的网站http://httpbin.org
3.在设计中文参数的传递时,返回的响应体部分无法解码出来
import urllib.request # 导入urllib.request模块
import urllib.parse #导入urllib.parse模块
url = 'http://httpbin.org/post' # 设置网址
data = bytes(urllib.parse.urlencode({'世界':' 你好','world':'hello'}), encoding='utf-8')
response = urllib.request.urlopen(url, data=data) # 发出Post请求,并将返回的对象赋给response
print(response.read().decode('utf-8'))
我们可以看到form中传递的字典参数{'世界':‘你好’}以unicode形式存在,未被解码。
{
"args": {},
"data": "",
"files": {},
"form": {
"world": "hello",
"\u4e16\u754c": " \u4f60\u597d"
}
decode()——解码,在官方文档中有这么一句话:”通常,只要能确定或猜出编码格式,就应将返回的字节串解码为字符串“
- timeout参数说明
timeout为可选参数,用于设置超时时间,单位为秒,即若发出的请求超过timeout时间,仍未得到服务器的响应,则抛出URLError异常,异常原因是超时。当然异常可以用try…except语句处理,这个以后会涉及。
2.1.3 Request类
从上篇(二)跨门槛——爬虫基础_猫猫猫耳的博客-CSDN博客中我们知道一个完整的请求不仅包含请求方法、请求网页,还包含请求头Headers,而且网站反爬虫策略中对请求头Headers审查是很常见的,所以有必要通过一定的技术手段对Headers进行构建,以模拟真正的浏览器操作。
那么问题来了,刚学的urlopen()方法的API中并没有关于headers的参数传递,这该如何是好?
此时一个名叫Request类的方法悄悄路过。
我们先看看Request类的官方定义:
class
urllib.request.
Request
(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)
其中对于参数data的要求,与urlopen()方法中参数的要求类似,参数url在urlopen()方法中可以是一个字符串或一个Request对象,在Request类中应为包含合法 URL 的字符串。
headers 应为字典对象。通常用于
User-Agent
头部数据的“伪装” ,浏览器用这些头部数据标识自己——某些 HTTP 服务器只允许来自普通浏览器的请求,而不接受来自脚本的请求。 urllib的默认 user agent 字符串则是"Python-urllib/3.9"
(在 Python 3.9 上)。origin_req_host 应为发起初始会话的请求主机。默认指为``http.cookiejar.request_host(self),这是用户发起初始请求的主机名或 IP 地址。
nverifiable 应该标示出请求是否无法验证,所谓无法验证的请求,是指用户没有机会对请求的 URL 做验证。
method 应为字符串,标示要采用的 HTTP 请求方法(例如
'HEAD'
)。如果给出本参数,其值会存储在method属性中,并由get_method()
使用。如果 data 为``None``, 则默认值为'GET'
,否则为'POST'
。
举个例子,使用Request并post一个参数:
import urllib.request # 导入urllib.request模块
import urllib.parse #导入urllib.parse模块
url = 'http://httpbin.org/post' # 设置网址
# 设置请求头
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
'AppleWebKit/537.36 (KHTML, like Gecko)'
' Chrome/92.0.4515.107 Safari/537.36 Edg/92.0.902.62',
'host':'httpbin.org'
}
# 设置要传递的参数
dict = {
'name':'Alex'
}
data = bytes(urllib.parse.urlencode(dict), encoding='utf-8')
req = urllib.request.Request(url=url, data=data, headers=headers)
print(type(req)) # 返回了Request类型的对象
response = urllib.request.urlopen(req)
print(response.read().decode('utf-8'))
返回的响应如下,可以看到form中传递了dict参数,在headers中的user-agent是自己设置的。
<class 'urllib.request.Request'>
{
"args": {},
"data": "",
"files": {},
"form": {
"name": "Alex"
},
"headers": {
"Accept-Encoding": "identity",
"Content-Length": "9",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36 Edg/92.0.902.62",
"X-Amzn-Trace-Id": "Root=1-610a4f44-5233dfb16db65c2354d6afbf"
},
"json": null,
"url": "http://httpbin.org/post"
}
2.2 处理异常
在上文我们了解了请求的发送过程,但是在网络不好的情况下,如果出现了异常,又不想看到程序终止运行而出现的红色警告,该怎么办呢?
此时一个名叫urllib.error的模块悄悄路过。
urllib.error 模块为 urllib.request 所引发的异常定义了异常类。 基础异常类是 URLError。
URLError 类来自 urllib 库的 error 模块,它继承自 OSError 类,是 error 异常模块的基类,由 request模块生的异常都可以通过捕获这个类来处理。它具有一个属性 reason,即返回错误的原因,它可以是一个消息字符串或另一个异常实例。
HTTPError类是URLError的一个子类,适用于处理特殊 HTTP 错误例如作为认证请求的时候。另外,HTPPError也可以作为一个非异常的文件类返回值(与 urlopen()返所回的对象相同)。
HTTPError具有3个属性:
code——一个HTTP 状态码;
reason——通常是一个解释本次错误原因的字符串;
headers——导致HTTPError 的特定 HTTP 请求的 HTTP 响应头。
关于异常的处理,通常会使用try…except语句。
try…except:
- Try …except语句——用于处理程序执行过程中的异常情况
Try:
Normal block
Except A:
Exc A block
Except:
Other block
Else:
Not normal block
–>执行normal block
–>发现有A错误,执行 exc A block(即处理异常)
–>结束
如果没有A错误呢?
–>执行normal block
–>发现B错误,开始寻找匹配B的异常处理方法,发现A,跳过,发现except others(即except:),执行exc other block
–>结束
如果没有错误呢?
–>执行normal block
–>全程没有错误,跳入else 执行noError block
–>结束
- Try…finally语句——无论执行过程中有没有异常,都要执行清场工作
try:
execution block ##正常执行模块
except A:
exc A block ##发生A错误时执行
except:
other block ##发生除了A,B错误以外的其他错误时执行
else:
if no exception, jump to here ##没有错误时执行
finally:
final block ##总是执行
- with…as 语句
with expression [as variable]:
with-block
1. as可以省略 2. 有一个句块要执行
with…as语句执行顺序:
–>首先执行expression里面的__enter__函数,它的返回值会赋给as后面的variable,想让它返回什么就返回什么,只要你知道怎么处理就可以了,如果不写as variable,返回值会被忽略。
–>然后,开始执行with-block中的语句,不论成功失败(比如发生异常、错误,设置sys.exit()),在with-block执行完成后,会执行expression中的__exit__函数
上下文管理器
简单的理解,同时包含 __enter__() 和 __exit__() 方法的对象就是上下文管理器。也就是说,上下文管理器必须实现如下两个方法:
- __enter__(self):进入上下文管理器自动调用的方法,该方法会在 with as 代码块执行之前执行。如果 with 语句有 as子句,那么该方法的返回值会被赋值给 as 子句后的变量;该方法可以返回多个值,因此在 as 子句后面也可以指定多个变量(多个变量必须由“()”括起来组成元组)。
- __exit__(self, exc_type, exc_value, exc_traceback):退出上下文管理器自动调用的方法。该方法会在 with as 代码块执行之后执行。如果 with as 代码块成功执行结束,程序自动调用该方法,调用该方法的三个参数都为 None:如果 with as 代码块因为异常而中止,程序也自动调用该方法,使用 sys.exc_info 得到的异常信息将作为调用该方法的参数。
当 with as 操作上下文管理器时,就会在执行语句体之前,先执行上下文管理器的 __enter__() 方法,然后再执行语句体,最后执行 __exit__() 方法。
2.3 分析robots协议
2.3.1 Robots协议
Robots 协议也称作爬虫协议、机器人协议,它的全名叫作网络爬虫排除标准( R obots ExclusionProtocol ),用来告诉爬虫和搜索引擎哪些页面可以抓取,哪些不可以抓取。 它通常是一个叫作 robots.txt的文本文件,一般放在网站的根目录下 。
如果你想在任何大型网站上查找 robots.txt 文件,可以在网站根目录 http://website.com/robots.txt 找到,website.com为目标域名,当然不仅仅只是以.com为后缀的商业网站。如:
值得注意的是:
- robots.txt 文件的语法没有标准格式。它是一种业内惯用的做法,但是没有人可以阻止别人创建自己版本的 robots.txt 文件(并不是说如果它不符合主流标准,机器人就可以不遵守)。它是一种被企业广泛认可的习惯,主要是因为这么做很直接,而且企业也没有动力去发展自己的版本,或者尝试去改进它。
- robots.txt 文件并不是一个强制性约束。它只是说“请不要抓网站的这些内容”。有很多网络爬虫库都支持 robots.txt 文件(虽然这些默认设置很容易修改)。另外,按照 robots.txt 文件采集信息比直接采集要麻烦得多(毕竟,你需要采集、分析,并在代码逻辑中处理页面内容)。
文件的第一行非注释内容是 User-agent:,注明具体哪些机器人需要遵守规则。后面是一组规则 Allow: 或 Disallow:,决定是否允许机器人访问网站的该部分内容。星号(*)是通配符,可以用于 User-agent:,也可以用于 URL 链接中。
如果一条规则后面跟着一个与之矛盾的规则,则按后一条规则执行,如:
#Welcome to my robots.txt file!
User-agent: *
Disallow: *
User-agent: Googlebot
Allow: *
Disallow: /private
在这个例子中,所有的机器人都被禁止访问网站的任意内容,除了 Google 的网络机器人,它被允许访问网站上除了 /private 位置的所有内容。
虽然看到一个指明爬虫采集限制范围的文件让人感觉很憋屈,但是它其实可以成为网络爬虫开发的指示灯。如果你发现一个 robots.txt 文件禁止采集网站上某个部分的内容,那么基本可以确定网管同意你采集其他部分的所有内容(如果他们不愿意让你采集,在 robots.txt文件中应该已经明确禁止了)
有关 robots.txt 文件结构的更多细节请参阅 http://www.robotstxt.org/orig.html。
2.3.2 urllib.robotparser
此模块提供了一个单独的类 RobotFileParser,它可以回答关于某个特定用户代理是否能在 Web 站点获取发布 robots.txt 文件的 URL 的问题。该模块定义了一个类,用起来比较简单,直接传递目标网站的robots.txt链接即可。
class
urllib.robotparser.
RobotFileParser
(url='')
该类有9种方法,其中常用的如下:
set_url()——用来设置 指向robots.txt 文件的URL 。 如果在创建 RobotFileParser 对象时传入了链接,那么就不需要再使用这个方法设置了;
read()——读取 robots . txt URL并将其输入解析器 。 注意,这个方法执行一个读取和分析操作,如果不调用这个方法 , 接下来的判断都会为 False ,所以一定记得调用这个方法 。 这个方法不会返回任何内容,但是执行了读取操作 ;
parse()——解析行参数。用来解析 robots. txt 文件,传人的参数是 robots .txt某些行的内容 ,它会按照 robots . txt的语法规则来分析这些内容。
can_fetch()——如果允许 useragent 按照被解析 robots.txt 文件中的规则来获取URL则返回 True。该方法传人两个参数 , 第一个是 User-agent ,第二个是要抓取的 URL 。 返回的内容是该搜索引擎是否可以抓取这个 URL ,返回结果是 True 或 False;
mtime()——返回最近一次获取robots.txt
文件的时间。 这适用于需要定期检查robots.txt
文件更新情况的长时间运行的网页爬虫;
modified()——将最近一次获取 robots.txt 文件的时间设置为当前时间。它同样对长时间分析和抓取的搜索爬虫很有帮助。site_maps()——以 list() 的形式从 robots.txt 返回 Sitemap 形参的内容。 如果此形参不存在或者此形参的 robots.txt 条目存在语法错误,则返回 None
一般情况下,在构造Headers时最好也把cookie加进去,基本上就可以对一部分网站进行爬取了,当然还得掌握基本的正则表达式。
动态Cookie和代理目前还没玩转,就先写到这,下篇将用目前学习的知识进行一个小项目。