geo/backend/app/services/distribution/publish_strategy.py

238 lines
8.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""发布策略服务"""
import random
from datetime import datetime, timedelta
from app.services.distribution.platform_rules import PLATFORM_RULES
# 行业-平台优先级映射
_INDUSTRY_PRIORITY: dict[str, list[str]] = {
"科技": ["zhihu", "wechat", "baijiahao", "toutiao"],
"美妆": ["xiaohongshu", "douyin", "wechat"],
"教育": ["wechat", "zhihu", "baijiahao"],
"金融": ["wechat", "baijiahao", "toutiao", "zhihu"],
"餐饮": ["xiaohongshu", "douyin", "wechat"],
"通用": ["wechat", "zhihu", "xiaohongshu", "baijiahao", "toutiao", "douyin"],
}
# 平台标签风格模板
_TAG_TEMPLATES: dict[str, list[str]] = {
"wechat": ["#{keyword}研究", "#{keyword}洞察", "#{keyword}实践"],
"zhihu": ["#{keyword}", "#{keyword}领域", "#{keyword}深度解析"],
"xiaohongshu": ["{keyword}", "{keyword}推荐", "{keyword}分享", "{keyword}攻略"],
"baijiahao": ["#{keyword}#", "#{keyword}话题"],
"douyin": ["#{keyword}", "#{keyword}热门", "#{keyword}话题"],
"toutiao": ["#{keyword}", "#{keyword}热点", "#{keyword}关注"],
}
# 策略提示模板
_STRATEGY_TIPS: list[str] = [
"知乎首发可获取原创保护标识",
"各平台间隔24h发布避免被判定为搬运",
"公众号适合深度长文,建议优先发布",
"小红书首图决定点击率,提前准备视觉素材",
"抖音内容建议配合短视频,纯文字效果有限",
"百家号和头条文章同步时,标题需差异化",
"周末流量分布与工作日不同,注意错峰",
"多平台发布时调整发布时间,避免同一时段密集推送",
]
class PublishStrategyService:
"""发布策略服务"""
def generate_strategy(
self,
content_title: str,
platforms: list[str],
industry: str = "通用",
) -> dict:
"""
生成多平台发布策略
返回: {
"schedule": [
{"platform": "zhihu", "suggested_time": "...", "reason": "..."},
...
],
"tags": {"zhihu": ["#AI营销", ...], ...},
"tips": ["...", ...]
}
"""
# 过滤有效平台
valid_platforms = [p for p in platforms if p in PLATFORM_RULES]
if not valid_platforms:
return {"schedule": [], "tags": {}, "tips": ["请选择至少一个有效平台"]}
# 按行业优先级排序
priority_order = _INDUSTRY_PRIORITY.get(industry, _INDUSTRY_PRIORITY["通用"])
sorted_platforms = sorted(
valid_platforms,
key=lambda p: priority_order.index(p) if p in priority_order else 999,
)
# 生成排期
schedule = self._build_schedule(sorted_platforms)
# 生成标签
tags = self._build_tags(content_title, valid_platforms)
# 选取策略提示
tips = self._pick_tips(valid_platforms)
return {
"schedule": schedule,
"tags": tags,
"tips": tips,
}
def _build_schedule(self, platforms: list[str]) -> list[dict]:
"""根据平台最佳发布时间生成排期"""
now = datetime.now()
schedule: list[dict] = []
offset_hours = 0
for idx, platform_key in enumerate(platforms):
rules = PLATFORM_RULES[platform_key]
best_times = rules.get("best_publish_times", [])
best_days = rules.get("best_publish_days", [])
# 计算下次发布时间:从当前时间往后,找到最近的推荐时段
suggested_time = self._find_next_publish_slot(
now, offset_hours, best_times, best_days
)
# 构建推荐原因
time_desc = best_times[0] if best_times else "任意时间"
day_desc = best_days[0] if best_days else "任意日期"
reason = f"{rules['name']}{day_desc}{time_desc}流量高峰"
schedule.append({
"platform": platform_key,
"platform_name": rules["name"],
"suggested_time": suggested_time.strftime("%Y-%m-%d %H:%M"),
"reason": reason,
})
# 平台间隔至少4小时
offset_hours += 4
return schedule
def _find_next_publish_slot(
self,
now: datetime,
offset_hours: int,
best_times: list[str],
best_days: list[str],
) -> datetime:
"""查找下一个推荐发布时间"""
# 基础时间 = 当前 + 偏移
base = now + timedelta(hours=offset_hours)
# 如果有推荐时段,尝试匹配
if best_times:
# 取第一个推荐时段的起始小时
first_slot = best_times[0]
try:
start_str = first_slot.split("-")[0]
target_hour, target_minute = int(start_str[:2]), int(start_str[3:5])
except (ValueError, IndexError):
target_hour, target_minute = 10, 0
# 构建当天目标时间
candidate = base.replace(hour=target_hour, minute=target_minute, second=0, microsecond=0)
# 如果已过,推到明天
if candidate < base:
candidate += timedelta(days=1)
return candidate
# 无推荐时段,直接使用基础时间(整点化)
return base.replace(minute=0, second=0, microsecond=0) + timedelta(hours=1)
def _build_tags(self, title: str, platforms: list[str]) -> dict[str, list[str]]:
"""为各平台生成标签建议"""
# 从标题提取关键词简化取2-4字片段
keywords = self._extract_keywords(title)
tags: dict[str, list[str]] = {}
for platform_key in platforms:
templates = _TAG_TEMPLATES.get(platform_key, ["{keyword}"])
platform_tags: list[str] = []
for kw in keywords[:5]:
for tpl in templates:
tag = tpl.format(keyword=kw)
# 去重
if tag not in platform_tags:
platform_tags.append(tag)
if len(platform_tags) >= 8:
break
if len(platform_tags) >= 8:
break
tags[platform_key] = platform_tags
return tags
def _extract_keywords(self, title: str) -> list[str]:
"""从标题提取关键词(简化实现)"""
# 按标点和空格分词
parts = [p.strip() for p in _split_title(title) if len(p.strip()) >= 2]
if not parts:
parts = [title[:4]] if len(title) >= 2 else [title]
return parts[:6]
def _pick_tips(self, platforms: list[str]) -> list[str]:
"""根据所选平台选取策略提示"""
tips: list[str] = []
# 通用提示
if len(platforms) > 1:
tips.append("多平台发布时建议间隔4-24小时避免判定搬运")
# 平台特定提示
if "zhihu" in platforms:
tips.append("知乎首发可获取原创保护标识,建议优先发布")
if "wechat" in platforms:
tips.append("公众号适合深度长文摘要控制在120字内")
if "xiaohongshu" in platforms:
tips.append("小红书首图决定点击率,提前准备高质量视觉素材")
if "douyin" in platforms:
tips.append("抖音内容建议配合短视频,纯文字效果有限")
if "baijiahao" in platforms and "toutiao" in platforms:
tips.append("百家号和头条同步时,标题需差异化处理")
# 补充通用提示至3-5条
remaining = [t for t in _STRATEGY_TIPS if t not in tips]
while len(tips) < 3 and remaining:
picked = remaining.pop(random.randint(0, len(remaining) - 1))
tips.append(picked)
return tips[:5]
def suggest_tags(self, content: str, platform: str, count: int = 5) -> list[str]:
"""为指定平台生成话题/标签建议"""
templates = _TAG_TEMPLATES.get(platform, ["{keyword}"])
keywords = self._extract_keywords(content[:200])
tags: list[str] = []
for kw in keywords:
for tpl in templates:
tag = tpl.format(keyword=kw)
if tag not in tags:
tags.append(tag)
if len(tags) >= count:
return tags
return tags
def _split_title(title: str) -> list[str]:
"""按常见分隔符拆分标题"""
import re
return re.split(r"[|\-·—\s,,、::]+", title)