本篇文章針對大家熟知的技術站點作為目標進行技術實踐。
確定需求
訪問目標網站並按照篩選條件(關鍵詞、日期、作者)進行檢索並獲取返回數據中的目標數據。進行技術拆分如下:
- 打開目標網站
- 找到輸入框元素輸入關鍵詞,找到日期元素設置日期,找到搜索按鈕觸發搜索動作
- 解析搜索返回的html元素構造目標數據
- 將目標數據保存
編寫代碼
'use strict';
const puppeteer = require('puppeteer');
const csv = require('fast-csv');
const fs = require('fs');
(async () => {
const startUrl = 'https://www.infoq.cn/';
const keyWord = 'CQRS';
const browser = await puppeteer.launch({
slowMo: 100, // 放慢速度
headless: false, // 是否有頭
defaultViewport: {// 界面設置
width: 1820,
height: 1080,
},
ignoreHTTPSErrors: false, // 忽略 https 報錯
args: ['--start-maximized', '--no-sandbox', '--disable-setuid-sandbox'],
});
const page = await browser.newPage();
await page.goto(startUrl).catch(error => console.log(error));
await page.waitFor(1 * 1000);
await page.click('.search,.iconfont');
await page.type('.search-input', keyWord, { delay: 100 });
const newPagePromise = new Promise(x => browser.once('targetcreated', target => x(target.page())));
await page.click('.search,.iconfont');
const targetPage = await newPagePromise;
const dataCount = await targetPage.$eval('.search-body-main-tips span', el => el && el.innerHTML).catch(error => console.error(error));
if (dataCount && dataCount > 0) {
const dataEle = await targetPage.$$('.search-item');
console.log(dataEle.length);
const stream = fs.createWriteStream('infoq.csv');
const csvStream = csv.format({ headers: true });
csvStream.pipe(stream).on('end', process.exit);
for (let index = 0; index < dataEle.length; index++) {
const element = dataEle[index];
const title = await element.$eval('a', el => el && el.innerHTML).catch(error => console.error(error))
const desc = await element.$eval('.desc', el => el && el.innerHTML).catch(error => console.error(error))
csvStream.write({
標題: title || '',
摘要: desc || '',
});
}
csvStream.end(() => { console.log('寫入完畢'); });
}
await targetPage.screenshot({ path: 'infoq.png' });
await browser.close();
})();
具體的如下
總結
上面的例子還是比較簡單的,站點本身是資訊站(其實有搜索接口根本不需要解析html😂),例子是一個簡單的爬蟲流程,方便了解puppeteer的能力,下面我也總結一下工作中和自己項目的實際情況。
- 上述例子還不能算是一個完整的應用,根據主要業務分析實際應用大概是這個樣子: 爬蟲的主要業務變化部分有目標站點、篩選條件、站點操作、數據解析、數據落地,這些都可以通過代碼搞定,也就是代碼是變化的,所以一個可用的爬蟲的應用是代碼是可動態調整的,根據上面的動態點將一個爬蟲業務抽象成一個Task,
- 它要具備一個參數輸入界面(動態輸入目標站點、篩選條件等腳本所需參數),每個task的業務不一樣,腳本參數不一樣,就需要一個動態表單可配置腳本所需參數(Ant design搞一個也夠用了);
- 站點操作會根據實際情況而希望能夠動態調整腳本代碼,那就引入一個在線的vscode編輯器(實際開發中通常都是測試好的腳本才會寫進去,這個地方引入有些牽強,主要是線上發現一些小bug需要快速解決);
- 數據解析完了后需要落地,這包括保存到本地、推到api接口、或是下載文件成功推送標識等皆可動態參數和腳本實現;
- Task主要流程還有一個重要的點就是觸發需要一個定時任務方便用戶設置各種周期性的需求,不管是考慮用戶需求還是躲避爬蟲頻率限制都很有必要。
- Task主要流程完成后還有很重要的基礎設施:
- 應用快速部署,當然要用到docker了。因為puppeteer其實是依賴了Chromium的能力,所以你需要在容器里部署一套可運行的chromium,這里面牽扯到字體、環境問題等麻煩事情,這里我推薦一個使用的鏡像browserless/chrome,這個鏡像大家可以根據自己需求定制,視頻中的界面演示即FullHead,容器環境往往不是完整的操作系統(應該沒人用完整的windows環境吧)而是盡量小的linux版本(我本人使用的是egg引入puppeteer),那么就不可能有界面,即無法實現FullHead,FullHead可以幫我們規避一些站點(部分站點檢測手段比較強)的反爬手段,這里使用的是xvfb實現了FullHead。
- 網絡檢測,無論是切換容器網絡還是在應用中使用ip代理我都沒搞過就不說了。分布式爬蟲我目前沒實現所以這里就不說了。
- 實際使用中遇到的一些問題,資訊站渲染的比較隨意,解析比較復雜,有些數據只是展示而無固定標簽或規律展示,puppeteer的元素選擇器有css和xpath,但是這個和大家平常用jquery不太一樣,最好去看看標准,它的xpath解析就不完整(比如string(.),因為puppeteer要包返回裝類型所以就完蛋了,我提了issue,答復目前也只能遍歷了,或者有誰知道可以提醒我下)。網絡問題,訪問時渲染有時候會超時這個要自己根據需求搞了。