07—小白学Python爬虫之Requests简介及基本使用

本文介绍Requests库的基本使用方法,包括安装、各种HTTP请求的发送、处理响应内容、定制请求头、状态响应码检查等。Requests库简化了HTTP请求的过程,提高了开发效率。

前面几篇文章的案例介绍,都是通过urllib完成的,不知各位发现没,使用起来比较繁琐,1. 创建Request对象,2. 调用urlopen方法,3. 拿到返回后,还需要进行read()并进行解码操作,4. 如果是https请求的话,还需要创建context对象等等,巴拉巴拉,使用还是非常不便的。
接下来就介绍一下Requests库及使用,通过这个库,可以大大提升效率,相信等看完了,会有种相见恨晚的感觉。
那么,Requests是什么呢?Requests就是用python语言基于urllib编写的,采用的是Apache2 Licensed开源协议的HTTP库。

基本使用

安装
pip insitall requests
Get请求
  1. 引入requests库
  2. 发送请求
  3. 接受返回并处理数据
无参
import requests

def main():
    resp = requests.get('http://www.baidu.com')
    print(resp.text)


if __name__ == '__main__':
    main()
url带参数

Requests 可以使用 params 关键字参数,以一个字符串字典来提供这些参数,拼装成url?key=value&key2=value2形式。
示例:

import requests


def main():
    params1 = {'name': '旺财', 'age': 12}
    resp = requests.get('http://www.baidu.com', params=params1)
    print(resp.url)
    print('*' * 50)
    params2 = {'name': 'zhangsan', 'age': 34, 'location': ['bj', 'sh']}
    resp = requests.get('http://www.baidu.com', params=params2)
    print(resp.url)


if __name__ == '__main__':
    main()

输出为:

http://www.baidu.com/?name=%E6%97%BA%E8%B4%A2&age=12
**************************************************
http://www.baidu.com/?name=zhangsan&age=34&location=bj&location=sh

可以看到,requests把url 键值参数已经进行的encode操作。

Post请求

键值对参数为一个dict类型数据,调用requests的post方法即可。

def demo_post():
    url = 'http://www.baidu.com'
    data = {'name': '旺财', 'age': 12}
    resp = requests.post(url, data=data)
其它HTTP请求
 r = requests.put('http://httpbin.org/put', data = {'key':'value'})
 r = requests.delete('http://httpbin.org/delete')
 r = requests.head('http://httpbin.org/get')
 r = requests.options('http://httpbin.org/get')
响应内容

Requests 会自动解码来自服务器的内容,大多数 unicode 字符集都能被无缝地解码。

请求发出后,Requests 会基于 HTTP 头部对响应的编码作出有根据的推测。当你访问 r.text 时,Requests 会使用其推测的文本编码。

def demo_response():
    url = 'http://www.baidu.com'
    resp = requests.get(url)
    print(resp.status_code)
    print(resp.encoding)
    print(resp.reason)
    print(resp.text)

输出为:

200
ISO-8859-1
OK
<!DOCTYPE html>
200
ISO-8859-1
OK
<!DOCTYPE html> ......

我们可以查看 Requests 使用了什么编码,并且能够使用 r.encoding 属性来改变它:

resp.encoding = 'gbk'
print(resp.encoding)    # gbk

如果你改变了编码,每当你访问 r.text ,Request 都将会使用 r.encoding 的新值。你可能希望在使用特殊逻辑计算出文本的编码的情况下来修改编码。比如 HTTP 和 XML 自身可以指定编码。这样的话,你应该使用 r.content 来找到编码,然后设置 r.encoding 为相应的编码。这样就能使用正确的编码解析 r.text 了。

在你需要的情况下,Requests 也可以使用定制的编码。如果你创建了自己的编码,并使用 codecs 模块进行注册,你就可以轻松地使用这个解码器名称作为 r.encoding 的值, 然后由 Requests 来为你处理编码。

二进制响应内容

我们可以以字节的方式访问请求响应体,对于非文本请求:

def demo_image():
    # 图片URL
    url = 'http://d.hiphotos.baidu.com/image/pic/item/8435e5dde71190efbdcd14ddc21b9d16fdfa60fa.jpg'
    resp = requests.get(url)
    print(resp.content)

输出为:

b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x01,\x01,\x00\x00...

Requests 会自动为你解码 gzipdeflate传输编码的响应数据。

例如,以请求返回的二进制数据创建一张图片,你可以使用如下代码:

def demo_image():
    # 图片URL
    url = 'http://d.hiphotos.baidu.com/image/pic/item/8435e5dde71190efbdcd14ddc21b9d16fdfa60fa.jpg'
    resp = requests.get(url)
    image = Image.open(BytesIO(resp.content))
    print(image)
JSON响应内容

Requests 中也有一个内置的 JSON 解码器,可以帮助我们处理 JSON 数据:

def demo_json():
    response = requests.get('https://api.github.com/events')
    print(response.json())

