以前看過一篇腦洞大開的文章,介紹了各個大廠的前端反爬蟲技巧,但也正如此文所說,沒有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
這種方法理論上能爬任何網站的內容,接下來咱們來一步步實現爬蟲,下面會用到的庫:api
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%的反爬蟲手段,咱們能作的只是給爬蟲增長一點難度。