模拟登录的基本原理

很多情况下,一些网站的页面或资源需要先登录才能看到。例如 GitHub 的个人设置页面,如果不登录就无法查看:12306 网站的提交订单页面,如果不登录就无法提交订单;在微博上写了一个新内容,如果不登录也是无法发送的。

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

答案是能,这时就需要用到一些模拟登录相关的技术。

网站登录验证的实现

要实现模拟登录,首先得了解网站如何验证登录内容。

登录一般需要两个内容一一用户名和密码,也有的网站是填写手机号获取验证码,或者微信扫码,或者 OAuth 验证等,从根本上看,这些方式都是把一些可供认证的信息提交给服务器。

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

形象点说,坐火车前,乘客要先用钱买票,有了票之后,让进站口查验一下,没问题就可以去候车了,这个票就是坐火车时的凭证。

那么问题来了,这个凭证是怎么生成的,服务器文是怎么校验的呢?答案其实在本章开头已经介绍过了,一种是基于 Session 和 Cookie,一种是基于 JWT。

不同网站对于用户登录状态的实现可能是不同的,但 Session 和 Cookie 一定是相互配合工作的,下面梳理一下。

  • Cookie 里可能只保存了 SessionID 相关的信息,服务器能根据这个信息找到对应的 Session。当用户登录后,服务器会在对应的 Session 里标记一个字段,代表用户已处于登录状态或者其他(如角色、登录时间)。这样一来,用户每次访问网站的时候都带着 Cookie,服务器每次都找到对应的 Session,然后看一下用户的状态是否为登录状态,再决定返回什么结果或执行什么操作

  • Cookie 里直接保存了某些凭证信息。例如用户发起登录请求,服务器校验通过后,返回给客户端的响应头里面可能带有 Set-Cookie 字段,里面就包含着类似凭证的信息,这样客户端会执行设置 Cookie 的操作,将那些类似凭证的信息保存到 Cookie 里,以后再访问网站时都携带着 Cookie 服务器拿着其中的信息进行校验,自然也能检测登录状态。

以上两种情况几乎能涵盖大部分这种模式的实现,具体的实现逻辑因服务器而异,但 Session 和 Cookie 一定是要相互配合的。

基于 JWT

Web 开发技术一直在发展,近儿年前后端分离的开发模式越来越火,传统的基于 Session 和 Cookie 的校验文存在一定问题,例如服务器需要维护登录用户的 Session 信息,而且分布式部署也不方便不太适合前后端分离的项目,所以 JWT 技术应运而生。

JWT 的英文全称为 JSON Web Token,是为了在网络应用环境中传递声明而执行的一种基于 JSON 的开放标准,实际上就是在每次登录时都通过一个 Token 字段校验登录状态。JWT 的声明一般用来在身份提供者和服务提供者之间传递要认证的用户身份信息,以便从资源服务器获取资源,此外可以增加一些业务逻辑必需的声明信息,总之 Token 可以直接用于认证,也可以传递一些额外信息。

有了 JWT,一些认证就不需要借助于 Session 和 Cookie 了,服务器也无须维护 Session 信息,从而减少了开销,只需要有一个校验 JWT 的功能就够了,同时还支持分布式部署和跨语言开发。

JWT 一般是一个经过 Base64 编码技术加密的字符串,有自已的标准,格式类似下面这样:

eyJoeXAxIjoiMTIzNCIsImFsZzIioiJhZG1pbiIsInR5cCI6IkpXVCIsImFsZyI6IkhTMjU2In0.eyJVc2VySWQi0jEyMywiVXNlck5hb WUi0iJhZG1pbiIsImV4cCI6MTU1MjI4NjcONi44NzcOMDE4fQ-pEgdmFAy73walFonEm2zbxg46Oth3d1To2HR9iVzXa8

其中有两个起分隔作用的 “.”,因此可以把 JWT 看成一个三段式的加密字符串。这三部分分别是 Header、Payload 和 Signature。

  • Header:声明了 JWT 的签名算法(如 RSA、SHA256 等),还可能包含 JWT 编号或类型等数据。

  • Payload:通常是一些业务需要但不敏感的信息(如 UserID),另外还有很多默认字段,如 JWT 签发者、JWT 接受者、JWT 过期时间等。

  • Signature:这就是一个签名,是利用密钥 secret 对 Header、Payload 的信息进行加密后形成的,这个密钥保存在服务端,不会轻易泄露。如此一来,如果 Payload 的信息被篡改,服务器就能通过 Signature 判断出这是非法请求,拒绝提供服务。

登录认证流程也很简单了,用户通过用户名和密码登录,然后服务器生成 JWT 字符串返回给客户端,之后客户端每次请求都带着这个 JWT,服务器会自动判断其有效情况,如果有效就返回对应的数据。JWT 的传递方式多种多样,可以放在请求头中,也可以放在 URL 里,甚至有的网站把它放在 Cookie 里,但总而言之,把它传给服务器进行校验就可以了。

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

模拟登录

经过前面几节的学习,想必大家已经有模拟登录的思路了。下面我们同样基于两种模式来实现。

如果要用爬虫实现基于 Session 和 Cookie 的模拟登录,最主要的是要维护好 Cookie 的信息,因为爬虫相当手客户端的浏览器,我们把浏览器做的事情模拟好就行。接下来结合本书之前所讲的技术总结一下如何用爬虫模拟登录。

  • 第一,如果已经在浏览器中登录了自已的账号,那么可以直接把 Cookie 复制给爬虫。这是最省时省力的方式,相当于手动在浏览器中登录。我们把 Cookie 放到爬虫代码里,爬虫每次请求的时候都将其放到请求头中,可以说完全模拟了浏览器的操作。之后服务器的动作和前面一样,通过 Cookie 校验登录状态,如果校验没问题,就执行某些操作或返回某些内容。

  • 第二,如果想让爬虫完全自动化操作,那么可以直接使用爬虫模拟登录过程。大多数时候,登录过程其实就是一个 POST 请求。用爬虫把用户名、密码等信息提交给服务器,服务器返回的响应头里面可能会有 Set-Cookie 字段,我们只需要把这个字段里的内容保存下来就行了。所以,最主要的是把这个过程中的 Cookie 维持好。当然,可能会遭遇一些困难,例如登录过程中伴随着各种校验参数,不好直接模拟请求;客户端设置 Cookie 的过程是通过 JavaScript 语言实现的,所以可能还得仔细分析其中的逻辑,尤其是用 requests 这样的请求库进行模拟登录时,遇到的问题总是会比较多。

  • 第三,可以用一些简单的方式模拟登录,即实现登录过程的自动化。例如用 Selenium、Pyppeteer 或 Playwright 驱动浏览器模拟执行一些操作(如填写用户名和密码、提交表单等)。登录成功后,通过 Selenium 或 Pyppeteer 获取当前浏览器的 Cookie 并保存。同样之后就可以拿着 Cookie 的内容发起请求,实现模拟登录。

以上介绍的就是一些常用的利用爬虫模拟登录的方案,核心是维护好客户端的 Cookie 信息。总之,每次请求时都携带 Cookie 信息就能实现模拟登录了。

基于 JWT 模拟登录

基于 JWT 的模拟登录思路也比较清晰,由于 JWT 的字符串就是用户访问的凭证,所以模拟登录只需要做到下面几步。

  1. 模拟登录操作。例如拿着用户名和密码信息请求登录接口,获取服务器返回的结果,这个结果中通常包含 JWT 信息,将其保存下来即可。

  2. 之后发送给服务器的请求均携带 JWT。在 JWT 不过期的情况下,通常能正常访问和执行操作。携带方式多种多样,因网站而异。

  3. 如果 JWT 过期了,可能需要再次做第一步,重新获取 JWT。

当然,模拟登录的过程肯定会带一些其他加密参数,需要根据实际情况具体分析。

账号池

如果爬虫要求爬取的数据量比较大或爬取速度比较快,网站又有单账号并发限制或者访问状态检测等反爬虫手段,我们的账号可能就无法访问网站或者面临封号的风险。

这时一般怎么处理呢?可以分流,建立一个账号池,用多个账号随机访问网站或爬取数据,这样能大幅提高爬虫的并发量,降低被封号的风险。例如准备 100 个账号,然后这 100 个账号都模拟登录,并保存对应的 Cookie 或 JWT,每次都随机从中选取一个来访问,账号多,所以每个账号被选取的概率就小,也就避免了单账号并发量过大的问题,从而降低封号风险。

总结

本节中我们首先了解了基于 Session 和 Cookie,以及基于 JWT 模拟登录的原理,接着初步了解了两种方式的实现思路,最后初步介绍了一下账号池。

后面我们会通过几个实战案例实现上述两种模拟登录,为了更好地理解实战内容,建议好好学习本节的知识。