接口测试

一、基本用法

1.1 实例引入

import requests

r = requests.get("https://www.baidu.com")
print("r:", r)
print("type r:", type(r))
print("r.status_code:", r.status_code)
print("type r.text:", type(r.text))
print("r.text:", r.text)
print("r.cookes:", r.cookies)

运行结果

r: <Response [200]>
type r: <class 'requests.models.Response'>
r.status_code: 200
type r.text: <class 'str'>
r.text: <!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> <body link=#0000cc> <div id=wrapper> <div id=head> <div class=head_wrapper> <div class=s_form> <div class=s_form_wrapper> <div id=lg> <img hidefocus=true src=//www.baidu.com/img/bd_logo1.png width=270 height=129> </div> <form id=form name=f action=//www.baidu.com/s class=fm> <input type=hidden name=bdorz_come value=1> <input type=hidden name=ie value=utf-8> <input type=hidden name=f value=8> <input type=hidden name=rsv_bp value=1> <input type=hidden name=rsv_idx value=1> <input type=hidden name=tn value=baidu><span class="bg s_ipt_wr"><input id=kw name=wd class=s_ipt value maxlength=255 autocomplete=off autofocus=autofocus></span><span class="bg s_btn_wr"><input type=submit id=su value=百度一下 class="bg s_btn" autofocus></span> </form> </div> </div> <div id=u1> <a href=http://news.baidu.com name=tj_trnews class=mnav>新闻</a> <a href=https://www.hao123.com name=tj_trhao123 class=mnav>hao123</a> <a href=http://map.baidu.com name=tj_trmap class=mnav>地图</a> <a href=http://v.baidu.com name=tj_trvideo class=mnav>视频</a> <a href=http://tieba.baidu.com name=tj_trtieba class=mnav>贴吧</a> <noscript> <a href=http://www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1 name=tj_login class=lb>登录</a> </noscript> <script>document.write('<a href="http://www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登录</a>');
                </script> <a href=//www.baidu.com/more/ name=tj_briicon class=bri style="display: block;">更多产品</a> </div> </div> </div> <div id=ftCon> <div id=ftConw> <p id=lh> <a href=http://home.baidu.com>关于百度</a> <a href=http://ir.baidu.com>About Baidu</a> </p> <p id=cp>&copy;2017&nbsp;Baidu&nbsp;<a href=http://www.baidu.com/duty/>使用百度前必读</a>&nbsp; <a href=http://jianyi.baidu.com/ class=cp-feedback>意见反馈</a>&nbsp;京ICP证030173号&nbsp; <img src=//www.baidu.com/img/gs.gif> </p> </div> </div> </div> </body> </html>

r.cookes: <RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]>
这里我们调用 `get()方法`得到一个 Response对象,然后分别输出了 Response 的类型、状态码、响应体的类型、内容以及 Cookies。

通过运行结果可以发现,它的返回类型是 requests.models.Response,响应体的类型是字符串str,Cookies的类型是RequestsCookieJar。

使用 get() 方法成功实现一个 GET 请求,这倒不算什么,更方便之处在于其他的请求类型依然可以用一句话来完成:
r = requests.post('http://httpsbin.org/post')
r = requests.put('http://httpsbin.org/put')
r = requests.delete('http://httpsbin.org/delete')
r = requests.head('http://httpsbin.org/get')
r = requests.options('http://httpsbin.org/get')

1.2 GET 请求

首先,构建一个最简单的GET请求,请求的连接为 http://httpbin.org/get ,该网站会判断如果客户端发起的是GET 请求的话,它返回相应的请求信息:
import requests

r = requests.get("http://httpbin.org/get")
print(r.text)
运行结果如下:
{
  "args": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.24.0", 
    "X-Amzn-Trace-Id": "Root=1-600ed10f-0f0cb6bc14f6daa030ecaceb"
  }, 
  "origin": "39.182.7.4", 
  "url": "http://httpbin.org/get"
}
如果要添加两个参数,该怎么写?
r = requests.get('http://httpbin.org/get?name=germey?age=22')
这样写不是不可以,但是非常不人性化。一般情况下,这种信息数据会用字段来存储。
import requests

data = {
    'name': 'germey',
    'age': 22
}

