Node: Puppeteer + 图像识别 实现百度指数爬虫 #4

Description

@Coffcer

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

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

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

百度指数的反爬虫策略

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

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

请求 1:


请求 2:

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

爬虫思路

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

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

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

  • puppeteer 模拟浏览器操作
  • node-tesseract tesseract的封装,用来做图像识别
  • 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则相反,会在你电脑上打开浏览器,显示浏览器每一操作。constbrowser=awaitpuppeteer.launch({headless:false});constpage=awaitbrowser.newPage();// 打开百度指数awaitpage.goto(BAIDU_INDEX_URL);// 模拟登陆awaitpage.click('...');awaitpage.waitForSelecto('...');// 输入百度账号密码然后登录awaitpage.type('...','username');awaitpage.type('...','password');awaitpage.click('...');awaitpage.waitForNavigation();console.log('✅ 登录成功');

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

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

// 获取chart第一天的坐标constposition=awaitpage.evaluate(()=>{const$image=document.querySelector('...');const$area=document.querySelector('...');constareaRect=$area.getBoundingClientRect();constimageRect=$image.getBoundingClientRect();// 滚动到图表可视化区域window.scrollBy(0,areaRect.top);return{x: imageRect.x,y: 200}});// 移动鼠标,触发tooltipawaitpage.mouse.move(position.x,position.y);awaitpage.waitForSelector('...');// 获取tooltip信息consttooltipInfo=awaitpage.evaluate(()=>{const$tooltip=document.querySelector('...');const$title=$tooltip.querySelector('...');const$value=$tooltip.querySelector('...');constvalueRect=$value.getBoundingClientRect();constpadding=5;return{title: $title.textContent.split(' ')[0],x: valueRect.x-padding,y: valueRect.y,width: valueRect.width+padding*2,height: valueRect.height}});

截图

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

awaitpage.screenshot({path: imgPath});// 对图片进行裁剪,只保留数字部分constimg=awaitjimp.read(imgPath);awaitimg.crop(tooltipInfo.x,tooltipInfo.y,tooltipInfo.width,tooltipInfo.height);// 将图片放大一些,识别准确率会有提升awaitimg.scale(5);awaitimg.write(imgPath);

图像识别

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

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

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

封装

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

