PhantomJs 2 Headless Chrome

PhantomJS is dead, long live headless browsers

這是一個從PhantomJs走到Headless Chrome的故事挫以,趟過了Highcharts的性能問題的坑诫惭,掉入過中文官方文檔的錯誤的坑,嘗試過依賴庫的源碼修改桩了,最后的結果卻是發(fā)現(xiàn)了Headless Chrome的新大陸附帽。

Highcharts性能問題的解決


事情得從一個bug說起,手頭的項目有一個很簡單的功能:下載PDF格式的月度報表井誉,報表里有三個scatter chart士葫,這里我們使用的是 Highcharts 去實現(xiàn)的。我們的策略是點擊下載的時候送悔,在后臺用 PhantomJs 實時生成PDF文件慢显。

chart

一切都很完美,穩(wěn)定性和性能的表現(xiàn)都很好欠啤,直到如上圖的chart荚藻,一共有一萬多個點,整個報表有三個這樣的chart洁段,總共差不多五萬個數(shù)據(jù)點应狱,這個時候就發(fā)現(xiàn)Highcharts的性能直線下降了,結果就是報表因為超時無法下載祠丝。
我們的實現(xiàn)如下:

//report-to-pdf.js
var page = require('webpage').create();
var system = require('system');
var address = system.args[1];
var renderTo = system.args[2]

page.viewportSize = { width:1190, height:1684 };

// guarantee the charts loaded!
// https://stackoverflow.com/questions/11340038/phantomjs-not-waiting-for-full-page-load
page.onConsoleMessage = function(msg, lineNum, sourceId) {

    if(msg=='hey lets take screenshot')
    {
        window.setInterval(function(){
            try
            {
                 var sta= page.evaluateJavaScript("function(){ return jQuery.active;}");
                 if(sta == 0)
                 {
                    window.setTimeout(function(){
                        page.render(renderTo);
                        clearInterval();
                        phantom.exit();
                    },1000);
                 }
            }
            catch(error)
            {
                console.log(error);
                phantom.exit(1);
            }
       },1000);
    }
};


page.open(address, function (status) {
    if (status !== "success") {
        console.log('Unable to load url');
        phantom.exit();
    } else {
       page.setContent(page.content.replace('</body>','<script>window.onload = function(){console.log(\'hey lets take screenshot\');}</script></body>'), address);
    }
});

#audit_report_controller.rb
  def download
    report = AuditReport.find_by(token: params[:token])
    if report && report.file_path && File.exist?(report.file_path)
      output_file = report.file_path
    else
      file_name = "REPORT_#{report.project_name}_#{report['started_at'].strftime('%Y%m')}_#{params[:token]}.pdf"
      js_path = "#{Rails.root}/app/assets/javascripts/headless-chrome-pdf.js"
      output_file = "#{Rails.root}/tmp/#{file_name}"
      url = audit_report_preview_url(token: params[:token])
       Phantomjs.run(js_path, url, output_file)
      report.update({file_path: output_file})
    end
    send_file(output_file, :filename => file_name, :type => "application/pdf")
  end

這個是我們嘗試疾呻,用瀏覽器直接打開用來生成pdf的html頁面,發(fā)現(xiàn)效率極差:


load time

經(jīng)過調研写半,得知Highcharst確實存在效率問題岸蜗,對于幾萬個點的scatter它是無能為力的,但是好在有 解決方案叠蝇,我們可以使用 Highchart-boost module.
這一方案使用WebGL實現(xiàn)圖表璃岳,替換標準解決方案中的SVG,最終獲得了百萬點位毫秒級加載的效果悔捶。當然需要注意铃慷,這也造成了一些效果上的限制:

The boost module is a stripped down version of the SVG renderer. As such, certain features are not available for boosted charts. Most of these features deals with interactivity, such as animation support. But there are a few that relates to visuals as well.

如是我們引入了boost.js, 然后設置了seriesThreshold參數(shù)為2000,也就是當點位數(shù)量達到2000以上時候蜕该,才開啟boost 模式犁柜。在瀏覽打開預覽頁面時候數(shù)據(jù)如下:


loading time

這是在development環(huán)境下,未壓縮assets時候的速度堂淡,基本chart的渲染無障礙馋缅,實現(xiàn)了秒內加載坛怪。

Boost && PhantomJs的坑


接下來我們嘗試使用PhantomJs將預覽頁面轉化為PDF,在terminal使用命令:

phantomjs report-to-pdf.js http://dev/audit_report_preview\?token\=EeHwf7Gi3fO6S2qzfM test.pdf

果然實現(xiàn)了快速下載股囊,但是結果卻是很慘烈的袜匿,chart完全沒有render出來,chart區(qū)白茫茫的一片稚疹。WTF!
繼續(xù)查找問題的所在居灯,想一想boost module對chart的實現(xiàn)方式有什么特點,用chrome打開報表預覽頁面内狗,查看html元素怪嫌,可以看到boost.js使用了Embeded Base64 image的方式,使用WebGL畫好圖片柳沙,使用base64 encode之后嵌入頁面相應 image tag的Data url屬性岩灭,懷疑是Embeded image在PhantomJs里打開有問題,搜索了PhantomJs的github issues赂鲤,確實看到了相關問題噪径,還是個open的issue,已經(jīng)一年半了数初,同時也看到PhantomJS 浩浩蕩蕩的未解決的issues找爱,到寫下此段時候,open issues的總共數(shù)目是:1812泡孩。這讓我嗅到了一絲絕望的氣息:首先說明PhantomJs確實有渲染Embed image的問題车摄,未解決。同時這個項目可能維護狀態(tài)不佳仑鸥。
絕望這下尋求了rails里的其他的一些常用pdf 生成插件吮播,比如:wicked_pdfpdfkit眼俊,但是結果竟然還是一樣意狠,生成的PDF里面chart區(qū)域白茫茫的一片。此時不得不考慮泵琳,真的是因為Embed Base64 image的問題摄职?還是得驗證一下問題誊役,于是在瀏覽器上copy下預覽頁面的的html获列,手動設置為report-to-pdf.js 中page的content,生成了PDF之后蛔垢,竟然發(fā)現(xiàn)chart出現(xiàn)了击孩,所以說明,白茫茫的chart的鹏漆,還真不是embed base64 image的鍋巩梢,那么可能在PhantomJs環(huán)境下创泄,可能chart根本沒有畫出來,而不是畫出來了顯示不了括蝠,就想使用如下代碼查看PhantomJs打印出來的page content:

//Javascript
var page = require('webpage').create();
var address='http://dev/audit_report_preview?token=EeHwf7Gi3fO6S2qzfMm';
page.onConsoleMessage = function(msg, lineNum, sourceId) {
  console.log('I am here: ' + msg)
};

page.open(address, function (status) {
  if (status !== "success") {
    console.log('Unable to load url');
  } else {
    var content = page.content;
    console.log('Content: ' + content);
  }
  phantom.exit();
})

結果發(fā)現(xiàn)真的是完全沒有image element鞠抑,更驚喜的是細看之下 ,打印內容第一行竟然給出了error信息:

I am here: Highcharts error #26: www.highcharts.com/errors/26

搜索了一下Highcharts的文檔忌警,error#26說的很直白了:

error#26 zh

原來是因為PhantomJs不支持 WebGL搁拙,在谷歌上做相關搜索,結果確實如此法绵,并且開發(fā)者表示箕速,此后也不會支持WebGL。這個時候嘗試了上面文檔里給出的方案朋譬,在boost.js之后引入了boost-convas.js模塊盐茎,讓在不支持WebGL的時候fallback到canvas,但是結果依然是白茫茫的一片徙赢,再次打出content log信息字柠,竟然還是同樣的錯誤,有點懷疑人生了狡赐,這次直接點開錯誤信息給出的鏈接募谎,走到英文文檔的地址,給出的信息是:

error#26 en

竟然是要求把boost-canvas.js模塊放在boost.js之前引入阴汇,所以這里被坑爹中文文檔帶偏了数冬!okay,按照文檔的要求做了調整搀庶,果然不再有#26 error了拐纱,不過不幸的是chart只有數(shù)軸,但是散點圖中的點都沒有顯示出來哥倔,再次回到瀏覽器預覽頁面秸架,去做調試,在console里進入源碼咆蒿,修改代碼 禁用WebGL东抹,強制fallback到canvas,在瀏覽器上看到效果也是一樣沃测,也是沒有畫出來散點缭黔,然后看到瀏覽器console里有一些關于boost-canvas.js的報錯,是一些類似undefined的初級問題蒂破,一一針對性的修改源碼做出解決馏谨,但是在沒有報錯情況下依舊沒有畫出散點,翻看源碼附迷,想找出問題所在惧互,同時也好奇google出品的代碼質量怎么會這樣哎媚,如是懷疑是不是版本問題,發(fā)現(xiàn)自己使用的Highcharts的版本比較老喊儡,和使用的boost 以及 boost-canvas不匹配拨与,更換版本之后終于可以顯示正常的chart,性能表現(xiàn)也不錯艾猜。