输出为:

[{'id': '7440343408', 'type': 'PushEvent', 'actor': {'id': 35484254,...}]

如果 JSON 解码失败,r.json() 就会抛出一个异常。例如,响应内容是 401 (Unauthorized),尝试访问 r.json() 将会抛出ValueError: No JSON object could be decoded 异常。

需要注意的是,成功调用 r.json()意味着响应的成功。有的服务器会在失败的响应中包含一个 JSON 对象(比如 HTTP 500 的错误细节)。这种 JSON 会被解码返回。要检查请求是否成功,请使用r.raise_for_status()或者检查 r.status_code 是否和你的期望相同。

原始响应内容

在罕见的情况下,可能想获取来自服务器的原始套接字响应,那么可以使用 r.raw。 并且在初始请求中设置 stream=True

 r = requests.get('https://api.github.com/events', stream=True)
 r.raw
<requests.packages.urllib3.response.HTTPResponse object at 0x101194810>
 r.raw.read(10)
'\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03'

但一般情况下,应该以下面的模式将文本流保存到文件:

with open(filename, 'wb') as fd:
    for chunk in r.iter_content(chunk_size):
        fd.write(chunk)

使用 Response.iter_content 将会处理大量你直接使用 Response.raw 不得不处理的。 当流下载时,上面是优先推荐的获取内容方式。 Note that chunk_size can be freely adjusted to a number that may better fit your use cases

定制请求头

如果想为请求添加 HTTP 头部,只要简单地传递一个 dict 给 headers 参数就可以了。

def demo_add_headers():
    url = 'http://www.baidu.com'
    headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36'
    }
    resp = requests.get(url, headers=headers)

注意: 定制 header 的优先级低于某些特定的信息源,例如:

  • 如果在 .netrc 中设置了用户认证信息,使用 headers= 设置的授权就不会生效。而如果设置了 auth= 参数,.netrc 的设置就无效了。
  • 如果被重定向到别的主机,授权 header 就会被删除。
  • 代理授权 header 会被 URL 中提供的代理身份覆盖掉。
  • 在我们能判断内容长度的情况下,header 的 Content-Length 会被改写

更进一步讲,Requests 不会基于定制 header 的具体情况改变自己的行为。只不过在最后的请求中,所有的 header 信息都会被传递进去。

注意: 所有的 header 值必须是 string、bytestring 或者 unicode。尽管传递 unicode header 也是允许的,但不建议这样做。

更加复杂的post请求

上面介绍了通过传递一个dict类型的data,完成一般的post请求。
很多时候想要发送的数据并非编码为表单形式的。如果你传递一个 string 而不是一个 dict,那么数据会被直接发布出去。

def demo_fuza_post():
    url = 'http://www.baidu.com'
    data = {'name': '旺财', 'age': 12}
    print(json.dumps(data)) #{"name": "\u65fa\u8d22", "age": 12}
    resp = requests.post(url, data=json.dumps(data))

此处除了可以自行对 dict 进行编码,还可以使用 json 参数直接传递,然后它就会被自动编码。这是 2.4.2 版的新加功能:

resp = requests.post(url, json=data)
post上传文件

Requests 使得上传多部分编码文件变得很简单:

def demo_upload_file():
    url = 'http://www.baidu.com'
    files = {'file': open('test.ppt', 'rb')}
    response = requests.post(url, files=files)

其实封装数据为:

{
  ...
  "files": {
    "file": "<censored...binary...data>"
  },
  ...
}

还可以显式地设置文件名,文件类型和请求头:

files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})}

此外,还可以发送作为文件来接收的字符串:

files = {'file': ('report.csv', 'some,data,to,send\nanother,row,to,send\n')}

发送请求为:

{
  ...
  "files": {
    "file": "some,data,to,send\\nanother,row,to,send\\n"
  },
  ...
}

如果你发送一个非常大的文件作为 multipart/form-data 请求,你可能希望将请求做成数据流。默认下 requests 不支持, 但有个第三方包requests-toolbelt 是支持的。你可以阅读toolbelt文档 来了解使用方法。

强烈建议用二进制模式(binary mode)打开文件。这是因为 Requests 可能会试图为你提供Content-Length header,在它这样做的时候,这个值会被设为文件的字节数(bytes)。如果用文本模式(text mode)打开文件,就可能会发生错误。

状态响应码

我们可以检测当前请求返回的状态码。

def demo_status_code():
    resp = requests.get('http://www.baidu.com')
    print(resp.status_code)#200

为方便引用,Requests还附带了一个内置的状态码查询对象:

print(resp.status_code == requests.codes.ok)

如果发送了一个错误请求(一个 4XX 客户端错误,或者 5XX 服务器错误响应),我们可以通过 Response.raise_for_status() 来抛出异常:

def demo_status_code():
    resp = requests.get('http://www.snsd.com/404/aaa.html')
    print(resp.status_code)
    print(resp.status_code == requests.codes.ok)
    resp.raise_for_status()

