Python 与 http请求

橙三吉。 提交于 2020-02-15 07:10:40

Python 与 http请求

本文由 CDFMLR 原创,收录于个人主页 https://clownote.github.io,并同时发布到 CSDN。本人不保证 CSDN 排版正确,敬请访问 clownote 以获得良好的阅读体验。

HTTP 基本原理

URI & URL

简写 全名 中文 说明
URI Uniform Resource Identifier 统一资源标志符 对应着一个资源
URL Uniform Resource Locator 统一资源定位符 说明从哪里可以找到一个资源
URN Universal Resource Name 统一资源名称 说明一个资源的唯一名字

URI 包含着 URL 和 URN。

通常,我们使用的网址,就是一个URI,同时也是一个URL。

超文本

超文本是用超链接的方法,将各种不同空间的文字信息组织在一起的网状文本。

我们平时看到的网页的源代码使用 HTML 写出的,HTML 就是一种超文本。

HTTP & HTTPS

我们用URL访问资源需要指定使用的协议类型,协议就是URL地址一开始的那个字段。

协议 全称 说明
HTTP Hyper Text Transfer Protocol(超文本传输协议) 用于从网络传输超文本数据到本地浏览器的传送协议,它能保证高效而准确地传送超文本文档
HTTPS Hyper Text Transfer Protocol over Secure Socket Layer(在SSL上的HTTP) 以安全为目标的 HTTP 通道,是 HTTP 的安全版

HTTPS 相较于 HTTP 有以下优点:

  • 建立一个信息安全通道来保证数据传输的安全。
  • 确认网站的真实性,凡是使用了 HTTPS 的网站,都可以通过点击浏览器地址栏的锁头标志来
    查看网站认证之后的真实信息,也可以通过 CA 机构颁发的安全签章来查询。

HTTP 请求过程

我们在浏览器中输入网址,便显示出一个网页的过程实际上是:

  1. 浏览器向网站的服务器发送了一个请求;
  2. 服务器接收到这个请求后进行解析和处理;
  3. 服务器返回对应的响应;
  4. 浏览器对收到的响应解析;
  5. 浏览器将解析的结果显示出来,即最后我们看到的网页。

我们可以在 Chrome浏览器 的开发者工具的“Network”页面看到的一个条目就是一次 发送请求和接收响应 的过程。

Network的列 说明
Name 请求的名称,一般会将 URL 的最后一部分内容当作名称 。
Status 响应的状态码,这里显示为 200, 代表响应是正常的 。 通过状态码,我们可以判断发送了请求之后是杏得到了正常的响应 。
Type 请求的文梢类型 。 这里为 document,代表我们这次请求的是一个 HTML文档, 内容就是一些 HTML代码。
Initiator 请求源 。 用来标记请求是由哪个对象或进程发起的 。
Size 从服务器下载的文件和请求的资源大小 。 如果是从缓存中取得的资源,则该列会显示from cache。
Time 发起请求到获取响应所用的总时间 。
Waterfall 网络请求的可视化瀑布流 。

点击条目,我们将看到更加详细的请求、响应信息。

请求与响应

请求(Request)

请求,是由 客户端服务端 发出的。