PhantomJs is dead


再次回到命令行截珍,嘗試服務端PhantomJs生成PDF,結果卻是大跌眼鏡箩朴,瀏覽器端的飛速性能又沒了岗喉,依舊是分鐘級的時間花費才完成生成動作。此時再回頭去求助google炸庞,以及翻看PhantomJs的issues钱床,看到很多類似的情況,在PhantomJs中使用canvas的效率低下問題埠居,page.open()在面對體量比較大的頁面的時候查牌,效率問題也一直為人所詬病,最后此類討論給出的結論都是 效率問題在PhantomJs中是無解的滥壕,只能去求助別的方案纸颜。最讓人傷心的是看到了這條issue: Archiving the project: suspending the development,創(chuàng)始人表示PhantomJs已經(jīng)結束開發(fā)了绎橘。想起之前嘗試幾種rails的gem生成PDF方案胁孙,效果都不好,感覺開發(fā)陷入了困境称鳞。

拯救者Headless Chrome


柳暗花明又一村涮较,無意中翻看關于PhantomJs 性能問題的各種相關文章的時候看到 benchmark headless chrome vs phantomjs,想到為啥不去嘗試一下Headless Chrome方案呢冈止,按照官方文檔做起來嘗試狂票,幾分鐘后發(fā)現(xiàn)竟然在這么一條命令下面,完美的解決了問題熙暴!

chrome --headless --print-to-pdf=file.pdf http://localhost:3000/dev\?token\=EeHwf7Gi3fO6S2qzfMm

畫面渲染正常闺属,chart正常,效率也很好周霉,接下來需要做的就是將Headless Chrome的方案和服務端Rails項目集成的問題:第一個掂器,服務器系統(tǒng)是linux,必須調研下安裝使用的問題诗眨,第二個是在Rails里調用的話唉匾,是否有成熟穩(wěn)定的方案。
現(xiàn)在有穩(wěn)定版本的Node API可以使用:

Puppeteer is a Node library which provides a high-level API to control headless Chrome or Chromium over the DevTools Protocol. It can also be configured to use full (non-headless) Chrome or Chromium.

于是對于第一個問題匠楚,默認安裝的時候巍膘,Puppeteer會一起默認下載安裝一個 Chromium內核,同時需要注意對于不同發(fā)型版的一些依賴庫芋簿,我們這里使用的是Ubuntu峡懈,可以提前安裝好依賴,再安裝Puppeteer:

sudo apt-get install  gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
npm instal puppeteer

對于第二個問題与斤,找到一篇guidline肪康,這里的方案是將我們自己的Rails項目文件夾初始化為一個node項目,也就是生成一個package.json和node_module文件夾做依賴管理撩穿,這樣在部署或者團隊開發(fā)中磷支,只要有node環(huán)境和npm install一下就可以正常使用node api的相關功能,然后在Rails代碼中是調用命令行node命令的方式執(zhí)行。按照這個方案做了集成,開發(fā)環(huán)境下嘗試功能沒有問題仑性,剩下的考慮怎么部署問題队橙。我們使用的是mina,在deploy.rb中加入以下任務模塊:

namespace :npm do
  desc 'Install node modules using Npm.'
  task install: :environment do
    queue %{echo "-----> Installing node modules using Npm"}
    queue "mkdir -p #{deploy_to}/#{shared_path}/node_modules"
    queue "ln -s #{deploy_to)}/#{shared_path}/node_modules" "node_modules"
    queue "npm install"
  end
end
...
 deploy do
    # Put things that will set up an empty directory into a fully set-up
    # instance of your project.
    ....
    invoke :'bundle:install'
    invoke :'npm:install'
    invoke :'rails:db_migrate'
    invoke :'rails:assets_precompile'
    invoke :'deploy:cleanup'

     ....

    end
  end

PhantomJS is dead, long live headless browsers


