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 相关文章推荐
JS实现从网页顶部掉下弹出层效果的方法
Aug 06 Javascript
Kindeditor在线文本编辑器如何过滤HTML
Apr 14 Javascript
jquery实现全选功能效果的实现代码
May 05 Javascript
JS使用正则表达式过滤多个词语并替换为相同长度星号的方法
Aug 03 Javascript
scroll事件实现监控滚动条并分页显示(zepto.js)
Dec 18 Javascript
微信小程序商城项目之购物数量加减(3)
Apr 17 Javascript
单行 JS 实现移动端金钱格式的输入规则
May 22 Javascript
JavaScript标准对象_动力节点Java学院整理
Jun 27 Javascript
微信小程序实现图片放大预览功能
Oct 22 Javascript
Swiper自定义分页器使用详解
Dec 28 Javascript
微信小程序使用车牌号输入法的示例代码
Aug 20 Javascript
vue实现简易音乐播放器
Aug 14 Vue.js
原生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 进度条实现代码
2009/03/10 PHP
thinkPHP框架实现图像裁剪、缩放、加水印的方法
2017/03/14 PHP
Web跨浏览器进程通信(Web跨域)
2013/04/17 Javascript
JQuery AJAX 中文乱码问题解决
2013/06/05 Javascript
使用js简单实现了tree树菜单
2013/11/20 Javascript
JavaScript之Object类型介绍
2015/04/01 Javascript
JQuery中DOM事件合成用法实例分析
2015/06/13 Javascript
Node.js+Express配置入门教程
2016/05/19 Javascript
JavaScript中用let语句声明作用域的用法讲解
2016/05/20 Javascript
webpack常用配置项配置文件介绍
2016/11/07 Javascript
jQuery学习之DOM节点的插入方法总结
2017/01/22 Javascript
JS与jQuery实现子窗口获取父窗口元素值的方法
2017/04/17 jQuery
详解vue.js2.0父组件点击触发子组件方法
2017/05/10 Javascript
Kotlin学习第一步 kotlin语法特性
2017/05/25 Javascript
JavaScript反弹动画效果的实现代码
2017/07/13 Javascript
NodeJS收发GET和POST请求的示例代码
2017/08/25 NodeJs
基于js 字符串indexof与search方法的区别(详解)
2017/12/04 Javascript
webpack本地开发环境无法用IP访问的解决方法
2018/03/20 Javascript
JS中判断某个字符串是否包含另一个字符串的五种方法
2018/05/03 Javascript
jQuery实现的模仿雨滴下落动画效果
2018/12/11 jQuery
12个提高JavaScript技能的概念(小结)
2019/05/09 Javascript
Vue双向绑定实现原理与方法详解
2020/05/07 Javascript
python实现的简单窗口倒计时界面实例
2015/05/05 Python
Django学习笔记之Class-Based-View
2017/02/15 Python
基于Python批量生成指定尺寸缩略图代码实例
2019/11/20 Python
2020新版本pycharm+anaconda+opencv+pyqt环境配置学习笔记,亲测可用
2020/03/24 Python
如何表示python中的相对路径
2020/07/08 Python
css3新单位vw、vh的使用教程
2018/03/23 HTML / CSS
JD Sports西班牙:英国领先的运动服装公司
2020/01/06 全球购物
亿阳信通股份有限公司C#笔试题
2016/12/06 面试题
运动会入场词100字
2014/02/06 职场文书
文明礼仪演讲稿
2014/05/12 职场文书
党的群众路线教育实践活动批评与自我批评范文
2014/10/16 职场文书
Sql Server之数据类型详解
2022/02/28 SQL Server
Python3使用Qt5来实现简易的五子棋小游戏
2022/05/02 Python
python库Tsmoothie模块数据平滑化异常点抓取
2022/06/10 Python