Session 与 Cookie:登录以后再抓
3 道练习题·预计 50 分钟·做对一题解锁下一段
前言
各位童鞋,到目前为止,咱们每次调用 requests.get() ,都是一次全新的请求,网站根本不知道你上一次来过没有。可平时上网的时候,明明登录一次就够了,后面点开的每个网页,网站都还记得你是谁。这是怎么做到的呢?
靠的是 Cookie。它的道理并不复杂:网站发给你一小段文字,浏览器把它存起来,以后每次访问这个网站,都带上它。网站一看,就认出你来了。
所以这一章,先弄清楚 Cookie 是怎么回事,再用 requests.Session 在自己电脑上登录一个练习网站。登录成功以后,还要想清楚一件更重要的事:登录了,能抓什么,不能抓什么?
这一章的规矩,会比前面几章都多。为什么呢?因为登录以后看到的东西,往往就不再是公开的了。技术上做得到,不等于就应该去做。
学这一章之前,需要先学完 爬虫课第 10 章 ,在自己电脑上配好环境,装好 requests 和 beautifulsoup4。
目录
这一章会学到:
- 一、网站怎么记住你——Cookie
- 二、让 requests 也记住——
requests.Session - 三、登录一个练习网站——表单、隐藏字段和 POST
- 四、登录以后,能抓什么
- 五、常见坑
- 六、自己查一次文档——用
http.cookies读懂一条 Cookie - 七、小结与回看
学完这一章,你能看懂 Cookie,用 Session 保持登录状态,并清楚登录以后的抓取边界在哪儿。
一、网站怎么记住你
先回到前言里的那个问题:网站是怎么记住你的?
要知道,HTTP 本身是「健忘」的:每一次请求都是独立的,网站不会因为你刚才来过,就认出你。所以要想记住你,网站得自己想个办法。
这个办法就是 Cookie。整个过程分三步:
- 你第一次访问,网站在响应头里加一行
Set-Cookie: session=一串文字,意思是「请把这个记下来」。 - 浏览器把它存起来。
- 之后你再访问这个网站,浏览器会自动在请求头里加一行
Cookie: session=一串文字。网站一看,就知道是你。
明白了这三步,登录也就好理解了:所谓登录,其实就是网站在 Cookie 里记下了「这个人已经登录了」。
道理说完了,来看一个真实的例子。在自己电脑上运行下面的代码,看看 quotes.toscrape.com 的登录页,都发回了什么 Cookie:
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 会自动把它带回去吗?
二、让 requests 也记住
答案是:单独用 requests.get() ,不会。每次调用 requests.get() ,都像是新开了一个浏览器窗口,上一次拿到的 Cookie,不会带到下一次去。
那怎么让 requests 像浏览器一样,记住 Cookie 呢?要用 Session(会话):
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 上,以后它发出的每个请求,都会带着:
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 会用了,下一节就用它,真正去登录一个练习网站。
三、登录一个练习网站
Session 能记住 Cookie 了,现在就用它来真正登录一次。quotes.toscrape.com 上有一个练习用的登录页。toscrape 的首页说明了:这个登录要带 CSRF 令牌,用户名和密码随便填,都能登录。CSRF 令牌是什么,下面马上就会讲到。
登录一共分三步来做。
第一步:看清表单。 在浏览器里打开登录页,用开发者工具看看登录表单长什么样。然后用 BeautifulSoup 把它取出来:
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 令牌。提交表单的时候,必须把它原样带回去。网站核对一致了,才相信这次提交是从它自己的登录页发出来的,而不是别的网站伪造的。所以登录的顺序是:先取登录页,从里面拿到令牌,再提交。
第二步:提交表单。 令牌拿到了,就和用户名、密码一起提交:
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() 呢?看到的还是没登录的样子。下面把两种放在一起比一比:
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 链接,没登录就没有。也就是说,同一个网址,登没登录,看到的内容是不一样的。这也是为什么有的数据,非得登录以后才能取到。
登录的三步都走通了。下面先做两道练习,再接着往下看。
推荐一个,全网最全最友好的中文 AI 编程指南: coding.stormzhang.ai