欢迎食用『主界面』~,这里是赵苦瓜的看板娘desu~

#
【点滴记录】爬虫基础 - Ⅱ(requests库、xpath解析)
首页 > 点滴记录    作者:赵苦瓜   2021年5月31日 14:37 星期一   热度:1469°   百度已收录  
时间:2021-5-31 14:37   热度:1469° 

上一篇主要讲述了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

哔哩哔哩音乐api的json解析.PNG

可以看到,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



本文作者:赵苦瓜      文章标题: 【点滴记录】爬虫基础 - Ⅱ(requests库、xpath解析)
本文地址:https://blog.jixiaob.cn/?post=58
版权声明:若无注明,本文皆为“赵苦瓜のBlog~”原创,转载请保留文章出处。

返回顶部    首页    后花园  
版权所有:赵苦瓜のBlog~    站长: 赵苦瓜    程序:emlog   鲁ICP备20030743号-1   鲁公网安备37048102006726 萌ICP备20222268号    sitemap