I. 简介
Requests是有史以来下载次数最多的Python软件包之一,每天下载量超过400,000次。
Requests的官方文档同样也非常的完善详尽,而且少见的有中文官方文档:http://cn.python-requests.org/zh_CN/latest/。
英文文档:http://docs.python-requests.org/en/master/api/
Requests的作者是Kenneth Reitz
使用requests库需要事先安装:
pip install requests
II. 发起请求
i. 请求方法
Requests的请求不再像urllib一样需要去构造各种Request、opener和handler,使用Requests构造的方法,并在其中传入需要的参数即可。
每一个请求方法都有一个对应的API,比如GET请求就可以使用get()方法。
1. get请求
import requests:
resp = requests.get('https://www.baidu.com')
当需要向后缀的内容(比如通过百度搜python)发起请求时应该怎么写呢?
step 1 先将对应的url复制,找到关键字段s?wd=python
。
step 2 创建形参params
,再传入requests.get
调用
params = {'wd': 'python'}
requests.get('https://www.baidu.com/s?', params=params)
这样就能访问到相关的内容了。
但是有些网站会出现403禁止访问的问题,需要访问权限。
r = requests.get('https://www.jianshu.com/')
print(r)
此时返回的内容是:
<Response [403]>
这种情况就需要加headers了。headeers要填入的内容可以通过浏览器登录httpbin.org/get来获取:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36"
}
r = requests.get('https://www.jianshu.com/', headers=headers)
print(r)
此时运行结果是:<Response [200]>
返回文本:
print(r.text)
返回二进制:
print(r.content)
当出现乱码时,可以加一段r.encoding = 'utf8'
。
查看网页代码时可以输入:
print(r.encoding)
2. post方法:
post的常规格式如下:
resp = requests.post(url='', data=)
这里以http://httpbin.org/post为例。把该url输入浏览器,点击回车,页面会显示"The method is not allowed for the requested URL."。为什么会出现这种问题?因为以上操作实际上都是get请求,而不是post。所以如果要打印出来相应的内容,只能通过print(r.text)
。
import requests
...
data = {'username':'Spiderman'}
r = requests.post(url='http://httpbin.org/post',data=data)
print(r.text)
打印结果是:
{
"args": {},
"data": "",
"files": {},
"form": {
"username": "Spiderman"
},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Content-Length": "13",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.21.0",
"X-Amzn-Trace-Id": "Root=1-5e78263f-703d0f1c7152ac7c5f4bd4f0"
},
"json": null,
"origin": "183.6.116.33",
"url": "http://httpbin.org/post"
}
输出结果可以看出,其数据类型是字符串。可以通过print(type(r.text))
查询出来。同时这个数据也是一个json方法获取的数据。所以也可以通过print(r.json)来打印,但是显示结果是字典。此时再用````print(type(r.json))```查询出来显示是dict类型。
注意:python里并没有json类型的数据,json主要用于前台。而json方法是可以被python调用的。
3. 自定义Cookies
url = 'http://www.baidu.com/cookies'
cookies = {'cookie': 'BIDUPSID=23DB26043207FD9E258CF9AD2EF83046; PSTM=1550374946; BDUSS=DU2M1VyMkl5bjNXenNpbWRVUXFsRjU5ajlGb1ZNQkRPYmhmQkcwTDRoVURGY2xjQVFBQUFBJCQAAAAAAAAAAAEAAABSbbJRwK23qMKzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOIoVwDiKFcc; MCITY=-%3A; BD_UPN=12314753; BAIDUID=1DEAB40A7C0DCDBBDA02B82DF1B5B65A:FG=1; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; shifen[89083533270_8408]=1584956655; BCLID=9015116421683587576; BDSFRCVID=SwFOJeC62AYd6Z5uf0YZUGQBJmJnmZcTH6aIcw4ZvwhJyapsWLBJEG0Pjf8g0KubtwsaogKK3gOTH4DF_2uxOjjg8UtVJeC6EG0Ptf8g0M5; H_BDCLCKID_SF=tJ48_CLMfC_3fP36q4rMM-LthfLX5-RLfa7fKq7F5l8-hljXb5jAjJLEDbrzLxbI0eQLaJrEyJOxOKQpytbbjhIB5U7Zh4oj-avr5qcN3KJmjRL9bT3v5tD35pbK2-biWb7M2MbdJpbP_IoG2Mn8M4bb3qOpBtQmJeTxoUJ25DnJhhCGe4bK-TrLjaLJtU5; __guid=136081015.4240479547906954000.1584956654804.911; BD_HOME=1; monitor_count=2; BDRCVFR[feWj1Vr5u3D]=I67x6TjHwwYf0; delPer=0; BD_CK_SAM=1; PSINO=7; H_PS_PSSID=30968_1447_31124_21116_30903_30824_31086_26350; H_PS_645EC=0c33BVWenHG4utxsBfFYWJ5l69VWujaNT2gZsRI9%2F56QHP3SWoM4s5QMNmUBACagqrgf; BDSVRTM=119;'}
resp = requests.get(url, cookies=cookies)
print(resp.text)
- 如何获取网页中的cookie信息?见下图:
4. 设置代理
在需要使用代理时,构造代理字典,传递给proxies参数
import requests
proxies = {
'http':'http://10.10.1.10:3128',
'https':'https//10.10.1.10:1080',
}
requests.get('http://httpbin.org', proxies=proxies)
5. 重定向
在网络请求中,我们常常会遇到状态码是3开头的重定向问题,在Requests中是默认开启允许重定向的,即遇到重定向时,会自动继续访问。
import requests
resp = request.get('http://github.com', allow_redirects=False)
print(resp.status_code)
III. 百度贴吧案例
创建session对象
session = requests.Session()
session.get()
session.post()
session调用和request调用的区别:request调用无法自动保存cookie数据;而session调用可以自动保存cookie数据,只有在关闭了session对话的情况下,cookie数据才会过期。
百度贴吧数据获取。这里以华为吧为例:
最初始的动作是导入requests库import requests
。因为还会用到正则,故还需导入re库,写法同前。
step 1 进入目标贴吧,点翻页到下一页。点击浏览器地址栏,复制url,粘贴到记事本备用。格式是https://tieba.baidu.com/f?kw=%E5%8D%8E%E4%B8%BA&ie=utf-8&pn=50
。可以把涉及编码、格式等内容的字符串去掉,也就是保留f?kw=%E5%8D%8E%E4%B8%BA&pn=50
的部分(pn=50表示第一页前50条)
resp = requests.get('https://tieba.baidu.com/f?kw=%E5%8D%8E%E4%B8%BA&pn=50').text
step 2 匹配出每一个帖子的url:随机找一篇文章新标签页打开。点击浏览器地址栏,会发现url地址是https://tieba.baidu.com/p/6568xxxxxx
的格式,复制这段数字。点击鼠标右键选“检查”,填入该数字查找标题后搜索,找到的对应的标题代码<a rel="noreferrer" href="/p/6568xxxxxx"
。复制该段代码粘贴至记事本备用。
通过findall方法匹配帖子标题,将<a rel="noreferrer" href="/p/6568xxxxxx"
填入r''
中,并改写/p/
后面的内容(因为是全数字,故使用d+进行匹配)
article_urls = re.findall(r'<a rel="noreferrer" href="(/p/\d+)"', resp, re.S)
step 3 url拼接
article_urls = ['https://tieba.baidu.com/'+url for url in article_urls]
step 4 对帖子的详情页面发起请求
article_resp = requests.get(article_urls[0]).text
step 5 解析想要的具体数据
我们尝试从title, author和create_time三个要素进行解析:
a. 先解析title:点开一篇贴吧文章,复制标题,点击右键查看源代码,然后ctrl+f,弹出搜索框,此时可以查找想要找的<title>
标签,复制标题<title>华为P40系列三个版本后置摄像头参数被曝光!_华为吧_百度贴吧
备用。
在pycharm里输入以下代码:
title = re.findall(r'<title>(.*?)_华为吧_百度贴吧, article_resp, re.S')[0]
print(title) # print用于检查代码是否能有效工作,检查ok后可删除
注意:_华为吧_百度贴吧
这部分字段要保留,仅标题用(.*?)
代替,否则打印时无任何内容显示
b. 解析author:复制一楼的作者名,在源代码页搜索框中输入作者名查找。此时出来的页面如下:
但这个网名并非我们要复制的字段,那应该怎么找呢?此时往前看会发现FF9Ps52xe1这个字段,而在原帖检查页面上页看到了user_name后面对应的也是这个字符串。
把这个字段复制后再在源代码页面搜索,此时找到了author:“FF9Ps52xe1”,这段才是我们要复制的内容。
那么author部分的代码则按如下写法:
author = re.findall(r'author:"(.*?)"', article_resp, re.S)[0]
注意: author:"(.*?)"的双引号后面可以再跟一个英文逗号,也可以不加。
c. 解析创建事件create_time:复制帖子1楼的时间,在源代码页搜索框中粘贴搜搜。找到"2020-03-22 10:20"
的字段复制,然后粘贴等到以下代码的r''
中,再将具体的时间替换为(\d+-\d+-\d+ \d+:\d+)
。
create_time = re.findall(r'"(\d+-\d+-\d+ \d+:\d+)', article_resp, re.S)[0]
接下来就可以打印想要的信息了:
print(title, ': ', author)
print('发布时间:{}'.format(create_time))
而要匹配出多篇文章,则需要用到遍历,完整的代码如下:
import re
import requests
resp = requests.get('https://tieba.baidu.com/f?kw=%E5%8D%8E%E4%B8%BA&pn=50').text
# 匹配出每一个帖子的url
article_urls = re.findall(r'<a rel="noreferrer" href="(/p/\d+)"', resp, re.S) # 单引号里的a rel的内容是通过浏览器页面解析查找出来的
# url拼接
article_urls = ['https://tieba.baidu.com/'+url for url in article_urls]
for url in article_urls:
# 对帖子的详情页面发起请求
article_resp = requests.get(url).text
# 解析出想要的具体数据
title = re.findall(r'<title>(.*?)_华为吧_百度贴吧', article_resp, re.S)[0]
author = re.findall(r'author:"(.*?)"', article_resp, re.S)[0]
create_time = re.findall(r'"(\d{4}-\d+-\d+ \d+:\d+)"', article_resp, re.S)[0]
print(title, ':', author)
print('发布时间:{}'.format(create_time))
如果我们想让这个爬虫程序也能用于爬取其他贴吧的内容,并且能够跳转到想要的页码去,应该怎么改程序呢?
"""
1. kw=和pn=要改写成变量,并加入.format(key, pn*50)使字段可以进行填充。
2. pn*50表示每页50条标题,翻页就是*50。
"""
key = input('请输入要获取的贴吧: ')
page = input('请输入要获取的页数: ')
for pn in range(int(page)): #此处页码需要转换为整数int,否则会报错
resp = requests.get('https://tieba.baidu.com/f?kw={}&pn={}'.format(key, pn*50)).text
# 匹配出每一个帖子的url
article_urls = re.findall(r'<a rel="noreferrer" href="(/p/\d+)"', resp, re.S) # 单引号里的a rel的内容是通过浏览器页面解析查找出来的
# url拼接
article_urls = ['https://tieba.baidu.com'+url for url in article_urls]
for url in article_urls: # 遍历url
# 对帖子的详情页面发起请求
article_resp = requests.get(url).text # url作为变量传入get请求中
# 解析出想要的具体数据
title = re.findall(r"'threadTitle': '(.*?)'", article_resp, re.S)[0]
"""
因为之前<title>标签匹配不到标题,这次在源代码中找到新的标题匹配标识,改用threadTitle
"""
try:
author = re.findall(r'author:"(.*?)"', article_resp, re.S)[0]
except IndexError as e:
print('数据匹配异常')
continue
create_time = re.findall(r'quot;(\d{4}-\d+-\d+ \d{2}:\d{2})"', article_resp, re.S)
if not create_time: # 如果以上规则不适用
print('第二次匹配时间')
create_time = re.findall(r'<span class="tail-info">(\d{4}-\d+-\d+ \d+:\d+)</span', article_resp, re.S) # 另一种匹配规则,因为不同吧的时间标签不一样
print(title, ':', author)
print('发布时间:{}'.format(create_time[0])) # 把索引加到这里以形参形式传入,是为了避免前面出错时不再往下运行