1. 编程圈首页
  2. 文库
  3. 后端开发

爬虫实战: 利用浏览器插件绕过登录验证码

前言

爬虫开发过程中,经常会遇到验证码的情况,这是反爬过程中相当有门槛的措施,破解成本非常高,需要深度学习、JS 逆向等相关的经验,而且成功率还不一定很高。即使破解成功,如果目标网站换了验证码生成的方法或算法,那么费了九牛二虎之力才成功破解的工作将前功尽弃。因此,破解验证码这种反人类的事情,真的不适合新手。

那么,用什么方法才能够避免验证码,从而完成登录呢?如果一个方法不行(Not working),我们不要死磕(Stick to it),我们可以尝试采用绕开的方式(Workaround)。而本文将介绍一种简单可行的绕开验证码的办法,并且实践证明是非常有效的。目前关于这种方法的网上资料并不多,本文将简单介绍一下,权当抛砖引玉。

整体思路

现在的网站一般都不要求用户每次访问时都重复输入信息登录,而是允许用户二次登录的时候能够直接通过登录校验,直接访问登录后的内容。那么这是如何实现的呢?网站一般是将一些加密信息存在浏览器上,类似于给了你一张有过期时间的房卡,你每次拿着这张房卡,通过的时候扫一下就进去了。这些加密信息绝大部分是以 Cookie 形式存在的,上面存有 Session 信息,而访问的时候 Cookie 与浏览器上的 Session 作比对,如果成功就能正常访问登录后的信息。而我们恰巧就是利用了这种机制来实现绕开验证码的操作,如下图所示。

爬虫实战: 利用浏览器插件绕过登录验证码

因此,我们的思路不是想方设法的破解验证码,例如利用打码平台,或者自研深度学习算法之类的,而是利用 Cookie 这个简单的浏览器储存方式来绕开登录验证码,从而达到我们抓取登录后数据的目的。

下面我们来介绍一下具体的实现方式。整体的实现思路如下图。

爬虫实战: 利用浏览器插件绕过登录验证码

简单来说,步骤如下:

  1. 我们通过浏览器获取目标网站的 Cookie(输入验证码登录后);
  2. 然后利用浏览器插件从浏览器中获取对应目标网站的 Cookie;
  3. 浏览器插件发送获取的 Cookie 到后台 API;
  4. API 将接收到的 Cookie 写入数据库中;
  5. 爬虫从数据库中获取 Cookie,并在每次请求时带上 Cookie。

这样,我们就完成了一个抓取登录后网站信息的流程。其中,黄色部分的数据库和 API 都可以换做本地文件,当然为了生产环境可用,我们还是推荐用 API + 数据库的方式。

浏览器插件

简介

浏览器插件主要是为了扩展浏览器功能,让用户在浏览器上实现一些比较实用功能的工具。本文将采用的是 Chrome 浏览器插件,因为开发比较简单,使用人数也较多。

Chrome 插件由不同的组件构成。组件可以包括背景脚本、内容脚本、可选的页面、UI 元素以及不同的逻辑文件。插件组件由 Web 开发技术所创建:HTML、CSS、JavaScript。插件组件将依赖它本身的功能,可能不要求配置所有的设置。

开发

manifest.json

Chrome 插件的开发从 manifest.json 开始。这是一个类似于 Node.js 中 package.json 的配置文件。这个配置文件将告诉浏览器这个插件的基本信息,包括名称、描述、版本等等;它还包括其可运行的范围(Scope),可以操作的权限;它还包含入口文件以及图标信息等等。

一个比较完整的 manifest.json 如下。

{
  "name": "ArtiPub",
  "version": "0.1.4",
  "manifest_version": 2,
  "description": "ArtiPub登陆助手,帮助登陆编程圈、SegmentFault、CSDN、知乎等技术平台",
  "icons": {
    "16": "icon.png",
    "48": "icon.png",
    "128": "icon.png"
  },
  "browser_action": {
    "default_title": "ArtiPub登陆助手",
    "default_icon": "icon.png",
    "default_popup": "popup.html"
  },
  "permissions": [
    "cookies",
    "http://*/*",
    "https://*/*",
    "storage"
  ]
}

其中比较重要的部分是 permissions,这个部分定义了插件的权限。大多数 API,包括储存 API 在内,都被注册在了 permissions 字段下面。如果想利用一些功能,例如 Cookie,你必须在 permissions 下加入 cookies 这个值。

这里的例子是开源一文多发平台 ArtiPub 的插件 manifest.json

HTML

在前面 manifest.json 中,我们看到又一个 popup.html HTML 文件,这其实是一个入口文件,代表当 Chrome 插件打开时,其触发的弹框 HTML。在这里,我们就可以做很多文章了,例如嵌入一些 UI 组件,做一些按钮,嵌入一些必要的 JS 文件等等。

为方便起见,我们利用了 React + TypeScript 来开发插件的 UI 部分。最终的输出将会是 popup.htmlmanifest.json 以及一些其他的静态文件。

Popup.tsx 如下。请注意 onGetLoginInfo 方法下的 chrome.cookies.getAllCookieStore 部分,这里是获取 Cookie 的核心逻辑。

import {Button, Card, Input} from 'antd';
import * as React from 'react';
import axios from 'axios';
import './Popup.scss';

interface AppProps {
}

interface AppState {
  allowedDomains: string[];
  configVisible: boolean;
  url: string;
  fetched: boolean;
  loading: boolean;
}

export default class Popup extends React.Component<AppProps, AppState> {
  constructor(props: AppProps, state: AppState) {
    super(props, state);
  }

  componentDidMount() {
    // Example of how to send a message to eventPage.ts.
    chrome.runtime.sendMessage({popupMounted: true});

    this.setState({
      allowedDomains: [],
      configVisible: false,
      url: localStorage.getItem('url') || 'http://localhost:3000',
      fetched: false,
      loading: false,
    });
  }

  async onGetLoginInfo() {
    this.setState({
      loading: true
    });

    // 从后台获取需要的域名
    const response = await axios.get(this.state.url + '/platforms');
    const platforms = response.data.data;
    this.setState({
      allowedDomains: platforms.map((d: any) => d.name)
    });

    // 这里比较重要,是获取 Cookie 的核心逻辑
    // 遍历所有 cookieStores
    chrome.cookies.getAllCookieStores(cookieStores => {
      // console.log(cookieStores);
      
      // 遍历所有 store
      cookieStores.forEach(store => {
        // 获取该 store 对应的 cookies
        chrome.cookies.getAll({storeId: store.id}, cookies => {
          // 过滤出需要的域名的 cookies
          const data = cookies.filter(c => {
            for (let domain of this.state.allowedDomains) {
              if (c.domain.match(domain)) {
                return true
              }
            }
            return false
          });

          // 发送 cookies 到后端
          axios.post(this.state.url + '/cookies', data)
            .then(() => {
              this.setState({fetched: true});
            })
            .finally(() => {
              this.setState({loading: false});
            });
        });
      });
    });
  }

  onConfig() {
    this.setState({
      configVisible: !this.state.configVisible,
    })
  }

  onUrlChange(ev: any) {
    localStorage.setItem('url', ev.target.value);
    this.setState({
      url: ev.target.value,
    });
  }

  render() {
    let btn = (
      <Button type="primary"
              onClick={this.onGetLoginInfo.bind(this)}>
        一键获取登陆信息
      </Button>
    );
    if (this.state && this.state.loading) {
      btn = (
        <Button type="primary" loading={true}>
          正在获取
        </Button>
      )
    } else if (this.state && this.state.fetched) {
      btn = (
        <Button className="success" type="primary">
          已成功获取
        </Button>
      )
    }

    let input;
    if (this.state && this.state.configVisible) {
      input = (
        <Input value={this.state.url} className="input-url" placeholder="后端地址" onChange={this.onUrlChange.bind(this)}/>
      );
    }

    return (
      <Card className="artipub-container">
        <h2>
          ArtiPub登陆助手
          <Button type="primary" shape="circle" icon="tool" className="config-btn"
                  onClick={this.onConfig.bind(this)}/>
        </h2>
        {input}
        {btn}
      </Card>
    )
  }
}

