import requests

r = requests.get('https://www.baidu.com/')

"""
Explanation: 用Python 3开发网络爬虫
By Terrill Yang (Github: https://github.com/yttty)
由你需要这些：Python3.x爬虫学习资料整理 - 知乎专栏整理而来。
本篇来自requests的基本使用
用Python 3开发网络爬虫 - Chapter 04
使用requests库
在 urllib 库中，有 urllib.request.urlopen(url) 的方法，实际上它是以 GET 方式请求了一个网页。
那么在 requests 中，相应的方法就是 requests.get(url) ，是不是感觉表达更明确一些？
下面我们用一个实例来感受一下：
导入并请求 https://www.baidu.com/
End of explanation
"""


print(type(r))

"""
Explanation: 请求响应的类型是 requests.models.Response
End of explanation
"""


print(r.status_code)

"""
Explanation: 状态码是200
End of explanation
"""


print(type(r.text))

"""
Explanation: 响应体的类型是字符串str
End of explanation
"""


print(r.headers)

"""
Explanation: 可以得到响应的 HTTP HEADER
End of explanation
"""


print(r.text)

"""
Explanation: 响应体内容
End of explanation
"""


print(r.cookies)

"""
Explanation: Cookies的类型是RequestsCookieJar
End of explanation
"""


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)

"""
Explanation: 我们可以发现，使用了 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 库的强大威力：
End of explanation
"""


r = requests.get('http://httpbin.org/get')
print(r.text)

"""
Explanation: 其实这只是 requests 库的冰山一角，更多的还在后面呢。
GET请求
HTTP 中最常见的请求之一就是 GET 请求，我们首先来详细了解下利用 requests 来构建 GET 请求的方法以及相关属性方法操作。
首先让我们来构建一个最简单的GET请求，请求 httpbin.org/get ，它会判断如果你是 GET 请求的话，会返回响应的请求信息。
End of explanation
"""


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

r = requests.get("http://httpbin.org/get", params=data)
print(r.text)

"""
Explanation: 可以发现我们成功发起了get请求，请求的链接和头信息都有相应的返回。
那么 GET 请求，如果要附加额外的信息一般是怎样来添加？没错，那就是直接当做参数添加到url后面。
比如现在我想添加两个参数，名字name是germey，年龄age是22。构造这个请求链接是不是我们要直接写成 
r = requests.get("http://httpbin.org/get?name=germey&amp;age=22")
可以是可以，但是不觉得很不人性化吗？一般的这种信息数据我们会用字典来存储，那么怎样来构造这个链接呢？
{"name": "germey", "age": 22}
同样很简单，利用params这个参数就好了。
实例如下：
End of explanation
"""


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')

"""
Explanation: 通过返回信息我们可以判断，请求的链接自动被构造成了 http://httpbin.org/get?age=22&amp;name=germey ，是不是很方便？
另外，网页的返回类型实际上是str类型，但是它很特殊，是Json的格式，所以如果我们想直接把返回结果解析，得到一个字典dict格式的话，可以直接调用json()方法。
用一个实例来感受一下：
End of explanation
"""


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)

"""
Explanation: 但注意，如果返回结果不是Json格式，便会出现解析错误，抛出 json.decoder.JSONDecodeError 的异常。
抓取网页
如上的请求链接返回的是Json形式的字符串，那么如果我们请求普通的网页，那么肯定就能获得相应的内容了。
下面我们以知乎－发现页面为例来体验一下：
End of explanation
"""


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

"""
Explanation: 如上代码，我们请求了知乎－发现页面 https://www.zhihu.com/explore ，在这里加入了头信息，头信息中包含了 User-Agent 信息，也就是上一节提到的浏览器标识信息。如果不加这个，知乎会禁止抓取。可以看到，我们成功提取出了所有的问题内容。
抓取二进制数据
在上面的例子中，我们抓取的是知乎的一个页面，实际上它返回的是一个HTML文档，那么如果我们想抓去图片、音频、视频等文件的话应该怎么办呢？
我们都知道，图片、音频、视频这些文件都是本质上由二进制码组成的，由于有特定的保存格式和对应的解析方式，我们才可以看到这些形形色色的多媒体。所以想要抓取他们，那就需要拿到他们的二进制码。
下面我们以GitHub的站点图标，也就是在浏览器每一个标签上显示的小图标，为例来感受一下：
End of explanation
"""


with open('data/favicon.ico', 'wb') as f:
    f.write(r.content)
    f.close()

