Python 环境加载中…

Session 与 Cookie:登录以后再抓

3 道练习题·预计 50 分钟·做对一题解锁下一段

完整通读本章 →

教学 01 / 08· 已读

前言

各位童鞋,到目前为止,咱们每次调用 requests.get() ,都是一次全新的请求,网站根本不知道你上一次来过没有。可平时上网的时候,明明登录一次就够了,后面点开的每个网页,网站都还记得你是谁。这是怎么做到的呢?

靠的是 Cookie。它的道理并不复杂:网站发给你一小段文字,浏览器把它存起来,以后每次访问这个网站,都带上它。网站一看,就认出你来了。

所以这一章,先弄清楚 Cookie 是怎么回事,再用 requests.Session 在自己电脑上登录一个练习网站。登录成功以后,还要想清楚一件更重要的事:登录了,能抓什么,不能抓什么?

这一章的规矩,会比前面几章都多。为什么呢?因为登录以后看到的东西,往往就不再是公开的了。技术上做得到,不等于就应该去做。

学这一章之前,需要先学完 爬虫课第 10 章 ,在自己电脑上配好环境,装好 requests 和 beautifulsoup4。

目录

这一章会学到:

  • 一、网站怎么记住你——Cookie
  • 二、让 requests 也记住——requests.Session
  • 三、登录一个练习网站——表单、隐藏字段和 POST
  • 四、登录以后,能抓什么
  • 五、常见坑
  • 六、自己查一次文档——用 http.cookies 读懂一条 Cookie
  • 七、小结与回看

学完这一章,你能看懂 Cookie,用 Session 保持登录状态,并清楚登录以后的抓取边界在哪儿。

教学 02 / 08· 已读

一、网站怎么记住你

先回到前言里的那个问题:网站是怎么记住你的?

要知道,HTTP 本身是「健忘」的:每一次请求都是独立的,网站不会因为你刚才来过,就认出你。所以要想记住你,网站得自己想个办法。

这个办法就是 Cookie。整个过程分三步:

  1. 你第一次访问,网站在响应头里加一行 Set-Cookie: session=一串文字 ,意思是「请把这个记下来」。
  2. 浏览器把它存起来。
  3. 之后你再访问这个网站,浏览器会自动在请求头里加一行 Cookie: session=一串文字 。网站一看,就知道是你。

明白了这三步,登录也就好理解了:所谓登录,其实就是网站在 Cookie 里记下了「这个人已经登录了」。

道理说完了,来看一个真实的例子。在自己电脑上运行下面的代码,看看 quotes.toscrape.com 的登录页,都发回了什么 Cookie:

Python· 在本地运行
import requests

response = requests.get('https://quotes.toscrape.com/login')
print(list(response.cookies.keys()))
print(response.headers['Set-Cookie'].split(';')[1:])

在自己电脑上运行,输出是:

文本
['session']
[' HttpOnly', ' Path=/']

第一行说明,网站发来了一个叫 session 的 Cookie。Set-Cookie 里除了名字和值,后面还跟着几个属性,第二行打印的就是它们:

  • Path=/ :这个网站的所有网址,都要带上这个 Cookie。
  • HttpOnly :网页里的 JavaScript 读不到它,它只在请求里发来发去,这样比较安全。

那 Cookie 的值呢?它是一长串看不懂的文字。这是网站自己的事,咱们不用去解读它,原样带回去就行。

问题来了:requests 会自动把它带回去吗?

教学 03 / 08· 已读

二、让 requests 也记住

答案是:单独用 requests.get() ,不会。每次调用 requests.get() ,都像是新开了一个浏览器窗口,上一次拿到的 Cookie,不会带到下一次去。

那怎么让 requests 像浏览器一样,记住 Cookie 呢?要用 Session(会话):

Python· 在本地运行
import requests

session = requests.Session()
session.get('https://quotes.toscrape.com/login')
print(list(session.cookies.keys()))

在自己电脑上运行,输出是:

文本
['session']

这回 session.cookies 里有了 session 这个 Cookie,说明它记住了。

requests.Session() 会创建一个会话对象,用法和 requests 几乎一样,也有 session.get() 、session.post() 。区别在于,它会把每次响应里的 Cookie 都存进 session.cookies ,下一次请求的时候,再自动带上。

除了记住 Cookie,Session 还有一个好处:对同一个网站的多次请求,它会复用同一个网络连接,比每次都重新连接要快一些。所以哪怕不用登录,要连续抓同一个网站的很多页时,用 Session 也是个好习惯。

还有一个方便的地方:请求头也可以设在 Session 上,以后它发出的每个请求,都会带着:

Python· 在本地运行
import requests

