上一篇主要讲述了Python自带的urllib库以及正则表达式的使用方法,戳链接:https://blog.jixiaob.cn/?post=53
————————————————————————————————————————
从urllib到requests
urllib库是Python自带的一个库,虽然是自带的,但是里面有很多地方不太方便。为了方便诸如登录还有网页验证、Cookies等操作,我们使用一个非常好用的库——requests库。
requests库是第三方库,所以如果没有安装的话是需要安装一下的
pip install requests
极为方便的请求发送
————————————————————————————————————————
requests库使用起来非常方便,举个例子,在requests里面get就是get请求,post就是post请求
import requests response = requests.get('https://www.baidu.com') print(response.content)
可以看到,控制台里面正确打印了一个bytes
此外,还可以输出其它的东西
response.status_code 状态码
response.text 响应体的内容
response.cookies Cookies
response.url URL
response.history 请求历史
requests里面内置有状态码查询功能requests.codes
exit() if not response.staus_code -- requests.codes.ok else print('OK')
比如
requests.codes.ok 代表的是200
requests.codes.not_found 是404
之前我们了解到还有其它的请求方式,都可以通过这种简单的方式进行请求的发送。
比如
requests.post()
requests.put()
requests.delete()
requests.head()
requests.options()
接下来,我们详细的讲解最常用的GET请求和POST请求以及一些高级的用法。
GET请求
————————————————————————————————————————
对于get请求的参数,我们除了可以直接写进url外,更多的是写进字典,然后传入params参数中。
def requests_get(): import requests # 这里要放传入的参数 data = { 'q': '赵苦瓜', } response = requests.get('https://www.so.com/s', params=data) print(response.text)
可以看到正确的网页的html的返回结果。
Json数据解析
有时候我们看见请求的一些东西又是大括号又是逗号,还有引号之类的,感觉跟Python的字典很像。其实,有一种很常用的Json格式的数据。为了更方便地使用,我们可以用json()方法进行解析,让他变成我们熟悉的字典类型的数据。
比如我们测试一下哔哩哔哩音乐的一个获取音乐信息的接口:
https://www.bilibili.com/audio/music-service-c/web/url?sid=284550 {"code":0,"msg":"success","data":{"sid":284550,"type":1,"info":"","timeout":10800,"size":85694,"cdns":["https://upos-sz-mirrorks3.bilivideo.com/ugaxcode/i180406ws1nqljsjw1zehw3art74jlub-192k.m4a?e=ig8euxZM2rNcNbdlhoNvNC8BqJIzNbfqXBvEqxTEto8BTrNvN0GvT90W5JZMkX_YN0MvXg8gNEV4NC8xNEV4N03eN0B5tZlqNxTEto8BTrNvNeZVuJ10Kj_g2UB02J0mN0B5tZlqNCNEto8BTrNvNC7MTX502C8f2jmMQJ6mqF2fka1mqx6gqj0eN0B599M=&uipk=5&nbs=1&deadline=1622452284&gen=playurlv2&os=ks3bv&oi=1695001155&trid=507cdac3c60d4e689cd6c7e2a5350b1fB&platform=pc&upsig=6414a7a281de0b42b760883cfa7a5350&uparams=e,uipk,nbs,deadline,gen,os,oi,trid,platform&mid=0&bvc=vod&orderid=0,1&logo=00000000"],"qualities":null,"title":null,"cover":null}}
可以看到,这个接口采用GET请求,传入参数sid,也就是歌曲的au号就可以了,返回的就是json数据。
我们在Python中试一试:
def get_bili_music_info(): import requests import json data = { # 歌曲的au号,我填的是我在b站测试这个功能的时候上传的音频 'sid': '284550', } response = requests.get('https://www.bilibili.com/audio/music-service-c/web/url', params=data) # 打印一下返回的源文本 print(response.text) info_dict = json.loads(response.text) return info_dict
可以看到,json.loads()可以正确地把json数据转换成字典dict。
当然,其实不需要自己导入json模块,json解析也被集成在了requests中,是不是非常方便?
所以,刚才的操作也可以直接info_dict = response.json()就直接完成了转换。
需要注意的是,如果返回的数据不是正确的json格式的话,会引发异常。
添加headers
有时有一些基于User-Agent的反爬策略,这个当然也可也在requests中设置。
只需要传入headers的参数即可(requests.get('url', headers=headers))。
headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.66' }除此之外headers里面还有其它的一些参数,可以自行查阅。
这样就能在一定程度上降低被阻止爬取的概率了,更多关于UA的内容,请参考上一篇urllib的相关内容。
抓取二进制内容
我们网页上的图片、视频、音乐等其实都是由二进制码,我们要对应正确的格式保存才能正确的查看。
我们刚才尝试对b站歌曲进行了解析,现在我们尝试下载歌曲的封面。
之前我们用的API基本已经被废弃了,我们使用另一个比较稳定的API
接口就不再详细解释了,去源代码的注释应该能看清楚。
def get_bili_music_info(): import requests data = { # 用户的uid,不知道为啥放这里,填啥都行,不填会报错缺少字段 'mid': '0', # 本来应该是验证平台的,实测也是填啥都行,不填会却字段报错 'platform': 'ios', # 怎么连权限也放这了?? 'privilege': '2', # 歌曲的质量,如果不存在则降低一个音质等级,直到存在为止 # 0:128K 1:192K 2:320K 3:无损flac 'quality': '2', # 歌曲的au号,我填的是我在b站测试这个功能的时候上传的音频 'songid': '284550', } response = requests.get('https://api.bilibili.com/audio/music-service-c/url', params=data) # 打印一下返回的源文本 print(response.text) info_dict = response.json() # 获取图片的二进制数据 cover_url = info_dict['data']['cover'] # 从二重字典里面获取cover的url cover_response = requests.get(cover_url) # 写到文件 wb 以二进制写的方式打开 with open('cover.jpg', 'wb') as file: file.write(cover_response.content) return 0
由于下载歌曲需要特定的UA(虽然我知道),而且涉及到版权问题,这里我们就不演示歌曲的下载了,而是专辑封面的下载。
通过我的运行,可以发现这个程序可以正确的把我的专辑封面下载下来。
POST请求
————————————————————————————————————————
上面讲解了get的请求,post请求也大致相同,直接requests.post()就可以了。
需要注意的一点就是传入post请求的参数不叫parms了,而是叫做data。
默认是通过提交form表单的方式发送的post请求。
高级用法
————————————————————————————————————————
文件上传
一般需要上传文件时,我们都采用POST的请求方式。我们也可也用requests来模拟文件上传的操作。
def file_upload(): import requests files = { 'file1': open('cover.jpg', 'rb') } response = requests.post('https://httpbin.org/post', files=files) print(response.text) return 0
可以看到结果,我们正确的把上面下载的图片上传了上去。
可以看到结果,files1字段并不在form字段里面,而是单独在file字段里面。
Cookies
1.获取Cookies
还是以百度为例
def get_cookie(): import requests response = requests.get('https://www.baidu.com') print(response.cookies) # 遍历打印内容 .items()方法可以将其转化为元组组成的列表 for key, value in response.cookies.items(): print(key + '=' + value)
可以看到结果出现了cookies。
有一些网站需要用Cookies进行登录,我们可以传入cookies
有两种方法,第一种是直接在headers里面传入Cookies字段,然后直接复制浏览器中对应的请求的对应字段即可。
第二种方法和urllib的方法类似,需要构造RequestsCookieJar对象,然后在请求的时候传入cookies='你的cookiejar对象'就可以了。
如果需要将第一种那种字段转换成CookieJar对象的话,可以采用两个split分割遍历来写。
# 首先按照;分号分隔每一条的Cookie for cookie in cookies.split(';'): # 然后按照等号分隔每一条cookie的key和value key, value = cookie.split('=')
这样就不用在header里面添加cookie字段了。
会话维持
每次进行get请求或post请求都是没有关系的,就好比你开了两个浏览器访问一个网页一样。
当然,可以通过设置Cookie的方式来维持会话,但是我们还有更简单的方式——Session对象。
session经常被用来做模拟登录。
httpbin有一个测试网址,可以设置cookie
比如访问https://httpbin.org/cookies/set?zkg=jixiaob
就会设置一个cookie,key是zkg,value是jixiaob
我们来测试一下会话维持
def session_test(): import requests print('1.普通两次请求测试') url = 'http://httpbin.org/cookies/set?zkg=jixiaob' # 首先测试不使用session能不能获取到登录信息 requests.get(url) response1 = requests.get('http://httpbin.org/cookies') print(response1.text) # 然后测试传入cookie参数 # res1 = requests.get(url) # (这里测试失败了,res1里面没有cookies,我也不知道从哪获取 # 测试的时候发现了requests.cookies获取到的cookies不全的问题,故此处获取使用urllib print('2.传入cookies测试') import urllib.request import http.cookiejar cookies = http.cookiejar.CookieJar() handler = urllib.request.HTTPCookieProcessor(cookies) opener = urllib.request.build_opener(handler) response = opener.open('https://httpbin.org/cookies/set?zkg=jixiaob') # 访问完之后Cookie就会进cookies这个cookiejar里面 res2 = requests.get('http://httpbin.org/cookies', cookies=cookies) print(res2.text) # 然后测试Session # 构建session对象 print('3.session测试') session = requests.Session() session.get(url) response = session.get('http://httpbin.org/cookies') print(response.text) # ==> {"cookies": {"zkg": "jixiaob" }} return 0
这里我遇到了一个问题,就是requests获取到的cookies不全的问题,所以其中第二个传入cookies的方法,我是先采用urllib来获取cookies的。
所以说一般我们还是采用session来处理类似的问题。
关于requests获取的cookies不全的问题:
目前我还没有找到对应的说明,欢迎大佬们前来指点!
我们可以拿最简单的百度来测试一下:
def get_cookie_test(): url = 'https://www.baidu.com' # url = 'http://httpbin.org/cookies/set?zkg=jixiaob' # 首先使用urllib看一下www.baidu.com给我们的cookies import urllib.request import http.cookiejar cookies = http.cookiejar.CookieJar() handler = urllib.request.HTTPCookieProcessor(cookies) opener = urllib.request.build_opener(handler) response = opener.open(url) print('这是urllib获取到的cookies:') for cookie in cookies: print(cookie.name + ' = ' + cookie.value) # ==> 打印了四条cookies import requests response = requests.get(url) print('这是requests直接获取到的cookies:') for cookie in response.cookies: print(cookie.name + ' = ' + cookie.value) # ==> 打印了1条cookies print('这是使用session获取到的cookies:') session = requests.Session() session.get(url) for cookie in session.cookies: print(cookie.name + ' = ' + cookie.value) # ==> 打印了1条cookies
可以发现,使用urllib获取到的cookies是四条,而使用requests只能获取到一条。
令我诧异的是,这次session和requests一样也是只有一条。
【后来我发现是UA的问题,,改了UA之后上面请求百度的cookies都变成正常的四条了】
【但是下面的问题没有解决】
之后我把开头的那一句给url赋值的语句改了一下:
url = 'http://httpbin.org/cookies/set?zkg=jixiaob'
然后又运行了一次,结果很让我意外:
这是urllib获取到的cookies: zkg = jixiaob 这是requests直接获取到的cookies: 这是使用session获取到的cookies: zkg = jixiaob
这次是requests里面没有东西,而使用urllib的和session的是正常的??
(求大佬指点qwq
SSL证书验证
可以使用verify参数控制是否检查证书,默认是True。
这样的话一些使用自签证书的验证会出现SSL错误,对于这种网站,只需要在请求时把verify参数设置为False即可。
直接这样设置的话会弹出一个警告,可以在请求之前写一条urllib3.disable_warnings()来忽略警告。
当然在请求时也可也指定证书
cert = ('crt文件', 'key文件') 其中key必须是解密状态的。
代理设置
还有一种反爬策略,就是限制同一个IP地址的频繁访问,可能会弹验证码等,还有可能封禁IP导致一段时间内无法访问。
为了解决这个问题,可以使用代理,让别的计算机帮我们访问爬取。可以用到proxies参数进行代理设置。
proxies = { 'http': 'http://127.0.0.1:1080', 'https': 'https://127.0.0.1:1080', }
然后请求的时候传入proxies=proxies参数即可。
如果代理需要使用HTTP Basic Auth,可以使用类似http://user:password@host:port的语法设置代理。
此外,requests还支持SOCKS协议的代理,需要先按照SOCKS库
pip3 install 'requests[socks]'
超时设置
本机网络不好或者网页相应太慢时,可能会等好久才能收到网页的响应,甚至不给响应然后报错。
为避免这种情况,可以在请求时设置超时时间timeout参数。单位是秒,类型是float。如timeout = 1
如果在设置的时间内没有响应,则会抛出异常。
响应时间分为两部分,一个是connect time(连接时间),一个是read time(读取时间)。timeout默认是二者的和,也可也分别进行设置:
timeout = (0.1, 0.1)
对应的,异常抛出也会不同,分别为connect time out和read time out
不设置超时的话默认和timeout=None是一致的。
身份验证
有时候会遇到需要身份验证的网站,这个上一篇urllib库中也提到过
requests使用起来就比较简单了,传一个auth参数即可。
def login_test(): import requests from requests.auth import HTTPBasicAuth auth = HTTPBasicAuth('用户名', '密码') response = requests.get('URL', auth=auth) print(response.text) return 0
上述构建方法可能有点麻烦,requests还提供了一种更为方便的办法
response = request.get(url, auth=('用户名', '密码'))
这样直接传一个tuble也是可以的
Prepared Request
可以自定义一个Request(请求)对象。
在相同类型的请求下,每次都写一遍参数不如直接用现成的自定义好的对象。
def prepared_request(): from requests import Request, Session url = 'http://httpbin.org/post' data = { 'name': 'Zhaokugua', } headers = { 'User-Agent': 'bilibili Edg/91.0.4472.77' } session = Session() request = Request('POST', url, data=data, headers=headers) req_prepared = session.prepare_request(request) response = session.send(req_prepared) print(response.text)
解析库
————————————————————————————————————————
之前我们采用了一种通用的正则表达式的匹配方法,还是比较繁琐,而且稍微写错就会导致匹配失败。
下面,我们将使用常用的开源解析库进行页面的解析处理。
Xpath
XML路径语言,本来是用来解析XML文件的(比如b站的XML弹幕文件),现在同样适用于HTML的搜索。
由于是第三方库,使用之前我们需要先安装一下
pip install lxml
基本语法规则:
nodename 选取此节点的所有子节点
/ 从当前节点选取直接子节点
// 从当前节点选取子孙节点
. 选取当前节点
.. 选取当前节点的父节点
@ 选取属性
由于内容比较简单,以下仅用代码加注释的方式进行演示,不做过多说明。
# 导包 from lxml import etree import requests # 获取文本 html_text = requests.get('https://www.jixiaob.cn').text print(html_text) # 调用html类进行初始化,构造Xpath解析对象 html = etree.HTML(html_text) # 也可也直接读取本地文件,直接解析 # html = etree.parse('./text.html', etree.HTMLParser) # 修正HTML文本,返回bytes类型 result = etree.tostring(html) # 打印修正后的结果 print(result.decode('utf-8')) # 匹配所有节点 result = html.xpath('//*') print(result) # ==> [<Element html at 0x14356c6a9c8>, <Element head at 0x14356022cc8>, <Element ...(省略) # 指定节点名称获取 result = html.xpath('//head') print(result) # ==> [<Element head at 0x14356022cc8>] # 由于是列表形式,我们可以用id来访问单个节点 print(result[0]) # ==> <Element head at 0x14356022cc8> # 匹配子节点,使用/匹配直接子节点,使用//匹配所有子孙节点 result = html.xpath('//p/span') print(result) # ==> [<Element span at 0x14356ceea88>] # 属性匹配和获取 # 参考文本: ''' <div id="social"> <a href="https://blog.jixiaob.cn/" id="s-twitter" title="赵苦瓜のBlog"></a> <a href="https://file.jixiaob.cn" id="s-facebook" title="赵苦瓜的文件系统"></a> <a href="https://chengji.jixiaob.cn/" id="s-google" title="中考成绩查询"></a> </div> ''' # 按照属性匹配一个a节点,然后匹配其父节点的id属性 result = html.xpath('//a[@href="https://blog.jixiaob.cn/"]/../@id') print(result) # ==> ['social'] # 中文乱码问题的解决 result = html.xpath('//a[@href="https://blog.jixiaob.cn/"]/@title') print(result) # ==> ['èµµè\x8b¦ç\x93\x9cã\x81®Blog'] result = result[0].encode('ISO-8859-1').decode('utf-8') print(result) # ==> 赵苦瓜のBlog # 获取属性id为social的div下的属性id为s-twitter的a标签的href属性 # 参考文本同上 result = html.xpath('//div[@id="social"]/a[@id="s-twitter"]/@href') print(result) # ==> ['https://blog.jixiaob.cn/'] # 文本获取——利用text()方法 # 参考文本: ''' <div id="skins-select"> <a href="#" class="blue skins" id="blue-skin">jixiaob</a> <a href="#" class="red skins" id="red-skin">jixiaob</a> <a href="#" class="purple skins" id="purple-skin">jixiaob</a> <a href="#" class="green skins" id="green-skin">jixiaob</a> </div> ''' result = html.xpath('//div[@id="skins-select"]/a[@id="blue-skin"]') print(result[0].text) # ==>jixiaob # 或者直接在里面加上/text() result = html.xpath('//div[@id="skins-select"]/a[@id="blue-skin"]/text()') print(result) # ==>['jixiaob'] # 模糊属性匹配——contains # 参考html文本同上 # a标签属性包含blue的id属性 result = html.xpath('//a[contains(@class, "blue")]/@id') print(result) # ==>['blue-skin'] # a标签属性包含skin的id属性 result = html.xpath('//a[contains(@class, "skin")]/@id') print(result) # ==>['blue-skin', 'red-skin', 'purple-skin', 'green-skin'] # Xpath的操作符 # 参考文本: ''' <a href="https://chengji.jixiaob.cn/" id="s-google" title="中考成绩查询"></a> ''' result = html.xpath('//a[@id="s-google" and @href="https://chengji.jixiaob.cn/"]/@title') print(result[0].encode('ISO-8859-1').decode('utf-8')) # ==>中考成绩查询
可以看到上面我们使用了and操作符,来匹配两个属性都符合的a标签。
此外还有其它运算符,如
and 或
or 与
mod 除余
| 计算两个节点集
+ - * div 加减乘除
= != < <= > >= 各种比较运算符
# 按序选择 result = html.xpath("//a[2]/text()") print(result) # ==>['jixiaob'] # 此外还可以使用last() last()-2 position()<3等方式 # 节点轴 # 祖先节点ancestor:: # 获取属性值attribute:: # 直接子节点child:: # 子孙节点descendant:: # 之后的所有节点following:: # 之后的同级节点following-sibling
Beautiful Soup
pyquery
本文地址:https://blog.jixiaob.cn/?post=58
版权声明:若无注明,本文皆为“赵苦瓜のBlog~”原创,转载请保留文章出处。