在很多情况下,一些网站的页面或资源我们通常需要登录才能看到。比如访问 GitHub 的个人设置页面,如果不登录是无法查看的;比如 12306 买票提交订单的页面,如果不登录是无法提交订单的;再比如要发一条微博,如果不登录是无法发送的。

我们之前学习的案例都是爬取的无需登录即可访问的站点,但是诸如上面例子的情况非常非常多,那假如我们想要用爬虫来访问这些页面,比如用爬虫修改 GitHub 的个人设置,用爬虫提交购票订单,用爬虫发微博,能做到吗?

答案是可以,这里就需要用到一些模拟登录相关的技术了。那么本课时我们就先来了解模拟登录的一些基本原理和实现吧。

网站登录验证的实现

我们要实现模拟登录,那就得首先了解网站登录验证的实现。

登录一般需要两个内容,用户名和密码,有的网站可能是手机号和验证码,有的是微信扫码,有的是 OAuth 验证等等,但根本上来说,都是把一些可供认证的信息提交给了服务器。

比如这里我们就拿用户名和密码来举例吧。用户在一个网页表单里面输入了内容,然后点击登录按钮的一瞬间,浏览器客户端就会向服务器发送一个登录请求,这个请求里面肯定就包含了用户名和密码信息,这时候,服务器需要处理这些信息,然后返回给客户端一个类似“凭证”的东西,有了这个“凭证”以后呢,客户端拿着这个“凭证”再去访问某些需要登录才能查看的页面,服务器自然就能“放行”了,然后返回对应的内容或执行对应的操作就好了。

形象地说,我们以登录发微博和买票坐火车这两件事来类比。发微博就好像要坐火车,没票是没法坐火车的吧,要坐火车怎么办呢?当然是先买票了,我们拿钱去火车站买了票,有了票之后,进站口查验一下,没问题就自然能去坐火车了,这个票就是坐火车的“凭证”。

发微博也一样,我们有用户名和密码,请求下服务器,获得一个“凭证”,这就相当于买到了火车票,然后在发微博的时候拿着这个“凭证”去请求服务器,服务器校验没问题,自然就把微博发出去了。

那么问题来了,这个“凭证“”到底是怎么生成和验证的呢?目前比较流行的实现方式有两种,一种是基于 Session + Cookies 的验证,一种是基于 JWT(JSON Web Token)的验证,下面我们来介绍下。

Session 和 Cookies

我们在模块一了解了 Session 和 Cookies 的基本概念。简而言之,Session 就是存在服务端的,里面保存了用户此次访问的会话信息,Cookies 则是保存在用户本地浏览器的,它会在每次用户访问网站的时候发送给服务器,Cookies 会作为 Request Headers 的一部分发送给服务器,服务器根据 Cookies 里面包含的信息判断找出其 Session 对象,不同的 Session 对象里面维持了不同访问用户的状态,服务器可以根据这些信息决定返回 Response 的内容。

我们以用户登录的情形来举例,其实不同的网站对于用户的登录状态的实现可能是不同的,但是 Session 和 Cookies 一定是相互配合工作的。

梳理如下:

  • 比如,Cookies 里面可能只存了 Session ID 相关信息,服务器能根据 Cookies 找到对应的 Session,用户登录之后,服务器会在对应的 Session 里面标记一个字段,代表已登录状态或者其他信息(如角色、登录时间)等等,这样用户每次访问网站的时候都带着 Cookies 来访问,服务器就能找到对应的 Session,然后看一下 Session 里面的状态是登录状态,就可以返回对应的结果或执行某些操作。
  • 当然 Cookies 里面也可能直接存了某些凭证信息。比如说用户在发起登录请求之后,服务器校验通过,返回给客户端的 Response Headers 里面可能带有 Set-Cookie 字段,里面可能就包含了类似凭证的信息,这样客户端会执行 Set Cookie 的操作,将这些信息保存到 Cookies 里面,以后再访问网页时携带这些 Cookies 信息,服务器拿着这里面的信息校验,自然也能实现登录状态检测了。

以上两种情况几乎能涵盖大部分的 Session 和 Cookies 登录验证的实现,具体的实现逻辑因服务器而异,但 Session 和 Cookies 一定是需要相互配合才能实现的。

JWT

Web 开发技术是一直在发展的,近几年前后端分离的趋势越来越火,很多 Web 网站都采取了前后端分离的技术来实现。而且传统的基于 Session 和 Cookies 的校验也存在一定问题,比如服务器需要维护登录用户的 Session 信息,而且不太方便分布式部署,也不太适合前后端分离的项目。

所以,JWT 技术应运而生。JWT,英文全称叫作 JSON Web Token,是为了在网络应用环境间传递声明而执行的一种基于 JSON 的开放标准。实际上就是每次登录的时候通过一个 Token 字符串来校验登录状态。

JWT 的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其他业务逻辑所必须的声明信息,所以这个 Token 也可直接被用于认证,也可传递一些额外信息。

有了 JWT,一些认证就不需要借助于 Session 和 Cookies 了,服务器也无需维护 Session 信息,减少了服务器的开销。服务器只需要有一个校验 JWT 的功能就好了,同时也可以做到分布式部署和跨语言的支持。

JWT 通常就是一个加密的字符串,它也有自己的标准,类似下面的这种格式:

  1. eyJ0eXAxIjoiMTIzNCIsImFsZzIiOiJhZG1pbiIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2In0.eyJVc2VySWQiOjEyMywiVXNlck5hbWUiOiJhZG1pbiIsImV4cCI6MTU1MjI4Njc0Ni44Nzc0MDE4fQ.pEgdmFAy73walFonEm2zbxg46Oth3dlT02HR9iVzXa8

