Node: Puppeteer + 圖像識別 實現百度指數爬蟲

以前看過一篇腦洞大開的文章,介紹了各個大廠的前端反爬蟲技巧,但也正如此文所說,沒有100%的反爬蟲方法,本文介紹一種簡單的方法,來繞過全部這些前端反爬蟲手段。javascript

下面的代碼以百度指數爲例,代碼已經封裝成一個百度指數爬蟲node庫: https://github.com/Coffcer/baidu-index-spiderhtml

note: 請勿濫用爬蟲給他人添麻煩前端

百度指數的反爬蟲策略

觀察百度指數的界面,指數數據是一個趨勢圖,當鼠標懸浮在某一天的時候,會觸發兩個請求,將結果顯示在懸浮框裏面:java

按照常規思路,咱們先看下這個請求的內容:node

請求 1:git

請求 2:github

能夠發現,百度指數實際上在前端作了必定的反爬蟲策略。當鼠標移動到圖表上時,會觸發兩個請求,一個請求返回一段html,一個請求返回一張生成的圖片。html中並不包含實際數值,而是經過設置width和margin-left,來顯示圖片上的對應字符。而且請求參數上帶有res、res1這種咱們不知如何模擬的參數,因此用常規的模擬請求或者html爬取的方式,都很難爬到百度指數的數據。web

爬蟲思路

怎麼突破百度這種反爬蟲方法呢,其實也很簡單,就是徹底不去管他是如何反爬蟲的。咱們只需模擬用戶操做,將須要的數值截圖下來,作圖像識別就行。步驟大概是:npm

  1. 模擬登陸
  2. 打開指數頁面
  3. 鼠標移動到指定日期
  4. 等待請求結束,截取數值部分的圖片
  5. 圖像識別獲得值
  6. 循環第3~5步,就獲得每個日期對應的值

這種方法理論上能爬任何網站的內容,接下來咱們來一步步實現爬蟲,下面會用到的庫:api

  • 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則相反,會在你電腦上打開瀏覽器,顯示瀏覽器每一操做。
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('✅ 登陸成功');
複製代碼

模擬移動鼠標,獲取須要的數據

須要將頁面滾動到趨勢圖的區域,而後移動鼠標到某個日期上,等待請求結束,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('❌ 識別失敗:' + 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%的反爬蟲手段,咱們能作的只是給爬蟲增長一點難度。

相關文章
相關標籤/搜索