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 相关文章推荐
你需要知道的JavsScript可以做什么?
Jun 29 Javascript
js截取字符串的两种方法及区别详解
Nov 05 Javascript
js图片向右一张张滚动效果实例代码
Nov 23 Javascript
原生JS实现美图瀑布流布局赏析
Sep 07 Javascript
Vue 单文件中的数据传递示例
Mar 21 Javascript
ReactNative中使用Redux架构总结
Dec 15 Javascript
javaScript中&quot;==&quot;和&quot;===&quot;的区别详解
Mar 16 Javascript
JS 使用 window对象的print方法实现分页打印功能
May 16 Javascript
JS实现仿微信支付弹窗功能
Jun 25 Javascript
Vue在页面数据渲染完成之后的调用方法
Sep 11 Javascript
React.js组件实现拖拽排序组件功能过程解析
Apr 27 Javascript
原生js实现自定义难度的扫雷游戏
Jan 22 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中文URL编解码(urlencode()rawurlencode()
2010/07/03 PHP
详解php设置session(过期、失效、有效期)
2015/11/12 PHP
PHP闭包定义与使用简单示例
2018/04/13 PHP
调试Node.JS的辅助工具(NodeWatcher)
2012/01/04 Javascript
打开新窗口关闭当前页面不弹出关闭提示js代码
2013/03/18 Javascript
select、radio表单回显功能实现避免使用jquery载入赋值
2013/06/08 Javascript
a标签的href和onclick 的事件的区别介绍
2013/07/26 Javascript
下拉列表选择项的选中在不同浏览器中的兼容性问题探讨
2013/09/18 Javascript
javascript中文本框中输入法切换的问题
2013/12/10 Javascript
node.js中的http.createServer方法使用说明
2014/12/14 Javascript
jQuery往返城市和日期查询实例讲解
2015/10/09 Javascript
Bootstrap框架动态生成Web页面文章内目录的方法
2016/05/12 Javascript
jQuery中get方法用法分析
2016/12/07 Javascript
详解vue.js根据不同环境(正式、测试)打包到不同目录
2018/07/13 Javascript
vue循环中点击选中再点击取消(单选)的实现
2020/09/10 Javascript
[01:14:31]Secret vs VG 2018国际邀请赛淘汰赛BO3 第一场 8.23
2018/08/24 DOTA
Python实现全排列的打印
2018/08/18 Python
python的中异常处理机制
2018/08/30 Python
简单了解python的break、continue、pass
2019/07/08 Python
wxPython实现带颜色的进度条
2019/11/19 Python
Python for i in range ()用法详解
2020/09/18 Python
Python爬虫JSON及JSONPath运行原理详解
2020/06/04 Python
关于python的缩进规则的知识点详解
2020/06/22 Python
Python 常用日期处理 -- calendar 与 dateutil 模块的使用
2020/09/02 Python
Selenium 配置启动项参数的方法
2020/12/04 Python
详解CSS3 弹性布局快速入门
2019/06/06 HTML / CSS
YSL Beauty加拿大官方商城:圣罗兰美妆加拿大
2017/05/15 全球购物
医学院护理专业应届生求职信
2013/11/12 职场文书
会计专业大学生求职信范文
2014/01/28 职场文书
美德好少年主要事迹
2014/01/29 职场文书
优秀党员自我评价范文
2014/09/15 职场文书
1000字打架检讨书
2014/11/03 职场文书
会计实训总结范文
2015/08/03 职场文书
python中os.path.join()函数实例用法
2021/05/26 Python
python 经纬度求两点距离、三点面积操作
2021/06/03 Python
HTML基础详解(上)
2021/10/16 HTML / CSS