r = requests.get("http://httpbin.org/get", params=data)
print(r.text)
运行结果
{
  "args": {
    "age": "22", 
    "name": "germey"
  }, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.24.0", 
    "X-Amzn-Trace-Id": "Root=1-600ed27c-7752b72b3cf473ca141478b6"
  }, 
  "origin": "39.182.7.4", 
  "url": "http://httpbin.org/get?name=germey&age=22"
}
通过运行结果可以判断,请求的链接自动被构造成了:`http://httpbin.org/get?name=germey&age=22`

网页会犯的类型虽然是str类型,但是是json格式的。所以,如果想解析返回结果,得到一个字典格式的话,可以直接调用 json()方法。
import requests

r = requests.get("http://httpbin.org/get")
print(type(r.text))
print(r.json())
print(type(r.json()))
运行结果
<class 'str'>
{'args': {}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.24.0', 'X-Amzn-Trace-Id': 'Root=1-601039cf-7d2c52606d54bde54c153c80'}, 'origin': '39.182.7.4', 'url': 'http://httpbin.org/get'}
<class 'dict'>
如果返回的不是 json 格式的数据,便会解析错误,抛出 `json.decoder.JSONDecodeError`异常

1.2.1 抓取网页

如果请求普通的网页,则肯定能获得响应的内容。(这里的 headers信息要加,不然会被知乎屏蔽)

import requests
import re

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36 Edg/88.0.705.50"
}

r = requests.get("https://www.zhihu.com/explore", headers=headers)
pattern = re.compile('ExploreSpecialCard-contentTitle.*?noopener noreferrer.*?>(.*?)</a>', re.S)
titles = re.findall(pattern, r.text)
print(titles)
运行结果
['人没有食物的时候吃自己的屎能活命吗?', '灵魂存在吗?为什么?', '有什么值得做的手工?', '如何看待 2020 年山东 GDP 73129 亿元,增长 3.6% ?山东未来的发展前景如何?', '数字寿光,味道如何?', '山东创新怎么办:解放思想,放手让人们去想、去看、去试、去干', '有哪些收纳神器能让你家里焕然一新?', '有哪些美好寓意适合过年的植物?', '有哪些年味十足的餐具或摆盘推荐?', '2020 年你听到的最劲爆的公司八卦是什么?', '2021 年,你们公司的年会是怎么办的?你中奖了吗?', '有哪些 2020 年工作中不能忍受的事,是你匿名才敢说出来的?']

1.2.2 抓取二进制数据

上面返回的 HTML 文档,如果要抓取图片、音频、视频等文件,该怎么办?

以github 的站点图标为例来看一下
import requests

r = requests.get("https://github.com/favicon.ico")
print(r.text)
print("读取r.content:", r.content)

运行结果:

前者是纯粹的乱码(打印一张二进制图片,当然会乱码),后者结果前带有一个b,这代表是bytes类型的数据。

尝试保存下来
import requests

r = requests.get("https://github.com/favicon.ico")
with open("favicon.ico", "wb") as f:
    f.write(r.content)
运行结果

同样的,音频和视频也可以用这种方式获取。

1.3 POST 请求

import requests

data = {"name": "germey", "age": "22"}
r = requests.post("http://httpbin.org/post", data=data)
print(r.text)

运行结果

{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "age": "22", 
    "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.24.0", 
    "X-Amzn-Trace-Id": "Root=1-600edbed-6bfedbd7231caafc0adb04c4"
  }, 
  "json": null, 
  "origin": "39.182.7.4", 
  "url": "http://httpbin.org/post"
}

1.4 响应

发送请求后,得到的自然就是响应。在上面的实例中,我们使用text和content获取了响应的内容。此外,还有很多属性和方法可以用来获取其他信息,比如状态码、响应头、Cookies等。
import requests

r = requests.get("http://www.jianshu.com")
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)
运行结果
<class 'int'> 403
<class 'requests.structures.CaseInsensitiveDict'> {'Server': 'Tengine', 'Date': 'Mon, 25 Jan 2021 15:00:01 GMT', 'Content-Type': 'text/html', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Vary': 'Accept-Encoding', 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload', 'Content-Encoding': 'gzip'}
<class 'requests.cookies.RequestsCookieJar'> <RequestsCookieJar[]>
<class 'str'> https://www.jianshu.com/
<class 'list'> [<Response [301]>]
状态码常用来判断请求是否成功,而requests还提供了一个内置的状态码查询对象 `requests.codes`
import requests

r = requests.get("http://www.jianshu.com")
exit() if not r.status_code == requests.codes.ok else print("Request Successfully")

或者
import requests

