Python爬虫从入门到精通(2): requests库详解与cookie操作

Python的第三方requests库是基于urllib编写的,但是比urllib库强大,非常适合爬虫的编写,可以帮我们节省很多工作。在Python爬虫从入门到精通(1): 爬虫原理, urllib库介绍及5个适合新手练手的爬虫我们已经介绍了urllib库用法的用法,比如当我们需要向一个url发送get参数或post数据时,我们先要对参数或数据进行urlencode编码,再用urllib.request.Request方法构建一个request_url对象加入参数和数据,最后使用request.urlopen方法打开构建好的request_url。这个操作requests库用一行代码就可以实现了。在打开一个url的时候,就能发送get参数或post数据。同时requests库还简化了对cookie的操作,比如请求头里带上cookie或者维持网站会话都非常方便。事实上自从用上了requests库,我已经不怎么用urllib库了。今天小编我就来详细介绍下requests库的基本用法和如何操作cookie,并用几个具体实例演示下如何利用requests库开发爬虫。

Python爬虫从入门到精通(2): requests库详解与cookie操作

 

requests库的安装及第一个requests爬虫

安装requests库只需要在终端中输入pip install requests。为了确保requests库已经成功安装,你可以用它写个非常简单的爬虫,爬取百度的首页(见下面代码)。如果返回的response的状态码status_code是200,那么你的requests库就安装成功了。你还可以选择打印response.text或response.content查看reponse的具体内容。

>>> import requests
>>> response = requests.get("https://www.baidu.com")
>>> print(response.status_code)
200
>>> print(response.text)
>>> print(response.content)

response.text和response.content的区别在于:

  • response.text是解过码的字符串(比如html代码)。当requests发送请求到一个网页时,requests库会推测目标网页的编码,并对其解码,转为字符串(str)。这种方法比较容易出现乱码。

  • response.content是未解码的二进制格式(bytes),不仅支持文本内容,还适用于二进制文件内容如图片和音乐等。如果需要把文本内容转化为字符串,一般使用response.content.decode('utf-8')方法即可。

requests库支持的请求方法

requests库支持多种HTTP请求方法,然而最常用的只有get和post方法。小编我其它方法也基本不用。

import requests

requests.get("http://xxxx.com/")
requests.post("http://xxxx.com/post", data = {'key':'value'})
requests.put("http://xxxx.com/put", data = {'key':'value'})
requests.delete("http://xxxx.com/delete")
requests.head("http://xxxx.com/get")
requests.options("http://xxxx.com/get")

发送带参数的get请求

使用requests发送带参数的get请求非常简单,在get方法里设置字典格式的params参数可。下例中,我们向百度的搜索页面发送了两个参数,关键词wd和每页显示的条目数pn。如果你此时打印response.url, 你会发现requests方法已经自动完成了url的拼接,实际请求的url已经变成了https://www.baidu.com/s?wd=python&pn=10。

import requests

params = {
    "wd": "python", "pn": 10,
}

response = requests.get('https://www.baidu.com/s', params=params)
print(response.url)
print(response.text)

发送带数据的post请求

使用requests发送带参数的post请求也非常简单,完全不像urllib那样先需要对数据进行urlencode编码才能发送,我们只需要在post方法里设置data参数即可。下面代码模拟了用户的登录。如果响应失败,reponse的raise_for_status方法会打印错误代码. 如果响应成功(状态码为200), raise_for_status会返回一个空值(None.)

import requests


post_data = {'username': 'value1', 'password': 'value2'}

response = requests.post("http://xxx.com/login/", data=post_data)
response.raise_for_status()

post也可以用于上传文件,示例代码如下所示:

>>> import requests
>>> url = 'http://httpbin.org/post'
>>> files = {'file': open('report.xls', 'rb')}
>>> r = requests.post(url, files=files)

 

设置与查看请求头(headers)