请求分为4部分:

  • 请求方法(Request Method):常见的请求方法分为两种 GET 和 POST:

    • GET 请求中的参数包含在 URL 里面,数据可以在 URL 中看到(可能暴露敏感信息)。GET请求提交的数据最多只有 1024 字节。
    • POST 请求中的 URL 不包含数据,数据都是通过表单形式传输的,会包含在请求体中。POST方式没有大小限制(可以传文件)。
    • 我们输入一个网址,这是一个 GET 请求。
    • 其实还有一些其他的请求方法,可以参考 HTTP请求方法
  • 请求地址(Request URL):统一资惊定位符 URL,唯一确定我们想请求的资源。

  • 请求头(Request Headers):用来说明服务器要使用的附加信息,比较重要的信息有 Cookie、 Referer、 User-Agent:

    • Accept: 请求报头域,用于指定客户端可 接受哪些类型的信息
    • Accept-Language: 指定客户端可接受的 语言类型
    • Acceot-Encoding: 指定客户端可接受的 内容编码
    • Host: 指定 请求资源的主机 IP 和端口号,其内容为 请求 URL 的原始服务器或网关的位置
    • Cookies: 网站为了辨别用户进行会话跟踪而 存储在用户本地的数据。它的主要功能是 维持当前访问会话,如检查用户登录。
    • Referer: 此内容用来标识这个请求是从哪个页面发过来的,服务器可以拿到这一信息并做相 应的处理,如做来源统计、防盗链处理等 。
    • User-Agent: 简称 UA,它可以使服务器识别客户使用的操作系统及版本、浏览器及版本等信息。 在做爬虫时加上此信息,可以伪装为浏览器; 如果不加,很可能会被识别州为爬虫 。
    • Content-Type: 也叫互联网媒体类型( Internet Media Type )或者 MIME类型,在 HTTP协议消息头中,它用来表示具体请求中的媒体类型信息。请求头是请求的重要组成部分,在写爬虫时,大部分情况下都需要设定请求头。
  • 请求体(Request Body):

    • POST 的请求体是表单数据;
    • GET 的请求体为空;

响应(Response)

响应,由 服务端 返回给 客户端

  • 响应状态码(Response Status Code):表示服务器的响应状态
    • HTTP状态码分类,具体HTTP状态码列表,见HTTP状态码
分类 分类描述
1** 信息,服务器收到请求,需要请求者继续执行操作
2** 成功,操作被成功接收并处理
3** 重定向,需要进一步的操作以完成请求
4** 客户端错误,请求包含语法错误或无法完成请求
5** 服务器错误,服务器在处理请求的过程中发生了错误
  • 响应头(Response Headers): 响应头包含了服务器对请求的应答信息

    • Date: 标识 响应产生的时间
    • Last-Modified: 指定 资源的最后修改时间
    • Content-Encoding: 指定响应 内容的编码
    • Server: 包含 服务器的信息,比如名称、版本号等
    • Content-Type: 文档类型,指定 返回的数据类型 是什么(和 Request 的一样)
    • Set-Cookie: 设置 Cookies。告诉浏览器需要将此内容放在 Cookies 中, 下次请求携带 Cookies 请求
    • Expires: 指定响应的过期时间,可以使代理服务器或浏览器将加载的内容更新到缓存中。如果再次访问时,就可以直接从缓存中加载,降低服务器负载,缩短加载时间。
  • 响应体(Response Body)响应的正文数据都在响应体中

    • 请求网页时,它的响应体就 是网页的 HTML代码
    • 请求一张图片时, 它的响应体就是图片的二进制数据
    • 我们做爬虫请求网页后,要解析的内容就是响应体

会话 和 Cookies

HTTP 本身是无状态的,这是指 HTTP 协议对事物的处理是没有记忆能力的。
换言之,服务器 不知道 客户端是说明状态。

然而,我们日常接触的很多网页都可以实现用户登录等需要服务器记住客户端的操作。
实现这一类操作,就会用到 会话Cookies

会话

会话,是一系列有始有终的动作/消息。例如,从拿起电话拨号到挂断 电话这中间的一系列过程可以称为一个会话。

在 Web 中,会话对象 用来存储特定用户会话所需的属性及配置信息。

当用户在应用程序的 Web 页之间跳转时,存储在会话对象中的变量将不会丢失(如用户的登录状态);
当用户请求来自应用程序的 Web 页时如果该用户还没有会话,则服务器将自动创建一个会话对象 ;
当会话过期或被放弃后,服务器将终止该会话;

Cookies

Cookies指某些网站为了辨别用户身份、进行会话跟踪而存储在用户本地终端上的数据。

服务器可以通过 Cookies 得知 那个客户端对应着那个会话、那个用户。
客户端Cookies服务端会话 的协作下,我们便可以实现会话的维持。