打包插件

这里参考 ArtiPub 的 插件仓库。只需要在这个下面运行 npm run build 就可以了。所有打包好的静态文件会在 build 目录。

导入插件

接下来是导入插件到浏览器。

在 Chrome 浏览器中点击设置,按照下图点击 Extensions

爬虫实战: 利用浏览器插件绕过登录验证码

然后按照下图点击 Load unpacked,表示加载未压缩的文件。点击并选择刚才打包构建好的 build 目录。

爬虫实战: 利用浏览器插件绕过登录验证码

然后插件就加载好了。您应该能在右上角看到插件图标。

使用插件

在使用插件之前,你需要人工登录到到目标网站,保证登录后的 Cookie 已经被保存在了浏览器上面。此外,你还需要保证后台的导入程序已经启动,这里请参考 ArtiPub 的 获取 Cookie 的后台 API。然后,打开刚刚导入的浏览器插件,点击“一键获取登陆信息”导入。

后台 API 与数据库

后台获取 Cookie 的 API 比较简单,这里我们简单展示一下,代码如下。

const models = require('../models')

module.exports = {
  addCookies: async (req, res) => {
    const cookies = req.body
    for (let i = 0; i < cookies.length; i++) {
      const c = cookies[i]
      let cookie = await models.Cookie.findOne({
        domain: c.domain,
        name: c.name
      })
      if (cookie) {
        // 已存在该cookie
        for (let k in c) {
          if (c.hasOwnProperty(k)) {
            cookie[k] = c[k]
          }
        }
      } else {
        // 不存在该cookie,新增
        cookie = new models.Cookie({ ...c })
      }
      await cookie.save()
    }
    res.json({
      status: 'ok'
    })
  },
}

数据库我们采用的是灵活度很高的 MongoDB。如果您想用其他数据库,例如 MySQL、SQLite,都是可以的。

爬虫

这里我们还是继续使用 ArtiPub 的例子。这里我们使用了 Puppeteer 作为我们的爬虫引擎。核心设置 Cookie 的代码如下。

  async setCookies() {
    const cookies = await models.Cookie.find({ domain: { $regex: this.platform.name } })
    for (let i = 0; i < cookies.length; i++) {
      const c = cookies[i]
      await this.page.setCookie({
        name: c.name,
        value: c.value,
        domain: c.domain,
      })
    }
  }

其实你已经在数据库中保存的相应的 Cookie,你需要做的就是取出来,在应用在对目标网站的请求中。这里不管你用哪种框架,例如 Scrapy、requests,都是可以的。

接下来,你就可以运行该爬虫来绕开验证登录了。

总结

本文实现了一个利用 Chrome 浏览器插件绕开验证登录的爬虫设计。这个设计的核心逻辑是利用 Chrome 浏览器获取其他域名 Cookie 的能力,然后应用在爬虫请求中,从而达到绕开登录验证码的目的。这种设计相较于直接破解验证码来说,非常简单,成本较低,易于操作,稳定性和成功率都非常高。开源一文多发平台 ArtiPub 就是利用这种设计来达到绕过登录验证而发布文章的。实践证明,这种方式的效果非常好,每个平台都能顺利绕过登录。缺点可能是需要一些前端尤其是浏览器插件开发经验,但都不难,半个小时就可以上手。对于生产环境上来说,利用浏览器插件的方法都可以作为有效的反爬手段。欢迎大家尝试用这种方法在需要登录操作的爬虫中。

发布者:编程圈,转转请注明出处:https://www.bianchengquan.com/article/43484.html

发表评论

登录后才能评论