很多网站都有反爬机制,如果一个请求不携带请求头headers, 很可能被禁止访问。我们可以按如下方式设置请求头。你也可以通过打印response.headers查看当前请求头。

import requests
headers = {

    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/"
                 "537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"
}

response1 =requests.get("https://www.baidu.com", headers=headers)
response2 =requests.post("https://www.xxxx.com", data={"key": "value"}, 
headers=headers)

print(response1.headers)
print(response1.headers['Content-Type'])
print(response2.text)

设置代理Proxy

有的网站反爬机制会限制单位时间内同一IP的请求次数,这时我们可以通过设置IP proxy代理来应对这个反爬机制。requests里设置proxy也非常简单,如下面代码所示。

import requests

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

requests.get("http://example.org", proxies=proxies)

 

Cookie的获取和添加

有时候我们需要爬取登录后才能访问的页面,这时我们就需要借助cookie来实现模拟登陆和会话维持了。那么服务器是如何知道我们已经登录了呢? 当用户首次发送请求时,服务器端一般会生成并存储一小段信息,包含在response数据里。如果这一小段信息存储在客户端(浏览器或磁盘), 我们称之为cookie。如果这一小段信息存储在服务器端,我们称之为session(会话)。这样当用户下次发送请求到不同页面时,请求自动会带上cookie,这样服务器就知道用户之前已经登录访问过了。

然而并不是访问所有的页面时服务器都会生成自动cookie或session。那么问题来了? 我们如何知道发送首次请求后服务器是否生成了cookie呢? 这时我们可以直接通过打印response.cookies来获取查看cookie内容。

下例中当我们发送请求到中国*网时,我们可以看到返回的reponse里的cookies是个空的RequestsCookieJar[],里面没有任何cookie。然而当我们发送请求到百度时,你可以看到百度已经生成了一个名为BAIDUID的cookie,放在RequestsCookieJar[]里了。你还可以通过打印response.cookies['BAIDUID']来打印BAIDUID的内容。

