前言
足球发展已经超百余年,但发现市面上没有真正比较好的预测分析软件,本着十几年的JAVA开发经验,想着亲手做一个关于足球走地大小球、让球、角球的分析软件看看情况是怎么样的。
开发本类工具需要按以下步骤进行,
一、选择稳定的网站足球网站数据采集数据
做此工具,我选择的是球琛网的走地数据,主要采集“即时比分”相关列表数据,及亚欧大相关初盘、终盘、滚球相关赔率。
软件采集的代码片段如下:
//从网页取得数据 InputStream in =null; try{ JavaScriptPage jspage = (JavaScriptPage) HtmlunitHelper.getPage(url, requestHead); in=jspage.getWebResponse().getContentAsStream(); jspage.getEnclosingWindow().getWebClient().closeAllWindows(); }catch(Exception e){ HtmlPage jspage = (HtmlPage) HtmlunitHelper.getPage(url, requestHead); in=jspage.getWebResponse().getContentAsStream(); jspage.getEnclosingWindow().getWebClient().closeAllWindows(); } InputStreamReader inr = new InputStreamReader(in, "UTF-8"); BufferedReader br = new BufferedReader(inr); String s = null; //String site="live"; while((s=br.readLine())!=null){ if(pattern.matcher(s).find()){ listStr.add(s); } }for(String s:listStr) { Match match = new Match(); String strData = s.split("\"", s.lastIndexOf("\""))[1]; String[] data = StringUtil.split(strData,"\\^"); try{ match.setJqqc(data[48]+"-"+data[49]); }catch(Exception e){} match.setId(data[0]); match.setGameName(data[2]); match.setGameFirstName(data[2].substring(0,1)); match.setGameNameC(data[3]); match.setTeams1(data[5].replace("<font color=#880000>", "").replace("</font>", "")); match.setTeams1C(data[6].replace("<font color=#880000>", "").replace("</font>", "")); match.setTeams2(data[8].replace("<font color=#880000>", "").replace("</font>", "")); match.setTeams2C(data[9].replace("<font color=#880000>", "").replace("</font>", "")); match.setScore(data[14]+"-"+data[15]); match.setYellowCard1(Integer.parseInt(data[20])); match.setYellowCard2(Integer.parseInt(data[21])); match.setRedCard1(Integer.parseInt(data[18])); String team1=match.getTeams1(); String team2=match.getTeams2(); try{ team1 = (String)engine.eval("T[\""+data[37]+"_3\"][0]"); team2 = (String)engine.eval("T[\""+data[38]+"_3\"][0]"); team1 = team1.replaceAll(" ", ""); team2 = team2.replaceAll(" ", ""); }catch(Exception e){} try{ match.setRedCard2(Integer.parseInt(data[19])); }catch(Exception e){} team1=team1.replace("U19", ""); team1=team1.replace("U20", ""); team1=team1.replace("U21", ""); team1=team1.replace("U22", ""); team1=team1.replace("U23", ""); team1=team1.replace("B队", ""); team1=team1.replace("(中)", ""); team2=team2.replace("U19", ""); team2=team2.replace("U20", ""); team2=team2.replace("U21", ""); team2=team2.replace("U22", ""); team2=team2.replace("U23", ""); team2=team2.replace("B队", ""); team2=team2.replace("(中)", ""); match.setTeamHg1(team1); match.setTeamHg2(team2); match.setHalfCourt(data[16]+"-"+data[17]); if("-".equals(match.getHalfCourt())){ match.setHalfCourt(match.getScore()); } String[] t = data[12].split(","); int statas = Integer.parseInt(data[13]); Date date = new Date(Integer.parseInt(t[0])-1900,Integer.parseInt(t[1]),Integer.parseInt(t[2]),Integer.parseInt(t[3]),Integer.parseInt(t[4]),Integer.parseInt(t[5])); long gotime = (System.currentTimeMillis() - date.getTime())/(1000*60); String strGotime = ""; try{ if(statas==1){ strGotime = gotime+""; if(gotime>45) strGotime = "45+"; }else if(statas==3){ strGotime = gotime+46+""; if(gotime+46>90) strGotime = "90+"; }else{ strGotime = state_ch[statas+14]; } }catch(Exception e){ //e.printStackTrace(); strGotime=""; log.info("007获取状态出错,team1:"+match.getTeams1()+",statas:"+statas+",gotime:"+gotime+",error:"+e.getMessage()); } match.setStatus(strGotime); // if(match.getStatus()==null||"".equals(match.getStatus())||"推迟".equals(match.getStatus())||"待定".equals(match.getStatus())){// if(!"true".equals(wkbsMap.get("wkbs"))){// continue;// }// } /*if("莫斯科斯巴达".equals(team1)||"清水心跳".equals(team1)||"托利马".equals(team1)){ log.info("比赛状态========>>"+strGotime+",team1==>>"+team1); }*/ //log.info("角球比分:"+match.getJqqc()+"==>>"+match.getTeamHg1()+" VS "+match.getTeamHg2()); //String strdataTime = t[0]+"-"+t[1]+"-"+t[2]+" "+data[11];// 2018,3,10,20,15,00 //log.info("data[12].trim()===>>"+data[12].trim()) match.setDataTime(DateUtils.parseDate(data[12].trim(), "yyyy,MM,dd,HH,mm,ss")); Calendar dateTime = Calendar.getInstance(); dateTime.setTime(match.getDataTime()); dateTime.add(Calendar.MONTH, 1); match.setDataTime(dateTime.getTime()); // Date dataTime = new Date(Integer.parseInt(t[0])-1900,Integer.parseInt(t[1]),Integer.parseInt(t[2]),Integer.parseInt(data[11].split(":")[0]),Integer.parseInt(data[11].split(":")[1]),0);// match.setDataTime(dataTime); String runMath="0"; try{ if(data[28].equals("True")){ String[] data1 = ch_goin.get(match.getId()); if(data1!=null){ if(data1[5].equals("2")) runMath ="2"; }else{ runMath ="1"; } }else{ runMath ="0"; } }catch(Exception e){ log.error("获取runMath异常"); } String[] goindata = goin.get(match.getId()); Double exponential = null; Double sbExponentiald = null; String sb = ""; if(goindata!=null){ if(goindata[14].equals("0")){ try { exponential = Double.parseDouble(goindata[3]); sbExponentiald = Double.parseDouble(goindata[4]); sb = this.Goal2GoalCn(goindata[2]); }catch(Exception e) { log.error("获取SB异常,"+team1+" VS "+team2+",goindata[3]:"+goindata[3]); } }else{ exponential = null; sbExponentiald = null; sb = "封"; } } match.setRunMath(runMath);
采集还是相对简单,主要是读取球琛的JSON数组,前端是通过开源的UI展示出来,界面如下:
但单是赔率数据是不够的,因此我们还采集了球琛的技术统计数据,如危险进攻数、射门数、射正射门数、传球成功率、控球率、进攻等,后面实现策略逻辑时,需要从这些数据中去查找规律。
二、开发筛选策略平台,动态设置筛选参数逻辑
这一步主要设置自己的动态策略,条件组合,参数调配等,下面简单列举其中一个策略,如:
70分钟前,主队让球,
控球率主队大于客队
危险进攻数主队大于客队12个以上
进攻主队大于客队20个
射门数主队大于客队
射正射门数主队大于客队
让球盘小于2
主队比分大于或等于客队比分
主队进攻大于 50
符合此所有条件后,看好主队获胜。
因此需要开发一些常用的界面,我们的界面如下,在这里就需要有一点点SQL的经验了:
三、定时任务执行,根据筛选策略生成相关符合策略的明细记录
策略写好后,就来到了这一步,这一步主要是配置系统的定任务,让定任务去执行条件策略的频繁筛选,
public void updatePreJudgment(SearchResult model, String todayStr,Integer writeType) { String smQuery=""; try { SearchMap searchMap = new SearchMap(); searchMap.eq("isHide", 0);// 是否隐藏 0--否 1--是 searchMap.notEq("status", "完");// 完成的不再判断 searchMap.notEq("status", "推迟"); searchMap.notEq("status", "待定"); // searchMap.notEq("status","未开");//未开的比赛不判断 searchMap.eq("sysTimeStr", todayStr); smQuery = "(" + model.getSmResult() + ")"; if (model.getIfInclude() != null && model.getIfInclude() == 1) {// 不包括 if (model.getGameName() != null && !"".equals(model.getGameName())) { model.setGameName(model.getGameName().replace(",", ",")); String gameName = ""; if (model.getGameName().indexOf(",") != -1) { for (String value : model.getGameName().split(",")) { gameName += "and gameNameC not like '%" + value + "%' "; } if (!"".equals(gameName)) { gameName = gameName.substring(3); } } else { gameName += " gameNameC not like '%" + model.getGameName() + "%' "; } smQuery += " and (" + gameName + ") "; } } else {// 包括 if (model.getGameName() != null && !"".equals(model.getGameName())) { model.setGameName(model.getGameName().replace(",", ",")); String gameName = ""; if (model.getGameName().indexOf(",") != -1) { for (String value : model.getGameName().split(",")) { gameName += "or gameNameC like '%" + value + "%' "; } if (!"".equals(gameName)) { gameName = gameName.substring(2); } } else { gameName += " gameNameC like '%" + model.getGameName() + "%' "; } smQuery += " and (" + gameName + ") "; } } searchMap.addHQL(smQuery); List<Match> listMatch = this.matchDAO.findObjects(searchMap, Match.class); for (Match match : listMatch) { match.setPreJudgment(model.getResultDesc()); /*match.setPreJudgment(((match.getPreJudgment() == null || "".equals(match.getPreJudgment())) ? "" : match.getPreJudgment() + ";\n") + model.getResultDesc());*/ String nowTime = CustomDateUtil.format(CustomDateUtil.createSysDate(), "HH:mm"); // 比分/+结论 String status = (match.getStatus() == null ? "" : match.getStatus()) + " "; String resultDesc = (match.getScore() == null ? "" : match.getScore()) + "/" + (model.getResultDesc() == null ? "" : model.getResultDesc()); if (model.getResultDesc() != null && model.getResultDesc().indexOf("角球") != -1) { resultDesc = match.getScore() + "/" + match.getJqqc() + "(角)" + "/" + (model.getResultDesc() == null ? "" : model.getResultDesc()); } String preJudgment = nowTime + " " + status + resultDesc; String searchResultId = match.getSearchResultId() == null ? "" : match.getSearchResultId();// 条件ID if (searchResultId.indexOf(model.getId()) == -1) {// 不存在相同的条件ID searchResultId += "," + model.getId(); } if (searchResultId.startsWith(",")) { searchResultId = searchResultId.substring(1); } match.setSearchResultId(searchResultId); String percentage = model.getPercentageStr();// 胜率 if (model.getWriteType() != null && model.getWriteType() == 1) {// 结论写入备注2 // 状态+比分+结论 String remark = ""; if (match.getRemark2() == null || "".equals(match.getRemark2())) { remark += preJudgment; } else { if (model.getType() != null && model.getType() == 1) {// 报警条件 if (match.getRemark2().indexOf(resultDesc) == -1) {// 不存在相同的报警结果 remark += match.getRemark2() + "\n" + preJudgment; } } else { if (match.getRemark2().indexOf(resultDesc) == -1) {// 不存在相同的预判结果 remark += match.getRemark2() + "\n" + preJudgment; } } } if (!"".equals(remark)) { match.setRemark2(remark + "; " + percentage); } } else {// 写入备注1 // 状态+比分+结论 String remark = ""; if (match.getRemark() == null || "".equals(match.getRemark())) { remark += preJudgment; } else { if (model.getType() != null && model.getType() == 1) {// 报警条件 if (match.getRemark().indexOf(resultDesc) == -1) {// 不存在相同的报警结果 remark += match.getRemark() + "\n" + preJudgment; } } else { if (match.getRemark().indexOf(resultDesc) == -1) {// 不存在相同的预判结果 remark += match.getRemark() + "\n" + preJudgment; } } } if (!"".equals(remark)) { match.setRemark(remark + "; " + percentage); } } if ((model.getType() != null && model.getType() == 1)) {// 报警条件 //log.info("报警条件生成统计明细********************************matchid:"+match.getId()); // 插入统计明细 SearchResult modelNew=new SearchResult(); modelNew = (SearchResult) MethodUtil.copyProperties(modelNew, model); this.createStatistics(match, modelNew); //log.info("========插入统计明细结束=======>>matchid:"+match.getId()); } if ((model.getType() != null && model.getType() == 1) || (model.getIfCanBet() != null && model.getIfCanBet() == 1)) {// 报警条件 match.setIsWarn(1); if (match.getBetTime() != null) { long interval = (new Date().getTime() - match.getBetTime().getTime()) / 1000; long seconds = Long.parseLong(CustomCommonConfig.getVoiceMap().get("seconds")); if (interval - seconds >= 0) {// 停止报警声音 match.setIsWarn(2); } } match.setIsTop(1);// 只有在报警时才自动置顶 match.setIsResult(1);// 符合结果 match.setColor(model.getColor()); match.setIsShow(1);// 显示 if (model.getIfCanBet() != null && model.getIfCanBet() == 1) { match.setIfCanBet(1); /*if (model.getWriteType() == 1) {// 赛前的 }*/ } // ****************保存结果********************* if ((model.getType() != null && model.getType() == 1)) {// 报警条件 if (model.getShowResult() != null && !"".equals(model.getShowResult())) { String showResult = match.getScore() == null ? "0-0" : match.getScore(); showResult += " " + model.getShowResult(); if (match.getShowResult() == null || "".equals(match.getShowResult())) { match.setShowResult(status + showResult); } else { if (match.getShowResult().indexOf(showResult) == -1) { match.setShowResult(match.getShowResult() + ";\n" + status + showResult); } } } } // ****************************************** } this.matchDAO.update(match); } if (model.getIfRight() != null && model.getIfRight() == 0) { this.matchDAO.updateJQL("update from SearchResult set ifRight=1 where id='"+model.getId()+"'", null); } } catch (Exception e) { e.printStackTrace(); log.error("007更新判断结果出错:" + e.getMessage() + ",预判结论:" + model.getResultDesc() + " 预判条件:"+ model.getSmResult()+"拼接后错误语句:"+smQuery); this.matchDAO.updateJQL("update from SearchResult set ifRight=0 where id='"+model.getId()+"'", null); } }
四、比赛完成后需要对生成的明细记录进行结算,统计每条策略的胜率情况
这是最后一步,就是对生成的预测条件进行胜率统计,下面展示一下我们的比赛结果结算方法代码及界面效果:
代码:
public static Integer getJsStatusNew(Statistics betRecord,String score){ //胜平负,如果主队进一球以上,购买胜的全赢,其它全输,如果均不进球,购买平的全赢,其它全输,如果客队进一球,购买负的全赢,其它全输 //赛前让球 //下1.14,盘口在上,如果当前比分是2-1,那么计算方法是2-1+(-0.25)下主队计算方法:,=0走水,=0.25赢半,>0.25全赢,=-0.25输半,<-0.25全输 //下0.51,盘口在下,如果当前比分是2-1,那么计算方法是2-1+(+0.5) //主分-客分+(±盘口)=0走水,主队计算方法=0.25赢半,>0.25全赢,=-0.25输半,<-0.25全输 //走地让球 //再说个例子 投注时候2-1,比赛结束2-2,投注的时候主队让球0.25 (2-2)-(2-1)+(-0.25)=-1.25 主队全输 //(主队分数-投注时候的主队分数)-(客队分数-投注时候的客队分数)+(±盘口) //大小球计算公式:两队-盘口(如果盘口带/,则取/后面的盘口), 主队计算方法=0走水,=0.25赢半,>0.25全赢,=-0.25输半,<-0.25全输 /*38 39 40 +0.5的应该是171 -0.5的-100 +0.5/1的是163*/ Integer jsStatus=0; Double score1=0D;//最终主队总分 Double score2=0D;//最终客队总分 Double betscore1=0D;//下注时主队总分 Double betscore2=0D;//下注时客队总分 betscore1=Double.parseDouble(betRecord.getScore().split("-")[0]); betscore2=Double.parseDouble(betRecord.getScore().split("-")[1]); score1=Double.parseDouble(score.split("-")[0]); score2=Double.parseDouble(score.split("-")[1]); Double zhpk=0D;//转换盘口 String handicap=betRecord.getHandicap(); if(handicap!=null&&!"".equals(handicap)&&betRecord.getBetType()!=20){ handicap=handicap.replace("+", ""); if("".equals(handicap)){ handicap="0"; } if(handicap.indexOf("/")!=-1){ zhpk=Double.parseDouble(handicap.split("/")[1])-0.25; }else{ zhpk=Double.parseDouble(handicap); }// if(ArithUtil.addDouble(zhpk, 0D)<0D){// zhpk=-zhpk;// } } if(betRecord.getBetType()==1||betRecord.getBetType()==8){//胜 if(score1>score2){//全赢,赔率包括本金 jsStatus=2; }else{//全输 jsStatus=4; } }else if(betRecord.getBetType()==3||betRecord.getBetType()==10){//平 if(ArithUtil.subDouble(score1, score2)==0D){//全赢,赔率包括本金 jsStatus=2; }else{//全输 jsStatus=4; } }else if(betRecord.getBetType()==2||betRecord.getBetType()==9){//负,赔率包括本金 if(score1<score2){//全赢 jsStatus=2; }else{//全输 jsStatus=4; } }else if(betRecord.getBetType()==20){//总进球数 Double zjq=Double.parseDouble(betRecord.getHandicap()); if(ArithUtil.subDouble(zjq, ArithUtil.addDouble(score1, score2))==0D||(zjq==7D&&ArithUtil.addDouble(score1, score2)>=7D)){//全赢 jsStatus=2; }else{//全输 jsStatus=4; } }else if(betRecord.getBetType()==4||betRecord.getBetType()==11){//让球主队 Double countVal=0D; //走地让球 //再说个例子 投注时候2-1,比赛结束2-2,投注的时候主队让球0.25 (2-2)-(2-1)+(-0.25)=-1.25 主队全输 //(主队分数-投注时候的主队分数)-(客队分数-投注时候的客队分数)+(±盘口) countVal=(score1-betscore1)-(score2-betscore2)+zhpk; if(countVal==0){//走水 jsStatus=6; }else if(countVal==0.25){//赢半 jsStatus=3; }else if(countVal>0.25){//全赢 jsStatus=2; }else if(countVal==-0.25){//输半 jsStatus=5; }else if(countVal<-0.25){//全输 jsStatus=4; } }else if(betRecord.getBetType()==5||betRecord.getBetType()==12){//让球客队 Double countVal=0D; //走地让球 //再说个例子 投注时候2-1,比赛结束2-2,投注的时候主队让球0.25 (2-2)-(2-1)+(-0.25)=-1.25 主队全输 //(主队分数-投注时候的主队分数)-(客队分数-投注时候的客队分数)+(±盘口) countVal=(score2-betscore2)-(score1-betscore1)+zhpk; if(countVal==0){//走水 jsStatus=6; }else if(countVal==0.25){//赢半 jsStatus=3; }else if(countVal>0.25){//全赢 jsStatus=2; }else if(countVal==-0.25){//输半 jsStatus=5; }else if(countVal<-0.25){//全输 jsStatus=4; } }else if(betRecord.getBetType()==6||betRecord.getBetType()==13||betRecord.getBetType()==15){//大球 //0走水,=0.25赢半,>0.25全赢,=-0.25输半,<-0.25全输 Double countVal=score1+score2-zhpk; if(countVal==0){ jsStatus=6; }else if(countVal==0.25){ jsStatus=3; }else if(countVal>0.25){ jsStatus=2; }else if(countVal==-0.25){ jsStatus=5; }else if(countVal<-0.25){ jsStatus=4; } }else if(betRecord.getBetType()==7||betRecord.getBetType()==14||betRecord.getBetType()==16){//小球 Double countVal=score1+score2-zhpk; if(countVal==0){ jsStatus=6; }else if(countVal==0.25){ jsStatus=5; }else if(countVal>0.25){ jsStatus=4; }else if(countVal==-0.25){ jsStatus=3; }else if(countVal<-0.25){ jsStatus=2; } } return jsStatus; }
胜率界面结果:
前端展示效果图:
总结
该软件适合初盘、滚球的大小球、让球、角球的相关分析,非常灵活,这些实践付出了不少,但也收获了很多,从陌生到熟悉,到最后慢慢深入,所以任何领域都需要我们认真学习,想干就干,敢于实践,才会做到你想做的东西。
相关系统演示地址:
谢谢大家。