By Terrill Yang (Github: https://github.com/yttty)
由你需要这些:Python3.x爬虫学习资料整理 - 知乎专栏整理而来。
本篇来自requests的基本使用
导入并请求 https://www.baidu.com/
In [1]:
import requests
r = requests.get('https://www.baidu.com/')
请求响应的类型是 requests.models.Response
In [2]:
print(type(r))
状态码是200
In [3]:
print(r.status_code)
响应体的类型是字符串str
In [4]:
print(type(r.text))
可以得到响应的 HTTP HEADER
In [5]:
print(r.headers)
响应体内容
In [6]:
print(r.text)
Cookies的类型是RequestsCookieJar
In [7]:
print(r.cookies)
我们可以发现,使用了 requests.get(url)
方法就成功实现了一个 GET
请求。这倒不算什么,更方便的在于其他的请求类型依然可以用一句话来完成。
下面介绍一个很棒的demo网站,httpbin(1): HTTP Request & Response Service (http://httpbin.org) ,这个网站的介绍大概是这个样子的
Testing an HTTP Library can become difficult sometimes. RequestBin is fantastic for testing POST requests, but doesn't let you control the response. This exists to cover all kinds of HTTP scenarios. Additional endpoints are being considered.
All endpoint responses are JSON-encoded.
简单的说就是你可以对这个网站发出各类HTTP请求,来测试你的请求报文。
下面用一个实例来感受一下 requests
库的强大威力:
In [8]:
r = requests.post('http://httpbin.org/post')
print('----POST----\n', r.text)
r = requests.put('http://httpbin.org/put')
print('----PUT----\n', r.text)
r = requests.delete('http://httpbin.org/delete')
print('----DELETE----\n', r.text)
r = requests.head('http://httpbin.org/get')
print('----HEAD----\n', r.text)
r = requests.options('http://httpbin.org/get')
print('----OPTIONS----\n', r.text)
其实这只是 requests
库的冰山一角,更多的还在后面呢。
In [9]:
r = requests.get('http://httpbin.org/get')
print(r.text)
可以发现我们成功发起了get请求,请求的链接和头信息都有相应的返回。
那么 GET
请求,如果要附加额外的信息一般是怎样来添加?没错,那就是直接当做参数添加到url后面。
比如现在我想添加两个参数,名字name
是germey
,年龄age
是22
。构造这个请求链接是不是我们要直接写成
r = requests.get("http://httpbin.org/get?name=germey&age=22")
可以是可以,但是不觉得很不人性化吗?一般的这种信息数据我们会用字典来存储,那么怎样来构造这个链接呢?
{"name": "germey", "age": 22}
同样很简单,利用params这个参数就好了。
实例如下:
In [10]:
data = {
'name': 'germey',
'age': 22
}
r = requests.get("http://httpbin.org/get", params=data)
print(r.text)
通过返回信息我们可以判断,请求的链接自动被构造成了 http://httpbin.org/get?age=22&name=germey
,是不是很方便?
另外,网页的返回类型实际上是str
类型,但是它很特殊,是Json
的格式,所以如果我们想直接把返回结果解析,得到一个字典dict
格式的话,可以直接调用json()
方法。
用一个实例来感受一下:
In [11]:
r = requests.get("http://httpbin.org/get")
print('type(r.text) : ', type(r.text), '\n')
print('r.json() : ', r.json(), '\n')
print('type(r.json()) : ', type(r.json()), '\n')
但注意,如果返回结果不是Json格式,便会出现解析错误,抛出 json.decoder.JSONDecodeError
的异常。
In [12]:
import requests
import re
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://www.zhihu.com/explore", headers=headers)
pattern = re.compile('explore-feed.*?question_link.*?>(.*?)</a>', re.S)
titles = re.findall(pattern, r.text)
print(titles)
如上代码,我们请求了知乎-发现页面 https://www.zhihu.com/explore ,在这里加入了头信息,头信息中包含了 User-Agent
信息,也就是上一节提到的浏览器标识信息。如果不加这个,知乎会禁止抓取。可以看到,我们成功提取出了所有的问题内容。
In [13]:
r = requests.get("https://github.com/favicon.ico")
print(r.text)
print(r.content)
在这里打印了 response
的两个属性,一个是 text
,另一个是 content
。前两行便是r.text
的结果,最后一行是r.content
的结果。
可以注意到,前者出现了乱码,后者结果前面带有一个b
,代表这是bytes
类型的数据。由于图片是二进制数据,所以前者在打印时转化为str
类型,也就是图片直接转化为字符串,理所当然会出现乱码。
两个属性有什么区别?前者返回的是字符串类型,如果返回结果是文本文件,那么用这种方式直接获取其内容即可。如果返回结果是图片、音频、视频等文件,requests
会为我们自动解码成bytes
类型,即获取字节流数据。
进一步地,我们可以将刚才提取到的图片保存下来。
In [14]:
with open('data/favicon.ico', 'wb') as f:
f.write(r.content)
f.close()
在这里用了open()
函数,第一个参数是文件名称,第二个参数代表以二进制写的形式打开,可以向文件里写入二进制数据,然后保存。
运行结束之后,可以发现在data
文件夹中出现了名为favicon.ico
的图标。
In [15]:
r = requests.get("https://www.zhihu.com/explore")
print(r.text)
In [16]:
import requests
data = {'name': 'germey', 'age': '22'}
r = requests.post("http://httpbin.org/post", data=data)
print(r.text)
可以发现,成功获得了返回结果,返回结果中的form
部分就是提交的数据,那么这就证明POST
请求成功发送了。
In [17]:
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)
在这里分别打印输出了响应状态吗status_code
,响应头headers
,Cookies
,请求连接,请求历史的类型和内容。可以看到,headers
还有cookies
这两个部分都是特定的数据结构,打开浏览器同样可以发现有同样的响应头信息。
In [18]:
r = requests.get('http://www.jianshu.com')
exit() if not r.status_code == requests.codes.ok else print('Request Successfully')
在这里,通过比较返回码和内置的成功的返回码是一致的,来保证请求得到了正常响应,输出成功请求的消息,否则程序终止。
那么肯定不能只有ok这个条件码,还有没有其他的呢?答案是肯定的。下面列出了返回码和相应的查询条件:
# Informational.
100: ('continue',),
101: ('switching_protocols',),
102: ('processing',),
103: ('checkpoint',),
122: ('uri_too_long', 'request_uri_too_long'),
200: ('ok', 'okay', 'all_ok', 'all_okay', 'all_good', '\\o/', '✓'),
201: ('created',),
202: ('accepted',),
203: ('non_authoritative_info', 'non_authoritative_information'),
204: ('no_content',),
205: ('reset_content', 'reset'),
206: ('partial_content', 'partial'),
207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'),
208: ('already_reported',),
226: ('im_used',),
# Redirection.
300: ('multiple_choices',),
301: ('moved_permanently', 'moved', '\\o-'),
302: ('found',),
303: ('see_other', 'other'),
304: ('not_modified',),
305: ('use_proxy',),
306: ('switch_proxy',),
307: ('temporary_redirect', 'temporary_moved', 'temporary'),
308: ('permanent_redirect',
'resume_incomplete', 'resume',), # These 2 to be removed in 3.0
# Client Error.
400: ('bad_request', 'bad'),
401: ('unauthorized',),
402: ('payment_required', 'payment'),
403: ('forbidden',),
404: ('not_found', '-o-'),
405: ('method_not_allowed', 'not_allowed'),
406: ('not_acceptable',),
407: ('proxy_authentication_required', 'proxy_auth', 'proxy_authentication'),
408: ('request_timeout', 'timeout'),
409: ('conflict',),
410: ('gone',),
411: ('length_required',),
412: ('precondition_failed', 'precondition'),
413: ('request_entity_too_large',),
414: ('request_uri_too_large',),
415: ('unsupported_media_type', 'unsupported_media', 'media_type'),
416: ('requested_range_not_satisfiable', 'requested_range', 'range_not_satisfiable'),
417: ('expectation_failed',),
418: ('im_a_teapot', 'teapot', 'i_am_a_teapot'),
421: ('misdirected_request',),
422: ('unprocessable_entity', 'unprocessable'),
423: ('locked',),
424: ('failed_dependency', 'dependency'),
425: ('unordered_collection', 'unordered'),
426: ('upgrade_required', 'upgrade'),
428: ('precondition_required', 'precondition'),
429: ('too_many_requests', 'too_many'),
431: ('header_fields_too_large', 'fields_too_large'),
444: ('no_response', 'none'),
449: ('retry_with', 'retry'),
450: ('blocked_by_windows_parental_controls', 'parental_controls'),
451: ('unavailable_for_legal_reasons', 'legal_reasons'),
499: ('client_closed_request',),
# Server Error.
500: ('internal_server_error', 'server_error', '/o\\', '✗'),
501: ('not_implemented',),
502: ('bad_gateway',),
503: ('service_unavailable', 'unavailable'),
504: ('gateway_timeout',),
505: ('http_version_not_supported', 'http_version'),
506: ('variant_also_negotiates',),
507: ('insufficient_storage',),
509: ('bandwidth_limit_exceeded', 'bandwidth'),
510: ('not_extended',),
511: ('network_authentication_required', 'network_auth', 'network_authentication'),
比如如果你想判断结果是不是404状态,你可以用requests.codes.not_found
来比对。
In [19]:
files = {'file': open('data/favicon.ico', 'rb')}
r = requests.post("http://httpbin.org/post", files=files)
print(r.text)
这个网站会返回一个响应,里面包含files
这个字段,而form
是空的,这证明文件上传部分,会单独有一个files来标识。
In [20]:
import requests
r = requests.get("https://www.baidu.com")
print(r.cookies)
for key, value in r.cookies.items():
print(key + '=' + value)
可以看到,首先打印输出了cookie
,可以发现它是一个RequestCookieJar
类型。然后用items()
方法将其转化为元组组成的列表,遍历输出每一个cookie
的名和值。
当然,我们也可以直接用Cookie来维持登录状态。比如我们以知乎为例,直接利用Cookie来维持登录状态。
首先登录知乎,将请求头中的Cookie复制下来。
(知乎已经更改了登陆方式,现在使用Cookie无法登陆)
In [21]:
headers = {
'Cookie': '_za=dc07d0bb-599c-46e9-8906-f6dd252910b4; d_c0="AIAABz1hvQmPTup3qtT92xkZN2UQoova_cc=|1460107885"; _zap=3c9b8860-2a38-4532-b476-c3617ff3fb0d; _ga=GA1.2.1945944829.1442545470; aliyungf_tc=AQAAANoM4BIxkAMAj67seLZyZFoQgALT; q_c1=a366104786da4623b23af9cd321e4d38|1484577173000|1468254315000; _xsrf=b9564d003dd4bdb7b6f9e6185b8a0b78; l_cap_id="MTZlMjVmODgwYTVlNGEyYzg1ZDBkNzhkMzNjYjMxYmI=|1484577173|1c7374494b0e1a7ef526ea93c7065b2a8b9736eb"; cap_id="MzU3OGUzZjYwYjIwNGNmNWFhMTk2OGU2NjRjOWE3MDk=|1484577173|b5723f8256e35bd66f86d1e070c3a3d4324c655a"; r_cap_id="YmQwYmEwNDIxMDYxNDBkZmI2NzAzNjI2ZjJkMzNmOGQ=|1484577175|01cabbd0c7a481a7e4a907ae60ba3e2c64d07ddc"; login="NWVhMDVlNzZjMzI0NGE3ZGIyMmExODFhNzEzNGVhNmY=|1484577185|45503ef9b8dc4c3468f776c202fbc7fe442f8521"; n_c=1; z_c0=Mi4wQUFDQXhFMGpBQUFBZ0FBSFBXRzlDUmNBQUFCaEFsVk5vV2FrV0FBQkU5RmpvbXljZE15a2FZNU8zQVRjS0g1Qm1n|1484577189|3dc63f1a6bd389c32162f4d901ebf10e6750dffc; nweb_qa=heifetz; __utma=51854390.1945944829.1442545470.1484577176.1484577176.1; __utmb=51854390.0.10.1484577176; __utmc=51854390; __utmz=51854390.1484577176.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __utmv=51854390.100-1|2=registration_date=20140101=1^3=entry_date=20140101=1',
'Host': 'www.zhihu.com',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36',
}
r = requests.get("http://www.zhihu.com", headers=headers)
print(r.text)
在requests
中,我们如果直接利用requests.get()
或requests.post()
等方法的确可以做到模拟网页的请求。但是这实际上是相当于不同的会话,即不同的session
,也就是说相当于你用了两个浏览器打开了不同的页面。
设想这样一个场景,你第一个请求利用了requests.post()
方法登录了某个网站,第二次想获取成功登录后的自己的个人信息,你又用了一次requests.get()
方法。实际上,这相当于打开了两个浏览器,是两个完全不相关的会话,你说你能成功获取个人信息吗?那当然不能。
有小伙伴就说了,我在两次请求的时候都设置好一样的Cookie不就行了?行是行,但是不觉得麻烦吗?每次都要这样。是我我忍不了。其实解决这个问题的主要方法就是维持同一个会话,也就是相当于打开一个新的浏览器选项卡而不是新开一个浏览器。但是我又不想每次设置Cookie
,那该咋办?这时候就有了新的利器Session
。利用它,我们可以方便地维护一个会话,而且不用担心Cookie的问题,它会帮我们自动处理好。
在下面的实例中我们请求了一个测试网址,http://httpbin.org/cookies/set/number/123456789
请求这个网址我们可以设置Cookie
,名称叫做number
,内容是123456789
,后面的网址http://httpbin.org/cookies
可以获取当前的Cookie
。
你觉得这样能成功获取到设置的Cookie吗?运行结果如下:
In [22]:
requests.get('http://httpbin.org/cookies/set/number/123456789')
r = requests.get('http://httpbin.org/cookies')
print(r.text)
并不行。那这时候我们想起刚才说的Session了,改成这个试试看:
In [23]:
s = requests.Session()
s.get('http://httpbin.org/cookies/set/number/123456789')
r = s.get('http://httpbin.org/cookies')
print(r.text)
成功获取!所以,利用Session
我们可以做到模拟同一个会话,而且不用担心Cookie
的问题,通常用于模拟登录成功之后再进行下一步的操作。
Session
在平常用到的非常广泛,可以用于模拟在一个浏览器中打开同一站点的不同页面。
现在我们用requests
来测试一下:
In [24]:
import requests
response = requests.get('https://www.12306.cn')
print(response.status_code)
提示一个错误,叫做SSLError
,证书验证错误。
所以如果我们请求一个https
站点,但是证书验证错误的页面时,就会报这样的错误,那么如何避免这个错误呢?很简单,把verify
这个参数设置为False
即可。
改成如下代码:
In [25]:
response = requests.get('https://www.12306.cn', verify=False)
print(response.status_code)
不过发现报了一个警告,它提示建议让我们给它指定证书。
当然你可以选择忽略警告:(依旧报出警告)
In [26]:
import requests
from requests.packages import urllib3
urllib3.disable_warnings()
response = requests.get('https://www.12306.cn', verify=False)
print(response.status_code)
不过这不是最好的方式,https
协议的请求把证书验证都忽略了还有什么意义?
当然你也可以指定一个本地证书用作客户端证书,可以是单个文件(包含密钥和证书)或一个包含两个文件路径的元组。
response = requests.get('https://www.12306.cn', cert=('/path/server.crt', '/path/key'))
print(response.status_code)
上面代码是实例,你需要有crt
和key
文件,指定它们的路径。注意本地私有证书的key
必须要是解密状态,加密状态的key
是不支持的。
当然直接运行这个实例可能不行,因为这个代理可能是无效的,请换成自己的有效代理试验一下。
若你的代理需要使用HTTP Basic Auth,可以使用类似 http://user:password@host/
这样的语法。
import requests
proxies = {
"http": "http://user:password@10.10.1.10:3128/",
}
requests.get("https://www.taobao.com", proxies=proxies)
In [27]:
import requests
r = requests.get("https://www.taobao.com", timeout = 1)
print(r.status_code)
通过这样的方式,我们可以将超时时间设置为1秒,如果1秒内没有响应,那就抛出异常。
实际上请求分为两个阶段,即连接和读取。上面的设置timeout
值将会用作connect
和read
二者的timeout
。如果要分别指定,就传入一个元组:
In [28]:
r = requests.get('https://www.taobao.com', timeout=(5, 30))
print(r.status_code)
如果想永久等待,那么你可以直接将timeout
设置为None
,或者不设置,直接留空,因为默认是None
。这样的话,如果服务器还在运行,但是响应特别慢,那就慢慢等吧,它永远不会返回超时错误的。
用法如下:
In [29]:
r = requests.get('https://www.taobao.com', timeout=None)
print(r.status_code)
或直接不加参数:
In [30]:
r = requests.get('https://www.taobao.com')
print(r.status_code)
如果遇到这样的网站验证,可以使用requests带的身份认证功能。
In [31]:
import requests
from requests.auth import HTTPBasicAuth
r = requests.get('http://120.27.34.24:9001', auth=HTTPBasicAuth('user', '123'))
print(r.status_code)
如果用户名和密码正确的话,认证成功,那么运行结果会返回200,如果认证失败,则会返回401状态码。
当然如果参数都传一个HTTPBasicAuth
类,那的确太繁琐了,所以requests
提供了一个简单的写法,你可以直接传一个元组,它会默认使用HTTPBasicAuth
这个类来认证。
所以上面的代码可以直接简写如下:
In [32]:
import requests
r = requests.get('http://120.27.34.24:9001', auth=('user', '123'))
print(r.status_code)
requests还提供了其他的认证方式,如OAuth
认证,不过你需要安装oauth
包。
pip3 install requests_oauthlib
使用OAuth的方法如下:
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_OAUTH_TOKEN', 'USER_OAUTH_TOKEN_SECRET')
requests.get(url, auth=auth)
更多详细的功能就可以参考requests_oauthlib
的官方文档,在此就不再赘述了。
In [ ]: