|
|
@@ -133,39 +133,12 @@ public class GzhReplyVideoRefreshJob {
|
|
|
List<String> existTitles = new ArrayList<>();
|
|
|
for (JSONObject obj : sortedList) {
|
|
|
String text = obj.getString("title");
|
|
|
- // 提取关键词
|
|
|
- String keywordPrompt =
|
|
|
- "你是一位精通算法推荐逻辑的世界级短视频SEO专家。你擅长从标题中提炼出搜索量最大、用户意图最明确的“核心流量词”。\n" +
|
|
|
- "\n" +
|
|
|
- "# Task\n" +
|
|
|
- "分析用户提供的视频标题,提炼出 2 个核心搜索关键词。\n" +
|
|
|
- "\n" +
|
|
|
- "# Constraints (必须严格遵守)\n" +
|
|
|
- "1. **字数限制**:每个关键词严格控制在 **3个汉字以内**(包含3个字)。\n" +
|
|
|
- "2. **选词逻辑**:\n" +
|
|
|
- " - 优先提取核心名词(人名/物名)或高频动词。\n" +
|
|
|
- " - 剔除虚词(如“的”、“了”、“吗”)。\n" +
|
|
|
- " - 必须与原标题强相关,能覆盖用户搜索意图。\n" +
|
|
|
- "3. **输出格式**:仅输出JSON数组,**严禁**包含任何解释、Markdown标记或其他文本。\n" +
|
|
|
- "\n" +
|
|
|
- "# Example\n" +
|
|
|
- "输入:新手如何快速学会剪映剪辑\n" +
|
|
|
- "输出:[\"剪映\",\"剪辑\"]\n" +
|
|
|
- "\n" +
|
|
|
- "输入:宝宝感冒流鼻涕怎么办\n" +
|
|
|
- "输出:[\"感冒\",\"流鼻涕\"]\n" +
|
|
|
- "\n" +
|
|
|
- "# Input\n" +
|
|
|
- "内容是: {{text}} \n" +
|
|
|
- "\n" +
|
|
|
- "# Output\n" +
|
|
|
- "请基于上述规则,输出最终的JSON:";
|
|
|
- keywordPrompt = keywordPrompt.replace("text", text);
|
|
|
+ String keywordPrompt = getKeyWordPrompt(text);
|
|
|
AIResult aiResult = deepSeekApiService.requestOfficialApi(keywordPrompt, null, null, false);
|
|
|
if (aiResult.isSuccess()) {
|
|
|
List<String> keywords = JSONObject.parseArray(aiResult.getResponse().getChoices().get(0).getMessage().getContent(), String.class);
|
|
|
log.info("GzhReplyVideoRefreshJob accountName:{} text:{} keywords:{}", accountName, text, keywords);
|
|
|
- VideoDetail videoDetail = searchVideoByKeyword(keywords, searchVideos, existTitles);
|
|
|
+ VideoDetail videoDetail = getVideoDetail(keywords, searchVideos, existTitles);
|
|
|
if (videoDetail != null) {
|
|
|
existTitles.add(videoDetail.getTitle());
|
|
|
existTitles.add(text);
|
|
|
@@ -181,6 +154,53 @@ public class GzhReplyVideoRefreshJob {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private String getKeyWordPrompt(String text) {
|
|
|
+ // 提取关键词
|
|
|
+ String keywordPrompt =
|
|
|
+ "# Role\n" +
|
|
|
+ "你是一位世界顶级的短视频SEO专家。你擅长剥离标题中的修饰语和状态词,精准捕捉用户真正想搜索的“核心实体”或“核心话题”。\n" +
|
|
|
+ "\n" +
|
|
|
+ "# Task\n" +
|
|
|
+ "分析给定的视频标题,提取 **1个 或 2个** 最具搜索价值的关键词。\n" +
|
|
|
+ "\n" +
|
|
|
+ "# Constraints (必须严格执行)\n" +
|
|
|
+ "1. **字数限制**:每个关键词必须 **≤ 3个汉字**。\n" +
|
|
|
+ "2. **数量限制**:输出 1 到 2 个关键词。如果只有一个核心词,不要强行凑数,只输出一个。\n" +
|
|
|
+ "3. **选词优先级 (核心逻辑)**:\n" +
|
|
|
+ " - **最高优先级**:核心名词、实体(如:燕子、粮食、早餐)。\n" +
|
|
|
+ " - **次优先级**:具有明确分类属性的动词(如:剪辑、做菜)。\n" +
|
|
|
+ "4. **负面清单 (严禁提取)**:\n" +
|
|
|
+ " - **剔除状态词/结果词**:如“过期”、“消失”、“变质”、“最好”、“几点”等描述物体状态或程度的词。\n" +
|
|
|
+ " - **剔除虚词**:如“的”、“了”、“吗”。\n" +
|
|
|
+ " - **剔除泛词**:如“今天”、“才知道”、“告诉”。\n" +
|
|
|
+ "5. **输出格式**:仅输出JSON数组,无其他字符。\n" +
|
|
|
+ "\n" +
|
|
|
+ "# Few-Shot Examples (学习选词逻辑)\n" +
|
|
|
+ "Input: 今天才知道5种粮食不怕过期!放越久越好!\n" +
|
|
|
+ "Output: [\"粮食\"]\n" +
|
|
|
+ "(解释:剔除“过期”,因为它只是粮食的一个状态,用户搜的是粮食)\n" +
|
|
|
+ "\n" +
|
|
|
+ "Input: 今年燕子消失了 你知道怎么回事吗?\n" +
|
|
|
+ "Output: [\"燕子\"]\n" +
|
|
|
+ "(解释:剔除“消失”,保留核心生物实体)\n" +
|
|
|
+ "\n" +
|
|
|
+ "Input: 新手如何快速学会剪映剪辑\n" +
|
|
|
+ "Output: [\"剪映\",\"剪辑\"]\n" +
|
|
|
+ "(解释:两个词都是核心技能点,且都在3字内)\n" +
|
|
|
+ "\n" +
|
|
|
+ "Input: 冰箱总是结冰怎么办\n" +
|
|
|
+ "Output: [\"冰箱\",\"结冰\"]\n" +
|
|
|
+ "(解释:“结冰”虽是状态,但“冰箱结冰”是常见故障搜索词,故保留;若无法判断,优先保实体)\n" +
|
|
|
+ "\n" +
|
|
|
+ "# Input\n" +
|
|
|
+ "内容是: {{text}} \n" +
|
|
|
+ "\n" +
|
|
|
+ "# Output\n" +
|
|
|
+ "请基于上述规则,输出最终的JSON:";
|
|
|
+ keywordPrompt = keywordPrompt.replace("text", text);
|
|
|
+ return keywordPrompt;
|
|
|
+ }
|
|
|
+
|
|
|
private Map<String, JSONObject> analysisImageText(List<AdPutCreativeComponentCostData> costDataList) {
|
|
|
Map<String, JSONObject> creativeIdTextMap = new HashMap<>();
|
|
|
for (AdPutCreativeComponentCostData costData : costDataList) {
|
|
|
@@ -222,6 +242,25 @@ public class GzhReplyVideoRefreshJob {
|
|
|
return creativeIdTextMap;
|
|
|
}
|
|
|
|
|
|
+ private VideoDetail getVideoDetail(List<String> keywords, List<VideoDetail> searchVideos, List<String> existTitles) {
|
|
|
+ VideoDetail videoDetail = null;
|
|
|
+ int maxRetries = 3;
|
|
|
+ int retryCount = 0;
|
|
|
+ while (retryCount < maxRetries) {
|
|
|
+ try {
|
|
|
+ videoDetail = searchVideoByKeyword(keywords, searchVideos, existTitles);
|
|
|
+ break;
|
|
|
+ } catch (Exception e) {
|
|
|
+ retryCount++;
|
|
|
+ log.error("GzhReplyVideoRefreshJob searchVideoByKeyword error, retry count: {}/{}", retryCount, maxRetries, e);
|
|
|
+ if (retryCount >= maxRetries) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return videoDetail;
|
|
|
+ }
|
|
|
+
|
|
|
private VideoDetail searchVideoByKeyword(List<String> keywords, List<VideoDetail> searchVideos, List<String> existTitles) {
|
|
|
if (CollectionUtils.isNotEmpty(keywords)) {
|
|
|
String searchVideoSql = "SELECT v.id, v.title, v.cover_img_path\n" +
|