Cookie有如下几个属性 :

  • Name: 该 Cookie 的名称。一旦创建,该名称便不可更改。

  • Value: 该 Cookie 的值。

    • 如果值为 Unicode 字符,需要为字符编码。
    • 如果值为二进制数据,则需要使用 BASE64 编码。
  • Domain: 可以访问该 Cookie的域名。例如,如果设置为 .zhihu.com,则所有以 zhihu.com 结尾的域名都可以访问该 Cookie。

  • MaxAge: 该 Cookie失效的时间,单位为秒,也常和 Expires 一起使用,通过它可以计算有效时间。

    • MaxAge 如果为正数,则该Cookie在MaxAge秒之后失效。
    • 如果为负数,则关闭 浏览器时 Cookie 即失效,浏览器也不会以任何形式保存该 Cookie。
  • Path : 该 Cookie 的使用路径。
    如果设置为 /path/,则只有路径为 /path/ 的页面可以访问该 Cookie。
    如果设置为 / 则本域名下的所有页面都可以访问该 Cookie。

  • Size字段: 此 Cookie 的大小。

  • HTTP 字段: Cookie 的 httponly 属性。

    • 若此属性为 true,则只有在 HTTP 头中会带有此 Cookie的信息,而不能通过 document.cookie来访问此 Cookie。
  • Secure: 该 Cookie 是否仅被使用安全协议传输。安全协议有 HTTPS 和 SSL 等,在网络上传输数据之前先将数据加密。默认为 false。

代理

在我们正常请求一个网站时,我们直接把请求发给了Web服务器,Web服务器又直接把响应回传给我们。

而如果我们使用 代理服务,就等于是多了一个中间人,即代理服务器,这个过程变成了:
我们直接把请求发给 代理服务器,代理服务器 把响应回传给 代理服务器,代理服务器再把这个消息传给我们。

通过代理,我们就可以伪装IP,防制在爬虫时被查出来封IP或事需要提供验证码,影响爬虫运行。

⚠️【注意】并不是所有的 代理 都可以让我们隐藏 IP!
有的 代理服务 会在向 web服务器 的请求 时,不会原封不动的转发我们的消息,而是在其中添加上我们的真实IP等信息,这时就隐藏不了了。

网页基础

网页分为三大部分 ———— HTML(框架),CSS(样式),JavaScript(逻辑)。

HTML, 超文本标记语言

详见 HTML 教程

CSS, 层叠样式表

详见 CSS 教程

JavaScript, 脚本语言

详见 JavaScript 教程

网页结构

在 HTML 中,所有标签定义的内容都是节点,它们构成了一个 HTML DOM 树。(DOM:文档对象模型)
通过 HTML DOM,树中的所有节点均可通过 JavaScript访问,所有 HTML 节点元素均可被修改,也可以被创建或删除 。

详见 HTML DOM 教程

选择器

在 css 中,我们使用 css 选择器来定位节点。

详见 CSS 选择器表

用 Python 发起 HTTP 请求

urllib

urllib 是 Python 内置的 HTTP 请求库。

urllib 有 request,error,parse,robotparser 四个模块。

urllib.request 发送请求

urlopen(): 发送请求

# 获取 python 官网 HTML 源码
import urllib.request

response = urllib.request.urlopen('https://www.python.org')

print(response.read().decode('utf-8'))  # 查看response内容
print(type(response))                   # 查看response类型
print(response.status)                  # 查看response状态码
print(response.getheaders())            # 查看响应头
print(response.getheader('Server'))     # 查看特定的响应头项

urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)

urlopen()data 参数: 用 POST 发送一些数据
import urllib.parse
import urllib.request

data = bytes(
    urllib.parse.urlencode({'Hello': 'World'}),
    encoding='utf-8'
    )
response = urllib.request.urlopen('http://httpbin.org/post', data=data)
print(response.read().decode('utf-8'))
urlopen()timeout 参数: 如果请求超出了设置的这个时间,还没有得到响应,就会抛出异常。

设置timeout=Sec,Sce 为超时的秒数(可以为小数)。

import socket
import urllib.request

try:
    response = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1)
except Exception as e:
    print(e)
        
print(response.read().decode('utf-8'))

Request 类构建 Headers

利用更强大的 Request类来构建一个完整的请求。

import urllib.request

request = urllib.request.Request('http://python.org')
response = urllib.request.urlopen(request)
print(response.read().decode('utf-8'))
Request类 的构建参数

