摘要:所以需要理解運行的原理,才能方便優化。如果啟動時能綁定到某個核上也能提升速度單核上進行進程切換耗費的時間更少。優化執行流程接下來我們再多帶帶優化對應的頁面。參考文章性能優化與執行速度提升利用優化
Puppeteer自身不會消耗太多資源,耗費資源的大戶是Chromium Headless。所以需要理解Chromium運行的原理,才能方便優化。
Chromium消耗最多的資源是CPU,一是渲染需要大量計算,二是Dom的解析與渲染在不同的進程,進程間切換會給CPU造成壓力(進程多了之后特別明顯)。其次消耗最多的是內存,Chromium是以多進程的方式運行,一個頁面會生成一個進程,一個進程占用30M左右的內存,大致估算1000個請求占用30G內存,在并發高的時候內存瓶頸最先顯現。
優化最終會落在內存和CPU上(所有軟件的優化最終都要落到這里),通常來說因為并發造成的瓶頸需要優化內存,計算速度慢的問題要優化CPU。使用Puppeteer的用戶多半會更關心計算速度,所以下面我們談談如何優化Puppeteer的計算速度。
優化Chromium啟動項通過查看Chromium啟動時都有哪些參數可以配置,能找到大部分線索,因為Chromium這種頂級的開源產品,文檔與接口都是非常清晰的,肯定可以找到相關配置項來定制啟動方式。Chromium 啟動參數列表
我們需要找到下面幾種配置來提升速度:
如果將Dom解析和渲染放到同一進程,肯定能提升時間(進程上下文切換的時間)。對應的配置是??single-process?
部分功能disable掉,比如GPU、Sandbox、插件等,減少內存的使用和相關計算。
如果啟動Chromium時能綁定到某個CPU核上也能提升速度(單核上進行進程切換耗費的時間更少)。可惜沒有找到對應的配置,官方文檔寫的是Chromium啟動時會自動綁定CPU大核(ARM架構的CPU通常有大小核之分),依此推測Chromium啟動時是會綁核的。(此處我并未驗證)
最后配置如下:
const browser = await puppeteer.launch(
{
headless:true,
args: [
‘–disable-gpu’,
‘–disable-dev-shm-usage’,
‘–disable-setuid-sandbox’,
‘–no-first-run’,
‘–no-sandbox’,
‘–no-zygote’,
‘–single-process’
]
});
Chromium 啟動參數列表?文檔中的配置項都可以嘗試看看,我沒有對所有選項做測試,但可以肯定存在某些選項能提升Chromium速度。
優化Chromium執行流程接下來我們再多帶帶優化Chromium對應的頁面。我之前的文章中提過,如果每次請求都啟動Chromium,再打開tab頁,請求結束后再關閉tab頁與瀏覽器。流程大致如下:
請求到達->啟動Chromium->打開tab頁->運行代碼->關閉tab頁->關閉Chromium->返回數據
真正運行代碼的只是tab頁面,理論上啟動一個Chromium程序能運行成千上萬的tab頁,可不可以復用Chromium每次只打開一個tab頁然后關閉呢?當然是可以的,Puppeteer提供了?puppeteer.connect()??方法,可以連接到當前打開的瀏覽器。流程如下:
請求到達->連接Chromium->打開tab頁->運行代碼->關閉tab頁->返回數據
代碼如下:
const MAX_WSE = 4; //啟動幾個瀏覽器
let WSE_LIST = []; //存儲browserWSEndpoint列表
init();
app.get("/", function (req, res) {
let tmp = Math.floor(Math.random()* MAX_WSE);
(async () => {
let browserWSEndpoint = WSE_LIST[tmp];
const browser = await puppeteer.connect({browserWSEndpoint});
const page = await browser.newPage();
await page.goto("file://code/screen/index.html");
await page.setViewport({
width: 600,
height: 400
});
await page.screenshot({path: "example.png"});
await page.close();
res.send("Hello World!");
})();
});
function init(){
(async () => {
for(var i=0;itrue,
args: [
"--disable-gpu",
"--disable-dev-shm-usage",
"--disable-setuid-sandbox",
"--no-first-run",
"--no-sandbox",
"--no-zygote",
"--single-process"
]});
browserWSEndpoint = await browser.wsEndpoint();
WSE_LIST[i] = browserWSEndpoint;
}
console.log(WSE_LIST);
})();
}
利用cluster優化Puppeteer
通常情況下我們會使用??.map()??搭配??Promise.all()??的方式并行處理異步,但是在使用?Puppeteer?批量截圖時發現?Promise.all?會打開多個瀏覽器,導致機器性能急劇下降。
?Promise.all()??并行處理
利用??Reduce??是多個?Promise?順序執行
await tasks.reduce((sequence, url, idx) => {
return sequence.then(() => {
// doAnalyze 是個異步函數
return doAnalyze(url, idx);
});
}, Promise.resolve())
場景:有40個URL,需要獲取每個博客的首頁截圖
如果是?Promise.all()?,程序啟動會同時打開20+的chromium瀏覽器,導致機器卡死。
使用?reduce?緩解了壓力,但沒充分利用多核性能
參入?Cluster?
// cluster_index.js 入口文件
const cluster = require("cluster");
(async () => {
let run;
if (cluster.isMaster) {
run = require("./cluster_master");
} else {
run = require("./cluster_worker");
}
try {
await run();
} catch (e) {
// 追蹤函數的調用軌跡
console.trace(e);
}
})();
// cluster_master.js master進程分配任務
const cluster = require("cluster");
const numCPUs = require("os").cpus().length;
// 處理的任務列表
let arr = [
"https://github.com/guoguoya",
"http://www.52cik.com",
"http://zhalice.com",
"https://www.yzqroom.cn",
"http://zxh.name",
"https://fogdong.github.io/",
"http://github.com/elsieyin",
"https://summer.tlb058.com",
"https://skymon4.cn",
"http://www.jiweiqing.cn",
"http://effect.im",
"http://dingkewz.com",
"http://xcdh.me",
"http://d2g.io",
"http://codingdemon.com",
"http://blog.leanote.com/dujuncheng",
"http://niexiaotao.com",
"http://zhengchengwen.com",
"http://blog.tophefei.com",
"https://zh-rocco.github.io",
"http://wangyn.net",
"http://dscdtc.ml",
"http://jweboy.github.io",
"http://www.wenghaoping.com",
"http://zhoujingchao.github.io",
"http://kyriejoshua.github.io/jo.github.io/",
"http://www.withyoufriends.com",
"http://if2er.com",
"https://github.com/zhou-yg",
"http://github/suoutsky",
"http://richardsleet.github.io",
"http://www.89io.com",
"https://guoshencheng.com",
"http://www.landluck.com.cn",
"http://www.89io.com",
"http://myoungxue.top",
"https://github.com/Wangszzju",
"http://www.hacke2.cn",
"https://github.com/enochjs",
"https://i.jakeyu.top",
"http://muyunyun.cn",
];
module.exports = async () => {
// 每個 CPU 分配 N 個任務
const n = Math.floor(arr.length / numCPUs);
// 未分配的余數
const remainder = arr.length % numCPUs;
for (let i = 1; i <= numCPUs; i += 1) {
const tasks = arr.splice(0, n + (i > remainder ");"exit", (worker) => {
console.log(`worker #${worker.id} PID:${worker.process.pid} died`);
});
cluster.on("error", (err) => {
console.log(`worker #${worker.id} PID ERROR: `, err);
});
};
// cluster_worker.js worker進程 完成任務
const cluster = require("cluster");
const puppeteer = require("puppeteer");
// 禁止直接啟動
if (cluster.isMaster) {
console.log("----", cluster.worker.id)
process.exit(0);
}
module.exports = async () => {
const env = process.env.tasks;
let tasks = [];
if (/^[.*]$/.test(env)) {
tasks = JSON.parse(env);
}
if (tasks.length === 0) {
console.log("");, tasks)
// 非法啟動, 釋放進程資源
process.exit(0);
}
console.log(`worker #${cluster.worker.id} PID:${process.pid} Start`);
await tasks.reduce((sequence, url, idx) => {
return sequence.then(() => {
return doAnalyze(url, idx);
});
}, Promise.resolve())
console.log(cluster.worker.id + " 順利完成");
process.exit(0);
};
async function doAnalyze(url, i) {
try {
const browser = await (puppeteer.launch({
// 若是手動下載的chromium需要指定chromium地址, 默認引用地址為 /項目目錄/node_modules/puppeteer/.local-chromium/
// executablePath: "/Users/huqiyang/Documents/project/z/chromium/Chromium.app/Contents/MacOS/Chromium",
//設置超時時間
timeout: 30000,
//如果是訪問https頁面 此屬性會忽略https錯誤
ignoreHTTPSErrors: true,
// 打開開發者工具, 當此值為true時, headless總為false
devtools: false,
// 關閉headless模式, 會打開瀏覽器
headless: false
}));
const page = await browser.newPage();
await page.setViewport({width: 1920, height: 1080});
await page.goto(url);
await page.waitFor(4000);
console.log(cluster.worker.id, url, i, "截圖中...");
await page.screenshot({
path: `./img_cluster/${cluster.worker.id}-${i}.png`,
// path: "3.png",
type: "png",
// quality: 100, 只對jpg有效
// fullPage: true,
// 指定區域截圖,clip和fullPage兩者只能設置一個
// clip: {
// x: 0,
// y: 0,
// width: 1920,
// height: 600
// }
});
browser.close();
} catch (error) {
console.log(cluster.worker.id, url, i)
console.log(error)
}
};
多個page輪詢與多個browser輪詢
為了性能,現有解決方案是初始化若干個browser,請求打過來時,直接在browserList中取一個browser實例使用。 作為對比,可以參考初始化一個browser,預先打開若干個page,請求打過來時,直接在pageList中取一個page實例使用。
參考文章:Puppeteer性能優化與執行速度提升 利用cluster優化Puppeteer
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/7302.html
摘要:前端每周清單第期現狀分析與優化策略單元測試爬蟲作者王下邀月熊編輯徐川前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點分為新聞熱點開發教程工程實踐深度閱讀開源項目巔峰人生等欄目。 showImg(https://segmentfault.com/img/remote/1460000011008022); 前端每周清單第 29 期:Web 現狀分析與優化策略...
摘要:導言對于大多數前端開發者而言,談到命令行工具,大家肯定都用過。但是談到開發命令行工具,估計就沒幾人有了解了。如何優化這個圖片爬蟲工具目前還有點啊,我們的目標是要開發一個交互式的命令行應用,肯定不能止于此。 導言:對于大多數前端開發者而言,談到命令行工具,大家肯定都用過。但是談到開發命令行工具,估計就沒幾人有了解了。本文旨在用最短的時間內,幫您開發一個實用(斜眼笑)的圖片爬蟲命令行應用。...
摘要:發布是由團隊開源的,操作接口庫,已成為事實上的瀏覽器操作標準。本周正式發布,為我們帶來了,,支持自定義頭部與腳部,支持增強,兼容原生協議等特性變化。新特性介紹日前發布了大版本更新,引入了一系列的新特性與提升,本文即是對這些變化進行深入解讀。 showImg(https://segmentfault.com/img/remote/1460000012940044); 前端每周清單專注前端...
摘要:前端每周清單年度總結與盤點在過去的八個月中,我幾乎只做了兩件事,工作與整理前端每周清單。本文末尾我會附上清單線索來源與目前共期清單的地址,感謝每一位閱讀鼓勵過的朋友,希望你們能夠繼續支持未來的每周清單。 showImg(https://segmentfault.com/img/remote/1460000010890043); 前端每周清單年度總結與盤點 在過去的八個月中,我幾乎只做了...
摘要:由于文件中版本號的特點,下面三個版本號在安裝的時候代表不同的含義。安裝版本統一為了防止拉取到不同的版本,有一個鎖定文件記錄了被確切安裝上的模塊的版本號。 showImg(https://segmentfault.com/img/bVbs8Rg?w=1920&h=1080); 一位用不好包管理器的前端,是一個入門級前端,一個用不好webpack的前端,是一個初級前端 三個包管理器是可以一...
閱讀 2246·2021-11-24 11:15
閱讀 3094·2021-11-24 10:46
閱讀 1390·2021-11-24 09:39
閱讀 3930·2021-08-18 10:21
閱讀 1485·2019-08-30 15:53
閱讀 1401·2019-08-30 11:19
閱讀 3332·2019-08-29 18:42
閱讀 2329·2019-08-29 16:58