requests的使用
2.1 节我们了解了 urllib 库的基本用法,其中确实有不方便的地方,例如处理网页验证和 Cookie 时,需要写 Opener 类和 Handler 类来处理。另外实现 POST、PUT 等请求时的写法也不太方便。
为了更加方便地实现这些操作,产生了更为强大的库——requests。 有了它,Cookie、登录验证、代理设置等操作都不是事儿。
接下来,让我们领略一下 requests 库的强大之处吧。
准备工作
在开始学习之前,请确保已经正确安装好 requests 库,如果尚未安装,可以使用 pip3 来安装:
pip3 install requests
更加详细的安装说明可以参考 https://setup.scrape.center/requests 。
实例引入
urllib 库中的 urlopen 方法实际上是以 GET 方式请求网页,requests 库中相应的方法就是 get 方法,是不是感觉表意更百接一些? 下面通过实例来看一下:
import requests
r = requests.get('https://www.baidu.com/')
print(type(r))
print(r.status_code)
print(type(r.text))
print(r.text[:100])
print(r.cookies)
运行结果如下:
<class 'requests.models.Response'>
200
<class 'str'>
<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charse
<RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]>
这里我们调用 get 方法实现了与 urlopen 方法相同的操作,返回一个 Response 对象,并将其存放在变量 r 中,然后分别输出了响应的类型、状态码,响应体的类型、内容,以及 Cookie。
观察运行结果可以发现,返回的响应类型是 requests.models.Response,响应体的类型是字符串 str,Cookie 的类型是 RequestsCookieJar。
使用 get 方法成功实现一个 GET 请求算不了什么,requests 库更方便之处在于其他请求类型依然可以用一句话完成,实例如下:
import requests
r = requests.get('https://www.httpbin.org/get')
r = requests.post('https://www.httpbin.org/post')
r = requests.put('https://www.httpbin.org/put')
r = requests.delete('https://www.httpbin.org/delete')
r = requests.patch('https://www.httpbin.org/patch')
这里分别用 post、put、delete 等方法实现了 POST、PUT、DELETE 等请求。是不是比 urllib 库简单太多了?
其实这只是冰山一角,更多的还在后面。
GET请求
HTTP 中最常见的请求之一就是 GET 请求,首先来详细了解一下利用 requests 库构建 GET 请求的方法。
基本示例
下面构建一个最简单的 GET 请求,请求的链接为 https://www.httpbin.org/get,该网站会判断客户端发起的是否为 GET 请求,如果是,那么它将返回相应的请求信息:
import requests
r = requests.get('https://www.httpbin.org/get')
print(r.text)
运行结果如下:
{
"args": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Host": "www.httpbin.org",
"User-Agent": "python-requests/2.32.3",
"X-Amzn-Trace-Id": "Root=1-6794bd91-7be031434a3533974025f44c"
},
"origin": "34.80.45.10",
"url": "https://www.httpbin.org/get"
}
可以发现,我们成功发起了 GET 请求,返回结果中包含请求头、URL、IP 等信息。
那么,对于 GET 请求,如果要附加额外的信息,一般怎样添加呢?例如现在想添加两个参数 name 和 age,其中 name 是 germey、age 是 25,于是 URL 就可以写成如下内容:
https://www.httpbin.org/get?name=germey&age=25
要构造这个请求链接,是不是要直接写成这样呢?
r = requests.get('https://www.httpbin.org/get?name=germey&age=25[https://www.]')
这样也可以,但是看起来有点不人性化哎?这些参数还需要我们手动去拼接,实现起来着实不优雅。
一般情况下,我们利用 params 参数就可以直接传递这种信息了,实例如下:
import requests
data = {
'name': 'germey',
'age': 25
}
r = requests.get('https://httpbin.org/get', params=data)
print(r.text)
运行结果如下:
{
"args": {
"age": "25",
"name": "germey"
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.32.3",
"X-Amzn-Trace-Id": "Root=1-6794bee0-7c11738f2ff03c840f3e32b5"
},
"origin": "34.80.45.10",
"url": "https://httpbin.org/get?name=germey&age=25"
}
上面我们把 URL 参数以字典的形式传给 get 方法的 params 参数,通过返回信息我们可以判断,请求的链接自动被构造成了 https://www.httpbin.org/get?name=germey&age=25 ,这样我们就不用自己构造 URL 了,非常方便。
另外,网页的返回类型虽然是 str 类型,但是它很特殊,是 JSON 格式的。所以,如果想直接解析返回结果,得到一个 JSON 格式的数据,可以直接调用 json 方法。实例如下:
import requests
r = requests.get('https://www.httpbin.org/get')
print(type(r.text))
print(r.json())
print(type(r.json()))
运行结果如下:
<class 'str'>
{'args': {}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Host': 'www.httpbin.org', 'User-Agent': 'python-requests/2.32.3', 'X-Amzn-Trace-Id': 'Root=1-6794c02c-4865d05421b098245885ef69'}, 'origin': '34.80.45.10', 'url': 'https://www.httpbin.org/get'}
<class 'dict'>
可以发现,调用 json 方法可以将返回结果(JSON 格式的字符串)转化为字典。
但需要注意的是,如果返回结果不是 JSON 格式,就会出现解析错误,抛出 json.decoder.JSONDecodeError 异常。
抓取网页
上面的请求链接返回的是 JSON 格式的字符串,那么如果请求普通的网页,就肯定能获得相应的内容了。我们以一个实例页面 https://ssr1.scrape.center/ 作为演示,往里面加入一点提取信息的逻辑,将代码完善成如下的样子:
import requests
import re
r = requests.get('https://ssr1.scrape.center/')
pattern = re.compile('<h2.*?>(.*?)</h2>', re.S)
titles = re.findall(pattern, r.text)
print(titles)
这个例子中,我们用最基础的正则表达式来匹配所有的标题内容。关于正则表达式,会在 2.3 节详细介绍,这里其只作为实例来配合讲解。
运行结果如下:
['肖申克的救赎 - The Shawshank Redemption', '霸王别姬 - Farewell My Concubine', '泰坦尼克号 - Titanic', '罗马假日 - Roman Holiday', '这个杀手不太冷 - Léon', '魂断蓝桥 - Waterloo Bridge', '唐伯虎点秋香 - Flirting Scholar', '喜剧之王 - The King of Comedy', '楚门的世界 - The Truman Show', '活着 - To Live']
我们发现,这里成功提取出了所有电影标题,只需一个最基本的抓取和提取流程就完成了。
抓取二进制数据
在上面的例子中,我们抓取的是网站的一个页面,实际上它返回的是一个 HTML 文档。要是想抓取图片、音频、视频等文件,应该怎么办呢?
图片、音频、视频这些文件本质上都是由二进制码组成的,由于有特定的保存格式和对应的解析方式,我们才可以看到这些形形色色的多媒体。所以,要想抓取它们,就必须拿到它们的二进制数据。
下面以示例网站的站点图标为例来看一下:
import requests
r = requests.get('https://scrape.center/favicon.ico', verify=False)
print(r.text)
print(r.content)
这里抓取的内容是站点图标,也就是浏览器中每一个标签上显示的小图标,如图 2-3 所示。
上述实例将会打印 Response 对象的两个属性,一个是 text,另一个是 content。
行结果如图 2-4 和图 2-5 所示,分别是 r.text 和 r.content 的结果。
可以注意到,r.text 中出现了乱码,r.content 的前面带有一个 b,代表这是 bytes 类型的数据。由于图片是二进制数据,所以前者在打印时会转化为 str 类型,也就是图片直接转化为字符串,理所当然会出现乱码。
上面的运行结果我们并不能看懂,它实际上是图片的二进制数据。不过没关系,我们将刚才提取到的信息保存下来就好了,代码如下:
import requests
r = requests.get('https://scrape.center/favicon.ico')
with open('favicon.ico', 'wb') as f:
f.write(r.content)
这里用了 open 方法,其第一个参数是文件名称,第二个参数代表以二进制写的形式打开文件,可以向文件里写入二进制数据。
上述代码运行结束之后,可以发现在文件夹中出现了名为 favicon.ico 的图标,如图 2-6 所示。
这样,我们就把二进制数据成功保存成了一张图片,这个小图标被我们成功爬取下来了。
同样地,我们也可以用这种方法获取音频和视频文件。
添加请求头
我们知道,在发起 HTTP 请求的时候,会有一个请求头 Request Headers,那么怎么设置这个请求头呢?
很简单,使用 headers 参数就可以完成了。
在刚才的实例中,实际上是没有设置请求头信息的,这样的话,某些网站会发现这并不是一个由正常浏览器发起的请求,于是可能会返回异常结果,导致网页抓取失败。
要添加请求头信息,例如这里我们想添加一个 User-Agent 字段,就可以这么写:
import requests
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36'
}
r = requests.get('https://ssr1.scrape.center/', headers=headers, verify=False)
print(r.text)
当然,可以在这个 headers 参数中添加任意其他字段信息。
POST请求
前面我们了解了最基本的 GET 请求,另外一种比较常见的请求方式是 POST。使用 requests 库实现 POST 请求同样非常简单,实例如下:
import requests
data = {'name': 'germey', 'age': '25'}
r = requests.post('http://httpbin.org/post', data=data)
print(r.text)
这里还是请求 https://www.httpbin.org/post,该网站可以判断请求是否为 POST 方式,如果是,就返回相关的请求信息。
运行结果如下:
{
"args": {},
"data": "",
"files": {},
"form": {
"age": "25",
"name": "germey"
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Content-Length": "18",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.32.3",
"X-Amzn-Trace-Id": "Root=1-6794de4d-757f7a8a2610dc81780b4f19"
},
"json": null,
"origin": "34.80.45.10",
"url": "http://httpbin.org/post"
}
可以发现,我们成功获得了返回结果,其中 form 部分就是提交的数据,这证明 POST 请求成功发送了。
响应
请求发送后,自然会得到响应。在上面的实例中,我们使用 text 和 content 获取了响应的内容。
此外,还有很多属性和方法可以用来获取其他信息,例如状态码、响应头、Cookie 等。实例如下:
import requests
r = requests.get('https://ssr1.scrape.center/')
print(type(r.status_code), r.status_code)
print(type(r.headers), r.headers)
print(type(r.cookies), r.cookies)
print(type(r.url), r.url)
print(type(r.history), r.history)
这里通过 status_code 属性得到状态码、通过 header 属性得到响应头、通过 cookies 属性得到 Cookie、通过 url 属性得到 URL、通过 history 属性得到请求历史。并将得到的这些信息分别打印出来。
运行结果如下:
<class 'int'> 200
<class 'requests.structures.CaseInsensitiveDict'> {'Date': 'Sat, 25 Jan 2025 12:57:39 GMT', 'Content-Type': 'text/html; charset=utf-8', 'Content-Length': '41667', 'Connection': 'keep-alive', 'X-Frame-Options': 'DENY', 'X-Content-Type-Options': 'nosniff', 'Expires': 'Sat, 25 Jan 2025 13:01:05 GMT', 'Cache-Control': 'max-age=600', 'Strict-Transport-Security': 'max-age=15724800; includeSubDomains'}
<class 'requests.cookies.RequestsCookieJar'> <RequestsCookieJar[]>
<class 'str'> https://ssr1.scrape.center/
<class 'list'> []
可以看到,headers 和 cookies 这两个属性得到的结果分别是 CaseInsensitiveDict 和 RequestsCookieJar 对象。
由第 1 章我们知道,状态码是用来表示响应状态的,例如 200 代表我们得到的响应是没问题的,上面例子输出的状态码正好也是 200,所以我们可以通过判断这个数字知道爬虫爬取成功了。
requests 库还提供了一个内置的状态码查询对象 requests.codes,用法实例如下:
import requests
r = requests.get('https://ssr1.scrape.center/')
exit() if not r.status_code == requests.codes.ok else print('Request Successfully')
这甲通过比较返回码和内置的表示成功的状态码,来保证请求是否得到了正常响应,如果是,就输出请求成功的消息,否则程序终止运行,这里我们用 requests.codes.ok 得到的成功状态码是 200。
这样我们就不需要再在程序里写状态码对应的数字了,用字符串表示状态码会显得更加直观。
当然,肯定不能只有 ok 这一个条件码。
下面列出了返回码和相应的查询条件:
例如想判断结果是不是 404 状态,就可以用 requests.codes.not_found 作为内置的状态码做比较。
高级用法
通过本节前面部分,我们已经了解了 requests 库的基本用法,如基本的 GET、POST 请求以及 Response 对象。本节我们再来了解一些 requests 库的高级用法,如文件上传、Cookie设置、代理设置等。
文件上传
我们知道使用 requests 库可以模拟提交一些数据。除此之外,要是有网站需要上传文件,也可以用它来实现,非常简单,实例如下:
import requests
files = {'file': open(favicon.ico''rb')}
I = requests.post("https://www.httpbin.org/post',files=files)
print(r.text)
在前一节,我们保存了一个文件 favicon.ico,这次就用它来模拟文件上传的过程。需要注意,favicon.ico 需要和当前脚本保存在同一目录下。如果手头有其他文件,当然也可以上传这些文件,更改下代码即可。
运行结果如下:
以上结果省略部分内容,上传文件后,网站会返回响应,响应中包含 files 字段和 form 字段,而 form 字段是空的,这证明文件上传部分会单独用一个 files 字段来标识。
Cookie设置
前面我们使用 urllib 库处理过 Cookie,写法比较复杂,有了 requests 库以后,获取和设置 Cookie 只需一步即可完成。
我们先用一个实例看一下获取 Cookie 的过程:
import requests
r=requests.get('https://www.baidu.com')
print(r.cookies)
for key,value in r.cookies.items():
print(key + '='+ value)
运行结果如下:
<RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]> BDORZ=27315
这里我们首先调用 cookies 属性,成功得到 Cookie,可以发现它属于 RequestCookieJar 类型。然后调用 items 方法将 Cookie 转化为由元组组成的列表,遍历输出每一个 Cookie 条目的名称和值,实现对 Cookie 的遍历解析。
当然,我们也可以直接用 Cookie 来维持登录状态。下面以 GitHub 为例说明一下,首先我们登录 GitHub,然后将请求头中的 Cookie 内容复制下来,如图 2-7 所示。
可以将图 2-7 中框起来的这部分内容替换成你自己的 Cookie,将其设置到请求头里面,然后发送请求,实例如下: