爬虫实战: 利用浏览器插件绕过登录验证码
前言
在爬虫开发过程中,经常会遇到验证码的情况,这是反爬过程中相当有门槛的措施,破解成本非常高,需要深度学习、JS 逆向等相关的经验,而且成功率还不一定很高。即使破解成功,如果目标网站换了验证码生成的方法或算法,那么费了九牛二虎之力才成功破解的工作将前功尽弃。因此,破解验证码这种反人类的事情,真的不适合新手。
那么,用什么方法才能够避免验证码,从而完成登录呢?如果一个方法不行(Not working),我们不要死磕(Stick to it),我们可以尝试采用绕开的方式(Workaround)。而本文将介绍一种简单可行的绕开验证码的办法,并且实践证明是非常有效的。目前关于这种方法的网上资料并不多,本文将简单介绍一下,权当抛砖引玉。
整体思路
现在的网站一般都不要求用户每次访问时都重复输入信息登录,而是允许用户二次登录的时候能够直接通过登录校验,直接访问登录后的内容。那么这是如何实现的呢?网站一般是将一些加密信息存在浏览器上,类似于给了你一张有过期时间的房卡,你每次拿着这张房卡,通过的时候扫一下就进去了。这些加密信息绝大部分是以 Cookie 形式存在的,上面存有 Session 信息,而访问的时候 Cookie 与浏览器上的 Session 作比对,如果成功就能正常访问登录后的信息。如果您对这种机制不了解,请参考 这篇文章。而我们恰巧就是利用了这种机制来实现绕开验证码的操作,如下图所示。
因此,我们的思路不是想方设法的破解验证码,例如利用打码平台,或者自研深度学习算法之类的,而是利用 Cookie 这个简单的浏览器储存方式来绕开登录验证码,从而达到我们抓取登录后数据的目的。
下面我们来介绍一下具体的实现方式。整体的实现思路如下图。
简单来说,步骤如下:
-
我们通过浏览器获取目标网站的 Cookie(输入验证码登录后); -
然后利用浏览器插件从浏览器中获取对应目标网站的 Cookie; -
浏览器插件发送获取的 Cookie 到后台 API; -
API 将接收到的 Cookie 写入数据库中; -
爬虫从数据库中获取 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.html
、manifest.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,
})
}
}
如果想看具体的爬虫代码,请参考 https://github.com/crawlab-team/artipub/blob/master/spiders/base.js
其实你已经在数据库中保存的相应的 Cookie,你需要做的就是取出来,在应用在对目标网站的请求中。这里不管你用哪种框架,例如 Scrapy、requests,都是可以的。
接下来,你就可以运行该爬虫来绕开验证登录了。
总结
本文实现了一个利用 Chrome 浏览器插件绕开验证登录的爬虫设计。这个设计的核心逻辑是利用 Chrome 浏览器获取其他域名 Cookie 的能力,然后应用在爬虫请求中,从而达到绕开登录验证码的目的。这种设计相较于直接破解验证码来说,非常简单,成本较低,易于操作,稳定性和成功率都非常高。开源一文多发平台 ArtiPub 就是利用这种设计来达到绕过登录验证而发布文章的。实践证明,这种方式的效果非常好,每个平台都能顺利绕过登录。缺点可能是需要一些前端尤其是浏览器插件开发经验,但都不难,半个小时就可以上手。对于生产环境上来说,利用浏览器插件的方法都可以作为有效的反爬手段。欢迎大家尝试用这种方法在需要登录操作的爬虫中。
参考
-
Chrome 插件开发教程: https://developer.chrome.com/extensions/getstarted -
ArtiPub: https://github.com/crawlab-team/artipub
- 点赞
- 收藏
- 关注作者
评论(0)