输出为:


404
False
Traceback (most recent call last):
  File "/Users/***/01-demo.py", line 88, in <module>
    main()
  File "/Users/***/01-demo.py", line 8, in main
    demo_status_code()
  File "/Users/***/01-demo.py", line 15, in demo_status_code
    resp.raise_for_status()
  File "/Users/***/.virtualenvs/env3.6/lib/python3.6/site-packages/requests/models.py", line 935, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 404 Client Error: Not Found for url: http://www.snsd.com/404/aaa.html
响应头

我们可以查看以一个 Python 字典形式展示的服务器响应头:

def demo_resp_headers():
    resp = requests.get('http://www.baidu.com')
    print(resp.headers)

输出为:

{'Server': 'bfe/1.0.8.18', 'Date': 'Tue, 27 Mar 2018 10:21:41 GMT', 'Content-Type': 'text/html', 'Last-Modified': 'Mon, 23 Jan 2017 13:27:36 GMT', 'Transfer-Encoding': 'chunked', 'Connection': 'Keep-Alive', 'Cache-Control': 'private, no-cache, no-store, proxy-revalidate, no-transform', 'Pragma': 'no-cache', 'Set-Cookie': 'BDORZ=27315; max-age=86400; domain=.baidu.com; path=/', 'Content-Encoding': 'gzip'}

我们可以使用任意大写形式来访问这些响应头字段:

print(resp.headers['Server'])
    print(resp.headers.get('Content-Type'))

如果响应中包含Cookie,可以方便的访问他们。

def demo_cookies():
    resp = requests.get('http://www.baidu.com')
    for item in resp.cookies.items():
        print(item)
    print(resp.cookies)
    print(resp.cookies.get('BDORZ'))

输出为:

('BDORZ', '27315')
<RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]>
27315

要想发送你的cookies到服务器,可以使用 cookies 参数:

cookies = dict(cookies_are='working')
resp = requests.get('http://www.baidu.com', cookies=cookies)
# {"cookies": {"cookies_are": "working"}}

Cookie 的返回对象为 RequestsCookieJar,它的行为和字典类似,但界面更为完整,适合跨域名跨路径使用。你还可以把 Cookie Jar 传到 Requests 中:

>>> jar = requests.cookies.RequestsCookieJar()
>>> jar.set('tasty_cookie', 'yum', domain='httpbin.org', path='/cookies')
>>> jar.set('gross_cookie', 'blech', domain='httpbin.org', path='/elsewhere')
>>> url = 'http://httpbin.org/cookies'
>>> r = requests.get(url, cookies=jar)
>>> r.text
'{"cookies": {"tasty_cookie": "yum"}}'
重定向与请求历史

默认情况下,除了 HEAD, Requests 会自动处理所有重定向。

可以使用响应对象的 history 方法来追踪重定向。

Response.history 是一个 Response 对象的列表,为了完成请求而创建了这些对象。这个对象列表按照从最老到最近的请求进行排序。

例如,Github 将所有的 HTTP 请求重定向到 HTTPS:

>>> r = requests.get('http://github.com')

>>> r.url
'https://github.com/'

>>> r.status_code
200

>>> r.history
[<Response [301]>]

如果你使用的是GET、OPTIONS、POST、PUT、PATCH 或者 DELETE,那么你可以通过 allow_redirects 参数禁用重定向处理:

>>> r = requests.get('http://github.com', allow_redirects=False)
>>> r.status_code
301
>>> r.history
[]

如果你使用了 HEAD,你也可以启用重定向:

>>> r = requests.head('http://github.com', allow_redirects=True)
>>> r.url
'https://github.com/'
>>> r.history
[<Response [301]>]
超时

可以告诉 requests 在经过以 timeout 参数设定的秒数时间之后停止等待响应。基本上所有的生产代码都应该使用这一参数。如果不使用,你的程序可能会永远失去响应:

>>> requests.get('http://github.com', timeout=0.001)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
requests.exceptions.Timeout: HTTPConnectionPool(host='github.com', port=80): Request timed out. (timeout=0.001)

timeout 仅对连接过程有效,与响应体的下载无关。timeout并不是整个下载响应的时间限制,而是如果服务器在timeout 秒内没有应答,将会引发一个异常(更精确地说,是在 timeout秒内没有从基础套接字上接收到任何字节的数据时)If no timeout is specified explicitly, requests do not time out

错误与异常

遇到网络问题(如:DNS 查询失败、拒绝连接等)时,Requests 会抛出一个 ConnectionError 异常。

如果 HTTP 请求返回了不成功的状态码, Response.raise_for_status() 会抛出一个 HTTPError 异常。

若请求超时,则抛出一个 Timeout 异常。

若请求超过了设定的最大重定向次数,则会抛出一个 TooManyRedirects 异常。

所有Requests显式抛出的异常都继承自 requests.exceptions.RequestException

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值