比Python,JavaScript才是更適合寫爬蟲的語言。原因有如下三個方面:
一、任務:爬取用戶在Github上的repo信息
通過實例的方式學習爬蟲是最好的方法,先定一個小目標:爬取github repo信息。入口URL如下,我們只需要一直點擊next按鈕就能夠遍歷到用戶的所有repo。
https://github.com/{{username}}?tab=repositories
獲取repo之后,可以做什么?
二、爬蟲雙股劍:axios和jQuery
axios是JavaScript中很常用的異步網絡請求庫,相比jQuery,它更輕量、更專業。既能夠用于瀏覽器端,也可以用于Node。它的語法風格是promise形式的。在本任務中,只需要了解如下用法就足夠了:
axios.get(url).then((resp) => { 請求成功,處理resp.data中的html數據 }).catch((err) => { 請求失敗,錯誤處理 })
請求之后需要處理回復結果,處理回復結果的庫當然是用jQuery。實際上,我們有更好的選擇:cheerio。
在node下,使用jQuery,需要使用jsdom庫模擬一個window對象,這種方法效率較低,四個字形容就是:笨重穩妥。
如下代碼使用jQuery解析haha.html文件
fs = require("fs") jquery=require('jquery') jsdom=require('jsdom') //fs.readFileSync()返回結果是一個buffer,相當于byte[] html = fs.readFileSync('haha.html').toString('utf8') dom= new jsdom.JSDOM(html) $=jquery(dom.window) console.log($('h1'))
cheerio只實現了jQuery中的DOM部分,相當于jQuery的一個子集。cheerio的語法和jQuery完全一致,在使用cheerio時,幾乎感覺不到它和jQuery的差異。在解析HTML方面,毫無疑問,cheerio是更好的選擇。如下代碼使用cheerio解析haha.html文件。
cheerio=require('cheerio') html=require('fs').readFileSync("haha.html").toString('utf8') $=cheerio.load(html) console.log($('h1'))
只需20余行,便可實現簡單的github爬蟲,此爬蟲只爬取了一頁repo列表。
var axios = require("axios") var cheerio = require("cheerio") axios.get("https://github.com/weiyinfu?tab=repositories").then(resp => { var $ = cheerio.load(resp.data) var lis = $("#user-repositories-list li") var repos = [] for (var i = 0; i < lis.length; i++) { var li = lis.eq(i) var repo = { repoName: li.find("h3").text().trim(), repoUrl: li.find("h3 a").attr("href").trim(), repoDesc: li.find("p").text().trim(), language: li.find("[itemprop=programmingLanguage]").text().trim(), star: li.find(".muted-link.mr-3").eq(0).text().trim(), fork: li.find(".muted-link.mr-3").eq(1).text().trim(), forkedFrom: li.find(".f6.text-gray.mb-1 a").text().trim() } repos.push(repo) } console.log(repos) })
三、更豐富的功能
爬蟲不是目的,而是達成目的的一種手段。獲取數據也不是目的,從數據中提取統計信息并呈現給人才是最終目的。
在github爬蟲的基礎上,我們可以擴展出更加豐富的功能:使用echarts等圖表展示結果。
要想讓更多人使用此爬蟲工具獲取自己的github統計信息,就需要將做成一個網站的形式,通過搜索頁面輸入用戶名,啟動爬蟲立即爬取github信息,然后使用echarts進行統計展示。網站肯定也要用js作為后端,這樣才能和js爬蟲無縫銜接,不然還要考慮跨語言調用。js后端有兩大web框架express和koa,二者API非常相似,并無優劣之分,但express更加流行。
如上設計有一處用戶體驗不佳的地方:當啟動爬蟲爬取github信息時,用戶可能需要等待好幾秒,這個過程不能讓用戶干等著。一種解決思路是:讓用戶看到爬蟲爬取的進度或者爬取過程。可以通過websocket向用戶推送爬取過程信息并在前端進行展示。展示時,使用類似控制臺的界面進行展示。
如何存儲爬取到的數據呢?使用MongoDB或者文件都可以,最好實現兩種存儲方式,讓系統的存儲方式變得可配置。使用MongoDB時,用到js中的連接池框架generic-pool。
整個項目用到的庫包括:
試用地址:
https://weiyinfu.cn/githubstatistic/search.html?
案例地址:https://github.com/weiyinfu/GithubStatistic
原文鏈接:https://zhuanlan.zhihu.com/p/53763115
載本文需注明出處:微信公眾號EAWorld,違者必究。
在互聯網高速發展的時代里,web應用大有取代桌面應用的趨勢,不必再去繁瑣的安裝各種軟件,只需一款主流瀏覽器即可完成大部分常規操作,這些原因都在吸引著軟件廠商和消費者。而隨著各大廠商瀏覽器版本的迭代,前端技術的不斷革新,消息推送用到的場景也越來越多了。
收發郵件提醒,在線IM聊天,自動化辦公提示等等,web系統里總是能見到消息推送的應用。消息推送用好了能增強用戶體驗,用不好則會起相反的效果。在司空見慣的使用過程中,有沒有對其中的原理產生興趣呢?實現消息推送有N種解決方案,本文針對其中的幾種,進行原理性的講解并附有簡單的代碼實現。
一、什么是消息推送
二、web端的消息推送
三、實現個性化的推送
當我在官網觀望猶豫時,突然看到了上面消息,一位神秘的徐老板竟然爆出了麻痹戒指!!我的天,于是我果斷開始了游戲!這消息很及時!
當我拿起手機不知干嘛時收到了這條招女婿的消息.......瞬間來了精神
上述兩種場景,是生活中很常見的場景,通過圖文描述,應該已經清楚了推送的場景,也引出了兩大推送種類,web端消息推送和移動端消息推送。接下來對消息推送進行具體的解釋。
消息推送(Push)指運營人員通過自己的產品或第三方工具對用戶當前網頁或移動設備進行的主動消息推送。用戶可以在網頁上或移動設備鎖定屏幕和通知欄看到push消息通知。以此來實現用戶的多層次需求,使得用戶能夠自己設定所需要的信息頻道,得到即時消息,簡單說就是一種定制信息的實現方式。我們平時瀏覽郵箱時突然彈出消息提示收到新郵件就屬于web端消息推送,在手機鎖屏上看到的微信消息等等都屬于APP消息推送。
這一章節主要對幾種消息推送的方式進行原理性的講解,并貼出簡單實現的代碼。
主要介紹其中的五種實現方式:短輪詢、Comet、Flash XMLSocket、Server-sent、WebSocket。
1、短輪詢
指在特定的的時間間隔(如每10秒),由瀏覽器對服務器發出HTTP request,然后由服務器返回最新的數據給客戶端的瀏覽器。瀏覽器做處理后進行顯示。無論后端此時是否有新的消息產生,都會進行響應。字面上看,這種方式是最簡單的。這種方式的優點是,后端編寫非常簡單,邏輯不復雜。但是缺點是請求中大部分中是無用的,浪費了帶寬和服務器資源。總結來說,簡單粗暴,適用于小型(偷懶)應用。
基于Jquery的ajax前端代碼:
<body> <div id="push"></div> <script> $(function () { setInterval(function () { getMsg(function (res) { $("#push").append("<p>" + res +"</p>"); }) },10000); }); ? function getMsg(handler){ $.ajax({ url:"/ShortPollingServlet", type:"post", success:function (res) { handler(res) } }); } </script> </body>
servlet簡單實現后端代碼:
public class ShortPollingServlet extends HttpServlet { ? public static int count = 0; ? protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //模擬業務代碼 count++; response.getWriter().print("msg:" + count ); } ? protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request,response); } }
2、Comet
包括了長輪詢和長連接,長輪詢是客戶端向服務器發送Ajax請求,服務器接到請求后hold住連接,直到有新消息才返回響應信息并關閉連接,客戶端處理完響應信息后再向服務器發送新的請求;長連接是在頁面中的iframe發送請求到服務端,服務端hold住請求并不斷將需要返回前端的數據封裝成調用javascript函數的形式響應到前端,前端不斷收到響應并處理。Comet的實現原理和短輪詢相比,很明顯少了很多無用請求,減少了帶寬壓力,實現起來比短輪詢復雜一丟丟。比用短輪詢的同學有夢想時,就可以用Comet來實現自己的推送。
長輪詢的優點很明顯,在無消息的情況下不會頻繁的請求,耗費資小并且實現了服務端主動向前端推送的功能,但是服務器hold連接會消耗資源,返回數據順序無保證,難于管理維護。WebQQ(好像掛了)就是這樣實現的。
基于Jquery的ajax前端代碼:
<body> <div id="push"></div> <script> $(function () { getMsg(); }); ? function getMsg() { $.ajax({ url:"/LongPollingServlet", type:"post", success:function (res) { $("#push").append("<p>" + res +"</p>"); getMsg(); } }); } </script> </body>
servlet簡單實現后端代碼:
public class LongPollingServlet extends HttpServlet { ? public static int count = 0; ? protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { count++; //睡眠時間模擬業務操作等待時間 double random = Math.round(Math.random()*10); long sleepTime = new Double(random).longValue(); try{ Thread.sleep(sleepTime*1000); response.getWriter().print("msg:" + count + " after " + sleepTime + "seconds servicing"); }catch (Exception e){ e.printStackTrace(); } ? } ? protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request,response); } }
長連接優點是消息即是到達,不發無用請求,管理起來也相對方便。缺點是服務端維護一個長連接會增加開銷。比如Gmail聊天(沒用過)就是這樣實現的。
基于Jquery的ajax前端代碼:
<head> <title>pushPage</title> <script type="text/javascript"> function loadData(msg) { var newChild = document.createElement("p"); newChild.innerHTML = msg; document.getElementById("push").appendChild(newChild); } </script> </head> <body> <div id="push"></div> <iframe src="/LongConnServlet" frameborder="0" name="longConn"></iframe> </body>
servlet簡單實現后端代碼:
public class LongConnServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { boolean flag = true; int i = 0; while (flag){ try { //模擬每1秒查詢一次數據庫,看是否有新的消息可以推送 Thread.sleep(1*1000); }catch (Exception e){ e.printStackTrace(); } ? String pushMsg = "push msg : " + i; response.setContentType("text/html;charset=GBK"); response.getWriter().write("<script type='text/javascript'>parent.loadData('" + pushMsg + "')</script>"); response.flushBuffer(); i++; if(i==5){ flag = false; } } } ? protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request,response); } }
3、Flash XMLSocket
在 HTML 頁面中內嵌入一個使用了 XMLSocket 類的 Flash 程序。JavaScript 通過調用此 Flash 程序提供的socket接口與服務器端的socket進行通信。JavaScript 在收到服務器端以 XML 格式傳送的信息后可以很容易地控制 HTML 頁面的內容顯示。
原理示意圖(引用http://t.cn/Ex6CYHk)
利用Flash XML Socket實現”服務器推”技術前提:
(1)Flash提供了XMLSocket類,服務器利用Socket向Flash發送數據;
(2)JavaScript和Flash的緊密結合JavaScript和Flash可以相互調用。
優點是實現了socket通信,不再利用無狀態的http進行偽推送。但是缺點更明顯:
1.客戶端必須安裝 Flash 播放器;
2.因為 XMLSocket 沒有 HTTP 隧道功能,XMLSocket 類不能自動穿過防火墻;
3.因為是使用套接口,需要設置一個通信端口,防火墻、代理服務器也可能對非 HTTP 通道端口進行限制。
這種方案在一些網絡聊天室,網絡互動游戲中已得到廣泛使用。不進行代碼示例。(可參考http://t.cn/aezSch)
4、Server-sent
服務器推指的是HTML5規范中提供的服務端事件EventSource,瀏覽器在實現了該規范的前提下創建一個EventSource連接后,便可收到服務端的發送的消息,實現一個單向通信。客戶端進行監聽,并對響應的信息處理顯示。該種方式已經實現了服務端主動推送至前端的功能。優點是在單項傳輸數據的場景中完全滿足需求,開發人員擴展起來基本不需要改后端代碼,直接用現有框架和技術就可以集成。
基于HTML5的Server-sent事件:
<head> <title>Title</title> <script> var source = new EventSource("/ServerSentServlet");//創建一個新的 EventSource對象, source.onmessage = function (evt) {//每接收到一次更新,就會發生 onmessage事件 var newChild = document.createElement("p"); newChild.innerHTML = evt.data; document.getElementById("push").appendChild(newChild); } </script> </head> <body> <div id="push"></div> </body>
servlet簡單實現后端代碼:
public class ServerSentServlet extends HttpServlet { ? public static int count = 0; ? protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { count++; response.setCharacterEncoding("UTF-8"); response.setHeader("Content-Type", "text/event-stream");//設置服務器端事件流 response.setHeader("Cache-Control","no-cache");//規定不對頁面進行緩存 response.setHeader("Pragma","no-cache"); response.setDateHeader("Expires",0); PrintWriter pw = response.getWriter(); pw.println("retry: 5000"); //設置請求間隔時間 pw.println("data: " + "msg:" + count +"\n\n"); } ? protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request,response); } }
5、WebSocket
WebSocket是HTML5下一種新的協議,是基于TCP的應用層協議,只需要一次連接,便可以實現全雙工通信,客戶端和服務端可以相互主動發送消息。客戶端進行監聽,并對響應的消息處理顯示。這個技術相信基本都聽說過,就算沒寫過代碼,也大概知道干嘛的。通過名字就能知道,這是一個Socket連接,一個能在瀏覽器上用的Socket連接。是HTML5標準中的一個內容,瀏覽器通過javascript腳本手動創建一個TCP連接與服務端進行通訊。優點是雙向通信,都可以主動發送消息,既可以滿足“問”+“答”的響應機制,也可以實現主動推送的功能。缺點就是編碼相對來說會多點,服務端處理更復雜(我覺得當一條有情懷的咸魚就應該用這個!)。
前端代碼:
<body> <div id="push"></div> </body> <script> $(function () { var webSocket = new WebSocket("ws://localhost:8080/ws"); webSocket.onmessage = function (ev) { $("#push").append("<p>" + ev.data +"</p>"); } }) </script>
基于注解簡單實現后端代碼:
@ServerEndpoint("/ws") public class MyWebSocket { ? private Session session; ? public MyWebSocket() { ? } ? @OnOpen public void onOpen(Session session) { this.session = session; System.out.println("someone connect"); int count = 1; while (count<=5){ //睡眠時間模擬業務操作等待時間 double random = Math.round(Math.random()*10); long sleepTime = new Double(random).longValue(); try { Thread.sleep(sleepTime*1000); session.getBasicRemote().sendText("msg:" + count +" from server after" + sleepTime + " seconds"); }catch (Exception e){ e.printStackTrace(); } count++; } ? } ? @OnError public void onError(Throwable t){ System.out.println("something error"); } }
以上是對五種推送方式原理的簡單講解和代碼的實現。
上面說了很多原理,也給出了簡單的代碼實現,但是在實際生產過程中,肯定不能用上面的代碼,針對自己系統的應用場景選擇合適的推送方案才是合理的,因此最后簡單說一下實現個性化推送的兩種方式。第一種很簡單,直接使用第三方實現的推送,無需復雜的開發運維,直接可以使用。第二種就是自己封裝,可以選擇如今較為火熱的WebSocket來實現系統的推送。
1、第三方
在這里推薦一個第三方推送平臺,GoEasy。
推薦理由是GoEasy的理念符合我們的選擇(可參考http://t.cn/Ex6jg3q):
(1)更簡單的方式將消息從服務器端推送至客戶端
(2)更簡單的方式將消息從各種客戶端推送至客戶端
GoEasy具體的使用方式這里不再贅述,詳見官網。對于后端后端開發者,可直接使用Rest方式調用推送,對于前端或web開發者,可以從web客戶端用javascript腳本進行調用推送。
2、封裝自己的推送服務
如果是一個老系統進行擴展,那么更推薦使用Server-sent,服務端改動量不會很大。如果是新系統,更推薦websocket,實現的功能功能更全面。
我們以websocket為例,不再貼出具體的代碼實現。
我們如果需要使用websocket技術實現自己的推送服務,需要注意哪些點,或者說需要踩哪些坑呢,本文最后列出幾點供大家參考:
長連接的心跳處理;
從WebSocket中獲取HttpSession進行用戶相關操作;
服務端調優實現高并發量client同時在線;
服務端維持多用戶的狀態;
群發消息;
等等等….
最后貼出上述代碼的git庫地址,所有demo均可運行。環境為jdk1.8+tomcat8。
http://t.cn/Ex6TRVZ
關于作者:徐曉明,普元開發工程師,畢業于遼寧科技大學,專注于使用移動開發平臺開發app,負責中國郵政集團移動平臺項目郵我行app開發和后臺開發運維工作。
關于EAWorld:微服務,DevOps,數據治理,移動架構原創技術分享。
ebSocket是一種在單個TCP連接上進行全雙工通信的協議,使得客戶端和服務器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。在WebSocket API中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,并進行雙向數據傳輸。
引入jquery文件
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
建立WebSocket連接及發送消息
以上是推送消息及發送消息實例代碼,以供參考、備忘!
*請認真填寫需求信息,我們會在24小時內與您取得聯系。