整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

使用JavaScript写爬虫

比Python,JavaScript才是更适合写爬虫的语言。原因有如下三个方面:

  • JavaScript异步IO机制适用于爬虫这种IO密集型任务。JavaScript中的回调非常自然,使用异步网络请求能够充分利用CPU。
  • JavaScript中的jQuery毫无疑问是最强悍的HTML解析工具,使用JavaScript写爬虫能够减少学习负担和记忆负担。虽然Python中有PyQuery,但终究还是比不上jQuery自然。
  • 爬取结果多为JSON,JavaScript是最适合处理JSON的语言。

一、任务:爬取用户在Github上的repo信息

通过实例的方式学习爬虫是最好的方法,先定一个小目标:爬取github repo信息。入口URL如下,我们只需要一直点击next按钮就能够遍历到用户的所有repo。

https://github.com/{{username}}?tab=repositories

获取repo之后,可以做什么?

  • 统计用户最常使用的语言,统计用户语言使用分布情况统计用户所获取的star数,fork数

二、爬虫双股剑: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。

整个项目用到的库包括:

  • express:后端框架
  • cheerio+axios:爬虫
  • ws:websocket展示爬取过程
  • webpack:打包工具
  • less:样式语言
  • echarts:图表展示
  • vue:模板渲染
  • jquery:DOM操作
  • mongodb:存储数据
  • 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端的消息推送

三、实现个性化的推送

一、什么是消息推送

  • 经典场景1

当我在官网观望犹豫时,突然看到了上面消息,一位神秘的徐老板竟然爆出了麻痹戒指!!我的天,于是我果断开始了游戏!这消息很及时!

  • 经典场景2

当我拿起手机不知干嘛时收到了这条招女婿的消息.......瞬间来了精神

上述两种场景,是生活中很常见的场景,通过图文描述,应该已经清楚了推送的场景,也引出了两大推送种类,web端消息推送和移动端消息推送。接下来对消息推送进行具体的解释。

  • 概念:

消息推送(Push)指运营人员通过自己的产品或第三方工具对用户当前网页或移动设备进行的主动消息推送。用户可以在网页上或移动设备锁定屏幕和通知栏看到push消息通知。以此来实现用户的多层次需求,使得用户能够自己设定所需要的信息频道,得到即时消息,简单说就是一种定制信息的实现方式。我们平时浏览邮箱时突然弹出消息提示收到新邮件就属于web端消息推送,在手机锁屏上看到的微信消息等等都属于APP消息推送。

二、web端的消息推送

这一章节主要对几种消息推送的方式进行原理性的讲解,并贴出简单实现的代码。

主要介绍其中的五种实现方式:短轮询、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中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

1、添加pom依赖



2、新建WebSocket服务类





3、创建获取任务状态的接口及实现类



4、JS部分相关代码

引入jquery文件

<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>

建立WebSocket连接及发送消息


以上是推送消息及发送消息实例代码,以供参考、备忘!