class urllib.request.Request(ur1, data=None, headers={},origin_req_host=None, unverifiable=False, method=None)

from urllib import request, parse

url = 'http://httpbin.org/post'
headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.3 Safari/605.1.15',
        'Host': 'httpbin.org'
        }
dic = {
        'name': 'CDFMLR'
        }
data = bytes(parse.urlencode(dic), encoding='utf-8')

req = request.Request(url=url, data=data, headers=headers, method='POST')
response = request.urlopen(req)
print(response.read().decode('utf-8'))

高级用法

Handler

Handler 包含 各种处理器,有专门处理登录验证的,有处理 Cookies 的,有处理代理设置的…

urllib.request 模块里的 BaseHandler 类是所有其他 Handler 的父类,它提供最基本的方法。

其他的 Handler 详见 Handler

Opener

之前用过 urlopen()方法,实际上就是 urllib 提供的一个 Opener。

urlopen() 相当于类库封装好了极其常用的请求方法,利用它可以完成基本的请求,但如果需要实现更高级的功能,就需要深入一层进行配置,使用更底层的实例来完成操作,就用到了 Opener。

Opener 可以使用 open()方法,返回的类型和 urlopen()如出一辙。

我们需要利用 Handler来构建 Opener。


(以下是几个 Hander & Opener 的应用)

处理 HTTP 基本认证

HTTP基本认证

有一种 web 登录方式不是通过 cookie,而是把 用户名:密码base64 编码之后的字符串放在 request 中的 header Authorization 中发送给服务端。
当打开网页提示需要输入账号和密码时,假设密码/账号错误,服务器会返回401错误。

这种网站在打开时就会弹出提示框,直接提示输入用户名和密码,验证成功后才能查看页面,我们现在来处理这种页面。

要请求这种页面,可以借用 HTTPBasicAuthHandler 完成:

在我们打算自己实现一个使用 http基本认证 的示例网站时,我们发现了 一个很好的例子,所以,我们现在来爬取它。

from urllib.request import HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, build_opener
from urllib.error import URLError

username = 'username'
password = 'password'
url = 'http://pythonscraping.com/pages/auth/login.php'

p = HTTPPasswordMgrWithDefaultRealm()
p.add_password(None, url, username, password)
auth_handler = HTTPBasicAuthHandler(p)
opener = build_opener(auth_handler)

try:
    result = opener.open(url)
    html = result.read().decode('utf-8', errors='ignore')
    print(html)
except URLError as e:
    print(e.reason)

在此,我们也想附上这种 http基本认证 的 php 实现,以供实践:

<?php
  if (!isset($_SERVER['PHP_AUTH_USER'])) {
    header('WWW-Authenticate: Basic realm="My Realm"');
    header('HTTP/1.0 401 Unauthorized');
    echo 'Text to send if user hits Cancel button';
    exit;
  } else {
    echo "<p>Hello {$_SERVER['PHP_AUTH_USER']}.</p>";
    echo "<p>You entered {$_SERVER['PHP_AUTH_PW']} as your password.</p>";
  }
?>
使用 代理

尝试找一个免费的代理服务器,然后用这个服务器来代理:

寻找可用代理的时候可以尝试在浏览器中访问代理服务器的 IP:Port,可用的是会出现结果的:)

from urllib.error import URLError
from urllib.request import ProxyHandler, build_opener

proxy_handler = ProxyHandler({
   'http': 'http://171.41.80.197:9999',        # 用 ‘<http||https>://代理服务器_IP:代理服务器_端口’
   'https': 'https://171.41.80.197:9999'
   })

opener = build_opener(proxy_handler)

try:
   response = opener.open('http://www.baidu.com')  # 要通过代理爬取的网页
   print(response.read().decode('utf-8', errors='ignore'))
   print(response.getheaders())
except URLError as e:
   print(e.reason)
处理 Cookies
获取 Cookies

获取 http://www.baidu.com 的 Cookies,依次打印出 name = value:

import http.cookiejar
import urllib.request

cookie = http.cookiejar.CookieJar()
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)

response = opener.open('http://www.baidu.com')