constrecognition=require('./src/recognition');constSpider=require('./src/spider');module.exports={asyncrun(word,options,puppeteerOptions={headless: true}){constspider=newSpider({ imgDir, ...options},puppeteerOptions);// 抓取数据awaitspider.run(word);// 读取抓取到的截图,做图像识别constwordDir=path.resolve(imgDir,word);constimgNames=fs.readdirSync(wordDir);constresult=[];imgNames=imgNames.filter(item=>path.extname(item)==='.png');for(leti=0;i<imgNames.length;i++){constimgPath=path.resolve(wordDir,imgNames[i]);constval=awaitrecognition.run(imgPath);result.push(val);}returnresult;}}

反爬虫

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
       blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      
      Skip to content

      Node: Puppeteer + 图像识别 实现百度指数爬虫 #4

      Description

      @Coffcer

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

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

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

      百度指数的反爬虫策略

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

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

      请求 1:


      请求 2:

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

      爬虫思路

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

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

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

      • puppeteer 模拟浏览器操作
      • node-tesseract tesseract的封装,用来做图像识别
      • 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则相反,会在你电脑上打开浏览器,显示浏览器每一操作。constbrowser=awaitpuppeteer.launch({headless:false});constpage=awaitbrowser.newPage();// 打开百度指数awaitpage.goto(BAIDU_INDEX_URL);// 模拟登陆awaitpage.click('...');awaitpage.waitForSelecto('...');// 输入百度账号密码然后登录awaitpage.type('...','username');awaitpage.type('...','password');awaitpage.click('...');awaitpage.waitForNavigation();console.log('✅ 登录成功');

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

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

      // 获取chart第一天的坐标constposition=awaitpage.evaluate(()=>{const$image=document.querySelector('...');const$area=document.querySelector('...');constareaRect=$area.getBoundingClientRect();constimageRect=$image.getBoundingClientRect();// 滚动到图表可视化区域window.scrollBy(0,areaRect.top);return{x: imageRect.x,y: 200}});// 移动鼠标,触发tooltipawaitpage.mouse.move(position.x,position.y);awaitpage.waitForSelector('...');// 获取tooltip信息consttooltipInfo=awaitpage.evaluate(()=>{const$tooltip=document.querySelector('...');const$title=$tooltip.querySelector('...');const$value=$tooltip.querySelector('...');constvalueRect=$value.getBoundingClientRect();constpadding=5;return{title: $title.textContent.split(' ')[0],x: valueRect.x-padding,y: valueRect.y,width: valueRect.width+padding*2,height: valueRect.height}});

      截图

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

      awaitpage.screenshot({path: imgPath});// 对图片进行裁剪,只保留数字部分constimg=awaitjimp.read(imgPath);awaitimg.crop(tooltipInfo.x,tooltipInfo.y,tooltipInfo.width,tooltipInfo.height);// 将图片放大一些,识别准确率会有提升awaitimg.scale(5);awaitimg.write(imgPath);

      图像识别

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

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

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

      封装

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

      constrecognition=require('./src/recognition');constSpider=require('./src/spider');module.exports={asyncrun(word,options,puppeteerOptions={headless: true}){constspider=newSpider({ imgDir, ...options},puppeteerOptions);// 抓取数据awaitspider.run(word);// 读取抓取到的截图,做图像识别constwordDir=path.resolve(imgDir,word);constimgNames=fs.readdirSync(wordDir);constresult=[];imgNames=imgNames.filter(item=>path.extname(item)==='.png');for(leti=0;i<imgNames.length;i++){constimgPath=path.resolve(wordDir,imgNames[i]);constval=awaitrecognition.run(imgPath);result.push(val);}returnresult;}}

      反爬虫

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

      Activity

      Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        No labels
        No labels

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
          Skip to content

          Node: Puppeteer + 图像识别 实现百度指数爬虫 #4

          Description

          @Coffcer

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

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

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

          百度指数的反爬虫策略

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

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

          请求 1:


          请求 2:

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

          爬虫思路

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

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

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

          • puppeteer 模拟浏览器操作
          • node-tesseract tesseract的封装,用来做图像识别
          • 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则相反,会在你电脑上打开浏览器,显示浏览器每一操作。constbrowser=awaitpuppeteer.launch({headless:false});constpage=awaitbrowser.newPage();// 打开百度指数awaitpage.goto(BAIDU_INDEX_URL);// 模拟登陆awaitpage.click('...');awaitpage.waitForSelecto('...');// 输入百度账号密码然后登录awaitpage.type('...','username');awaitpage.type('...','password');awaitpage.click('...');awaitpage.waitForNavigation();console.log('✅ 登录成功');

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

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

          // 获取chart第一天的坐标constposition=awaitpage.evaluate(()=>{const$image=document.querySelector('...');const$area=document.querySelector('...');constareaRect=$area.getBoundingClientRect();constimageRect=$image.getBoundingClientRect();// 滚动到图表可视化区域window.scrollBy(0,areaRect.top);return{x: imageRect.x,y: 200}});// 移动鼠标,触发tooltipawaitpage.mouse.move(position.x,position.y);awaitpage.waitForSelector('...');// 获取tooltip信息consttooltipInfo=awaitpage.evaluate(()=>{const$tooltip=document.querySelector('...');const$title=$tooltip.querySelector('...');const$value=$tooltip.querySelector('...');constvalueRect=$value.getBoundingClientRect();constpadding=5;return{title: $title.textContent.split(' ')[0],x: valueRect.x-padding,y: valueRect.y,width: valueRect.width+padding*2,height: valueRect.height}});

          截图

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

          awaitpage.screenshot({path: imgPath});// 对图片进行裁剪,只保留数字部分constimg=awaitjimp.read(imgPath);awaitimg.crop(tooltipInfo.x,tooltipInfo.y,tooltipInfo.width,tooltipInfo.height);// 将图片放大一些,识别准确率会有提升awaitimg.scale(5);awaitimg.write(imgPath);

          图像识别

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

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

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

          封装

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

          constrecognition=require('./src/recognition');constSpider=require('./src/spider');module.exports={asyncrun(word,options,puppeteerOptions={headless: true}){constspider=newSpider({ imgDir, ...options},puppeteerOptions);// 抓取数据awaitspider.run(word);// 读取抓取到的截图,做图像识别constwordDir=path.resolve(imgDir,word);constimgNames=fs.readdirSync(wordDir);constresult=[];imgNames=imgNames.filter(item=>path.extname(item)==='.png');for(leti=0;i<imgNames.length;i++){constimgPath=path.resolve(wordDir,imgNames[i]);constval=awaitrecognition.run(imgPath);result.push(val);}returnresult;}}

          反爬虫

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

          Activity

          Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            No labels
            No labels

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              Node: Puppeteer + 图像识别 实现百度指数爬虫 #4

              Description

              @Coffcer

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

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

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

              百度指数的反爬虫策略

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

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

              请求 1:


              请求 2:

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

              爬虫思路

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

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

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

              • puppeteer 模拟浏览器操作
              • node-tesseract tesseract的封装,用来做图像识别
              • 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则相反,会在你电脑上打开浏览器,显示浏览器每一操作。constbrowser=awaitpuppeteer.launch({headless:false});constpage=awaitbrowser.newPage();// 打开百度指数awaitpage.goto(BAIDU_INDEX_URL);// 模拟登陆awaitpage.click('...');awaitpage.waitForSelecto('...');// 输入百度账号密码然后登录awaitpage.type('...','username');awaitpage.type('...','password');awaitpage.click('...');awaitpage.waitForNavigation();console.log('✅ 登录成功');

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

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

              // 获取chart第一天的坐标constposition=awaitpage.evaluate(()=>{const$image=document.querySelector('...');const$area=document.querySelector('...');constareaRect=$area.getBoundingClientRect();constimageRect=$image.getBoundingClientRect();// 滚动到图表可视化区域window.scrollBy(0,areaRect.top);return{x: imageRect.x,y: 200}});// 移动鼠标,触发tooltipawaitpage.mouse.move(position.x,position.y);awaitpage.waitForSelector('...');// 获取tooltip信息consttooltipInfo=awaitpage.evaluate(()=>{const$tooltip=document.querySelector('...');const$title=$tooltip.querySelector('...');const$value=$tooltip.querySelector('...');constvalueRect=$value.getBoundingClientRect();constpadding=5;return{title: $title.textContent.split(' ')[0],x: valueRect.x-padding,y: valueRect.y,width: valueRect.width+padding*2,height: valueRect.height}});

              截图

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

              awaitpage.screenshot({path: imgPath});// 对图片进行裁剪,只保留数字部分constimg=awaitjimp.read(imgPath);awaitimg.crop(tooltipInfo.x,tooltipInfo.y,tooltipInfo.width,tooltipInfo.height);// 将图片放大一些,识别准确率会有提升awaitimg.scale(5);awaitimg.write(imgPath);

              图像识别

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

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

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

              封装

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

              constrecognition=require('./src/recognition');constSpider=require('./src/spider');module.exports={asyncrun(word,options,puppeteerOptions={headless: true}){constspider=newSpider({ imgDir, ...options},puppeteerOptions);// 抓取数据awaitspider.run(word);// 读取抓取到的截图,做图像识别constwordDir=path.resolve(imgDir,word);constimgNames=fs.readdirSync(wordDir);constresult=[];imgNames=imgNames.filter(item=>path.extname(item)==='.png');for(leti=0;i<imgNames.length;i++){constimgPath=path.resolve(wordDir,imgNames[i]);constval=awaitrecognition.run(imgPath);result.push(val);}returnresult;}}

              反爬虫

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

              Activity

              Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                No labels
                No labels

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
                  Skip to content

                  Node: Puppeteer + 图像识别 实现百度指数爬虫 #4

                  Description

                  @Coffcer

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

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

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

                  百度指数的反爬虫策略

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

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

                  请求 1:


                  请求 2:

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

                  爬虫思路

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

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

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

                  • puppeteer 模拟浏览器操作
                  • node-tesseract tesseract的封装,用来做图像识别
                  • 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则相反,会在你电脑上打开浏览器,显示浏览器每一操作。constbrowser=awaitpuppeteer.launch({headless:false});constpage=awaitbrowser.newPage();// 打开百度指数awaitpage.goto(BAIDU_INDEX_URL);// 模拟登陆awaitpage.click('...');awaitpage.waitForSelecto('...');// 输入百度账号密码然后登录awaitpage.type('...','username');awaitpage.type('...','password');awaitpage.click('...');awaitpage.waitForNavigation();console.log('✅ 登录成功');

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

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

                  // 获取chart第一天的坐标constposition=awaitpage.evaluate(()=>{const$image=document.querySelector('...');const$area=document.querySelector('...');constareaRect=$area.getBoundingClientRect();constimageRect=$image.getBoundingClientRect();// 滚动到图表可视化区域window.scrollBy(0,areaRect.top);return{x: imageRect.x,y: 200}});// 移动鼠标,触发tooltipawaitpage.mouse.move(position.x,position.y);awaitpage.waitForSelector('...');// 获取tooltip信息consttooltipInfo=awaitpage.evaluate(()=>{const$tooltip=document.querySelector('...');const$title=$tooltip.querySelector('...');const$value=$tooltip.querySelector('...');constvalueRect=$value.getBoundingClientRect();constpadding=5;return{title: $title.textContent.split(' ')[0],x: valueRect.x-padding,y: valueRect.y,width: valueRect.width+padding*2,height: valueRect.height}});

                  截图

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

                  awaitpage.screenshot({path: imgPath});// 对图片进行裁剪,只保留数字部分constimg=awaitjimp.read(imgPath);awaitimg.crop(tooltipInfo.x,tooltipInfo.y,tooltipInfo.width,tooltipInfo.height);// 将图片放大一些,识别准确率会有提升awaitimg.scale(5);awaitimg.write(imgPath);

                  图像识别

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

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

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

                  封装

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

                  constrecognition=require('./src/recognition');constSpider=require('./src/spider');module.exports={asyncrun(word,options,puppeteerOptions={headless: true}){constspider=newSpider({ imgDir, ...options},puppeteerOptions);// 抓取数据awaitspider.run(word);// 读取抓取到的截图,做图像识别constwordDir=path.resolve(imgDir,word);constimgNames=fs.readdirSync(wordDir);constresult=[];imgNames=imgNames.filter(item=>path.extname(item)==='.png');for(leti=0;i<imgNames.length;i++){constimgPath=path.resolve(wordDir,imgNames[i]);constval=awaitrecognition.run(imgPath);result.push(val);}returnresult;}}

                  反爬虫

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

                  Activity

                  Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    No labels
                    No labels

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                      Skip to content

                      Node: Puppeteer + 图像识别 实现百度指数爬虫 #4

                      Description

                      @Coffcer

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

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

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

                      百度指数的反爬虫策略

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

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

                      请求 1:


                      请求 2:

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

                      爬虫思路

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

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

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

                      • puppeteer 模拟浏览器操作
                      • node-tesseract tesseract的封装,用来做图像识别
                      • 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则相反,会在你电脑上打开浏览器,显示浏览器每一操作。constbrowser=awaitpuppeteer.launch({headless:false});constpage=awaitbrowser.newPage();// 打开百度指数awaitpage.goto(BAIDU_INDEX_URL);// 模拟登陆awaitpage.click('...');awaitpage.waitForSelecto('...');// 输入百度账号密码然后登录awaitpage.type('...','username');awaitpage.type('...','password');awaitpage.click('...');awaitpage.waitForNavigation();console.log('✅ 登录成功');

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

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

                      // 获取chart第一天的坐标constposition=awaitpage.evaluate(()=>{const$image=document.querySelector('...');const$area=document.querySelector('...');constareaRect=$area.getBoundingClientRect();constimageRect=$image.getBoundingClientRect();// 滚动到图表可视化区域window.scrollBy(0,areaRect.top);return{x: imageRect.x,y: 200}});// 移动鼠标,触发tooltipawaitpage.mouse.move(position.x,position.y);awaitpage.waitForSelector('...');// 获取tooltip信息consttooltipInfo=awaitpage.evaluate(()=>{const$tooltip=document.querySelector('...');const$title=$tooltip.querySelector('...');const$value=$tooltip.querySelector('...');constvalueRect=$value.getBoundingClientRect();constpadding=5;return{title: $title.textContent.split(' ')[0],x: valueRect.x-padding,y: valueRect.y,width: valueRect.width+padding*2,height: valueRect.height}});

                      截图

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

                      awaitpage.screenshot({path: imgPath});// 对图片进行裁剪,只保留数字部分constimg=awaitjimp.read(imgPath);awaitimg.crop(tooltipInfo.x,tooltipInfo.y,tooltipInfo.width,tooltipInfo.height);// 将图片放大一些,识别准确率会有提升awaitimg.scale(5);awaitimg.write(imgPath);

                      图像识别

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

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

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

                      封装

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

                      constrecognition=require('./src/recognition');constSpider=require('./src/spider');module.exports={asyncrun(word,options,puppeteerOptions={headless: true}){constspider=newSpider({ imgDir, ...options},puppeteerOptions);// 抓取数据awaitspider.run(word);// 读取抓取到的截图,做图像识别constwordDir=path.resolve(imgDir,word);constimgNames=fs.readdirSync(wordDir);constresult=[];imgNames=imgNames.filter(item=>path.extname(item)==='.png');for(leti=0;i<imgNames.length;i++){constimgPath=path.resolve(wordDir,imgNames[i]);constval=awaitrecognition.run(imgPath);result.push(val);}returnresult;}}

                      反爬虫

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

                      Activity

                      Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        No labels
                        No labels

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                          Skip to content

                          Node: Puppeteer + 图像识别 实现百度指数爬虫 #4

                          Description

                          @Coffcer

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

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

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

                          百度指数的反爬虫策略

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

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

                          请求 1:


                          请求 2:

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

                          爬虫思路

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

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

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

                          • puppeteer 模拟浏览器操作
                          • node-tesseract tesseract的封装,用来做图像识别
                          • 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则相反,会在你电脑上打开浏览器,显示浏览器每一操作。constbrowser=awaitpuppeteer.launch({headless:false});constpage=awaitbrowser.newPage();// 打开百度指数awaitpage.goto(BAIDU_INDEX_URL);// 模拟登陆awaitpage.click('...');awaitpage.waitForSelecto('...');// 输入百度账号密码然后登录awaitpage.type('...','username');awaitpage.type('...','password');awaitpage.click('...');awaitpage.waitForNavigation();console.log('✅ 登录成功');

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

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

                          // 获取chart第一天的坐标constposition=awaitpage.evaluate(()=>{const$image=document.querySelector('...');const$area=document.querySelector('...');constareaRect=$area.getBoundingClientRect();constimageRect=$image.getBoundingClientRect();// 滚动到图表可视化区域window.scrollBy(0,areaRect.top);return{x: imageRect.x,y: 200}});// 移动鼠标,触发tooltipawaitpage.mouse.move(position.x,position.y);awaitpage.waitForSelector('...');// 获取tooltip信息consttooltipInfo=awaitpage.evaluate(()=>{const$tooltip=document.querySelector('...');const$title=$tooltip.querySelector('...');const$value=$tooltip.querySelector('...');constvalueRect=$value.getBoundingClientRect();constpadding=5;return{title: $title.textContent.split(' ')[0],x: valueRect.x-padding,y: valueRect.y,width: valueRect.width+padding*2,height: valueRect.height}});

                          截图

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

                          awaitpage.screenshot({path: imgPath});// 对图片进行裁剪,只保留数字部分constimg=awaitjimp.read(imgPath);awaitimg.crop(tooltipInfo.x,tooltipInfo.y,tooltipInfo.width,tooltipInfo.height);// 将图片放大一些,识别准确率会有提升awaitimg.scale(5);awaitimg.write(imgPath);

                          图像识别

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

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

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

                          封装

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

                          constrecognition=require('./src/recognition');constSpider=require('./src/spider');module.exports={asyncrun(word,options,puppeteerOptions={headless: true}){constspider=newSpider({ imgDir, ...options},puppeteerOptions);// 抓取数据awaitspider.run(word);// 读取抓取到的截图,做图像识别constwordDir=path.resolve(imgDir,word);constimgNames=fs.readdirSync(wordDir);constresult=[];imgNames=imgNames.filter(item=>path.extname(item)==='.png');for(leti=0;i<imgNames.length;i++){constimgPath=path.resolve(wordDir,imgNames[i]);constval=awaitrecognition.run(imgPath);result.push(val);}returnresult;}}

                          反爬虫

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

                          Activity

                          Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            No labels
                            No labels

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                              Skip to content

                              Node: Puppeteer + 图像识别 实现百度指数爬虫 #4

                              Description

                              @Coffcer

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

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

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

                              百度指数的反爬虫策略

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

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

                              请求 1:


                              请求 2:

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

                              爬虫思路

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

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

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

                              • puppeteer 模拟浏览器操作
                              • node-tesseract tesseract的封装,用来做图像识别
                              • 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则相反,会在你电脑上打开浏览器,显示浏览器每一操作。constbrowser=awaitpuppeteer.launch({headless:false});constpage=awaitbrowser.newPage();// 打开百度指数awaitpage.goto(BAIDU_INDEX_URL);// 模拟登陆awaitpage.click('...');awaitpage.waitForSelecto('...');// 输入百度账号密码然后登录awaitpage.type('...','username');awaitpage.type('...','password');awaitpage.click('...');awaitpage.waitForNavigation();console.log('✅ 登录成功');

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

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

                              // 获取chart第一天的坐标constposition=awaitpage.evaluate(()=>{const$image=document.querySelector('...');const$area=document.querySelector('...');constareaRect=$area.getBoundingClientRect();constimageRect=$image.getBoundingClientRect();// 滚动到图表可视化区域window.scrollBy(0,areaRect.top);return{x: imageRect.x,y: 200}});// 移动鼠标,触发tooltipawaitpage.mouse.move(position.x,position.y);awaitpage.waitForSelector('...');// 获取tooltip信息consttooltipInfo=awaitpage.evaluate(()=>{const$tooltip=document.querySelector('...');const$title=$tooltip.querySelector('...');const$value=$tooltip.querySelector('...');constvalueRect=$value.getBoundingClientRect();constpadding=5;return{title: $title.textContent.split(' ')[0],x: valueRect.x-padding,y: valueRect.y,width: valueRect.width+padding*2,height: valueRect.height}});

                              截图

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

                              awaitpage.screenshot({path: imgPath});// 对图片进行裁剪,只保留数字部分constimg=awaitjimp.read(imgPath);awaitimg.crop(tooltipInfo.x,tooltipInfo.y,tooltipInfo.width,tooltipInfo.height);// 将图片放大一些,识别准确率会有提升awaitimg.scale(5);awaitimg.write(imgPath);

                              图像识别

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

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

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

                              封装

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

                              constrecognition=require('./src/recognition');constSpider=require('./src/spider');module.exports={asyncrun(word,options,puppeteerOptions={headless: true}){constspider=newSpider({ imgDir, ...options},puppeteerOptions);// 抓取数据awaitspider.run(word);// 读取抓取到的截图,做图像识别constwordDir=path.resolve(imgDir,word);constimgNames=fs.readdirSync(wordDir);constresult=[];imgNames=imgNames.filter(item=>path.extname(item)==='.png');for(leti=0;i<imgNames.length;i++){constimgPath=path.resolve(wordDir,imgNames[i]);constval=awaitrecognition.run(imgPath);result.push(val);}returnresult;}}

                              反爬虫

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

                              Activity

                              Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                No labels
                                No labels

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions