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

413 lines
14 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 re
PLATFORM_RULES: dict[str, dict] = {
"wechat": {
"name": "微信公众号",
"icon": "wechat",
"max_title_length": 22,
"max_content_length": 20000,
"min_content_length": 300,
"supported_media": ["image", "video", "audio"],
"max_images": 20,
"best_publish_times": ["07:30-08:30", "11:30-12:30", "17:30-18:30", "20:30-22:00"],
"best_publish_days": ["周二", "周四", "周六"],
"format_features": {
"supports_markdown": False,
"supports_html": True,
"max_heading_level": 3,
"supports_code_block": True,
},
"rules": [
"不得使用诱导分享/关注语句",
"图片单张不超过10MB",
"标题不含连续特殊符号(如!!!",
"正文不含外部链接(仅支持公众号链接和小程序)",
"不得包含未经授权的商标/品牌名称",
],
"seo_tips": [
"首段包含核心关键词",
"使用小标题分段(适配搜一搜)",
"文末设置话题标签",
"摘要控制在120字内",
],
},
"zhihu": {
"name": "知乎",
"icon": "zhihu",
"max_title_length": 30,
"max_content_length": 50000,
"min_content_length": 500,
"supported_media": ["image", "video"],
"max_images": 50,
"best_publish_times": ["09:00-10:00", "14:00-15:00", "20:00-22:00"],
"best_publish_days": ["周一", "周三", "周五"],
"format_features": {
"supports_markdown": True,
"supports_html": False,
"max_heading_level": 4,
"supports_code_block": True,
},
"rules": [
"回答需针对问题本身",
"不得过度营销或硬广",
"引用需标注来源",
"不得使用AI水文内容需有信息增量",
],
"seo_tips": [
"回答开头直接给出结论",
"使用数据和案例支撑",
"合理设置二级标题",
"文末引导关注专栏",
],
},
"xiaohongshu": {
"name": "小红书",
"icon": "xiaohongshu",
"max_title_length": 20,
"max_content_length": 1000,
"min_content_length": 100,
"supported_media": ["image", "video"],
"max_images": 18,
"best_publish_times": ["07:00-09:00", "12:00-13:00", "18:00-20:00", "21:00-23:00"],
"best_publish_days": ["周末", "周三"],
"format_features": {
"supports_markdown": False,
"supports_html": False,
"max_heading_level": 0,
"supports_code_block": False,
"supports_emoji": True,
},
"rules": [
"首图质量决定点击率",
"正文控制在300-800字",
"话题标签3-8个",
"不得出现其他平台引流信息",
"图片不含水印",
],
"seo_tips": [
"标题含数字更吸引点击",
"正文用短句+emoji分段",
"话题标签放文末",
"首句即核心观点",
],
},
"baijiahao": {
"name": "百家号",
"icon": "baidu",
"max_title_length": 30,
"max_content_length": 30000,
"min_content_length": 800,
"supported_media": ["image", "video"],
"max_images": 30,
"best_publish_times": ["08:00-09:00", "11:00-12:00", "17:00-18:00"],
"best_publish_days": ["工作日"],
"format_features": {
"supports_markdown": False,
"supports_html": True,
"max_heading_level": 3,
"supports_code_block": False,
},
"rules": [
"原创内容优先推荐",
"标题不含夸张/标题党词汇",
"正文需含至少1张配图",
"不得搬运/洗稿",
],
"seo_tips": [
"标题包含百度搜索热词",
"文章结构化H2小标题",
"适当添加内链",
"发布后及时答复评论",
],
},
"douyin": {
"name": "抖音",
"icon": "douyin",
"max_title_length": 55,
"max_content_length": 5000,
"min_content_length": 0,
"supported_media": ["video", "image"],
"max_images": 35,
"best_publish_times": ["06:00-08:00", "12:00-13:00", "18:00-20:00", "22:00-23:00"],
"best_publish_days": ["每天"],
"format_features": {
"supports_markdown": False,
"supports_html": False,
"max_heading_level": 0,
"supports_code_block": False,
"supports_emoji": True,
},
"rules": [
"视频/图文需原创",
"不得含其他平台水印",
"话题标签2-5个",
"文案简短有吸引力",
],
"seo_tips": [
"前3秒决定完播率",
"标题含热点关键词",
"评论区互动提升权重",
"合适的话题+POI定位",
],
},
"toutiao": {
"name": "今日头条",
"icon": "toutiao",
"max_title_length": 30,
"max_content_length": 30000,
"min_content_length": 500,
"supported_media": ["image", "video"],
"max_images": 30,
"best_publish_times": ["07:00-08:00", "12:00-13:00", "18:00-19:00", "21:00-22:00"],
"best_publish_days": ["每天"],
"format_features": {
"supports_markdown": False,
"supports_html": True,
"max_heading_level": 3,
"supports_code_block": False,
},
"rules": [
"标题不得标题党",
"内容需有信息价值",
"首发原创优先推荐",
"配图清晰不模糊",
],
"seo_tips": [
"标题含核心关键词",
"文章1500字以上推荐更高",
"合理使用头条话题",
"发布频率稳定提升权重",
],
},
}
# 诱导分享/关注关键词
_INDUCING_PATTERNS = re.compile(
r"(转发|分享|关注|点赞|收藏).{0,4}(领|获|得|拿|解锁|免费)",
re.IGNORECASE,
)
# 连续特殊符号
_CONSECUTIVE_SYMBOLS = re.compile(r"[!?]{3,}")
# 外部链接(排除公众号和小程序链接)
_EXTERNAL_LINK = re.compile(
r"https?://(?!mp\.weixin\.qq\.com|wx\.qq\.com|weixin://)[^\s<>)]+",
re.IGNORECASE,
)
# 标题党关键词
_CLICKBAIT_WORDS = {"震惊", "惊呆", "吓死", "笑死", "疯传", "刷屏", "出大事", "不敢相信"}
# 水印检测(简化:检测常见平台水印文本)
_WATERMARK_PATTERNS = re.compile(
r"(抖音|快手|小红书|微博|B站|bilibili).*(水印|logo)",
re.IGNORECASE,
)
class PlatformRuleEngine:
"""平台规则引擎"""
def get_platforms(self) -> list[dict]:
"""获取所有支持平台列表"""
return [
{
"id": key,
"name": val["name"],
"icon": val["icon"],
"max_title_length": val["max_title_length"],
"max_content_length": val["max_content_length"],
"min_content_length": val["min_content_length"],
"supported_media": val["supported_media"],
"max_images": val["max_images"],
}
for key, val in PLATFORM_RULES.items()
]
def get_platform_rules(self, platform: str) -> dict | None:
"""获取指定平台的完整规则"""
return PLATFORM_RULES.get(platform)
def validate_content(self, content: str, title: str, platform: str) -> dict:
"""
校验内容是否符合平台规则
返回: {
"is_valid": bool,
"score": int (0-100),
"issues": [{"severity": "high|medium|low", "message": "..."}],
"passed": ["规则1", "规则2"]
}
"""
rules = PLATFORM_RULES.get(platform)
if rules is None:
return {
"is_valid": False,
"score": 0,
"issues": [{"severity": "high", "message": f"不支持的平台: {platform}"}],
"passed": [],
}
issues: list[dict] = []
passed: list[str] = []
# --- 标题长度 ---
title_len = len(title)
max_title = rules["max_title_length"]
if title_len > max_title:
issues.append({
"severity": "high",
"message": f"标题长度 {title_len} 超过限制 {max_title}",
})
else:
passed.append(f"标题长度合规({title_len}/{max_title}")
# --- 内容长度 ---
content_len = len(content)
max_content = rules["max_content_length"]
min_content = rules["min_content_length"]
if content_len > max_content:
issues.append({
"severity": "high",
"message": f"内容长度 {content_len} 超过限制 {max_content}",
})
elif min_content > 0 and content_len < min_content:
issues.append({
"severity": "medium",
"message": f"内容长度 {content_len} 低于建议最低 {min_content}",
})
else:
passed.append(f"内容长度合规({content_len}/{max_content}")
# --- 平台特有规则 ---
platform_specific = self._validate_platform_specific(content, title, platform)
issues.extend(platform_specific["issues"])
passed.extend(platform_specific["passed"])
# --- 计算分数 ---
total_checks = len(issues) + len(passed)
if total_checks == 0:
score = 100
else:
# high=扣15分, medium=扣8分, low=扣3分
penalty = sum(
15 if i["severity"] == "high" else 8 if i["severity"] == "medium" else 3
for i in issues
)
score = max(0, 100 - penalty)
return {
"is_valid": len([i for i in issues if i["severity"] == "high"]) == 0,
"score": score,
"issues": issues,
"passed": passed,
}
def _validate_platform_specific(
self, content: str, title: str, platform: str
) -> dict:
"""平台特定规则校验"""
issues: list[dict] = []
passed: list[str] = []
if platform == "wechat":
# 诱导分享/关注
if _INDUCING_PATTERNS.search(title) or _INDUCING_PATTERNS.search(content):
issues.append({
"severity": "high",
"message": "包含诱导分享/关注语句",
})
else:
passed.append("无诱导分享/关注语句")
# 连续特殊符号
if _CONSECUTIVE_SYMBOLS.search(title):
issues.append({
"severity": "medium",
"message": "标题包含连续特殊符号",
})
else:
passed.append("标题无连续特殊符号")
# 外部链接
if _EXTERNAL_LINK.search(content):
issues.append({
"severity": "high",
"message": "正文包含外部链接(仅支持公众号链接和小程序)",
})
else:
passed.append("无外部链接")
elif platform == "zhihu":
# 营销内容检测(简化)
marketing_words = {"购买", "下单", "优惠价", "限时折扣", "点击购买"}
found_marketing = marketing_words & set(content)
if found_marketing:
issues.append({
"severity": "medium",
"message": f"疑似营销用语: {', '.join(found_marketing)}",
})
else:
passed.append("未检测到过度营销用语")
elif platform == "xiaohongshu":
# 字数建议
content_len = len(content)
if content_len > 800:
issues.append({
"severity": "medium",
"message": f"正文建议300-800字当前 {content_len}",
})
elif content_len < 300:
issues.append({
"severity": "low",
"message": f"正文建议300-800字当前仅 {content_len}",
})
else:
passed.append(f"正文字数适宜({content_len}字)")
# 其他平台引流
cross_platform_keywords = ["微信", "公众号", "抖音号", "微博"]
found_cross = [p for p in cross_platform_keywords if p in content]
if found_cross:
issues.append({
"severity": "high",
"message": f"疑似其他平台引流: {', '.join(found_cross)}",
})
else:
passed.append("未检测到其他平台引流信息")
elif platform in ("baijiahao", "toutiao"):
# 标题党检测
found_clickbait = _CLICKBAIT_WORDS & set(title)
if found_clickbait:
issues.append({
"severity": "high",
"message": f"标题含标题党词汇: {', '.join(found_clickbait)}",
})
else:
passed.append("标题无标题党词汇")
elif platform == "douyin":
# 水印检测
if _WATERMARK_PATTERNS.search(content):
issues.append({
"severity": "high",
"message": "内容包含其他平台水印信息",
})
else:
passed.append("未检测到其他平台水印")
return {"issues": issues, "passed": passed}
def get_optimization_tips(self, platform: str) -> list[str]:
"""获取平台优化建议"""
rules = PLATFORM_RULES.get(platform)
if rules is None:
return []
return rules.get("seo_tips", [])