>>> import requests
>>> response = requests.get(""http://www.gov.cn/2018-09/29/content_5326686.htm")
>>> print(response.cookies)
<RequestsCookieJar[]>

>>> response1 = requests.get("https://fanyi.baidu.com")
>>> print(response1.cookies)
< RequestsCookieJar[ < Cookie BAIDUID = 6BA7A5263775F7D67E0A4B88BF330717:FG = 1'
'for .baidu.com / >, < Cookie locale=zh for.baidu.com / >] >'
>>> print(response1.cookies['BAIDUID'])
6BA7A5263775F7D67E0A4B88BF330717:FG=1

如果你希望在发送请求时添加某些cookie, 最简单的方法就是设置cookies参数。

import requests

headers = {
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/"
                 "537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"
}

cookies = {"cookie_name": "cookie_value", }
response = requests.get("https://www.baidu.com", headers=headers, cookies=cookies)

然而更专业的方式是先实例化一个RequestCookieJar的类,然后把值set进去,最后在get,post方法里面指定cookies参数。代码如下所示:

>>> import requests
>>> from requests.cookies import RequestsCookieJar
>>> cookie_jar = RequestsCookieJar()
>>> cookie_jar.set("BAIDUID", "4EDT7A5263775F7E0A4B&F330724:FG=1", domain="baidu.com")
>>> response = requests.get("https://fanyi.baidu.com/", cookies=cookie_jar)

Session会话的维持

session与cookie不同,因为session一般存储在服务器端。session对象能够帮我们跨请求保持某些参数,也会在同一个session实例发出的所有请求之间保持cookies。为了保持会话的连续,我们最好的办法是先创建一个session对象,用其打开一个url, 而不是直接使用requests.get方法打开一个url。每当我们使用这个session对象重新打开一个url时,请求头都会带上首次产生的cookie,实现了会话的延续。代码如下图所示。

import requests


headers = {
    "content-type": "application/x-www-form-urlencoded;charset=UTF-8",
    "User-Agent" : "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1.6) ",
}

#设置一个会话session对象s
s = requests.session()
resp = s.get('https://www.baidu.com/s?wd=python', headers=headers)
# 打印请求头和cookies
print(resp.request.headers)
print(resp.cookies)

# 利用s再访问一次
resp = s.get('https://www.baidu.com/s?wd=python', headers=headers)

# 请求头已保持首次请求后产生的cookie
print(resp.request.headers)
print(resp.cookies)

输出结果如下所示:

{'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1.6) ', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'content-type': 'applicat

ion/x-www-form-urlencoded;charset=UTF-8'}

<RequestsCookieJar[<Cookie BIDUPSID=761555A95ECB9986056F34E679D317CC for .baidu.com/>, <Cookie PSTM=1538225799 for .baidu.com/>, <Cookie BD_NOT_HTTPS=1 for www.baidu.com/>]>

{'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1.6) ', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive', 'content-type': 'applicat

ion/x-www-form-urlencoded;charset=UTF-8', 'Cookie': 'BIDUPSID=761555A95ECB9986056F34E679D317CC; PSTM=1538225799; BD_NOT_HTTPS=1'}

<RequestsCookieJar[<Cookie BD_NOT_HTTPS=1 for www.baidu.com/>]>

requests实战: 爬取百度搜索前20个搜索页面的标题和链接

我们的总体思路是使用requests发送带参数的get请求,其中wd为关键词,pn为每页显示的搜索条目数(默认为10条每页), 关键词由命令终端输入。我们使用BeautifulSoup解析出百度的跳转链接,然后直接访问那些链接,打印出页面的title和url。

我们新建一个文件baidu_spider.py, 添加如下代码:

# coding:utf-8

import requests
import sys
from bs4 import BeautifulSoup as bs
import re

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) '
                  'Chrome/57.0.2987.133 Safari/537.36',
}


def main(keyword):
    file_name = "{}.txt".format(keyword)
    # 创建空文件
    f = open(file_name, 'w', encoding="utf-8")
    f.close()
    for pn in range(0, 20, 10):
        params = {"wd": keyword, "pn": pn, }
        r = requests.get(url='http://www.baidu.com/s', params=params, headers=headers)
        soup = bs(r.content, "html.parser")
        urls = soup.find_all(name='a', attrs={'href': re.compile(('.'))})
        # 抓取百度搜索结果中的a标签,其中href是包含了百度的跳转地址
        for i in urls:
            if 'www.baidu.com/link?url=' in i['href']:
                # 抓取跳转后的页面
                a = requests.get(url=i['href'], headers=headers)
                soup1 = bs(a.content, "html.parser")
                title = soup1.title.string
                with open(keyword + '.txt', 'r', encoding="utf-8") as f:
                    if a.url not in f.read():
                        f = open(keyword + '.txt', 'a', encoding="utf-8")
                        f.write(title + '\n')
                        f.write(a.url + '\n')
                        f.close()


if __name__ == '__main__':
    if len(sys.argv) != 2:
        print('no keyword')
        print('Please enter keyword')
        sys.exit(-1)
    else:
        main(sys.argv[1])
        print("下载完成。")

当我们在终端输入python baidu_spider.py "django"时,大约等待3分钟,你就可以看到生成的django.txt里包含20条搜索记录(标题与链接), 效果如下图所示:

Python爬虫从入门到精通(2): requests库详解与cookie操作

 

小结

我们详细介绍了如何使用requests库发送带参数的get请求,带数据的post请求,如何设置headers和代理,如何操作cookie,以及如何维持会话。我们还用requests库开发一个非常有用的爬虫,可以用来爬取baidu搜索前20条搜索记录的标题和链接。希望本文对大家有所帮助。

 

大江狗

2018.9.29

Python爬虫从入门到精通(2): requests库详解与cookie操作