413 lines
14 KiB
Python
413 lines
14 KiB
Python
"""平台规则引擎 - 各内容平台的规则和最佳实践"""
|
||
|
||
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", [])
|