r = requests.get("http://www.jianshu.com")
exit() if not r.status_code == requests.codes.not_found else print("Request Successfully")

这里通过比较返回码和内置的成功的返回码,来保证请求得到了正常响应,输出成功请求的消息,否则程序终止,这里我们用 request.codes.ok 来代码200 状态码。not_found 表示404。状态码和查询条件如下:

http://tools.jb51.net/table/http_status_code

二、高级用法

2.1 文件上传

注意点:``Content-Type: multipart/form-data; boundary=${bound}这个 headers不要传入

import requests

files = {"file": open("favicon.ico", "rb")}
r = requests.post("http://httpbin.org/post", files=files)
print(r.text)

运行结果

{
  "args": {}, 
  "data": "", 
  "files": {
    "file": "data:application/octet-stream;base64,AAABAAIAEBAAAAEAIAAoBQAAJgAAACAgAAABACA..."
  }, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Content-Length": "6665", 
    "Content-Type": "multipart/form-data; boundary=0229b636b80f365a842431005fcc3b87", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.24.0", 
    "X-Amzn-Trace-Id": "Root=1-600ee40b-25f57563781bc63e62e290de"
  }, 
  "json": null, 
  "origin": "39.182.7.4", 
  "url": "http://httpbin.org/post"
}

2.2 Cookies

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
当然,我们也可以直接用 Cookie 来维持登录状态,下面以博客园为例。先登录,再抓取

放置到 Headers 里面,然后发送请求:
import requests

headers = {
    "Cookie": "_ga=GA1.2.1413714814.1594529717; UM_distinctid=175e989f222d0f-04911..."

}

r = requests.get("https://www.cnblogs.com/", headers=headers)
print(r.text)

运行结果

当然,你也可以通过cookies参数来设置,不过这样就需要构造 RequestsCookieJar对象,而且要分割一下 cookies。相对繁琐,但是效果是相同的。
import requests

cookies = "_ga=GA1.2.1413714814.1594529717; UM_distinctid=175e989f222d0f-0491105d9619f4-5a30124d-384000-175e989f223cb3; CNZZDATA5897703=cnzz_eid=951549392-1608963889-https%3A%2F%2Fwww.baidu.com%2F&ntime=1608963889; CNZZDATA1274152299=842200016-1611152649-https%3A%2F%2Fhome.cnblogs.com%2F|1611152649; __gads=ID=f93b14b1270a542b-2234aa7bcec50020:T=1611378123:R:S=ALNI_Mb2gIxmcINNv-gOKrzt7Zt9SbDvRw..."
jar = requests.cookies.RequestsCookieJar()

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36 Edg/88.0.705.50",
    "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"
}
for cookie in cookies.split(";"):
    key, value = cookie.split("=", 1)
    jar.set(key, value)

r = requests.get("https://www.cnblogs.com/", cookies=jar, headers=headers)
print(r.text)
如上,先新建了一个 RequestCookieJar 对象,然后将复制下来的 cookies 利用 split() 方法分割,接着利用 set() 方法设置好每个 Cookie 的 key 和 value,然后通过调用 requests 的 get() 方法并传递给 cookies 参数几颗。

2.3 会话维持

正常情况下,如果第一个请求利用 post() 方法登录了某个网站,第二次想获取成功登录后的自己的个人信息,你又用了一次 get() 方法去请求个人信息页面。这时候是不能获取个人信息的。

解决方法一就是设置cookies。但是这样做很麻烦。方法二是利用 Session 对象,这样可以方便的维护一个会话,而且不用担心 cookies 的问题,它会帮我们自动处理好。
import requests

requests.get("http://httpbin.org/cookies/set/number/123456789")
r = requests.get("http://httpbin.org/cookies")
print(r.text)

运行结果

{
  "cookies": {}
}

set/number/123456789 实际上设置了一个cookie,但是第二次get的时候没有取到。

换上 Session 试试看:

import requests

s = requests.Session()
s.get("http://httpbin.org/cookies/set/number/123456789")
r = s.get('https://httpbin.org/cookies')
print(r.text)

运行结果

{
  "cookies": {
    "number": "123456789"
  }
}

利用 Session,可以做到模拟同一个会话而不用担心 Cookies 的问题。它通常用于模拟登录成功之后再进行下一步的操作。

2.4 SSL 证书验证

当https网站,且此网站证书没有被官方CA机构信任,会出现证书验证错误的结果(您的链接不是私密链接)的开关, 用于认证SSL证书, 默认为True。(之前12306网站不是CA机构信任的。现在应该试了。)