"""
Explanation: 在这里打印了 response 的两个属性，一个是 text，另一个是 content 。前两行便是r.text的结果，最后一行是r.content的结果。
可以注意到，前者出现了乱码，后者结果前面带有一个b，代表这是bytes类型的数据。由于图片是二进制数据，所以前者在打印时转化为str类型，也就是图片直接转化为字符串，理所当然会出现乱码。
两个属性有什么区别？前者返回的是字符串类型，如果返回结果是文本文件，那么用这种方式直接获取其内容即可。如果返回结果是图片、音频、视频等文件，requests会为我们自动解码成bytes类型，即获取字节流数据。
进一步地，我们可以将刚才提取到的图片保存下来。
End of explanation
"""


r = requests.get("https://www.zhihu.com/explore")
print(r.text)

"""
Explanation: 在这里用了open() 函数，第一个参数是文件名称，第二个参数代表以二进制写的形式打开，可以向文件里写入二进制数据，然后保存。
运行结束之后，可以发现在data文件夹中出现了名为favicon.ico的图标。
添加头信息
如urllib.request一样，我们也可以通过headers参数来传递头信息。你可以在headers这个数组中任意添加其他的头信息。
比如上面的知乎的例子，如果不传递头信息，就不能正常请求：
End of explanation
"""


import requests

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

"""
Explanation: 基本POST请求
在前面我们讲解了最基本的 GET 请求，另外一种比较常见的请求方式就是 POST 了，就像模拟表单提交一样，将一些数据提交到某个链接。
使用 requests 实现 POST 请求同样非常简单，下面的例子请求的是httpbin.org/post，它可以判断如果请求是POST方式，就把相关请求信息输出出来。
End of explanation
"""


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)

"""
Explanation: 可以发现，成功获得了返回结果，返回结果中的form部分就是提交的数据，那么这就证明POST请求成功发送了。
响应
发送请求之后，得到的自然就是响应，在上面的实例中我们使用了text和content获取了响应内容。不过还有很多属性和方法可以获取其他的信息。比如响应状态码、响应头、Cookies。
下面用一个实例来感受一下：
End of explanation
"""


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

"""
Explanation: 在这里分别打印输出了响应状态吗status_code，响应头headers，Cookies，请求连接，请求历史的类型和内容。可以看到，headers还有cookies这两个部分都是特定的数据结构，打开浏览器同样可以发现有同样的响应头信息。
状态码
在这里状态码常用来判断请求是否成功，requests还提供了一个内置的状态码查询对象requests.codes。比如你可以通过if r.status_code == requests.codes.ok来判断请求是否成功。
End of explanation
"""


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

"""
Explanation: 在这里，通过比较返回码和内置的成功的返回码是一致的，来保证请求得到了正常响应，输出成功请求的消息，否则程序终止。
那么肯定不能只有ok这个条件码，还有没有其他的呢？答案是肯定的。下面列出了返回码和相应的查询条件：
```bash
    # 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来比对。
响应头
如果想得到响应头信息，可以使用headers属性。它其实本质上也是一个字典形式。可以通过数组索引或者get()方法来获取某一条头信息内容。
比如获取Content-Type可以用r.headers['Content-Type']，也可以用r.headers.get('content-type')。
文件上传
我们知道reqeuests可以模拟提交一些数据，假如有的网站需要我们上传文件，我们同样可以利用它来上传，实现非常简单。
在上面一节中我们下载保存了一个文件叫做favicon.ico，这次我们用它为例来模拟文件上传的过程。
End of explanation
"""


import requests

r = requests.get("https://www.baidu.com")
print(r.cookies)
for key, value in r.cookies.items():
    print(key + '=' + value)

"""
Explanation: 这个网站会返回一个响应，里面包含files这个字段，而form是空的，这证明文件上传部分，会单独有一个files来标识。
Cookie处理
在前面我们使用了urllib，让它处理cookie真的是挺麻烦的，而有了requests，获得和提交cookies只需要一步。我们先用一个实例感受一下获取Cookie的过程：
End of explanation
"""


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)

"""
Explanation: 可以看到，首先打印输出了cookie，可以发现它是一个RequestCookieJar类型。然后用items()方法将其转化为元组组成的列表，遍历输出每一个cookie的名和值。
当然，我们也可以直接用Cookie来维持登录状态。~~比如我们以知乎为例，直接利用Cookie来维持登录状态。~~
~~首先登录知乎，将请求头中的Cookie复制下来。~~
（知乎已经更改了登陆方式，现在使用Cookie无法登陆）
End of explanation
"""


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

"""
Explanation: 会话维持
在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吗？运行结果如下：
End of explanation
"""


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

"""
Explanation: 并不行。那这时候我们想起刚才说的Session了，改成这个试试看：
End of explanation
"""


import requests

response = requests.get('https://www.12306.cn')
print(response.status_code)

