基于PhantomJS的網(wǎng)站截圖服務(wù)API設(shè)計(jì)與開發(fā)

原文鏈接:http://www.mostclan.com/post-329.html
作者:Veris
Blog:最族 [ http://www.mostclan.com ]

為公司某業(yè)務(wù)實(shí)現(xiàn)“服務(wù)端對(duì)網(wǎng)站截圖”功能髓迎,搜羅了很多技術(shù)最終采用了PhantomJS無頭瀏覽器技術(shù)。

什么是PhantomJS票摇?

PhantomJS是一個(gè)基于webkit的javaScript API略就。它使用QtWebKit作為它核心瀏覽器的功能,使用webkit來編譯解釋執(zhí)行javaScript代碼。任何你可以基于在webkit瀏覽器做的事情,它都能做到联逻。它不僅是個(gè)隱性的瀏覽器,提供了諸如css選擇器检痰、支持wen標(biāo)準(zhǔn)包归、DOM操作、json铅歼、HTML5等公壤,同時(shí)也提供了處理文件I/O的操作,從而使你可以向操作系統(tǒng)讀寫文件等椎椰。phantomJS的用處可謂非常廣泛諸如網(wǎng)絡(luò)監(jiān)測(cè)境钟、網(wǎng)頁截屏、無需瀏覽器的wen測(cè)試俭识、頁面訪問自動(dòng)化等。

網(wǎng)上流傳PhantomJS已暫停維護(hù)洞渔,轉(zhuǎn)而投入selenium的開發(fā)套媚,所以用戶更傾向于目前流行的瀏覽器自動(dòng)化測(cè)試框架“selenium”,這里作者選擇使用PhantomJS來做服務(wù)磁椒,因?yàn)椴渴鸨容^簡(jiǎn)單堤瘤,且完全能滿足業(yè)務(wù)需求。

1浆熔、實(shí)現(xiàn)截圖

var webpage = require('webpage');
var page = webpage.create();
page.settings.javascriptEnabled = false; // 禁用JavaScript代碼
page.open("http://www.mostclan.com", function (status) {
    if (status === "success") {
        console.log(page.title);
        console.log("截圖成功");
        page.render("screenshot.png");
    } else {
        console.log("截圖失敗");
    }
});

將文件保存為webpage.js本辐,使用如下命令執(zhí)行程序

phantomjs webpage.js

(這里需要安裝PhantomJS,詳細(xì)步驟不做闡述医增,請(qǐng)自行百度)

如果返回截圖成功慎皱,那么恭喜你截圖服務(wù)已經(jīng)寫好了,就是這么簡(jiǎn)單叶骨,你可以查看目錄下的“screenshot.png”有一張我的博客截圖_茫多。

webpage 是 PhantomJS 的核心模塊,上面的代碼中忽刽,open() 方法有兩個(gè)參數(shù)天揖。

  • 第一個(gè)參數(shù)是請(qǐng)求地址(不要忘記協(xié)議頭),默認(rèn)使用 GET 方式

  • 第二個(gè)參數(shù)是回調(diào)函數(shù)跪帝,回調(diào)參數(shù)status表示網(wǎng)頁狀態(tài)主要有success和fail兩種今膊。

值的注意的是,只要請(qǐng)求有返回結(jié)果伞剑,status參數(shù)就是success斑唬,即使服務(wù)器返回的狀態(tài)是404或500錯(cuò)誤。

如果需要使用POST請(qǐng)求或其他請(qǐng)求要求,可以使用如下方式:

var webpage = require('webpage');
var page = webpage.create();
var settings = {
    operation: "POST",
    encoding: "utf8",
    headers: {
        "Content-Type": "application/json"
    },
    data: JSON.stringify({
        params: "data",
        array: ["1", "2"]
    })
};
page.settings.javascriptEnabled = false; // 禁用JavaScript代碼
page.open("http://www.mostclan.com", settings, function (status) {
    if (status === "success") {
        console.log(page.title);
        console.log("截圖成功");
        page.render("screenshot.png");
    } else {
        console.log("截圖失敗");
    }
});

這里第二個(gè)參數(shù)變成一個(gè)對(duì)象類型的配置信息赖钞,可以配置請(qǐng)求方式腰素、編碼、頭信息雪营、數(shù)據(jù)等弓千。

另外代碼中有一句 page.settings.javascriptEnabled = false;,如果不需要頁面渲染時(shí)執(zhí)行JS代碼献起,可以禁用此選項(xiàng)來加速截圖洋访,因?yàn)橐话憔W(wǎng)站都是由HTML+CSS來渲染頁面的,其他基本上對(duì)渲染結(jié)果影響不大谴餐。

page.render()可以將打開的網(wǎng)頁截圖并保存成本地圖片姻政,可以將指定的圖片文件名作為參數(shù)傳入,render 方法可以根據(jù)文件名的后綴將圖片保存成對(duì)應(yīng)的格式岂嗓。

目前支持PNG汁展、GIF、JPEG厌殉、PDF四種圖片格式食绿。

page.render('temp.jpeg', {format: 'jpeg', quality: '100'});

其他參數(shù)詳細(xì)細(xì)節(jié)可參考官方文檔

2、搭建WebServer
這里用到一個(gè)基于mongoose的WebServer模塊公罕,因?yàn)樵撃K目前僅允許10個(gè)并發(fā)請(qǐng)求器紧,所以在大流量并發(fā)環(huán)境下請(qǐng)選擇其他Web服務(wù)模塊,或并發(fā)量不高的話可以做個(gè)多服務(wù)的負(fù)載集群楼眷,后文會(huì)介紹操作方法铲汪。

var webserver = require('webserver').create();
webserver.listen(8080, function (request, response) {
    response.statusCode = 200;
    response.write('<html><h1>Hello World!</h1></html>');
    response.close();
});

將文件保存為webserver.js,使用如下命令執(zhí)行程序

phantomjs webserver.js

然后訪問 http://127.0.0.1:8080 便可看到剛剛搭建的Web服務(wù)

listen方法的第一個(gè)參數(shù)可以為一個(gè)端口號(hào)罐柳,也可以是ip:port這種形式
第二個(gè)參數(shù)是回調(diào)方法掌腰,主要有兩個(gè)回調(diào)參數(shù):requestresponse

request參數(shù)的幾個(gè)常用屬性:

  • method硝清,請(qǐng)求方式(get辅斟、post等)
  • url,請(qǐng)求的URL芦拿,包含請(qǐng)求的get參數(shù)
  • post士飒,POST請(qǐng)求數(shù)據(jù)
  • postRaw,POST數(shù)據(jù)原始信息蔗崎,就是application/x-www-form-urlencoded編碼提交的數(shù)據(jù)
  • headers酵幕,請(qǐng)求頭信息

response參數(shù)的幾個(gè)常用方法:

  • statusCode(code),設(shè)置HTTP狀態(tài)碼
  • write(data)缓苛,向response中寫入數(shù)據(jù)
  • close()芳撒,關(guān)閉HTTP連接(這個(gè)比較重要邓深,如果未關(guān)閉會(huì)一直占用連接,后面會(huì)講這個(gè)坑)

3笔刹、API服務(wù)網(wǎng)關(guān)設(shè)計(jì)

Web服務(wù)搭建好后芥备,我們就可以做服務(wù)網(wǎng)關(guān)了,這樣通過調(diào)用網(wǎng)關(guān)便能獲取網(wǎng)站截圖舌菜。
首先規(guī)定交互協(xié)議和返回?cái)?shù)據(jù)格式萌壳,我們協(xié)定如下:

請(qǐng)求參數(shù):(POST請(qǐng)求服務(wù)網(wǎng)關(guān))

  • url: 需要截圖的網(wǎng)站地址
  • out_order_id: 外部訂單ID,如果是異步回調(diào)通知形式日月,可以加這個(gè)參數(shù)來識(shí)別圖片
  • method: 請(qǐng)求方式
  • headers: 請(qǐng)求頭
  • data: 請(qǐng)求參數(shù)(json格式)
    • screen_width: 截圖屏幕寬度(如果有要求的話)
    • screen_height: 截圖屏幕高度(如果有要求的話)
  • sign: 簽名(這里如果要暴露服務(wù)給外網(wǎng)使用袱瓮,又想限定用戶的話可以設(shè)置簽名參數(shù)來授權(quán),詳細(xì)設(shè)計(jì)方法不再闡述)

響應(yīng)參數(shù):

  • code: 狀態(tài)值 0-失敗 1-成功
  • msg: 回饋消息
  • data: 響應(yīng)數(shù)據(jù)
    • screen_image_data: 截屏圖片數(shù)據(jù)(base64加密)
    • out_order_id: 外部訂單ID
  • timestamp: 時(shí)間戳

設(shè)計(jì)好數(shù)據(jù)格式就可以運(yùn)行服務(wù)了爱咬,完整代碼我分享在github上(見文末)尺借,可運(yùn)行src/server.js

我這在本地使用postman請(qǐng)求服務(wù)網(wǎng)關(guān),可以看到時(shí)間響應(yīng)還是挺快的精拟,部署到服務(wù)器會(huì)更快點(diǎn)燎斩。

渲染效果如下:(我這里圖片采用了jpg壓縮了,追求質(zhì)量可以改成png蜂绎,不過響應(yīng)渲染的速度會(huì)下降一些)

4瘫里、服務(wù)化部署、異常情況及解決方案

高可用問題:為了使服務(wù)正车茨耄可控,一定要使用try()catch(e){}形式捕獲異常局装!部署到Linux需要注意的是——如何在后臺(tái)保持穩(wěn)定運(yùn)行坛吁,這里推薦大家使用Supervisor來守護(hù)進(jìn)程,可以在后臺(tái)運(yùn)行的同時(shí)記錄日志铐尚,當(dāng)進(jìn)程異常奔潰還可以自動(dòng)重載服務(wù)拨脉。(詳情可自行查閱資料)

超時(shí)問題:因?yàn)榻貓D比較耗時(shí),可以改為異步的形式宣增,即服務(wù)處理完成后回調(diào)通知結(jié)果玫膀,圖片則可使用本地存儲(chǔ)/OSS存儲(chǔ)圖片數(shù)據(jù),返回圖片鏈接形式加快響應(yīng)速度爹脾。

字體問題:如果部署在linux上帖旨,很容易出現(xiàn)中文亂碼或顯示不出的問題,這里需要在系統(tǒng)上安裝字體灵妨,可參考https://blog.csdn.net/weiguang1017/article/details/80229133

另外請(qǐng)求服務(wù)的生命周期也至關(guān)重要:
我們可以試著將response.close();注釋掉解阅,然后啟動(dòng)服務(wù)再請(qǐng)求,會(huì)發(fā)現(xiàn)一直在等待響應(yīng)中泌霍,當(dāng)我如此反復(fù)請(qǐng)求10次發(fā)現(xiàn)無法再請(qǐng)求了

這是因?yàn)?strong>WebServer模塊只支持10個(gè)并發(fā)货抄,而且可以response完沒有close連接,就導(dǎo)致一直占用tcp連接,造成服務(wù)不可用蟹地,我們可以通過linux命令來查看情況

ps aux | grep server.js

拿到進(jìn)程的PID后使用lsof命令查看進(jìn)程詳情

lsof -nPp 39207

情況如下圖积暖,可以看到有10個(gè)TCP連接,其中有幾個(gè)是CLOSE_WAIT狀態(tài)怪与。

出現(xiàn)大量close_wait的現(xiàn)象夺刑,主要原因是某種情況下對(duì)方關(guān)閉了socket鏈接(我這里postman取消了請(qǐng)求),但是服務(wù)方處于忙線狀態(tài)琼梆,沒有關(guān)閉連接性誉。

所以response.close();這一句千萬不能漏,無論是請(qǐng)求成功還是異常情況都要正常響應(yīng)用戶并關(guān)閉連接茎杂。

5错览、服務(wù)擴(kuò)展與展望

如若想要實(shí)現(xiàn)高并發(fā)大流量的服務(wù),則需增加服務(wù)網(wǎng)關(guān)(可分布式部署)煌往,使用nginx反向代理來負(fù)載均衡(反向到各個(gè)服務(wù)網(wǎng)關(guān)的WebServer地址)倾哺,這里和一般網(wǎng)站的高并發(fā)架構(gòu)方式類似,設(shè)計(jì)如下圖:

如果服務(wù)網(wǎng)關(guān)數(shù)量不夠刽脖,扛不住大量并發(fā)訪問羞海,則可以通過限流的形式緩解壓力

至此一個(gè)簡(jiǎn)單的網(wǎng)站截圖服務(wù)就做好了,以如今的設(shè)計(jì)應(yīng)該能滿足日常需要曲管,有更好設(shè)計(jì)方案或想法歡迎留言和issue却邓。

本項(xiàng)目源代碼公開在 https://github.com/VerisFung/WebScreenshotService ,歡迎Star院水!

轉(zhuǎn)載請(qǐng)注明出處:
作者:Veris
最族 [ http://www.mostclan.com ]

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末腊徙,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子檬某,更是在濱河造成了極大的恐慌撬腾,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,348評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恢恼,死亡現(xiàn)場(chǎng)離奇詭異民傻,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)场斑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門漓踢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人漏隐,你說我怎么就攤上這事彭雾。” “怎么了锁保?”我有些...
    開封第一講書人閱讀 156,936評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵薯酝,是天一觀的道長(zhǎng)半沽。 經(jīng)常有香客問我,道長(zhǎng)吴菠,這世上最難降的妖魔是什么者填? 我笑而不...
    開封第一講書人閱讀 56,427評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮做葵,結(jié)果婚禮上占哟,老公的妹妹穿的比我還像新娘。我一直安慰自己酿矢,他們只是感情好榨乎,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,467評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瘫筐,像睡著了一般蜜暑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上策肝,一...
    開封第一講書人閱讀 49,785評(píng)論 1 290
  • 那天肛捍,我揣著相機(jī)與錄音,去河邊找鬼之众。 笑死拙毫,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的棺禾。 我是一名探鬼主播缀蹄,決...
    沈念sama閱讀 38,931評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼膘婶!你這毒婦竟也來了袍患?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,696評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤竣付,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后滞欠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體古胆,經(jīng)...
    沈念sama閱讀 44,141評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,483評(píng)論 2 327
  • 正文 我和宋清朗相戀三年筛璧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了逸绎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,625評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡夭谤,死狀恐怖棺牧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情朗儒,我是刑警寧澤颊乘,帶...
    沈念sama閱讀 34,291評(píng)論 4 329
  • 正文 年R本政府宣布参淹,位于F島的核電站,受9級(jí)特大地震影響乏悄,放射性物質(zhì)發(fā)生泄漏浙值。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,892評(píng)論 3 312
  • 文/蒙蒙 一檩小、第九天 我趴在偏房一處隱蔽的房頂上張望开呐。 院中可真熱鬧,春花似錦规求、人聲如沸筐付。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瓦戚。三九已至,卻和暖如春冕茅,著一層夾襖步出監(jiān)牢的瞬間伤极,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工姨伤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留哨坪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓乍楚,卻偏偏與公主長(zhǎng)得像当编,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子徒溪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,492評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • API定義規(guī)范 本規(guī)范設(shè)計(jì)基于如下使用場(chǎng)景: 請(qǐng)求頻率不是非常高:如果產(chǎn)品的使用周期內(nèi)請(qǐng)求頻率非常高忿偷,建議使用雙通...
    有涯逐無涯閱讀 2,521評(píng)論 0 6
  • phantomjs實(shí)現(xiàn)了一個(gè)無界面的webkit瀏覽器。雖然沒有界面臊泌,但dom渲染鲤桥、js運(yùn)行、網(wǎng)絡(luò)訪問渠概、canva...
    卍卍_卐卐閱讀 38,478評(píng)論 1 13
  • 概要 64學(xué)時(shí) 3.5學(xué)分 章節(jié)安排 電子商務(wù)網(wǎng)站概況 HTML5+CSS3 JavaScript Node 電子...
    阿啊阿吖丁閱讀 9,125評(píng)論 0 3
  • 8. 方法定義(Method Definitions) 通用的HTTP/1.0的方法集將在下面定義茶凳,雖然該方法集可...
    Palomar閱讀 3,149評(píng)論 0 2
  • 談?wù)揥EB編程的時(shí)候常說天天在寫CGI,那么CGI是什么呢播揪?可能很多時(shí)候并不會(huì)去深究這些基礎(chǔ)概念贮喧,再比如除了CGI...
    __七把刀__閱讀 2,185評(píng)論 2 11