import requests

r = requests.request('GET', 'https://kyfw.12306.cn', verify=False)
print(r.text)

如果直接禁用,会有警告,它建议我们给它指定证书。我们可以设置忽略警告来屏蔽

import requests
from requests.packages import  urllib3

urllib3.disable_warnings()
r = requests.request('GET', 'https://kyfw.12306.cn', verify=False)
print(r.text)

或者通过捕获警告到日志的方式忽略警告

import logging
import requests

logging.captureWarnings(True)

r = requests.request('GET', 'https://kyfw.12306.cn', verify=False)
print(r.status_code) 

当然,我们也可以制定一个本地证书用作客户端证书,这可以是单个文件(包含秘钥和证书)或一个包含两个文件路径的元组:

import requests

response = requests.get("https://www.12306.cn",cert=("/path/server.crt","./path/key"))
print(response.status_code)

2.5 代理设置

如果大规模爬取,对于频繁的请求,网站直接回封禁客户端IP。这时候就需要代理

import requests

proxies = {
    "http": "http://10.10.1.10:3128",
    "http": "http://10.10.1.10:1080"
}

requests.get("https://www.taobao.com", proxies=proxies)

若代理需要使用 HTTP Basic Auth ,可以使用类似 http://user:password@host:port 这样的语法来设置代理

import requests

proxies = {
    "http": "http://user:password@10.10.1.10:3128/",
}

requests.get("https://www.taobao.com", proxies=proxies)

除了基本的 HTTP 代理外,requests 还支持 SOCKS 协议的代理。

首先要安装 socks 这个库:

pip3 install 'requests[socks]'

然后就可以使用 SOCKS 协议代理了,示例如下:

import requests

proxies = {
    "http": "socks5://user:password@10.10.1.10:3128",
    "https": "socks5://user:password@10.10.1.10:3128"
}
requests.get("https://www.taobao.com", proxies=proxies)

2.6 超时设置

用于设定超时时间, 单位为秒,当发起一个get请求时可以设置一个timeout时间, 如果在timeout时间内请求内容没有返回, 将产生一个timeout的异常。

import request

r = requests.get("https://www.taobao.com", timeout=1)
print(r.status_code)

实际上,请求分为两个阶段,即连接(connect)读取(read)。单独设置的话,为两者的 timeout 总和。如果要分别指定,就可以传入一个元祖:

r = requests.get("https://www.taobao.com", timeout=(5,11,30))

如果想永久等待,可以直接将timeout设置为None,或者不设置直接留空,因为默认是None。

2.7 身份认证

requests自带的身份认证功能
​```python
import requests
from requests.auth import HTTPBasicAuth

r = requests.get("http://localhost:5000", auth=HTTPBasicAuth("username", "password"))
print(r.status_code)

​ 成功会返回200状态码,失败会返回401状态码

​ 有一个简便写法

import requests

r = requests.get("http://localhost:5000", auth=("username", "password"))
print(r.status_code)

​ 此外,requests 还提供了其他认证方式,如OAuth认证,不过此时需要安装oauth包,安装命令如下:

pip3 install requests_oauthlib

​ 使用 OAuth1认证的方法如下:

import requests
from requests_oauthlib import OAuth1

url = "https://api.twitter.com/1.1/account/verify_credentials.json"
auth = OAuth1("YOUR_APP_KEY","YOUR_APP_SECRET","USER_OATH_TOKEN", "USER_OATH_TOKEN_SECRET")
requests.get(url, auth=auth)

2.8 Prepared Request

可以将请求表示为数据结构,其中各个参数都可以通过一个Request对象来表示。

url = "http://httpbin.org/post"
data = {
    "name": "germey"
}

headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36 Edg/88.0.705.50"
}
s = Session()
req = Request("POST", url, data=data, headers=headers)
prepped = s.prepare_request(req)
r = s.send(prepped)
print(r.text)

​ 这里我们引入了 Request,然后用url,data和headers参数够早了一个 Request对象,这时需要再调用 Session 的prepare_request() 方法将其转换为一个 Prepared Request对象,然后调用 send() 方法发送即可。

​ 有了Request这个对象,就可以将请求当做独立的对象来看待,这样在进行队列调度时会非常方便。后面我们会用它来构造一个 Request队列。

原文地址:https://www.cnblogs.com/dongye95/p/14327864.html