可以发现中间有两个“.”来分割开,可以把它看成是一个三段式的加密字符串。它由三部分构成,分别是 Header、Payload、Signature。

Header,声明了 JWT 的签名算法,如 RSA、SHA256 等等,也可能包含 JWT 编号或类型等数据,然后整个信息 Base64 编码即可。
Payload,通常用来存放一些业务需要但不敏感的信息,如 UserID 等,另外它也有很多默认的字段,如 JWT 签发者、JWT 接受者、JWT 过期时间等等,Base64 编码即可。
Signature,这个就是一个签名,是把 Header、Payload 的信息用秘钥 secret 加密后形成的,这个 secret 是保存在服务器端的,不能被轻易泄露。这样的话,即使一些 Payload 的信息被篡改,服务器也能通过 Signature 判断出来是非法请求,拒绝服务。
这三部分通过“.”组合起来就形成了 JWT 的字符串,就是用户的访问凭证。

所以这个登录认证流程也很简单了,用户拿着用户名密码登录,然后服务器生成 JWT 字符串返回给客户端,客户端每次请求都带着这个 JWT 就行了,服务器会自动判断其有效情况,如果有效,那自然就返回对应的数据。但 JWT 的传输就多种多样了,可以放在 Request Headers,也可以放在 URL 里,甚至有的网站也放在 Cookies 里,但总而言之,能传给服务器校验就好了。

好,到此为止呢,我们就已经了解了网站登录验证的实现了。

模拟登录

好,了解了网站登录验证的实现后,模拟登录自然就有思路了。下面我们也是分两种认证方式来说明。

Session 和 Cookies

基于 Session 和 Cookies 的模拟登录,如果我们要用爬虫实现的话,其实最主要的就是把 Cookies 的信息维护好,因为爬虫就相当于客户端浏览器,我们模拟好浏览器做的事情就好了。

那一般情况下,模拟登录一般可以怎样实现呢,我们结合之前所讲的技术来总结一下:

第一,如果我们已经在浏览器里面登录了自己的账号,我们要想用爬虫模拟的话,可以直接把 Cookies 复制过来交给爬虫就行了,这也是最省事省力的方式。这就相当于,我们用浏览器手动操作登录了,然后把 Cookies 拿过来放到代码里面,爬虫每次请求的时候把 Cookies 放到 Request Headers 里面,就相当于完全模拟了浏览器的操作,服务器会通过 Cookies 校验登录状态,如果没问题,自然可以执行某些操作或返回某些内容了。
第二,如果我们不想有任何手工操作,可以直接使用爬虫来模拟登录过程。登录的过程其实多数也是一个 POST 请求,我们用爬虫提交用户名密码等信息给服务器,服务器返回的 Response Headers 里面可能带了 Set-Cookie 的字段,我们只需要把这些 Cookies 保存下来就行了。所以,最主要的就是把这个过程中的 Cookies 维护好就行了。当然这里可能会遇到一些困难,比如登录过程还伴随着各种校验参数,不好直接模拟请求,也可能网站设置 Cookies 的过程是通过 JavaScript 实现的,所以可能还得仔细分析下其中的一些逻辑,尤其是我们用 requests 这样的请求库进行模拟登录的时候,遇到的问题可能比较多。
第三,我们也可以用一些简单的方式来实现模拟登录,即把人在浏览器中手工登录的过程自动化实现,比如我们用 Selenium 或 Pyppeteer 来驱动浏览器模拟执行一些操作,如填写用户名、密码和表单提交等操作,等待登录成功之后,通过 Selenium 或 Pyppeteer 获取当前浏览器的 Cookies 保存起来即可。然后后续的请求可以携带 Cookies 的内容请求,同样也能实现模拟登录。
以上介绍的就是一些常用的爬虫模拟登录的方案,其目的就是维护好客户端的 Cookies 信息,然后每次请求都携带好 Cookies 信息就能实现模拟登录了。

JWT

基于 JWT 的真实情况也比较清晰了,由于 JWT 的这个字符串就是用户访问的凭证,那么模拟登录只需要做到下面几步即可:

第一,模拟网站登录操作的请求,比如携带用户名和密码信息请求登录接口,获取服务器返回结果,这个结果中通常包含 JWT 字符串的信息,保存下来即可。
第二,后续的请求携带 JWT 访问即可,一般情况在 JWT 不过期的情况下都能正常访问和执行对应的操作。携带方式多种多样,因网站而异。
第三,如果 JWT 过期了,可能需要重复步骤一,重新获取 JWT。
当然这个模拟登录的过程也肯定带有其他的一些加密参数,需要根据实际情况具体分析。

优化方案
如果爬虫要求爬取的数据量比较大或爬取速度比较快,而网站又有单账号并发限制或者访问状态检测并反爬的话,可能我们的账号就会无法访问或者面临封号的风险了。这时候一般怎么办呢?

我们可以使用分流的方案来解决,比如某个网站一分钟之内检测一个账号只能访问三次或者超过三次就封号的话,我们可以建立一个账号池,用多个账号来随机访问或爬取,这样就能数倍提高爬虫的并发量或者降低被封的风险了。

比如在访问某个网站的时候,我们可以准备 100 个账号,然后 100 个账号都模拟登录,把对应的 Cookies 或 JWT 存下来,每次访问的时候随机取一个来访问,由于账号多,所以每个账号被取用的概率也就降下来了,这样就能避免单账号并发过大的问题,也降低封号风险。