最后咕别,填好坑回到題記的話題,這幾年各種headless的browser崛起,讓老一代的server端的解決方案失去了市場移盆,包括 PhantomJSSelenium IDE for Firefox 等伤为,隨著 PhantomJs的Maintainer Vitaly Slobodin 宣布不再維護該項目咒循,標志PhantomJs的時代已經(jīng)遠去,使用Headless Chrome等新一代的解決方案有如下優(yōu)點:

  • They are real browsers with a broad feature support (PhantomJS uses a very old version of WebKit – and in the meanwhile Chrome switched to Blink anyway)
  • They are faster and more stable (PhantomJS has a lot of open issues)
  • They use less memory
  • They can be started non-headless, which allows easier debugging
  • No more goofy PhantomJS binary installation with NPM

這樣绞愚,我們也可以入了Headless Chrome的坑了剑鞍,可以用來當做爬蟲,用來作為測試的Javascript driver方案等爽醋。

  • Generate screenshots and PDFs of pages.
  • Crawl a SPA and generate pre-rendered content (i.e. "SSR").
  • Automate form submission, UI testing, keyboard input, etc.
  • Create an up-to-date, automated testing environment. Run your tests directly in the latest version of Chrome using the latest JavaScript and browser features.
  • Capture a timeline trace of your site to help diagnose performance issues.

歡迎入坑蚁署!

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蚂四,隨后出現(xiàn)的幾起案子光戈,更是在濱河造成了極大的恐慌,老刑警劉巖遂赠,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件久妆,死亡現(xiàn)場離奇詭異,居然都是意外死亡跷睦,警方通過查閱死者的電腦和手機筷弦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人烂琴,你說我怎么就攤上這事爹殊。” “怎么了奸绷?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵梗夸,是天一觀的道長。 經(jīng)常有香客問我号醉,道長反症,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任畔派,我火速辦了婚禮铅碍,結果婚禮上,老公的妹妹穿的比我還像新娘线椰。我一直安慰自己胞谈,他們只是感情好,可當我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布士嚎。 她就那樣靜靜地躺著呜魄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪莱衩。 梳的紋絲不亂的頭發(fā)上爵嗅,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天,我揣著相機與錄音笨蚁,去河邊找鬼睹晒。 笑死,一個胖子當著我的面吹牛括细,可吹牛的內容都是我干的伪很。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼奋单,長吁一口氣:“原來是場噩夢啊……” “哼锉试!你這毒婦竟也來了?” 一聲冷哼從身側響起览濒,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤呆盖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后贷笛,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體应又,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年乏苦,在試婚紗的時候發(fā)現(xiàn)自己被綠了株扛。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖洞就,靈堂內的尸體忽然破棺而出盆繁,到底是詐尸還是另有隱情,我是刑警寧澤奖磁,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布改基,位于F島的核電站繁疤,受9級特大地震影響咖为,放射性物質發(fā)生泄漏。R本人自食惡果不足惜稠腊,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一躁染、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧架忌,春花似錦吞彤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至井仰,卻和暖如春埋嵌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背俱恶。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工雹嗦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人合是。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓了罪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親聪全。 傳聞我的和親對象是個殘疾皇子泊藕,可洞房花燭夜當晚...
    茶點故事閱讀 45,585評論 2 359

推薦閱讀更多精彩內容

  • Awesome Ruby Toolbox Awesome A collection of awesome Ruby...
    debbbbie閱讀 2,896評論 0 3
  • 用到的組件 1、通過CocoaPods安裝 2难礼、第三方類庫安裝 3娃圆、第三方服務 友盟社會化分享組件 友盟用戶反饋 ...
    SunnyLeong閱讀 14,626評論 1 180
  • 技術雷達快訊:自2017年中以來踊餐,Chrome用戶可以選擇以headless模式運行瀏覽器。此功能非常適合運行前端...
    ThoughtWorks閱讀 7,872評論 8 28
  • 廉貞上前觀察了一遍臀稚,道:“山洞內濕氣很重吝岭,這木門的質地卻是干燥堅硬得很,想必是上等木材,兄弟們隨我一同推開它窜管,挺沉...
    Myron馬閱讀 367評論 2 4
  • 投射兒子回到學校能靜下心來好好復習散劫,期末考試成績有較大的提升。投射女兒越來越活潑開朗幕帆,每天都快快樂樂的获搏。投射老公...
    旦子閱讀 116評論 0 0