简单JAVA币世界爬虫通知
最近玩数字货币,老想去刷新‘币世界’,受不了了,就准备自己写一个简单的爬虫。
构想是这样的:本地运行的简单爬虫程序去爬币世界的信息,然后通知给我,最好能显示内容。
OK,写个桌面应用?用Swing?用AWT?感觉麻烦,不想去复习JSE的东西。
突然想到web端好像也可以通知。(下图为效果图)
Ok,只需要写一个简单的web应用就行,开通H5的notification。
准备工作及必要技能
1.java web ---使用spring boot 开箱即用,非常简单。
2.简单的爬虫使用jsoup。
3.前端使用了angularJs。(socket and H5 notification)
Step 1
先去看看“币世界” http://www.bishijie.com/kuaixun/ (显然我只需要kuaixun部分)
想法: 我可以直接开始扒,但是显得非常的不美丽,每次扒取还要和上一次的数据对比。
想法: “币世界” 上面有一个东西是未读信息条目数,感觉这东西应该会对我的程序有所帮助。
开工:
1.打开浏览器中的币世界的network,可以发现这里有轮询的出现,看URL就知道,这是一个获取
未读消息数目的API。
www.bishijie.com/api/news/unreadNum?timestamp=1525350000
经过简单的推理就知道后面得是当前的秒时间戳,OK,币世界自己也是通过时间戳为参数轮询的,我们来模拟URL结果。如下图
OK,已经有思路了,我们按照他官网的思路来做,避免过多的拉内容,对他对我们都有好处。
2.贴上获取unread num的核心代码,非常简单。(完成代码在后面会附上的githup上)
package com.bishijie.alert.service.impl; import com.bishijie.alert.asset.CommonUrl; import com.bishijie.alert.bean.Timestamp; import com.bishijie.alert.service.IUnderReadNum; import com.bishijie.alert.util.HttpUtil; import com.bishijie.alert.util.JSONUtil; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.text.MessageFormat; import java.util.HashMap; @Service public class UnderReadNumImpl implements IUnderReadNum { @Resource Timestamp timestamp; @Override public int getUnderReadNum(long timestamp) { String req = HttpUtil.get(MessageFormat.format(CommonUrl.underReadNum, timestamp + "")); return this.getNumFromMap(this.jsonToMap(req)); } @Override public int getUnderReadNum() { String req = HttpUtil.get(MessageFormat.format(CommonUrl.underReadNum, this.timestamp.getTimestamp() + "")); return this.getNumFromMap(this.jsonToMap(req)); } /////////////////////////////////////////////////////////// private /////////////////////////////////////////////////////////// private HashMap jsonToMap(String req) { HashMap<String, Object> reqMap = new HashMap<>(); try { reqMap = JSONUtil.toObject(req, HashMap.class); } catch (Exception e) { e.printStackTrace(); } return reqMap; } private int getNumFromMap(HashMap reqMap){ int tempNum = 0; if(reqMap.get("data")!=null){ HashMap<String,Integer> dataMap= (HashMap<String, Integer>) reqMap.get("data"); tempNum = dataMap.get("num"); } return tempNum; } }
3.OK 拿到了未读信息数目,不为0的时候,当然就要去真正的趴币世界了。(tab1 记得加时间戳,避免302)
下面贴上获取消息的核心代码片段。(完成代码在后面会附上的githup上)
package com.bishijie.alert.service.impl; import com.bishijie.alert.asset.CommonUrl; import com.bishijie.alert.bean.Timestamp; import com.bishijie.alert.bean.UnderReadMessage; import com.bishijie.alert.service.IUnderReadMessage; import com.bishijie.alert.util.HttpUtil; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.select.Elements; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.ArrayList; import java.util.Date; import java.util.List; @Service public class UnderReadMessageImpl implements IUnderReadMessage{ @Resource Timestamp timestamp; @Override public List<UnderReadMessage> getUnderReadMessage(int underReadNum) { this.timestamp.initTimestamp();//reset timestamp List<UnderReadMessage> mesList = new ArrayList<>(); String req = HttpUtil.get(CommonUrl.coinWorld+"/?time="+new Date().getTime()); Document doc = Jsoup.parse(req); Elements lis = doc.select("li.lh32"); for(int i = 0;i<underReadNum;i++){ mesList.add(new UnderReadMessage( lis.get(i).select("h2").first().toString().replace("<h2>","").replace("</h2>","").replace("<h2 style=\"color:#ff0000;\">",""), lis.get(i).select("div").first().toString().replace("<div>","").replace("</div>",""))); } return mesList; } @Override public String getUnderReadMessagePage() { String req = HttpUtil.get(CommonUrl.coinWorld+"/?time="+new Date().getTime()); return req; } }
4.OK,数据都有了,我的构想是基于socket给前端推送然后前端通过H5通知给用户弹窗。完美!
具体逻辑是: 0.5秒的定时查询未读消息数目,如果不为0,这去爬网站信息,获取前未读消息数目的信息,通过socket给前端。(其中send无用,纯测试哈)
核心代码片段如下:
package com.bishijie.alert.web; import com.bishijie.alert.bean.ServerMesList; import com.bishijie.alert.bean.SocketMessage; import com.bishijie.alert.service.IUnderReadMessage; import com.bishijie.alert.service.IUnderReadNum; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Controller; import javax.annotation.Resource; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; @EnableScheduling @Controller public class SocketController { @Autowired private SimpMessagingTemplate messagingTemplate; @Resource IUnderReadNum iUnderReadNum; @Resource IUnderReadMessage iUnderReadMessage; @Resource ServerMesList mesList; @Deprecated @MessageMapping("/send") @SendTo("/topic/send") public SocketMessage send(SocketMessage message) throws Exception { System.out.println(message.date); DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); message.date = df.format(new Date()); return message; } @Scheduled(fixedRate = 500) @Deprecated //no send socket to customer public void sendUnderReadNum() throws Exception { int num = iUnderReadNum.getUnderReadNum(); if (num != 0) { mesList.setListMes(iUnderReadMessage.getUnderReadMessage(num)); sendUnderReadMessage(); messagingTemplate.convertAndSend("/coinWorld/underReadNum", num); System.out.println(new Date()+"####Send Msg####The Num is "+num); } } //core method private void sendUnderReadMessage() { messagingTemplate.convertAndSend("/coinWorld/underReadMessage", mesList); } }
5.最后附上前端代码,非常简单。
<body ng-app="app" ng-controller="MainController"> <div ng-repeat="item in listMes"> <h3>{{item.title}}</h3> <h4>{{item.body}}</h4> <br/> </div> </body> <script src="https://cdn.bootcss.com/angular.js/1.5.6/angular.min.js"></script> <script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script> <script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script> <script type="text/javascript"> var stompClient = null; var app = angular.module('app', []); app.controller('MainController', function ($rootScope, $scope, $http) { // ********************************************* data ********************************************* $scope.listMes = []; $scope.historyMes = []; // ********************************************* method ********************************************* $scope.connect = function () { var socket = new SockJS('/my-websocket'); stompClient = Stomp.over(socket); stompClient.connect({}, function (frame) { stompClient.subscribe('/coinWorld/underReadMessage', function (req) { $scope.listMes = JSON.parse(req.body).listMes ; console.log($scope.listMes); for(var i=0;i<$scope.listMes.length;i++){ $scope.notificationSend($scope.listMes[i].title, $scope.listMes[i].body,'/symbol.png',null); } $scope.$apply(); }); }); }; $scope.disconnect = function () { if (stompClient != null) { stompClient.disconnect(); } $scope.data.connected = false; } $scope.send = function () { stompClient.send("/app/send", {}, JSON.stringify({ 'message': $scope.data.message })); } $scope.valid = function () { if (!window.Notification) { alert("Your browser not support Notification!") } } $scope.notification = function (title, msg, imagePath, onclick) { if (Notification.permission == "granted") { var notification = new Notification(title, { body: msg, icon: imagePath }); notification.onclick = onclick; return notification; } } $scope.notificationSend = function (title, msg, imagePath, onclick) { var notification = $scope.notification(title, msg, imagePath, function () { notification.close(); onclick(); }); } // ********************************************* init ********************************************* $scope.connect(); $scope.valid(); }); </script>
前端无任何显示,启动该应用后,只需打开网页输入localhost:12345 即可达到通知效果。
当有消息来得时候,就会弹窗了哦
轻松愉快。
附上githup地址:https://github.com/liaoke0123/digitalCashTool
可直接运行的哦。
愿比特币2年后20万美元。