谈及到Python爬虫,必不可少的就是requests模块。前面内容中我们也讲到Python有丰富多彩的第三方模块,那么requests就是其中一个,requests模块是一个常用的访问网络的模块。使用requests可以模拟浏览器的请求,比起之前用到的urllib,requests模块的api更加便捷(requests的本质也是对urllib3进行了封装)
初识Requests
先来一段简单的代码,看下request的作用。
代码语言:javascript复制#爬取百度首页的内容
import requests
url = 'https://www.baidu.com/'
response = requests.get(url)
# 输出响应状态码
print("状态码:{0}".format(response.status_code))
# 输出响应字符编码
print("字符编码:{0}".format(response.encoding))
# 输出响应文本内容
print("响应文本:{0}".format(response.text))
然后运行一下可以看见程序的结果:
代码语言:javascript复制状态码:200
字符编码:ISO-8859-1
响应文本:<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type ontent=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>ç¾åº¦ä¸ä¸ï¼ä½ å°±ç¥é</title></head>
…….
由于内容比较长,就先放一部分结果。我们可以看到请求响应状态是200,正确响应。
响应的内容就是百度网页的HTML源码,然后浏览器拿到接收到的源码后,将其展示到页面上。而当我们用Python爬虫浏览页面的时候,只能看到的是网站的源码,然后在其中获取我们需要的信息。
在上图上,我们可以发现,其实除了英文,还有一些乱码,乱码是由于编码格式造成的。一般情况下,网站都是使用兼容度高的UTF8编码格式,而当前的网页是使用的ISO-8859-1,我们可以把拿到的response设置下编码格式:
#爬取百度首页的内容
代码语言:javascript复制#爬取百度首页的内容
url = 'https://www.baidu.com/'
response = requests.get(url)
response.encoding = "UTF8"
# 输出响应状态码
print("状态码:{0}".format(response.status_code))
# 输出响应字符编码
print("字符编码:{0}".format(response.encoding))
# 输出响应文本内容
print("响应文本:{0}".format(response.text))
然后重新运行看下效果,结果如下图,现在可以看到正常的编码的网页源码。
代码语言:javascript复制状态码:200
字符编码:UTF8
响应文本:<!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><link rel=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head>
…….
Requests模块使用简单,功能强大,完全可以实现常规简单爬虫的编写,所以熟练使用Requests是获取数据的核心基础,接下来我们了解一些常用的功能。
请求的方式
上面示例中requests.get()是用的GET请求方式,而HTTP 请求可以使用多种请求方法,最常用的就是GET和POST请求。除此之外HEAD、OPTIONS、PUT、PATCH、DELETE、TRACE 和CONNECT ,一共9种请求方式。各个请求方式主要的差别如下表所示,其中Requests是支持前7种请求方式。
方法 | 功能描述 |
---|---|
GET | 请求指定的页面信息,并返回响应(response)主体。 |
HEAD | 类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头 |
POST | 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立或已有资源的修改。 |
PUT | 从客户端向服务器传送的数据取代指定的文档的内容。 |
DELETE | 请求服务器删除指定的页面。 |
CONNECT | HTTP/1.1 协议中预留给能够将连接改为管道方式的代理服务器。 |
OPTIONS | 允许客户端查看服务器的性能。 |
TRACE | 回显服务器收到的请求,主要用于测试或诊断。 |
PATCH | 是对 PUT 方法的补充,用来对已知资源进行局部更新 。 |
使用方式跟GET方式一样,直接requests跟请求方式关键词即可,如果发送一个POST请求:
代码语言:javascript复制import requests
url = 'https://www.baidu.com/'
# post请求
response = requests.post(url)
# head请求
response = requests.head(url)
# put请求
response = requests.put(url)
请求中传参数
通常情况下,我们使用requests获取一个网页的内容,都会携带一些参数,服务器会根据这些参数的不同做出不同的响应,爬虫中最常使用的就是分页参数。比如说,我们想查看某个页面中第5页的内容,我们一般可以把页码参数设置为5加在请求连接中。那么怎么添加请求参数呢?
如果是GET请求,最直接的方法可以手动构建,在链接后面添加参数。比如:
代码语言:javascript复制import requests
url = ' https://www.baidu.com/?pageNum=5&pageSize=10'
response = requests.get(url)
当然Requests 允许你使用 params 关键字参数,以一个字符串字典来提供这些参数。拿上面的参数来说,代码如下:
代码语言:javascript复制import requests
url = 'https://www.baidu.com'
params = {'pageNum': '5', 'pageSize': '10'}
response = requests.get(url, params=params)
print("完整请求地址:{0}".format(response.url))
而Requests最终的请求地址也是跟我们手动组装的链接是一样的,代码运行结果如下:
完整请求地址:https://www.baidu.com/?pageNum=5&pageSize=10
注意:字典里的值为 None 的键都不会被添加到URL中。
除了GET请求,还有常用的POST请求。我们都知道POST请求安全性会比GET高,请求体不会直接添加在明文的链接中。一般网页中表单数据的提交都是通过POST请求进行,所以我们也需要知道,requests怎么在POST请求中添加参数。
其实也可以跟GET请求一下,用一个字典来存放你需要提交的数据。同样用上面的例子,也可以这样做改成POST请求来处理:
代码语言:javascript复制import requests
url = 'https://www.baidu.com'
params = {'pageNum': '5', 'pageSize': '10'}
response = requests.post(url, data=params)
print("完整请求地址:{0}".format(response.url))
注意:跟GET请求不同的是,POST请求的参数名为data而不是params,虽然他们可以都是字典。
在这种需要提交表单的链接中,很多的还会采用Json来传输数据,Json是一种轻量级的数据交换格式。而且在前后端分离的系统中,多数也是用Json作为数据交换方式。Requests也是支持json数据作为参数提交请求,如下所示:
代码语言:javascript复制import requests
#需要导入json模块
import json
url = 'https://www.baidu.com'
data = {'pageNum': '5', 'pageSize': '10'}
json = json.dumps(data)
response = requests.post(url, json= json)
json模块提供了Pyhon字典对象和Json对象的转换方法,我们可以直接使用json.dumps()方法来转换对象。我们也可以直接创建一个json对象。
代码语言:javascript复制import requests
#需要导入json模块
import json
url = 'https://www.baidu.com'
data = {“pageNum”: “5”, “pageSize” : “10”}
response = requests.post(url, json= data)
上面我们手动创建的对象跟上之前的json.dumps(data)得到的结果是一样的,感兴趣的读者可以试一下。在数据获取中,多数情况我们获取得到的是Json对象,需要将其转换成Python对象来使用。
优化请求头
在前面提到一个用来告诉服务器访问者身份的参数——User-Agent,这就是Request请求头中的一个参数。在上面所有的例子,我们并没有设置一个请求头,也没有User-Agent也是可以正常访问的。这是因为百度并没有对请求头做限制,如果在其他的网站中没有User-Agent,服务器会认为这些请求是机器人发送的,为了保护自身的服务器安全,减少服务器压力,会拒绝继续响应这些请求,并且返回一些非正常请求的提示。
我们换一个网站,请求一下豆瓣电影看看会得到什么结果。
代码语言:javascript复制#爬取豆瓣电影首页的信息
import requests
url = 'https://movie.douban.com/'
response = requests.get(url)
# 输出响应状态码
print("状态码:{0}".format(response.status_code))
# 输出响应文本内容
print("响应文本:{0}".format(response.text))
输出结果如下:
状态码:418
响应文本:
这就是因为豆瓣电影的服务器认为当前请求不是正常的浏览器请求,所以拒绝响应。那怎么让服务器做出正确的响应呢,给requests.get()方法上加上请求头。我们可以模拟一个正常的浏览器,那浏览器的User-Agent是啥呢?
这个很好看。以谷歌浏览器为例子,先在浏览器打开豆瓣电影首页(https://movie.douban.com),然后按F12,找到NetWork栏目,在按下F5刷新下,这时候浏览器会加载这一次访问的所有内容,我们找到Name值为movie.douban.com的请求,点开会发现后面是这次请求的详情,如图所示。
其中在Request Headers中的最后一个key-value就是我们要找的User-Agent,如图中所示,它的值是Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0
这一串的内容意思说这是来自安装64位的win10的85版本的火狐浏览器发出的请求。所以网站服务器才给出了正确的响应。只要我们把这一串内容加到Requests的请求上,那就可以得到正确的回应。
代码如下:
代码语言:javascript复制#爬取豆瓣电影首页的信息
import requests
url = 'https://movie.douban.com/'
#设置请求头
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'
}
#添加headers参数
response = requests.get(url, headers=headers)
# 输出响应状态码
print("状态码:{0}".format(response.status_code))
# 输出响应文本内容
print("响应文本:{0}".format(response.text))
现在我们重新run一下,看下是否能得到我们想要的结果。
代码语言:javascript复制状态码:200
响应文本:<!DOCTYPE html>
<html lang="zh-CN" class="ua-windows ua-webkit">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="renderer" content="webkit">
<meta name="referrer" content="always">
<meta name="google-site-verification" content="ok0wCgT20tBBgo9_zat2iAcimtN4Ftf5ccsh092Xeyw" />
<title>
豆瓣电影
</title>
………
这样添加请求头user-agent,服务器认为这次请求不是非法请求,做出了正确回应,得到了我们想要的结果。除了user-agent,在Request Headers中还有一些比较重要的参数,比如说Cookie,Cookie是由服务器产生,发送给User-Agent,浏览器会将Cookie的key/value缓存起来,下次请求同一网站将会在Request Headers携带Cookie访问服务器,以此来保持回话。Cookie也是在爬虫编写中一个很重要的参数,后面遇到的时候会详细说明。
分析响应内容
在前面的操作中,我们已经遇见了两种状态码,200的状态码表示当前请求是正常响应的。不过这并不是判断是否得到我们想要的内容的判断标准,更主要的是看响应的内容。有时候还会需要响应头的一些内容,响应头怎么查看呢,直接使用response.headers既可以查看:
代码语言:javascript复制#打印响应头
print(response.headers)
#输出结果:
{'Date': 'Sun, 07 Feb 2021 15:54:36 GMT', 'Content-Type': 'text/html; charset=utf-8', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Keep-Alive': 'timeout=30', 'Vary': 'Accept-Encoding, Accept-Encoding', 'X-Xss-Protection': '1; mode=block', 'X-Douban-Mobileapp': '0', 'Expires': 'Sun, 1 Jan 2006 01:00:00 GMT', 'Pragma': 'no-cache', 'Cache-Control': 'must-revalidate, no-cache, private', 'Set-Cookie': 'll="108296"; path=/; domain=.douban.com; expires=Mon, 07-Feb-2022 15:54:36 GMT, bid=sinsxKkZRR4; Expires=Mon, 07-Feb-22 15:54:36 GMT; Domain=.douban.com; Path=/', 'X-DAE-App': 'movie', 'X-DAE-Instance': 'default', 'X-DOUBAN-NEWBID': 'sinsxKkZRR4', 'Server': 'dae', 'X-Content-Type-Options': 'nosniff', 'Content-Encoding': 'gzip'}
响应headers也是一个字典格式的数据,通过字典的访问即可获取值,比如:
代码语言:javascript复制#打印响应头X-Content-Type-Options的值
print(response.headers['X-Content-Type-Options'])
#输出结果:
nosniff
不过更多的内容还是需要在响应体中,就是上面response.text的内容。有时候不同网站的服务器可能会有很多的拦截验证,返回的内容可能并不是真正想要的内容,这时候我们跟浏览器中的页面内容比对一下,确认是不是获得了正确的响应。
比较简单的方法是,在浏览器中,右键页面,点击查看页面源代码,如果跟response.text的内容基本一样的话,说明我们的请求没问题的,接下来的工作就是解析获得到的内容,它的类型是str对象,内容就是HTML页面的代码。
在解析HTML之前,先简单的了解一下HTML的知识,这样会提高我们后面的数据采集和收集速度,下一节我们一起学习下HTML基本内容