Node Puppeteer图像识别实现百度指数爬虫的示例


Posted in Javascript onFebruary 22, 2018

之前看过一篇脑洞大开的文章,介绍了各个大厂的前端反爬虫技巧,但也正如此文所说,没有100%的反爬虫方法,本文介绍一种简单的方法,来绕过所有这些前端反爬虫手段。

下面的代码以百度指数为例,代码已经封装成一个百度指数爬虫node库: https://github.com/Coffcer/baidu-index-spider

note: 请勿滥用爬虫给他人添麻烦

百度指数的反爬虫策略

观察百度指数的界面,指数数据是一个趋势图,当鼠标悬浮在某一天的时候,会触发两个请求,将结果显示在悬浮框里面:

Node Puppeteer图像识别实现百度指数爬虫的示例

按照常规思路,我们先看下这个请求的内容:

请求 1:

Node Puppeteer图像识别实现百度指数爬虫的示例 

Node Puppeteer图像识别实现百度指数爬虫的示例 

请求 2:

Node Puppeteer图像识别实现百度指数爬虫的示例

可以发现,百度指数实际上在前端做了一定的反爬虫策略。当鼠标移动到图表上时,会触发两个请求,一个请求返回一段html,一个请求返回一张生成的图片。html中并不包含实际数值,而是通过设置width和margin-left,来显示图片上的对应字符。并且请求参数上带有res、res1这种我们不知如何模拟的参数,所以用常规的模拟请求或者html爬取的方式,都很难爬到百度指数的数据。

爬虫思路

怎么突破百度这种反爬虫方法呢,其实也很简单,就是完全不去管他是如何反爬虫的。我们只需模拟用户操作,将需要的数值截图下来,做图像识别就行。步骤大概是:

  1. 模拟登录
  2. 打开指数页面
  3. 鼠标移动到指定日期
  4. 等待请求结束,截取数值部分的图片
  5. 图像识别得到值
  6. 循环第3~5步,就得到每一个日期对应的值

这种方法理论上能爬任何网站的内容,接下来我们来一步步实现爬虫,下面会用到的库:

  1. puppeteer 模拟浏览器操作
  2. node-tesseract tesseract的封装,用来做图像识别
  3. jimp 图片裁剪

安装Puppeteer, 模拟用户操作

Puppeteer是Google Chrome团队出品的Chrome自动化工具,用来控制Chrome执行命令。可以模拟用户操作,做自动化测试、爬虫等。用法非常简单,网上有不少入门教程,顺着本文看完也大概可以知道如何使用。

API文档: https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md

安装:

npm install --save puppeteer

Puppeteer在安装时会自动下载Chromium,以确保可以正常运行。但是国内网络不一定能成功下载Chromium,如果下载失败,可以使用cnpm来安装,或者将下载地址改成淘宝的镜像,然后再安装:

npm config set PUPPETEER_DOWNLOAD_HOST=https://npm.taobao.org/mirrors
npm install --save puppeteer

你也可以在安装时跳过Chromium下载,通过代码指定本机Chrome路径来运行:

// npm
npm install --save puppeteer --ignore-scripts

// node
puppeteer.launch({ executablePath: '/path/to/Chrome' });

实现

为版面整洁,下面只列出了主要部分,代码涉及到selector的部分都用了...代替,完整代码参看文章顶部的github仓库。

打开百度指数页面,模拟登录

这里做的就是模拟用户操作,一步步点击和输入。没有处理登录验证码的情况,处理验证码又是另一个话题了,如果你在本机登录过百度,一般不需要验证码。

// 启动浏览器,
// headless参数如果设置为true,Puppeteer将在后台操作你Chromium,换言之你将看不到浏览器的操作过程
// 设为false则相反,会在你电脑上打开浏览器,显示浏览器每一操作。
const browser = await puppeteer.launch({headless:false});
const page = await browser.newPage();

// 打开百度指数
await page.goto(BAIDU_INDEX_URL);

// 模拟登陆
await page.click('...');
await page.waitForSelecto('...');
// 输入百度账号密码然后登录
await page.type('...','username');
await page.type('...','password');
await page.click('...');
await page.waitForNavigation();
console.log(':white_check_mark: 登录成功');

模拟移动鼠标,获取需要的数据

需要将页面滚动到趋势图的区域,然后移动鼠标到某个日期上,等待请求结束,tooltip显示数值,再截图保存图片。

// 获取chart第一天的坐标
const position = await page.evaluate(() => {
 const $image = document.querySelector('...');
 const $area = document.querySelector('...');
 const areaRect = $area.getBoundingClientRect();
 const imageRect = $image.getBoundingClientRect();

 // 滚动到图表可视化区域
 window.scrollBy(0, areaRect.top);

 return { x: imageRect.x, y: 200 };
});

// 移动鼠标,触发tooltip
await page.mouse.move(position.x, position.y);
await page.waitForSelector('...');

// 获取tooltip信息
const tooltipInfo = await page.evaluate(() => {
 const $tooltip = document.querySelector('...');
 const $title = $tooltip.querySelector('...');
 const $value = $tooltip.querySelector('...');
 const valueRect = $value.getBoundingClientRect();
 const padding = 5;

 return {
 title: $title.textContent.split(' ')[0],
 x: valueRect.x - padding,
 y: valueRect.y,
 width: valueRect.width + padding * 2,
 height: valueRect.height
 }
});

截图

计算数值的坐标,截图并用jimp对裁剪图片。

await page.screenshot({ path: imgPath });

// 对图片进行裁剪,只保留数字部分
const img = await jimp.read(imgPath);
await img.crop(tooltipInfo.x, tooltipInfo.y, tooltipInfo.width, tooltipInfo.height);
// 将图片放大一些,识别准确率会有提升
await img.scale(5);
await img.write(imgPath);

图像识别

这里我们用Tesseract来做图像识别,Tesseracts是Google开源的一款OCR工具,用来识别图片中的文字,并且可以通过训练提高准确率。github上已经有一个简单的node封装: node-tesseract ,需要你先安装Tesseract并设置到环境变量。

