|
@@ -18,7 +18,7 @@ import jakarta.annotation.PostConstruct;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
-import org.jeecg.common.util.RedisUtil;
|
|
|
+import org.jeecg.modules.adweb.common.util.AdwebRedisUtil;
|
|
|
import org.jeecg.modules.adweb.common.util.CommonUtil;
|
|
|
import org.jeecg.modules.adweb.common.util.DateUtil;
|
|
|
import org.jeecg.modules.adweb.common.util.ListUtil;
|
|
@@ -29,15 +29,16 @@ import org.jeecg.modules.adweb.seo.service.ISeoKeywordsSerpService;
|
|
|
import org.jeecg.modules.adweb.seo.service.ISeoKeywordsService;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
-import org.springframework.data.redis.core.RedisTemplate;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
import java.util.*;
|
|
|
|
|
|
/**
|
|
|
- * DataForSEO Serp查询 - 基于Redis同步
|
|
|
+ * DataForSEO Serp查询 - 基于Redis定时同步
|
|
|
*
|
|
|
- * <p>暂不使用DataForSEO callback实时更新方式,考虑稳定性
|
|
|
+ * <p>1. 暂不使用DataForSEO callback实时更新方式,考虑AdWeb API稳定性
|
|
|
+ *
|
|
|
+ * <p>2. 暂不使用DataForSEO serp/google/organic/tasks_ready API - 每次仅返回一个task,与文档不符
|
|
|
*
|
|
|
* @author wfansh
|
|
|
*/
|
|
@@ -66,9 +67,7 @@ public class DataForSEOService {
|
|
|
|
|
|
@Autowired private ISeoKeywordsSerpService seoKeywordsSerpService;
|
|
|
|
|
|
- @Autowired private RedisUtil redisUtil;
|
|
|
-
|
|
|
- @Autowired private RedisTemplate<String, Object> redisTemplate;
|
|
|
+ @Autowired private AdwebRedisUtil redisUtil;
|
|
|
|
|
|
private SerpApi serpApi;
|
|
|
|
|
@@ -88,7 +87,7 @@ public class DataForSEOService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 全局查询更新DataForSEO keywords Serp数据
|
|
|
+ * 全局查询更新DataForSEO keywords Serp数据 - 启动Serp任务并保存到Redis
|
|
|
*
|
|
|
* @param keywordType 1 - 指定词; 2 - 长尾词
|
|
|
* @param limit 最大查询条数
|
|
@@ -100,14 +99,14 @@ public class DataForSEOService {
|
|
|
log.info("暂无需要Serp查询的关键词");
|
|
|
} else {
|
|
|
// DataForSEO - each POST call containing no more than 100 tasks
|
|
|
- // https://docs.dataforseo.com/v3/serp/google/organic/task_post/?bash
|
|
|
+ // https://docs.dataforseo.com/v3/serp/google/organic/task_post
|
|
|
Lists.partition(seoKeywords, MAX_TASKS_PER_SERP_REQUEST).forEach(this::sendSerpRequest);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/** 同步DataForSEO Serp结果 - 从Redis中获取正在运行的任务 */
|
|
|
public void syncKeywordsSerpResults() {
|
|
|
- Set<String> serpTaskRedisKeys = redisTemplate.keys(this.getSerpTaskRedisKey("*"));
|
|
|
+ Set<String> serpTaskRedisKeys = redisUtil.keys(this.getSerpTaskRedisKey("*"));
|
|
|
|
|
|
if (CollectionUtil.isEmpty(serpTaskRedisKeys)) {
|
|
|
log.info("Redis中暂无需要同步的Serp关键词");
|
|
@@ -144,25 +143,23 @@ public class DataForSEOService {
|
|
|
serpTaskRequestInfoList.add(serpTaskRequestInfo);
|
|
|
}
|
|
|
|
|
|
- // 2. 发送DataForSEO Serp查询请求,验证并打印响应结果
|
|
|
+ // 2. 发送DataForSEO Serp查询请求,验证并记录响应结果
|
|
|
SerpGoogleOrganicTaskPostResponseInfo serpTaskPostResponseInfo =
|
|
|
serpApi.googleOrganicTaskPost(serpTaskRequestInfoList);
|
|
|
log.info(
|
|
|
"创建DataForSEO Serp任务,response = {}",
|
|
|
JSONUtil.toJsonStr(serpTaskPostResponseInfo));
|
|
|
if (serpTaskPostResponseInfo.getStatusCode() != SERP_STATUS_CODE_SUCCESS) {
|
|
|
- log.error(serpTaskPostResponseInfo.getStatusMessage());
|
|
|
throw new ApiException(serpTaskPostResponseInfo.getStatusMessage());
|
|
|
}
|
|
|
|
|
|
- // 3. 过滤状态为成功的Serp task
|
|
|
+ // 3. 过滤创建成功的Serp tasks,保存到Redis
|
|
|
List<SerpGoogleOrganicTaskPostTaskInfo> serpingTasks =
|
|
|
serpTaskPostResponseInfo.getTasks().stream()
|
|
|
.filter(task -> task.getStatusCode() == SERP_STATUS_CODE_TASK_CREATED)
|
|
|
.toList();
|
|
|
List<Integer> serpingKeywordIds = Lists.newArrayList();
|
|
|
|
|
|
- // 4. 将正在查询的Serp task放进Redis
|
|
|
for (SerpGoogleOrganicTaskPostTaskInfo serpingTask : serpingTasks) {
|
|
|
Map<String, String> data = (Map<String, String>) serpingTask.getData();
|
|
|
String keywordId = data.get("tag");
|
|
@@ -171,26 +168,24 @@ public class DataForSEOService {
|
|
|
serpingKeywordIds.add(Integer.parseInt(keywordId));
|
|
|
}
|
|
|
|
|
|
- // 5. 更新SeoKeywords表
|
|
|
+ // 4. 更新seo_keywords表
|
|
|
List<SeoKeywords> serpingKeywords =
|
|
|
seoKeywords.stream()
|
|
|
.filter(keyword -> serpingKeywordIds.contains(keyword.getId()))
|
|
|
.toList();
|
|
|
serpingKeywords.forEach(
|
|
|
seoKeyword -> {
|
|
|
- // TODO: Why
|
|
|
- seoKeyword.setTimerLastSearchTime(DateUtil.getTodayZeroTime(now));
|
|
|
- // 状态 -> 正在查询
|
|
|
- seoKeyword.setSearchStatus(1);
|
|
|
+ seoKeyword.setTimerLastSearchTime(now); // 定时器执行时间
|
|
|
+ seoKeyword.setSearchStatus(1); // 状态 -> 正在查询
|
|
|
});
|
|
|
seoKeywordsService.updateBatchById(serpingKeywords);
|
|
|
|
|
|
log.info(
|
|
|
- "{}个关键词Serp查询任务创建完成 {}",
|
|
|
+ "{}个关键词Serp查询任务创建完成 - {}",
|
|
|
serpingKeywords.size(),
|
|
|
serpingKeywords.stream().map(SeoKeywords::getId).toList());
|
|
|
} catch (ApiException e) {
|
|
|
- log.error(e.getMessage(), e);
|
|
|
+ log.error("创建DataForSEO Serp任务失败", e);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -206,17 +201,21 @@ public class DataForSEOService {
|
|
|
private boolean onSerpResult(String serpTaskRedisKey) {
|
|
|
try {
|
|
|
// 1. 查询Serp task
|
|
|
- String taskId = redisUtil.get(serpTaskRedisKey).toString();
|
|
|
+ String taskId = redisUtil.getString(serpTaskRedisKey);
|
|
|
SerpGoogleOrganicTaskGetRegularTaskInfo serpTask =
|
|
|
serpApi.googleOrganicTaskGetRegular(taskId).getTasks().get(0);
|
|
|
log.info("获取DataForSEO Serp任务,response = {}", JSONUtil.toJsonStr(serpTask));
|
|
|
if (serpTask.getStatusCode() != SERP_STATUS_CODE_SUCCESS) {
|
|
|
- log.error(serpTask.getStatusMessage());
|
|
|
+ log.warn(
|
|
|
+ "DataForSEO Serp任务 {} 状态为 {} {}",
|
|
|
+ taskId,
|
|
|
+ serpTask.getStatusCode(),
|
|
|
+ serpTask.getStatusMessage());
|
|
|
throw new ApiException(serpTask.getStatusMessage());
|
|
|
}
|
|
|
SerpGoogleOrganicTaskGetRegularResultInfo serpResult = serpTask.getResult().get(0);
|
|
|
|
|
|
- // 2. 查询SeoKeywords表,并根据域名过滤Serp result items
|
|
|
+ // 2. 查询seo_keywords表,根据域名过滤Serp result items
|
|
|
int keywordId = Integer.parseInt(this.getKeywordIdFromRedisKey(serpTaskRedisKey));
|
|
|
SeoKeywords seoKeyword = seoKeywordsService.getById(keywordId);
|
|
|
if (Objects.isNull(seoKeyword)) {
|
|
@@ -232,34 +231,31 @@ public class DataForSEOService {
|
|
|
serpResult.getItems().stream()
|
|
|
.map(item -> (OrganicSerpElementItem) item)
|
|
|
.filter(item -> item.getType().equalsIgnoreCase("organic"))
|
|
|
- // 根据域名匹配
|
|
|
- .filter(item -> item.getDomain().contains(topPrivateDomain))
|
|
|
+ .filter(item -> item.getDomain().contains(topPrivateDomain)) // 根据域名匹配
|
|
|
.findAny()
|
|
|
.orElse(null);
|
|
|
|
|
|
- String seDomain = serpResult.getSeDomain();
|
|
|
- String checkUrl = serpResult.getCheckUrl();
|
|
|
- String languageCode = serpResult.getLanguageCode();
|
|
|
+ // 读取Serp相关数据
|
|
|
Date seDatetime =
|
|
|
DateUtil.parseDate(serpResult.getDatetime(), DateUtil.ZONED_DATE_TIME_PATTERN);
|
|
|
String positionUrl =
|
|
|
- Objects.nonNull(serpItem) ? StringUtils.remove(serpItem.getUrl(), "/") : null;
|
|
|
+ Objects.nonNull(serpItem)
|
|
|
+ ? StringUtils.removeEnd(serpItem.getUrl(), "/")
|
|
|
+ : null;
|
|
|
int rankGroup = Objects.nonNull(serpItem) ? serpItem.getRankGroup() : 0;
|
|
|
int rankAbsolute = Objects.nonNull(serpItem) ? serpItem.getRankAbsolute() : 0;
|
|
|
|
|
|
- // 3.更新SeoKeywords表
|
|
|
+ // 3.更新seo_keywords表
|
|
|
UpdateWrapper<SeoKeywords> seoKeywordsUpdateWrapper = new UpdateWrapper<>();
|
|
|
seoKeywordsUpdateWrapper.eq("id", keywordId);
|
|
|
- // TODO: Why
|
|
|
seoKeywordsUpdateWrapper.set("last_search_time", seDatetime);
|
|
|
seoKeywordsUpdateWrapper.set("position_url", positionUrl);
|
|
|
seoKeywordsUpdateWrapper.set("last_rank", rankGroup);
|
|
|
- // 状态 -> 查询结束
|
|
|
- seoKeywordsUpdateWrapper.set("search_status", 0);
|
|
|
+ seoKeywordsUpdateWrapper.set("search_status", 0); // 状态 -> 查询结束
|
|
|
seoKeywordsService.update(seoKeywordsUpdateWrapper);
|
|
|
|
|
|
- // 4. 更新SeoKeywordsSerp表
|
|
|
- // 4.1 填充与上次更新时间之间的数据
|
|
|
+ // 4. 更新seo_keywords_serp表
|
|
|
+ // 4.1 填充与上次更新时间之间的数据, 截止到seDatetime的前一天
|
|
|
seoKeywordsSerpService.fillKeywordsSerpHistory(keywordId, seDatetime);
|
|
|
// 4.2 更新Serp表
|
|
|
SeoKeywordsSerp keywordSerp =
|
|
@@ -277,9 +273,9 @@ public class DataForSEOService {
|
|
|
.orElse(new SeoKeywordsSerp());
|
|
|
|
|
|
keywordSerp.setKeywordsId(keywordId);
|
|
|
- keywordSerp.setSearchUrl(checkUrl);
|
|
|
- keywordSerp.setSeDomain(seDomain);
|
|
|
- keywordSerp.setLanguageCode(languageCode);
|
|
|
+ keywordSerp.setSearchUrl(serpResult.getCheckUrl());
|
|
|
+ keywordSerp.setSeDomain(serpResult.getSeDomain());
|
|
|
+ keywordSerp.setLanguageCode(serpResult.getLanguageCode());
|
|
|
keywordSerp.setType("organic_results");
|
|
|
keywordSerp.setPageNumber(rankGroup > 0 ? rankGroup / GOOGLE_SEARCH_PAGE_SIZE + 1 : 0);
|
|
|
keywordSerp.setRankGroup(rankGroup);
|
|
@@ -291,7 +287,7 @@ public class DataForSEOService {
|
|
|
redisUtil.del(serpTaskRedisKey);
|
|
|
return true;
|
|
|
} catch (ApiException e) {
|
|
|
- log.error(e.getMessage(), e);
|
|
|
+ log.error("同步DataForSEO Serp任务失败", e);
|
|
|
return false;
|
|
|
}
|
|
|
}
|