爬虫基本库:requests

requests

    在 urllib 的应用中,有一些不方便的地方,比如处理网页验证和 Cookies 时,需要写 Opener 和 Handler 来处理。为了更加方便地实现这些操作,需要用到更强大的库:requests。

1. 基本用法

1.1. 简单示例

    urllib 库中的 urlopen() 方法以 GET 方式请求网页,而 requests 中相应的就是 get() 方法。

import requests

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

print(type(r))
# <class 'requests.models.Response'>

print(r.status_code)
# 200

# 响应体的类型
print(type(r.text))
# <class 'str'>

# 响应体的内容
print(r.text)

# Cookies的类型
print(r.cookies)
# <RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]>

1.2. GET请求

    HTTP中最常见的请求之一就是GET请求。构建一个最简单的GET请求,链接为 http://httpbin.org/get,该网站会判断请求方式,如果是GET请求的话就返回相应的请求信息。

1.2.1. 基本实例

import requests

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

"""
运行结果:
{
  "args": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Connection": "close", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.20.1"
  }, 
  "origin": "123.160.224.118", 
  "url": "http://httpbin.org/get"
}
"""

    如果要在GET请求中附加额外的信息,一般用字典来存储:

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", 
    "Connection": "close", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.20.1"
  }, 
  "origin": "123.160.224.118", 
  "url": "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', 'Connection': 'close', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.20.1'}, 'origin': '123.160.224.118', 'url': 'http://httpbin.org/get'}