session = requests.Session()
session.headers['User-Agent'] = 'learn-py-practice/1.0 (contact: you@example.com)'
response = session.get('https://quotes.toscrape.com/')
print(response.request.headers['User-Agent'])

在自己电脑上运行,输出是:

文本
learn-py-practice/1.0 (contact: you@example.com)

上一章,每次请求都要写一遍 headers=HEADERS ;现在,设一次就够了。

Session 会用了,下一节就用它,真正去登录一个练习网站。

教学 04 / 08

三、登录一个练习网站

Session 能记住 Cookie 了,现在就用它来真正登录一次。quotes.toscrape.com 上有一个练习用的登录页。toscrape 的首页说明了:这个登录要带 CSRF 令牌,用户名和密码随便填,都能登录。CSRF 令牌是什么,下面马上就会讲到。

登录一共分三步来做。

第一步:看清表单。 在浏览器里打开登录页,用开发者工具看看登录表单长什么样。然后用 BeautifulSoup 把它取出来:

Python· 在本地运行
import requests
from bs4 import BeautifulSoup

session = requests.Session()
page = session.get('https://quotes.toscrape.com/login')
form = BeautifulSoup(page.text, 'html.parser').select_one('form')
print(form['action'], form['method'])
for field in form.select('input'):
    print(field.get('name'), field.get('type'))

在自己电脑上运行,输出是:

文本
/login post
csrf_token hidden
username text
password password
None submit

从输出里,能看出两件事:

  • 表单提交到 /login ,方法是 post 。第 2 章说过,取网页用 GET ,提交表单用 POST 。
  • 要填的字段有三个:username 、password ,还有一个藏起来的 csrf_token 。

那么,csrf_token 是什么呢?它是网站在登录页里埋下的一串随机文字,叫 CSRF 令牌。提交表单的时候,必须把它原样带回去。网站核对一致了,才相信这次提交是从它自己的登录页发出来的,而不是别的网站伪造的。所以登录的顺序是:先取登录页,从里面拿到令牌,再提交。

第二步:提交表单。 令牌拿到了,就和用户名、密码一起提交:

Python· 在本地运行
import requests
from bs4 import BeautifulSoup

session = requests.Session()
page = session.get('https://quotes.toscrape.com/login')
token = BeautifulSoup(page.text, 'html.parser').select_one('input[name="csrf_token"]')['value']
response = session.post('https://quotes.toscrape.com/login', data={
    'csrf_token': token,
    'username': 'practice',
    'password': 'any-password',
})
print(response.url)
print([r.status_code for r in response.history])
print('Logout' in response.text)

在自己电脑上运行,输出是:

文本
https://quotes.toscrape.com/
[302]
True

这段代码和输出,有三个地方要看一看:

  • session.post(网址, data=字典) 用来提交表单,字典的键就是表单里各个字段的 name 。
  • 提交以后,网站用 302 让我们跳转回首页,requests 自动跟了过去。response.history 里记着这次跳转,所以第二行打印出来的是 [302] 。
  • 首页上出现了 Logout (退出登录),说明登录成功了。

第三步:带着登录状态继续抓。 登录成功了,接下来怎么抓呢?很简单,用同一个 session 接着请求,看到的就是登录以后的样子。那要是换成单独的 requests.get() 呢?看到的还是没登录的样子。下面把两种放在一起比一比:

Python· 在本地运行
import requests
from bs4 import BeautifulSoup

session = requests.Session()
page = session.get('https://quotes.toscrape.com/login')
token = BeautifulSoup(page.text, 'html.parser').select_one('input[name="csrf_token"]')['value']
session.post('https://quotes.toscrape.com/login', data={'csrf_token': token, 'username': 'practice', 'password': 'any-password'})

logged_in = session.get('https://quotes.toscrape.com/')
anonymous = requests.get('https://quotes.toscrape.com/')
print('Logout' in logged_in.text, 'Logout' in anonymous.text)
print('Goodreads' in logged_in.text, 'Goodreads' in anonymous.text)

在自己电脑上运行,输出是:

文本
True False
True False

每一行左边的结果来自 logged_in ,也就是用 session 取到的首页;右边的来自 anonymous ,也就是单独用 requests.get() 取到的首页。

第二行说明,登录以后,这个网站会在每条名言旁边多显示一个 Goodreads 链接,没登录就没有。也就是说,同一个网址,登没登录,看到的内容是不一样的。这也是为什么有的数据,非得登录以后才能取到。

登录的三步都走通了。下面先做两道练习,再接着往下看。

做对当前题解锁下一段 ·03

推荐一个,全网最全最友好的中文 AI 编程指南: coding.stormzhang.ai

学习目录