"""
Explanation: 成功获取！所以，利用Session我们可以做到模拟同一个会话，而且不用担心Cookie的问题，通常用于模拟登录成功之后再进行下一步的操作。
Session在平常用到的非常广泛，可以用于模拟在一个浏览器中打开同一站点的不同页面。
SSL证书验证
reqeusts 提供了证书验证的功能，当一个请求是 https 的时候，它会检查 SSL 证书，可以使用 verify 这个参数。(其实如果不加的话默认是True，会自动验证的)
现在以12306( https://www.12306.cn)为例来感受一下它的用法，我们现在访问它都可以看到一个证书问题的页面，如下：

现在我们用requests来测试一下：
End of explanation
"""


response = requests.get('https://www.12306.cn', verify=False)
print(response.status_code)

"""
Explanation: 提示一个错误，叫做SSLError，证书验证错误。
所以如果我们请求一个https站点，但是证书验证错误的页面时，就会报这样的错误，那么如何避免这个错误呢？很简单，把verify这个参数设置为False即可。
改成如下代码：
End of explanation
"""


import requests
from requests.packages import urllib3

urllib3.disable_warnings()
response = requests.get('https://www.12306.cn', verify=False)
print(response.status_code)

"""
Explanation: 不过发现报了一个警告，它提示建议让我们给它指定证书。
~~当然你可以选择忽略警告：~~（依旧报出警告）
End of explanation
"""


import requests

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

"""
Explanation: 不过这不是最好的方式，https协议的请求把证书验证都忽略了还有什么意义？
当然你也可以指定一个本地证书用作客户端证书，可以是单个文件（包含密钥和证书）或一个包含两个文件路径的元组。
python
response = requests.get('https://www.12306.cn', cert=('/path/server.crt', '/path/key'))
print(response.status_code)
上面代码是实例，你需要有crt和key文件，指定它们的路径。注意本地私有证书的key必须要是解密状态，加密状态的key是不支持的。
代理设置
对于某些网站，在测试的时候请求几次，能正常获取内容。但是一旦开始大规模爬取，对于大规模且频繁的请求，网站可能会直接登录验证，验证码，甚至直接把IP给封禁掉。为了防止这种情况的发生，我们就需要设置代理来解决这个问题，需要用到proxies这个参数。
```python
import requests
proxies = {
  "http": "http://10.10.1.10:3128",
  "https": "http://10.10.1.10:1080",
}
requests.get("https://www.taobao.com", proxies=proxies)
```
当然直接运行这个实例可能不行，因为这个代理可能是无效的，请换成自己的有效代理试验一下。
若你的代理需要使用HTTP Basic Auth，可以使用类似 http://user:password@host/ 这样的语法。
```python
import requests
proxies = {
    "http": "http://user:password@10.10.1.10:3128/",
}
requests.get("https://www.taobao.com", proxies=proxies)
```
超时设置
在本机网络状况不好或者服务器网络响应太慢甚至无响应时，我们可能会等待特别久的时间才可能会收到一个响应，甚至到最后收不到响应而报错。为了防止服务器不能及时响应，我们应该设置一个超时时间，即超过了这个时间还没有得到响应，那就报错。
设置超时时间需要用到timeout参数。这个时间的计算是发出request请求到服务器响应response的时间。
下面用一个实例来感受一下：
End of explanation
"""


r = requests.get('https://www.taobao.com', timeout=(5, 30))
print(r.status_code)

"""
Explanation: 通过这样的方式，我们可以将超时时间设置为1秒，如果1秒内没有响应，那就抛出异常。
实际上请求分为两个阶段，即连接和读取。上面的设置timeout值将会用作connect和read二者的timeout。如果要分别指定，就传入一个元组：
End of explanation
"""


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

"""
Explanation: 如果想永久等待，那么你可以直接将timeout设置为None，或者不设置，直接留空，因为默认是None。这样的话，如果服务器还在运行，但是响应特别慢，那就慢慢等吧，它永远不会返回超时错误的。
用法如下：
End of explanation
"""


r = requests.get('https://www.taobao.com')
print(r.status_code)

"""
Explanation: 或直接不加参数：
End of explanation
"""


import requests
from requests.auth import HTTPBasicAuth

r = requests.get('http://120.27.34.24:9001', auth=HTTPBasicAuth('user', '123'))
print(r.status_code)

"""
Explanation: 身份认证
在访问网站时，我们可能会遇到这样的认证页面：

如果遇到这样的网站验证，可以使用requests带的身份认证功能。
End of explanation
"""


import requests

r = requests.get('http://120.27.34.24:9001', auth=('user', '123'))
print(r.status_code)

"""
Explanation: 如果用户名和密码正确的话，认证成功，那么运行结果会返回200，如果认证失败，则会返回401状态码。
当然如果参数都传一个HTTPBasicAuth类，那的确太繁琐了，所以requests提供了一个简单的写法，你可以直接传一个元组，它会默认使用HTTPBasicAuth这个类来认证。
所以上面的代码可以直接简写如下：
End of explanation
"""


