第二部分:网络请求
本部分主要介绍如何通过url链接访问到目标服务器的过程。主要介绍两个库:urllib库和request库。知识结构如下:
Part 1: urllib库
urllib库是python自带的库,功能比较基础,全面。在python3中,所有的urllib库的网络请求功能,都被集中到urllib.request模块下面了。
1.1 urlopen函数
这个函数的作用是,通过url链接,访问服务器。
例1:通过urlopen()访问百度主页:
from urllib import request
url = 'http://www.baidu.com'
resp = request.urlopen(url, timeout = 3)
print(type(resp))
print(resp.read())
返回的结果如下:
<class 'http.client.HTTPResponse'>
b'<!DOCTYPE html>\n<!--STATUS OK-->\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n
可以看到,返回值的类型是http定义下的client下的HTTPResponse类;
需要对这个类进行仔细研究,其中的比较重要的属性和方法如下:
a. _method = ‘GET’ #说明访问的方法为前面介绍的8种中的get;
b. status = 200 #说明返回访问正常
c. url = ‘http://www.baidu.com'# 说明这个网页最后定位的地址是百度主页;
d. fp # 这个属性实际上是可以将这个网页写入本地的buffer,读取这个buffer的方式为’rb’
e. headers # 这个是服务器给网页申请发回的头文件,其子属性headers中存在着一些有用的信息,如:Content-Encoding, Date, Server等内容;
其次是对.read()内容的说明。这里输出的格式是一行(Pycharm),而且也没有出现类似于“百度一下,你就知道”这样的logo,这是因为编码的方式不对。这里将编码模式修改为:utf-8,即可获取下面的结果:
<!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">
<meta name="theme-color" content="#2932e1">
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
<link rel="search" type="application/opensearchdescription+xml" href="/content-search.xml" title="百度搜索" />
<link rel="icon" sizes="any" mask href="//www.baidu.com/img/baidu_85beaf5496f291521eb75ba38eacbd87.svg">
<link rel="dns-prefetch" href="//s1.bdstatic.com"/>
<link rel="dns-prefetch" href="//t1.baidu.com"/>
<link rel="dns-prefetch" href="//t2.baidu.com"/>
<link rel="dns-prefetch" href="//t3.baidu.com"/>
<link rel="dns-prefetch" href="//t10.baidu.com"/>
<link rel="dns-prefetch" href="//t11.baidu.com"/>
<link rel="dns-prefetch" href="//t12.baidu.com"/>
<link rel="dns-prefetch" href="//b1.bdstatic.com"/>
<title>百度一下,你就知道</title>
...
为了省略,就只显示部分内容,并将空白删除。可以看到,这里的结果已经能够显示我们获取了百度网页的内容。
具体地代码修改为:print(resp.read().decode('utf-8'))
;
1.2 urlretrieve函数
这个函数的作用是,能够将网页上的内容保存下来。
例2:利用urlretrieve函数将百度主页下载下来:
from urllib import request
request.urlretrieve('http://www.baidu.com/','baidu.html')
运行之后,可以看到本地的路径下多了一个baidu.html文件,这个文件就是复制的百度主页的文件。
这里首先对urlretrieve这个函数进行解析:
urlretrieve(url, filename=None, reporthook=None, data=None)
参数url:下载链接地址
参数filename:指定了保存本地路径(如果参数未指定,urllib会生成一个临时文件保存数据。)
参数reporthook:是一个回调函数,当连接上服务器、以及相应的数据块传输完毕时会触发该回调,我们可以利用这个回调函数来显示当前的下载进度。
参数data:指post导服务器的数据,该方法返回一个包含两个元素的(filename, headers) 元组,filename 表示保存到本地的路径,header表示服务器的响应头。
urlretrieve还能够提示下载进度;
例3:利用urlretrieve函数将百度主页下载下来,并显示下载的进度:
from urllib import request
def cbk(down, block, size):
'''回调函数
down:已经下载的数据块
block:数据块的大小
size:远程文件的大小
'''
per = 100.0 * down * block / size
if per > 100:
per = 100
print('%.2f%%' % per)
url = 'http://www.baidu.com'
request.urlretrieve(url,reporthook = cbk)
此时,就可以显示下载进度了。
1.3 urlencode和parse_qs函数
发送浏览器请求时,如果有中文或者特殊字符,浏览器会自动给这些字符进行编码。
备注:这里用到的两个函数是在parse类中。
例4:在百度中搜索“爬虫”,打开Chrome浏览器的检查,翻到Network页面,就会发现此时跳转链接如下截图:![]()
那么,为什么是上面那个链接呢?是因为浏览器将输入的爬虫进行了编码。这里使用的编码方式可以通过urlencode进行模拟。
例4:用urlencode模拟浏览器搜索爬虫的时候的字符:
from urllib import parse
kw = {'wd':'爬虫'}
res = parse.urlencode(kw)
print(res)
这里的输出结果就是“wd=%E7%88%AC%E8%99%AB”了,与浏览器的一致;
至于为什么有wd=,就要看百度这个界面的搜索模式了。百度的搜索方法是:
在www.baidu.com/s?ite = utf-8&wd=关键字
例5:通过编程的方式,返回百度搜索“爬虫”的界面:
from urllib import request, parse
kw = {'wd':'爬虫'}
url = 'http://www.baidu.com/s?'
url_get = url + 'e=UTF-8&' + parse.urlencode(kw)
request.urlretrieve(url_get,'spider.html')
此时即可观察到返回的html网页。
如果想把输出的编码的字符转换为汉字或者能够读懂的字体的话,需要做编码格式的转换,这里就用到了parse_qs函数。
例6:将爬虫的编码形式恢复到汉字形式:
from urllib import parse
wd = 'wd=%E7%88%AC%E8%99%AB'
res = parse.parse_qs(wd)
print(type(res))
print(res)
输出的结果为:<class 'dict'>
和{'wd': ['爬虫']}
,可以看到,输出的结果是字典的形式,而且输入的字典中value的值得形式是str,现在变成了list。
1.4 urlparse和urlsplit函数
这两个函数的作用是,对url的信息进行分割。
备注:这里的两个函数属于parse库。
例7:分别利用urlparse和urlsplit函数对例5的url_get进行分割:from urllib import parse url_get = 'https://www.baidu.com/s?ie=UTF-8&wd=%E7%88%AC%E8%99%AB' res_parse = parse.urlparse(url_get) res_split = parse.urlsplit(url_get) print(res_parse) print(res_split)
输出的结果为:
ParseResult(scheme='https', netloc='www.baidu.com', path='/s', params='', query='ie=UTF-8&wd=%E7%88%AC%E8%99%AB', fragment='') SplitResult(scheme='https', netloc='www.baidu.com', path='/s', query='ie=UTF-8&wd=%E7%88%AC%E8%99%AB', fragment='')
可以看到,这两个函数都对这个url进行了拆分,对比上一节的内容,可以看出拆分的结果。需要说明的是,这两个函数的区别在于,parse还会对params进行拆分。
1.5 实例1: 爬取京东的主页并下载
from urllib import parse, request
def main(url):
res = request.urlopen(url)
print(res.status)
print(res.read().decode('utf-8'))
request.urlretrieve(url, 'jd.txt')
if __name__ == '__main__':
url = 'https://www.jd.com/'
main(url)
可以在文件中查看下载的资源。这里其实1个例子足够了。
Part 2: requests库
这个库是需要自己下载安装的,直接pip install requests就可以了。
2.1 首先介绍requests库的7个方法:
其实,这些方法也可以作为request方法的请求方式,如下:
这里的**kwargs常用的13个参数如下:
a. params:字典或字节序列,作为参数增加到URL中;
b. data:字典、字节序列或文件对象,作为request的内容,但是并不放到URL链接里,放在URL链接对应位置作为数据存储;当然也可以把一个字符串赋值给data,该字符串放在URL所对应的位置;
c. json::JSON数据,作为request的内容提交,放在服务器的json里面;
d. headers:字典,HTTP定制头;
e. cookies:字典或CookieJar,request中的cookie;
f. auth:元组,支持HTTP认证功能;
g. files:字典类型,向某个链接提交传输文件;
h. timeout:设定超时时间,单位为s.如果规定时间内服务器不能返回就会产生一个timeout异常;
j. proxis:字典类型,设定访问代理服务器,可以增加登录认证,如增加登录用户名和密码。主要用于隐藏爬取网页的源网站,防止爬虫的逆追踪;
k. allow_redirects:True/False,默认为True,重定向开关;
l. steam:True/False,默认为True,获取内容后立即下载;
m. verify:True/False,默认为True,认账SSL证书开关;
n. cert:本地SSL证书路径
例8:抓取百度主页:
import requests
url = 'http://www.baidu.com'
res = requests.get(url)
print(type(res))
print(res.status_code)
print(res.text)
这里可以返回百度主页的信息。需要关注的是res所属的类别<class 'requests.models.Response'>
。这个类中需要我们关注如下的5种属性:
借助于上述属性,对例8的代码进行优化,如下:
import requests
def getHtml(url):
try:
res = requests.get(url, timeout = 3)
res.raise_for_status()
res.encoding = res.apparent_encoding
return res.text
except:
return '产生异常'
if __name__ == '__main__':
url = 'http://www.baidu.com'
res = getHtml(url)
print(res)
其中,timeout是访问超时设置,raise_for_status是说返回码如果不对的话,就发出警报;res.encoding = res.apparent_encoding是用来改变text的输出的码制。
实例2:爬取亚马逊主页
按照例8中展示的方法,当我们爬取亚马逊的主页的时候,出现了异常,代码为503,这个代码表示服务器拒绝访问这个页面。
这是因为我们发出的请求头不对。我们在代码中加入:print(res.request.headers)
可以看到此时的请求头为:
{'User-Agent': 'python-requests/2.22.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}
其中,可以看到我们访问的爬虫的头部信息为python的requests类,并不是浏览器。因而亚马逊为了反爬虫,对我们的访问提出了拒绝。改进方案:
import requests
def getHtml(url):
try:
kv = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36'}
res = requests.get(url, timeout=3, headers = kv)
print(res.request.headers)
res.raise_for_status()
res.encoding = res.apparent_encoding
return res.text
except:
return '产生异常'
if __name__ == '__main__':
url = 'https://www.amazon.cn/'
res = getHtml(url)
print(res)
这里可以看到,我们将输出的请求的头部伪装成为一个正常的浏览器,亚马逊就响应我们的请求了。其中,kv是头部信息,主要修改了User-Agent。里面的字典内的值是取自于Chorme的检查。
那么,如何利用request库将文件保存在本地呢?这里利用如下格式:
with open(path, 'wb') as f:
f.write(res.content)
例9:保存迈克尔杰克逊的一张图片:
import requests
def getHtml(url, path):
try:
kv = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36'}
res = requests.get(url, timeout=3, headers = kv)
with open(path, 'wb') as f:
f.write(res.content)
except:
return '产生异常'
if __name__ == '__main__':
path = 'D:/Pycharm/spider/MJ.jpg'
url = 'https://dss0.baidu.com/6ONWsjip0QIZ8tyhnq/it/u=1135862589,761779521&fm=179&app=42&f=JPEG?w=121&h=140'
res = getHtml(url, path)
print(res)
这里就可以看到文档下保存的MJ的图片了。
备注:注意urlretrieve和requests的文件保存方式的区别。
Part 3:补充问题
补充问题1:如何将申请伪装成为浏览器发送的?
如果使用urllib库,那么需要如下操作:
from urllib import request
url = 'http://www.baidu.com'
kv = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36'}
req = request.Request(url, headers = kv)
resp = request.urlopen(req)
print(resp.read().decode('utf-8'))
需要说明的是,这里利用了Request这个类,将浏览器的信息塞给这个类的实例化中的headers部分。
如果使用requests库,已经在实例2中展示过了。
补充问题2:如何设置访问的IP地址?
由于爬虫需要快速访问服务器多次,而有的服务器就会对相同IP地址的访问次数进行限制。为了反反爬虫,这里通过设置代理IP的形式进行对抗。
常见的IP代理网址:
a. 西刺免费代理:http://www.xicidaili.com
b. 快代理:http://www.kuaidaili.com
c. 代理云:http://www.dailiyun.com
备注:免费代理使用的人较多,可能会产生各种问题。
类型1:使用urllib库:from urllib import request url = 'http://httpbin.org/ip' handler = request.ProxyHanlder('http':'218.66.161.88:31769'} opener = request.build_opener(handler) req = request.Request(url) resp = opener.open(req) print(resp.read())
这里是有错误的,报值一直如下:
urllib.error.URLError: <urlopen error [WinError 10060] 由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败。>
之后学到改进方案,再回这里整改。
补充问题3:如何给访问申请增加cookie?
之前介绍过,cookie是用来给服务器提供身份验证的东西。
cookie的参数如下:
a. name:cookie的名字;
b. value:cookie的值;
c. expirse:cookie的过期时间;
d. path:cookie作用的路径;
e. domain:cookie作用的域名;
f. secure:是否只有在http协议下起作用。
那么,如何利用呢?这里需要使用http.cookiejar库。那么首先介绍这个库。
CookieJar类有一些子类,分别是FileCookieJar,MozillaCookieJar,LWPCookieJar。
CookieJar:管理HTTP cookie值、存储HTTP请求生成的cookie、向传出的HTTP请求添加cookie的对象。整个cookie都存储在内存中,对CookieJar实例进行垃圾回收后cookie也将丢失。
FileCookieJar (filename,delayload=None,policy=None):从CookieJar派生而来,用来创建FileCookieJar实例,检索cookie信息并将cookie存储到文件中。filename是存储cookie的文件名。delayload为True时支持延迟访问访问文件,即只有在需要时才读取文件或在文件中存储数据。
MozillaCookieJar (filename,delayload=None,policy=None):从FileCookieJar派生而来,创建与Mozilla浏览器 cookies.txt兼容的FileCookieJar实例。
LWPCookieJar (filename,delayload=None,policy=None):从FileCookieJar派生而来,创建与libwww-perl标准的 Set-Cookie3 文件格式兼容的FileCookieJar实例。
例10:访问百度主页,并获取主页返回的cookies:
import urllib
from http.cookiejar import CookieJar
url = 'http://www.baidu.com/'
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36'}
cookie = CookieJar()
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
resp = opener.open(url)
print(cookie)
输出的结果为:
<CookieJar[<Cookie BAIDUID=99909F7DF63F80ADD7D4FFF9E355E748:FG=1 for .baidu.com/>, <Cookie BIDUPSID=99909F7DF63F80ADA18B24053F86FD2D for .baidu.com/>, <Cookie H_PS_PSSID=1459_21107_30841_30823_26350_22157 for .baidu.com/>, <Cookie PSTM=1582962305 for .baidu.com/>, <Cookie delPer=0 for .baidu.com/>, <Cookie BDSVRTM=0 for www.baidu.com/>, <Cookie BD_HOME=0 for www.baidu.com/>]>
这里输出的cookiejar的一个实例。其中的关键字是BAIDUID, BIDUPSID, H_PS_PSSID, PSTM, delPer, BDSVRTM以及BD_HOME。如果给过程添加debug,我们观察一下opener。opener中实际上已经包含了服务器返回给申请的cookie信息,链接为:opener->handlers->08->cookiejar->_cookies->’.baidu.com’->’/‘下面。
如果想利用返回的cookie继续访问百度的话,直接用opener即可。
import urllib
from urllib import parse
from http.cookiejar import CookieJar
url = 'http://www.baidu.com/'
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36'}
cookie = CookieJar()
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
resp = opener.open(url)
print(cookie)
kw = {'wd':'爬虫'}
url = 'http://www.baidu.com/s?'
url_get = url + 'e=UTF-8&' + parse.urlencode(kw)
resp = opener.open(url_get)
print(cookie)
那requests库怎么使用cookie?
import requests
url = 'http://www.baidu.com'
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36'}
session = requests.session()
session.post(url, headers = headers)
url_get = 'http://www.baidu.com/s?wd=Cokey'
resp = session.get(url_get)
在requests库中,将所有的信息存在session中了。debug一下,观察session的类别,会发现cookies的位置如下:session->cookies->_cookies->’baidu.com’->’/‘。
这一块的内容总结到此。
备注:requests库是依赖于python自带的urllib库的。这两个库的使用习惯来说,requests库更加好用,更加推荐。
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!