<class 'dict'>
"""

    调用 json() 方法可以将返回结果是JSON格式的字符串转化为字典,但是在返回结果不是JSON格式时会出现解析错误,抛出 json.decoder.JSONDecoderError 异常。

1.2.2. 抓取网页

    以“知乎”的“发现”页面为例:

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)

"""
运行结果:
['\n历史上有那些长期流传的,错误的地理记载,后来是如何被推翻的?\n', 
'\n人生应该活成什么样子,该以什么样的方式活着?\n', 
'\nfgo里面的从者和礼装卡面有哪些不仔细看看不出来的小彩蛋?\n', 
'\n如何看待新浪娱乐的对话特辑《听老高鹿晗说》?\n', 
'\n如何评价《群星(Stellaris)》新DLC《巨型企业(Megacorp)》?\n', 
'\n有哪些句子是真正写到你的心里去了?\n', 
'\n经过此次马思纯回怼风波后,杨紫后续的资源是否更差?\n', 
'\n你有过哪些像段子的亲身经历?\n', 
'\n如何看待网传朱一龙抽烟并随地扔烟头?\n', 
'\n怎么评价防弹续约7年?\n']
"""

    User-Agent 字段信息是浏览器标识信息,不加会被禁止抓取。

1.2.3. 抓取二进制数据

    以 GitHub 的站点图标为例:

import requests

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

结果如下:
r.text 部分结果:�������t������������������������������
r.content 部分结果:
b'\x00\x00\x01\x00\x02\x00\x10\x10\x00\x00\x01\x00
图片是二进制数据,前者在打印时转化为 str 类型,图片直接转化为字符串,会出现乱码

将提取的图片保存下来

import requests

r = requests.get('https://github.com/favicon.ico')
with open('favicon.ico', 'wb') as f:
    f.write(r.content)

    使用 open() 方法,第一个参数是文件名称,第二个参数代表以二进制形式打开,可以向文件中写入二进制数据。运行结束会在文佳家中产生名为 favicon.ico 的图片文件。
    同样,音频和视频文件也可以用这种方法获取。

1.2.4. 添加 headers

    与 urllib.request 一样,requests 也通过 headers 来传递头消息。例如前面的“知乎”例子中,如果不传递 headers,就不能正常请求:

import requests

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

"""
运行结果:
<html>
<head><title>400 Bad Request</title></head>
<body bgcolor="white">
<center><h1>400 Bad Request</h1></center>
<hr><center>openresty</center>
</body>
</html>
"""

1.3. POST请求

    请求 http://httpbin.org/post,该网站判断是POST请求后把相关信息返回,其中 form 部分就是提交的数据,证明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", 
    "Connection": "close", 
    "Content-Length": "18", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.20.1"
  }, 
  "json": null, 
  "origin": "123.160.224.118", 
  "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)
# 输出Cookies
print(type(r.cookies), r.cookies)
# 输出URL
print(type(r.url), r.url)
# 输出请求历史
print(type(r.history), r.history)

<class 'int'> 403
<class 'requests.structures.CaseInsensitiveDict'> {'Date': 'Sat, 24 Nov 2018 09:26:34 GMT', 'Content-Type': 'text/html', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Server': 'Tengine', 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload', 'Content-Encoding': 'gzip', 'X-Via': '1.1 PSzjwzdx11at80:10 (Cdn Cache Server V2.0), 1.1 PShbycdx6yh51:11 (Cdn Cache Server V2.0)'}
<class 'requests.cookies.RequestsCookieJar'> <RequestsCookieJar[]>
<class 'str'> https://www.jianshu.com/
<class 'list'> []
headers 和 cookies 两个属性得到的结果分别是CaseInsensitiveDict 和 RequestsCookieJar 类型

    状态码常用来判断请求是否成功,而 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')

    通过比较返回码和内置的成功的返回码,来保证请求得到了正常响应,输出成功请求的消息,否则程序终止,用 requests.codes.ok 得到的成功的状态码是 200。

一些返回码和相应的查询条件:
信息性状态码、成功状态码、重定向状态吗

客户端错误状态码

服务端错误状态码

    如果想判断结果是不是 404 状态,可以用 requests.codes.not_foend 来对比。

2. 高级用法

2.1. 文件上传

import requests

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

    上传的部分会在一个单独的 files 字段里标识出来。

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)

    可以直接用 Cookies 维持登录状态,登录后将 Headers 中的 Cookie 内容复制下来,替换成自己的 Cookie, 将其设置到 Headers 里,然后发送请求。

2.3. 会话维持

import requests

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

运行结果为:
{
"cookies": {}
}
不能获取到设置的 Cookies。

import requests

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

运行结果为:
{
"cookies": {
"number": "123456789"
}
}
Yes,获取成功!

2.4. SSL 证书验证

    requests 还提供了证书验证的功能。当发送 HTTP 请求的时候,它会检查 SSL 证书,我们可以使用 verify 参数控制是否检查此证书.其实如果不加 verify 参数的话,默认是 True ,会自动验证。

import requests

response = requests.get('https://www.12306.cn')
print(response.status_code)
"""
requests.exceptions.SSLError: ("bad handshske: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)",)
"""

    这里提示一个错误 SSLError ,表示证书验证错误。所以,如果请求一个 HTTPS 站点,但是证书验证错误的页面时,就会报这样的错误,那么如何避免这个错误呢?很简单,把 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)

    代码是演示示例,需要的有 crt 和 key 文件,并且指定它们的路径。注意:本地私有证书的 key 必须是解密状态,加密状态的 key 是不支持的。

2.5. 代理设置

    对于某些网站,在测试的时候请求几次,能正常获取内容。但是一旦开始大规模爬取,对于大规模且频繁的请求,网站可能会弹出验证码,或者跳转到登录认证页面 更甚者可能会直接封禁客户端的 IP,导致一定时间段内无法访问。
    那么,为了防止这种情况发生,需要设置代理来解决这个问题,这就需要用到 proxies 参数,可以用这样的方式设置:

import requests

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

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

# 如果代理需要使用 HTTP Basic Auth ,可以使用类似 http://user:password@host:port 这样的语法来设置代理
# requests.get("https://www.taobao.com", proxies=proxies)

2.6. 超时设置

    在本机网络状况不好或者服务器网络响应太慢甚至无响应时,我们可能会等待特别久的时间才能收到响应,甚至到最后收不到响应而报错。为了防止服务器不能及时响应,应该设置一个超时时间,即超过了这个时间还没有得到响应,那就报错,这需要用到 timeout 参数。这个时间的计算是发出请求到服务器返回响应的时间。示例如下:

import requests

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

    超时时间设置为1秒,1秒内没有响应就抛出异常。实际上请求分为两个阶段:连接(connect)和读取(read),上面设置的 timeout 是两者用时的总和。

其他设置方式:
如果需要分开指定,可以传入一个元组:
r = requests.get('https://www.taobao.com', timeout=(5, 11, 30))
如果想永久等待,timeout 默认值为 None ,可以直接设置为 None:
r = requests.get("https://www.taobao.com", timeout=None)
也可以直接不加参数:
r = requests.get("https://www.taobao.com")

2.7. 身份认证

    requests 自带的身份认证功能:

import requests
from requests.auth import HTTPBasicAuth

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

    如果用户名和密码正确,请求时会自动认证成功,返回 200 状态码;如果认证失败,则返回 401 状态码。
    还有一种简单的写法,不用传 HTTPBasicAuth 类参数,直接传一个元组,会默认使用 HTTPBasicAuth 这个类来认证:

import requests

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

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

pip install requests.oauthlib

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 官方文档 https://requests-oauthlib.readthedocs.org/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容