for item in cookie:
    print(item.name, '=', item.value)

还是获取 http://www.baidu.com 的 Cookies,把它们保存到文件中(正如实际上 Cookies 的保存方式那样):

import http.cookiejar
import urllib.request

filename = 'cookies_from_baidu.txt'

cookie = http.cookiejar.MozillaCookieJar(filename)      # 用 MozillaCookieJar 类把 Cookies 储存为 Mozilla 型浏览器的格式。
# cookie = http.cookiejar.LWPCookieJar(filename)         # 用这行代替上一行,可以把 Cookies 储存为 libwww-perl(LWP) 的格式。

handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)

response = opener.open('http://www.baidu.com')

cookie.save(ignore_discard=True, ignore_expires=True)

MozillaCookieJarLWPCookieJar 都可以读取保存 Cookies。只是它们的保存格式有别。

取用 Cookies

取用我们刚才保存下来的 Cookies:

import http.cookiejar
import urllib.request

cookie = http.cookiejar.MozillaCookieJar()
cookie.load('cookies_from_baidu.txt', ignore_discard=True, ignore_expires=True)

handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)

response = opener.open('http://www.baidu.com')
print(response.read().decode('utf-8', errors='ignore'))

urllib.error 处理异常

URLError:

URLError 类来自 Urllib 库的 error 模块,它继承自 OSError 类,是 error 异常模块的基类,由 request 模块生的异常都可以通过捕获这个类来处理。

它具有一个属性 reason,即返回错误的原因:

from urllib import request, error
try:
    response = request.urlopen('http://www.google.com')
except error.URLError as e:
    print(e.reason)

通过如上操作,我们就可以避免程序异常终止,同时异常得到了有效处理。

HTTPError

HTTPErrorURLError 的子类。用来处理 HTTP 请求错误,比如认证请求失败等等。

HTTPError 的属性:

  • code,返回 HTTP Status Code,即状态码,比如 404 网页不存在,500 服务器内部错误等等。
  • reason,同父类一样,返回错误的原因。
  • headers,返回 Request Headers。
from urllib import request,error
try:
    response = request.urlopen('http://cr0123.gz01.bdysite.com/no_such_a_page.html')
except error.HTTPError as e:
    print('\n[Reason]', e.reason,
        '\n[code]', e.code,
        '\n[headers]', e.headers
        )

综合使用:

在实际的使用过程中,我们可以先选择捕获子类的错误,再去捕获父类的错误,从而使处理完整,层次清晰。

from urllib import request, error

try:
    response = request.urlopen('http://www.google.com')
except error.HTTPError as e:
    print('HTTPError:')
    print('[Reason]', e.reason, '[code]', e.code, '[headers]', e.headers, sep='\n')
except error.URLError as e:
    print('URLError:')
    print('[Error Reason]\n', e.reason)
except Exception as e:
    print('Exception:')
    print(e)
else:
    print('Request Successfully')

urllib.parse 解析链接

urllib.parse 模块定义了处理 URL 的标准接口,例如实现 URL 各部分的抽取,合并以及链接转换。

quote() 将内容转化为 URL 编码的格式

可以用来把中文字符转化为 URL 编码:

>>> from urllib.parse import quote
>>> quote('中文')
'%E4%B8%AD%E6%96%87'
>>> url = 'https://www.baidu.com/s?wd=' + quote('URL 编码')    # 合成的网址即可百度搜索‘URL 编码’
>>> print(url)
https://www.baidu.com/s?wd=URL%20%E7%BC%96%E7%A0%81

要解码,可是使用对应的 unquote()

urlparse() URL的识别和分段

from urllib.parse import urlparse

result = urlparse('http://www.baidu.com/index.html;user?id=5#comment')
print(type(result))
print(result)
print(result.netloc)

'''(results)
<class 'urllib.parse.ParseResult'>
ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')
www.baidu.com
'''

ParseResult 包含:

  • scheme ———— 协议
  • netloc ———— 主机名
  • path ———— 路径
  • params ———— ;XXX
  • query ———— ?XXX
  • fragment ———— #XXX

urlunparse() 合成URL