Tesseract.process(imgPath, (err, val) => {
if (err || val == null) {
 console.error(':x: 识别失败:' + imgPath);
 return;
}
console.log(val);

实际上未经训练的Tesseracts识别起来会有少数几个错误,比如把9开头的数字识别成`3,这里需要通过训练去提升Tesseracts的准确率,如果识别过程出现的问题都是一样的,也可以简单通过正则去修复这些问题。

封装

实现了以上几点后,只需组合起来就可以封装成一个百度指数爬虫node库。当然还有许多优化的方法,比如批量爬取,指定天数爬取等,只要在这个基础上实现都不难了。

const recognition = require('./src/recognition');
const Spider = require('./src/spider');

module.exports = {
 async run (word, options, puppeteerOptions = { headless: true }) {
 const spider = new Spider({ 
 imgDir, 
 ...options 
 }, puppeteerOptions);

 // 抓取数据
 await spider.run(word);

 // 读取抓取到的截图,做图像识别
 const wordDir = path.resolve(imgDir, word);
 const imgNames = fs.readdirSync(wordDir);
 const result = [];

 imgNames = imgNames.filter(item => path.extname(item) === '.png');

 for (let i = 0; i < imgNames.length; i++) {
 const imgPath = path.resolve(wordDir, imgNames[i]);
 const val = await recognition.run(imgPath);
 result.push(val);
 }

 return result;
 }
}

反爬虫

最后,如何抵挡这种爬虫呢,个人认为通过判断鼠标移动轨迹可能是一种方法。当然前端没有100%的反爬虫手段,我们能做的只是给爬虫增加一点难度。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript Object与Function使用
Jan 11 Javascript
Javascript中获取出错代码所在文件及行数的代码
Sep 23 Javascript
在vs2010中调试javascript代码方法
Feb 11 Javascript
jQuery动态添加的元素绑定事件处理函数代码
Aug 02 Javascript
jquery foreach使用示例
Sep 12 Javascript
js生成随机数之random函数随机示例
Dec 20 Javascript
jQuery实现网站添加高亮突出显示效果的方法
Jun 26 Javascript
浅谈EasyUi ComBotree树修改 父节点选择的问题
Nov 07 Javascript
jQuery电话号码验证实例
Jan 05 Javascript
Vue form 表单提交+ajax异步请求+分页效果
Apr 22 Javascript
详解vue前后台数据交互vue-resource文档
Jul 19 Javascript
微信小程序实现tab左右切换效果
Nov 15 Javascript
原生js调用json方法总结
Feb 22 #Javascript
babel的使用及安装配置教程
Feb 22 #Javascript
vue-cli中的babel配置文件.babelrc实例详解
Feb 22 #Javascript
利用adb shell和node.js实现抖音自动抢红包功能(推荐)
Feb 22 #Javascript
浅谈webpack打包生成的bundle.js文件过大的问题
Feb 22 #Javascript
babel之配置文件.babelrc入门详解
Feb 22 #Javascript
javascript填充默认头像方法
Feb 22 #Javascript
You might like
php 判断是否是中文/英文/数字示例代码
2013/09/30 PHP
php inc文件使用的风险和注意事项
2013/11/12 PHP
php实现的漂亮分页方法
2014/04/17 PHP
Symfony2学习笔记之控制器用法详解
2016/03/17 PHP
laravel5.4利用163邮箱发送邮件的步骤详解
2017/09/22 PHP
弹出遮罩层后禁止滚动效果【实现代码】
2016/04/29 Javascript
Node.js的Web模板引擎ejs的入门使用教程
2016/06/06 Javascript
JSONP跨域请求
2017/03/02 Javascript
使用jQuery实现一个类似GridView的编辑,更新,取消和删除的功能
2017/03/15 Javascript
详解如何用webpack打包一个网站应用项目
2017/07/12 Javascript
原生js中ajax访问的实例详解
2017/09/19 Javascript
基于element-ui的rules中正则表达式
2018/09/04 Javascript
vue-cli3 配置开发与测试环境详解
2019/05/17 Javascript
小程序如何构建骨架屏
2019/05/29 Javascript
JavaScript setInterval()与setTimeout()计时器
2019/12/27 Javascript
Vue中this.$nextTick的作用及用法
2020/02/04 Javascript
详细分析vue响应式原理
2020/06/22 Javascript
vue+canvas实现拼图小游戏
2020/09/18 Javascript
vue 函数调用加括号与不加括号的区别
2020/10/29 Javascript
Nuxt.js的路由跳转操作(页面跳转nuxt-link)
2020/11/06 Javascript
[01:58]DOTA2上海特级锦标赛现场采访:RTZ这个ID到底好不好
2016/03/25 DOTA
使用python实现滑动验证码功能
2019/08/05 Python
python 模拟银行转账功能过程详解
2019/08/06 Python
Python3.7将普通图片(png)转换为SVG图片格式(网站logo图标)动起来
2020/04/21 Python
英国度假别墅预订:Sykes Cottages
2017/06/12 全球购物
元旦文艺汇演主持词
2014/03/26 职场文书
授权委托书样本
2014/04/03 职场文书
学校安全生产月活动总结
2014/07/05 职场文书
工程部岗位职责
2015/02/10 职场文书
骨干教师个人总结
2015/02/11 职场文书
乱世佳人观后感
2015/06/08 职场文书
小学数学教师研修日志
2015/11/13 职场文书
研究生学习计划书应该怎么写?
2019/09/10 职场文书
实例讲解Python中sys.argv[]的用法
2021/06/03 Python
Python装饰器的练习题
2021/11/23 Python
Android RecyclerView实现九宫格效果
2022/06/28 Java/Android