Merge remote-tracking branch 'origin/master'
# Conflicts: # bocai.db # src/main/java/com/tem/bocai/util/HttpClientExample.java
This commit is contained in:
@@ -52,6 +52,10 @@ public class LoginInfoResult {
|
|||||||
@Column(name = "cookie")
|
@Column(name = "cookie")
|
||||||
private String cookie;
|
private String cookie;
|
||||||
|
|
||||||
|
//余额
|
||||||
|
@Column(name = "balance")
|
||||||
|
private String balance;
|
||||||
|
|
||||||
@Column(name = "create_time", nullable = false, updatable = false)
|
@Column(name = "create_time", nullable = false, updatable = false)
|
||||||
@CreationTimestamp
|
@CreationTimestamp
|
||||||
@Convert(converter = SQLiteDateConverter.class)
|
@Convert(converter = SQLiteDateConverter.class)
|
||||||
|
|||||||
@@ -6,19 +6,14 @@ import com.tem.bocai.repository.LoginInfoRepository;
|
|||||||
import com.tem.bocai.repository.LotteryResultRepository;
|
import com.tem.bocai.repository.LotteryResultRepository;
|
||||||
import com.tem.bocai.util.*;
|
import com.tem.bocai.util.*;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import us.codecraft.webmagic.Spider;
|
import us.codecraft.webmagic.Spider;
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -174,7 +169,7 @@ public class CrawlerSchedule {
|
|||||||
|
|
||||||
|
|
||||||
// 从7:00分30秒起每5分钟执行一次爬取今日已经结算
|
// 从7:00分30秒起每5分钟执行一次爬取今日已经结算
|
||||||
//@Scheduled(cron = "35 0/5 * * * ?")
|
@Scheduled(cron = "35 0/5 * * * ?")
|
||||||
public void executeSettlement() {
|
public void executeSettlement() {
|
||||||
log.info("开始爬取今日已经结算...");
|
log.info("开始爬取今日已经结算...");
|
||||||
int retryCount = 0;
|
int retryCount = 0;
|
||||||
@@ -237,23 +232,109 @@ public class CrawlerSchedule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每5分钟执行一次获取余额信息
|
||||||
|
* 从7:00分40秒起每5分钟执行一次
|
||||||
|
*/
|
||||||
|
@Scheduled(cron = "40 0/5 * * * ?")
|
||||||
|
//@Scheduled(cron = "*/9 * * * * ?")
|
||||||
|
public void executeGetBalance() {
|
||||||
|
log.info("开始获取余额信息");
|
||||||
|
|
||||||
/*public void executeSettlement() {
|
// 获取最新的登录信息
|
||||||
|
LoginInfoResult firstByOrderByCreateTimeDesc = loginInfoRepository.findFirstByOrderByCreateTimeDesc()
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
|
if (firstByOrderByCreateTimeDesc == null) {
|
||||||
|
log.warn("未找到登录信息,跳过余额获取");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstByOrderByCreateTimeDesc.getOnOff() == ONOFF) {
|
||||||
|
log.info("开关关闭,跳过余额获取");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取token
|
||||||
String token = tokenCacheService.getToken();
|
String token = tokenCacheService.getToken();
|
||||||
System.out.println("得到token = " + token);
|
if (token == null || token.isEmpty()) {
|
||||||
if (token != null && !token.isEmpty()) {
|
return;
|
||||||
// 2. 创建爬虫实例,传入token
|
}
|
||||||
CompletedTodayCrawler crawler = new CompletedTodayCrawler(token);
|
|
||||||
LoginInfoResult firstByOrderByCreateTimeDesc = loginInfoRepository.findFirstByOrderByCreateTimeDesc()
|
int retryCount = 0;
|
||||||
.orElse(null);
|
boolean success = false;
|
||||||
// 4. 执行爬虫
|
|
||||||
String url = firstByOrderByCreateTimeDesc.getLoginUrl()+"/member/bets?settled=true";
|
while (!success && retryCount < MAX_CRA) {
|
||||||
|
log.info("=== 第 {} 次尝试获取余额信息 ===", retryCount + 1);
|
||||||
|
|
||||||
|
// 重新获取token(如果重试)
|
||||||
|
if (retryCount > 0) {
|
||||||
|
token = tokenCacheService.getTokenSqlite();
|
||||||
|
if (token == null) {
|
||||||
|
log.error("重试时无法获取有效token");
|
||||||
|
retryCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("使用token: " + (token.length() > 20 ? token.substring(0, 20) + "..." : token));
|
||||||
|
|
||||||
|
// 创建爬虫实例
|
||||||
|
BalanceWebMagicCrawler crawler = new BalanceWebMagicCrawler(token);
|
||||||
|
|
||||||
|
// 构建URL
|
||||||
|
String url = firstByOrderByCreateTimeDesc.getLoginUrl() + "/member/index";
|
||||||
|
|
||||||
|
// 执行爬虫
|
||||||
Spider.create(crawler)
|
Spider.create(crawler)
|
||||||
.addUrl(url)
|
.addUrl(url)
|
||||||
.thread(1)
|
.thread(1)
|
||||||
.run();
|
.run();
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
|
// 检查是否成功解析数据
|
||||||
|
success = BalanceWebMagicCrawler.isLastParseSuccess();
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
// 获取并处理余额信息
|
||||||
|
Map<String, String> balanceInfo = BalanceWebMagicCrawler.getBalanceInfo();
|
||||||
|
if (!balanceInfo.isEmpty()) {
|
||||||
|
log.info("成功获取余额信息:");
|
||||||
|
for (Map.Entry<String, String> entry : balanceInfo.entrySet()) {
|
||||||
|
if (entry.getKey().contains("快开彩额度")) {
|
||||||
|
firstByOrderByCreateTimeDesc.setBalance(entry.getValue());
|
||||||
|
if (entry.getValue()!=null) {
|
||||||
|
loginInfoRepository.save(firstByOrderByCreateTimeDesc);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.warn("解析到空余额信息");
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
log.info("本次尝试未解析到余额信息");
|
||||||
|
retryCount++;
|
||||||
|
|
||||||
|
// 等待一下再重试
|
||||||
|
if (retryCount < MAX_CRA) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(2000 * retryCount);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
log.error("获取余额信息失败,所有重试均未成功");
|
||||||
|
} else {
|
||||||
|
log.info("余额信息获取完成");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
225
src/main/java/com/tem/bocai/util/BalanceWebMagicCrawler.java
Normal file
225
src/main/java/com/tem/bocai/util/BalanceWebMagicCrawler.java
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
package com.tem.bocai.util;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
import org.jsoup.select.Elements;
|
||||||
|
import us.codecraft.webmagic.Page;
|
||||||
|
import us.codecraft.webmagic.Site;
|
||||||
|
import us.codecraft.webmagic.Spider;
|
||||||
|
import us.codecraft.webmagic.processor.PageProcessor;
|
||||||
|
import us.codecraft.webmagic.selector.Html;
|
||||||
|
import us.codecraft.webmagic.selector.Selectable;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class BalanceWebMagicCrawler implements PageProcessor {
|
||||||
|
|
||||||
|
private final String token;
|
||||||
|
private Site site;
|
||||||
|
private static volatile boolean lastParseSuccess = false;
|
||||||
|
private static Map<String, String> balanceInfo = new HashMap<>();
|
||||||
|
|
||||||
|
public BalanceWebMagicCrawler(String token) {
|
||||||
|
this.token = token;
|
||||||
|
initSite();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化Site配置
|
||||||
|
*/
|
||||||
|
private void initSite() {
|
||||||
|
site = Site.me()
|
||||||
|
.setRetryTimes(3)
|
||||||
|
.setSleepTime(1000)
|
||||||
|
.setTimeOut(10000)
|
||||||
|
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36");
|
||||||
|
|
||||||
|
// 设置cookie
|
||||||
|
if (token != null && !token.isEmpty()) {
|
||||||
|
site.addHeader("cookie", "token=" + token);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process(Page page) {
|
||||||
|
try {
|
||||||
|
// 获取页面HTML
|
||||||
|
Html html = page.getHtml();
|
||||||
|
String htmlContent = html.toString();
|
||||||
|
|
||||||
|
log.info("访问余额页面URL: " + page.getUrl());
|
||||||
|
log.info("页面标题: " + html.xpath("//title/text()").get());
|
||||||
|
|
||||||
|
// 解析HTML获取余额信息
|
||||||
|
Map<String, String> result = parseBalanceInfo(htmlContent);
|
||||||
|
|
||||||
|
if (!result.isEmpty()) {
|
||||||
|
lastParseSuccess = true;
|
||||||
|
balanceInfo = result;
|
||||||
|
|
||||||
|
// 打印解析到的余额信息
|
||||||
|
log.info("========== 余额信息解析成功 ==========");
|
||||||
|
for (Map.Entry<String, String> entry : result.entrySet()) {
|
||||||
|
log.info("{}: {}", entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
log.info("=====================================");
|
||||||
|
|
||||||
|
// 将数据存入结果
|
||||||
|
page.putField("balanceInfo", result);
|
||||||
|
page.putField("html", htmlContent.substring(0, Math.min(500, htmlContent.length())) + "...");
|
||||||
|
|
||||||
|
} else {
|
||||||
|
lastParseSuccess = false;
|
||||||
|
log.warn("未解析到余额信息");
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
lastParseSuccess = false;
|
||||||
|
log.error("解析余额页面时发生错误: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析余额信息
|
||||||
|
* @param htmlContent HTML内容
|
||||||
|
* @return 余额信息Map
|
||||||
|
*/
|
||||||
|
private Map<String, String> parseBalanceInfo(String htmlContent) {
|
||||||
|
Map<String, String> result = new HashMap<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Document doc = Jsoup.parse(htmlContent);
|
||||||
|
|
||||||
|
// 查找账户信息部分
|
||||||
|
Element userInfoDiv = doc.selectFirst("div.user_info");
|
||||||
|
if (userInfoDiv == null) {
|
||||||
|
log.warn("未找到账户信息div");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找所有账户类型
|
||||||
|
Elements accountDivs = userInfoDiv.select("div.accounts");
|
||||||
|
|
||||||
|
for (Element accountDiv : accountDivs) {
|
||||||
|
// 检查是否显示(有些账户类型可能不显示)
|
||||||
|
String style = accountDiv.attr("style");
|
||||||
|
if (style.contains("display:none")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取账户类型标签和余额
|
||||||
|
Elements infoDivs = accountDiv.select("div.info");
|
||||||
|
for (Element infoDiv : infoDivs) {
|
||||||
|
Element label = infoDiv.selectFirst("label");
|
||||||
|
Element value = infoDiv.selectFirst("span");
|
||||||
|
|
||||||
|
if (label != null && value != null) {
|
||||||
|
String labelText = label.text().trim();
|
||||||
|
String valueText = value.text().trim();
|
||||||
|
|
||||||
|
// 特别关注快开彩额度
|
||||||
|
if (labelText.contains("快开彩额度")) {
|
||||||
|
result.put("快开彩额度", valueText);
|
||||||
|
log.info("找到快开彩额度: {}", valueText);
|
||||||
|
} else if (labelText.contains("未结算金额")) {
|
||||||
|
result.put("未结算金额", valueText);
|
||||||
|
} else if (labelText.contains("全国彩额度")) {
|
||||||
|
result.put("全国彩额度", valueText);
|
||||||
|
} else if (labelText.contains("香港彩额度")) {
|
||||||
|
result.put("香港彩额度", valueText);
|
||||||
|
} else if (labelText.contains("第三方额度")) {
|
||||||
|
result.put("第三方额度", valueText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没找到账户信息,尝试其他方式查找
|
||||||
|
if (result.isEmpty()) {
|
||||||
|
// 查找账号信息
|
||||||
|
Element accountSpan = doc.selectFirst("div.inline-name span");
|
||||||
|
if (accountSpan != null) {
|
||||||
|
result.put("账号", accountSpan.text().trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找所有包含"额度"的元素
|
||||||
|
Elements balanceElements = doc.select("span.balance");
|
||||||
|
for (Element balance : balanceElements) {
|
||||||
|
Element parent = balance.parent();
|
||||||
|
if (parent != null) {
|
||||||
|
Element label = parent.selectFirst("label");
|
||||||
|
if (label != null && label.text().contains("额度")) {
|
||||||
|
result.put(label.text().trim(), balance.text().trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("解析余额信息时发生错误: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Site getSite() {
|
||||||
|
return site;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取解析状态
|
||||||
|
*/
|
||||||
|
public static boolean isLastParseSuccess() {
|
||||||
|
return lastParseSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取解析到的余额信息
|
||||||
|
*/
|
||||||
|
public static Map<String, String> getBalanceInfo() {
|
||||||
|
return new HashMap<>(balanceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除余额信息缓存
|
||||||
|
*/
|
||||||
|
public static void clearBalanceInfo() {
|
||||||
|
balanceInfo.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试方法
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// 测试用的token
|
||||||
|
String testToken = "31ea12c0a75f1f17dc2004ea5f7501aed73c00fa";
|
||||||
|
String url = "https://4701268539-esh.qdk63ayw8g.com/member/index";
|
||||||
|
|
||||||
|
log.info("开始测试余额爬虫...");
|
||||||
|
log.info("使用URL: " + url);
|
||||||
|
|
||||||
|
// 创建爬虫
|
||||||
|
Spider.create(new BalanceWebMagicCrawler(testToken))
|
||||||
|
.addUrl(url)
|
||||||
|
.thread(1)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
// 打印结果
|
||||||
|
if (isLastParseSuccess()) {
|
||||||
|
Map<String, String> info = getBalanceInfo();
|
||||||
|
System.out.println("\n========== 余额信息 ==========");
|
||||||
|
for (Map.Entry<String, String> entry : info.entrySet()) {
|
||||||
|
System.out.println(entry.getKey() + ": " + entry.getValue());
|
||||||
|
}
|
||||||
|
System.out.println("==============================");
|
||||||
|
} else {
|
||||||
|
System.out.println("获取余额信息失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,6 @@ import com.fasterxml.jackson.databind.JsonNode;
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -14,11 +12,6 @@ import java.net.HttpURLConnection;
|
|||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.security.KeyManagementException;
|
|
||||||
import java.security.KeyStoreException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user