依次传入六个部分:(scheme, netloc, path, params, query, fragment),
将合成一个: scheme://netloc/path;params?query#fragment

>>> from urllib.parse import urlunparse
>>> data = ['https', 'www.baidu.com', '/index.php', 'user', 'id=5', '123']
>>> urlunparse(data)
'https://www.baidu.com/index.php;user?id=5#123'

urlencode() 字典类型 转化为 GET请求参数

from urllib.parse import urlencode

params = {
    'name': 'germey',
    'age': 22
}
base_url = 'http://www.baidu.com?'
url = base_url + urlencode(params)
print(url)

'''(result)
http://www.baidu.com?name=germey&age=22
'''

parse_qs() GET请求参数 转化为 字典类型

from urllib.parse import parse_qs

query = 'name=germey&age=22'
print(parse_qs(query))

'''(result)
{'name': ['germey'], 'age': ['22']}
'''

另有一个 parse_qsl()parse_qs() 类似,只是得到的结果是 元组

其他

这个库中还有一些 urljoin 等方式,可以在 这里 查看。

urllib.robotparser 分析Robots协议

Robots协议

网络爬虫排除标准(Robots Exclusion Protocol),用来告诉爬虫和搜索引擎哪些页面可以抓取,哪些不可以抓取。
它通常是一个叫做 robots.txt 的文本文件,放在网站的根目录下。

Robots协议的写法:

User-agent: *               这里的*代表的所有的搜索引擎种类,*是一个通配符
Disallow: /admin/           这里定义是禁止爬寻admin目录下面的目录
Disallow: /require/         这里定义是禁止爬寻require目录下面的目录
Disallow: /ABC/             这里定义是禁止爬寻ABC目录下面的目录
Disallow: /cgi-bin/*.htm    禁止访问/cgi-bin/目录下的所有以".htm"为后缀的URL(包含子目录)。
Disallow: /*?*              禁止访问网站中所有包含问号 (?) 的网址
Disallow: /.jpg$            禁止抓取网页所有的.jpg格式的图片
Disallow:/ab/adc.html       禁止爬取ab文件夹下面的adc.html文件。
Allow: /cgi-bin/           这里定义是允许爬寻cgi-bin目录下面的目录
Allow: /tmp                 这里定义是允许爬寻tmp的整个目录
Allow: .htm$                仅允许访问以".htm"为后缀的URL。
Allow: .gif$                允许抓取网页和gif格式图片
Sitemap:                    网站地图 告诉爬虫这个页面是网站地图

使用例子:

禁止所有搜索引擎访问网站的任何部分
User-agent: *
Disallow: /

允许所有的robot访问
User-agent: *
Allow: /

允许某个搜索引擎的访问
User-agent: Baiduspider
allow:/

解析 Robots.txt ---- urllib.robotparser.RobotFileParser

robotparser 模块提供了一个 RobotFileParser 类。它可以根据某网站的 robots.txt 文件来判断一个爬取爬虫是否有权限来爬取这个网页。

RobotFileParser 实例的构造
urllib.robotparser.RobotFileParser(url='')      # 传入 robots.txt 的链接
RobotFileParser 常用方法
  • set_url(url) 用来设置 robots.txt 的链接
  • read() 读取 robots.txt 文件并进行分析。
    ⚠️【注意】如果不 read(),所有的判断函数都会返回 False!
  • parse() 用来解析 robots.txt 文件,传入的参数是 robots.txt 某些行的内容,它会按照 robots.txt 的语法规则来分析这些内容。
  • can_fetch() 传入两个参数,第一个是 User-agent,第二个是要抓取的 URL,返回的内容是该搜索引擎是否可以抓取这个 URL,返回结果是 True 或 False。
  • mtime(),返回的是上次抓取和分析 robots.txt 的时间,这个对于长时间分析和抓取的搜索爬虫是很有必要的,你可能需要定期检查来抓取最新的 robots.txt。
  • modified(),同样的对于长时间分析和抓取的搜索爬虫很有帮助,将当前时间设置为上次抓取和分析 robots.txt 的时间。

requests

相较于 Python 内置的 Urllib,第三方库 Requests 为我们提供了一个更加优雅的解决方案。
得益于 requests 的强大,我们可以在解决 Cookies、登录验证、代理设置 等问题时更加方便快捷,而无需再琢磨 Urllib 的 Opener、Handler。

requests.get() —— GET请求

使用 requests.get(url, *params={}, headers={}, timeout=None) 可以完成 GET 请求,返回 response 的各种内容。

import requests

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

print('status_code: ', type(r.status_code), r.status_code)
print('headers: ', type(r.headers), r.headers)
print('cookies: ', type(r.cookies), r.cookies)
print('url: ', type(r.url), r.url)
print('history: ', type(r.history), r.history)

status_code 属性得到状态码, headers 属性得到 Response Headers,cookies 属性得到 Cookies,url 属性得到 URL,history 属性得到请求历史。

使用 params={} ,就等于在url中添加了 '?xxx&xxx' 的信息:

import requests

data = {
    'name': 'germey',
    'age': 22
}
r = requests.get("http://httpbin.org/get", params=data)    # 相当于 GET请求 ‘http://httpbin.org/get?name=germey&age=22’
print(r.text)

在上例中,我们可以看到返回的 r.text 实际上是 JSON 的字符串,requests 提供了 一个 Response.json() 可以直接把 JSON 解析成 dict:

#  承接上一代码块
d = r.json()
print(type(d), d, sep='\n')

若返回结果不是 Json 格式,调用 Response.json(),会抛出 https://www.zhihu.com/explorejson.decoder.JSONDecodeError 的异常。

我们还可以在调用 requests.get() 的时候传入一个 dict 作为 headers:

import requests

headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.3 Safari/605.1.15',
        }

response = requests.get('http://httpbin.org/get', headers=headers)
print(response.text)

我们还可以设置超时,有三种方法:

  • r = requests.get('https://www.taobao.com', timeout=None),connect 和 read 二者都是 永久等待,这是默认设置
  • r = requests.get('https://www.taobao.com', timeout=1),connect 和 read 二者的 timeout 总和设置为 1 秒
  • r = requests.get('https://www.taobao.com', timeout=(5, 11)),connect 5秒,read 11秒

获取二进制数据

当我们获取得二进制的数据时,Response.text 显然是不好用了,这时我们可以通过 Response.content 取得二进制数据。

import requests

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

print(r.text)       # 输出乱码
print(r.content)    # 输出十六进制码的bytes(b'\x..\x........')

with open('favicon.ico', 'wb') as f:        # 保存到文件
    f.write(r.content)

抓取网页

我们来做一个简单的爬虫实践————爬取 知乎-发现 里的问题。

import requests
import re
# 知乎-发现的url
url = 'https://www.zhihu.com/explore'
# 匹配问题的正则表达式
pattern = re.compile('explore-feed.*?question_link.*?>(.*?)</', re.S)       # *?表示惰性匹配

headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.3 Safari/605.1.15',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
        }

try:
    response = requests.get(url, headers=headers)
    questions = re.findall(pattern, response.text)
    for i in questions:
        print(i)
except Exception as e:
    print(e)

requests.post() —— POST请求

requests.post(url, data={}) 我们就可以 POST 请求,这和使用 requests.get() 非常类似。

import requests
data = {'Data': 'Hello, world!'}

r = requests.post('http://httpbin.org/post', data=data)
print(r.text)

我们同样可以从 Response 中获取各种数据,例如:status_code 属性得到状态码, headers 属性得到 Response Headers,cookies 属性得到 Cookies,url 属性得到 URL,history 属性得到请求历史。

文件上传

我们可以使用 POST请求 来完成文件的上传。

import requests

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

print(r.text)

Cookies

获取Cookies

import requests

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

调用Cookies

我们先登录一个网站,然后从记录下Cookies,在另一次登录中,把这个Cookies传上去,就可以维持登录。

例如,我们现在用浏览器登录「百度」,然后打开一次百度首页,我们检查 www.baidu.com 的 Request Headers,我们会发现一项 Cookies,我们把这个值复制下来,让爬虫伪造 headers,带上这个 Cookies,我们就可以模拟登录了:)

import requests

url = 'https://www.baidu.com'
headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.3 Safari/605.1.15',
        'Cookie': 'BDORZ******3254',
        'Connection': 'keep-alive',
        'Host': 'www.baidu.com'
        }

r = requests.get(url, headers=headers)

print(r.text)

从结果中,我们可以看到自己的名字,说明成功模仿登录!

也可以这样:

import requests

cookies = 'q_c1=316******3b0'
jar = requests.cookies.RequestsCookieJar()
headers = {
    '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'
}
for cookie in cookies.split(';'):
    key, value = cookie.split('=', 1)
    jar.set(key, value)
    
r = requests.get('http://www.zhihu.com', cookies=jar, headers=headers)
print(r.text)

会话维持

在 Requests 中,我们如果直接利用 get() 或 post() 等方法的确可以做到模拟网页的请求。
但是这实际上是相当于不同的会话,即不同的 Session,也就是说每请求一次都相当于用不同浏览器打开,会话不会维持。
但我们常需要模拟在一个浏览器中打开同一站点的不同页面,这就需要我们进行会话维持。

不使用会话维持
import requests

requests.get('http://httpbin.org/cookies/set/number/123456789')     # 一次会话
r = requests.get('http://httpbin.org/cookies')      # 另一次会话
print(r.text)

'''(result)
{
  "cookies": {}
}
'''

我们得到的结果是空的,说明两次get不是同一会话了。

使用会话维持

使用会话维持最直接的办法就是我们可以获取上一次的 Cookies,下一次请求时把它传上去,但这样做过于繁琐,requests 为我们提供了更加简洁、优雅的解决方案 —— requests.Session

import requests

s = requests.Session()      # 开启一个新会话 s
s.get('http://httpbin.org/cookies/set/number/123456789')    # 会话 s
r = s.get('http://httpbin.org/cookies')     # 同样还是会话 s
print(r.text)

'''(result)
{
  "cookies": {
    "number": "123456789"
  }
}
'''

这一次,我们就维持了会话了。

SSL 证书验证

Requests 提供了证书验证的功能,当发送 HTTP 请求的时候,它会检查 SSL 证书(针对https),我们可以使用 verify 参数来控制是否检查此证书,缺省值是 True,会自动验证。
如果我们不需要检查证书,可以让 verify=False

import requests

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

指定本地证书用作客户端证书:

客户端证书可以是单个文件(包含密钥和证书)或一个包含两个文件路径的元组。

import requests

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

⚠️【注意】本地私有证书的 key 必须要是解密状态,加密状态的 key 是不支持的。

代理设置

找一个免费的代理服务器,然后用这个服务器来代理。

import requests

headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.3 Safari/605.1.15',
        'Accept-Language': 'zh-cn',
        'Accept-Encoding': 'br, gzip, deflate'
        }

proxies = {
        'http': 'http://110.52.235.57:9999',
        'https': 'https://110.52.235.57:9999'
        }

r = requests.get('http://www.taobao.com', proxies=proxies, headers=headers)

print(r.text)

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

import requests

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

Requests 还支持 SOCKS 协议的代理:

$ pip3 install "requests[socks]"
import requests

proxies = {
    'http': 'socks5://user:password@host:port',
    'https': 'socks5://user:password@host:port'
}
requests.get('https://www.taobao.com', proxies=proxies)

HTTP 基本认证

import requests
from requests.auth import HTTPBasicAuth

r = requests.get('http://pythonscraping.com/pages/auth/login.php', auth=HTTPBasicAuth('username', 'password'))
print(r.status_code)

还可以更简单:

import requests

r = requests.get('http://pythonscraping.com/pages/auth/login.php', auth=('username', 'password'))
print(r.status_code)

Requests 还支持其他的认证,如 OAuth。关于这方面,可以查看 文档

Prepared Request

类似于 Urllib 中的 Request ,我们可以在 Requests 中使用 Prepared Request 来表示一个请求:

from requests import Request, Session

url = 'http://httpbin.org/post'
data = {
    'name': 'germey'
}
headers = {
    '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'
}
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 对象,我们就可以将一个个请求当做一个独立的对象来看待,这可以方便调度。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!