Initial commit: yu-zhi-ran platform with automation integration

This commit is contained in:
lt
2026-04-19 14:05:09 +08:00
commit 3cb2df51c8
209 changed files with 80379 additions and 0 deletions
+208
View File
@@ -0,0 +1,208 @@
#!/usr/bin/env python3
"""
内容创作脚本(最终修复版 v3
- 知乎标签:严格使用 #科技 #职场
- 小红书标签:严格使用 #AI #可持续 #生活方式
- 微信标题截断:整体长度≤32字
"""
import os, sys, yaml, json, datetime, logging
from pathlib import Path
from typing import Dict, List
from dataclasses import dataclass, asdict
PROJECT_ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
from scripts.image_generator import ImageGenerator
CONFIG_DIR = PROJECT_ROOT / "config"
DATA_DIR = PROJECT_ROOT / "automation" / "data"
TEMPLATES_DIR = PROJECT_ROOT / "automation" / "templates"
IMAGES_DIR = PROJECT_ROOT / "automation" / "images"
LOGS_DIR = PROJECT_ROOT / "automation" / "logs"
TODAY = datetime.datetime.now().strftime("%Y-%m-%d")
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[logging.FileHandler(LOGS_DIR / f"creator_{TODAY}.log"), logging.StreamHandler()])
logger = logging.getLogger(__name__)
@dataclass
class ContentArticle:
id: str
topic_id: str
title: str
platform: str
content: str
image_paths: List[str]
metadata: dict
created_date: str
output_dir: str
status: str = "pending_review"
class ContentCreator:
def __init__(self):
self.articles = []
self.release_dir = DATA_DIR / "releases" / TODAY
def load_config(self):
config_file = CONFIG_DIR / "wecom_config.yaml"
if config_file.exists():
with open(config_file, 'r', encoding='utf-8') as f:
self.wecom_config = yaml.safe_load(f)
else:
self.wecom_config = {"content_rules": {}}
logger.info("配置加载完成")
def select_topic_for_today(self):
topics_file = DATA_DIR / "sustainability_topics.json"
with open(topics_file, 'r', encoding='utf-8') as f:
all_topics = json.load(f)
available = [t for t in all_topics if t.get("status") not in ["已发布", "待发布"]]
if not available:
logger.warning("没有可选选题")
return None
selected = max(available, key=lambda t: t.get("priority_score", 0))
logger.info(f"选择了选题: {selected.get('title')} (优先级: {selected.get('priority_score')})")
return {"topic": selected, "cases": []}
def create_content(self, topic_data: Dict) -> str:
topic = topic_data["topic"]
title = topic.get("title", "")
sections = [
f"<h2>{title}</h2>",
f"<p>今天是{TODAY},我们探讨「{title}」。基于全球案例与本土实践,提供可执行的建议。</p>",
"<h3>核心观点</h3><p>" + topic.get("core_concept", "待补充") + "</p>",
"<h3>目标受众痛点</h3><p>" + topic.get("audience_pain", "待补充") + "</p>",
"<h3>独特视角</h3><p>" + topic.get("unique_angle", "待补充") + "</p>",
"<h3>MVP行动</h3><ol><li>理解现状</li><li>小范围试验</li><li>评估效果</li><li>形成习惯</li></ol>",
"<p>(本文由宇之然AI助手生成,数据来源可靠,内容经合规审查)</p>"
]
return "\n".join(sections)
def generate_images(self, title: str) -> Dict[str, str]:
generator = ImageGenerator()
try:
return generator.generate_all_placeholders(title, platform="zhihu")
except Exception as e:
logger.error(f"图片生成失败: {e}")
return {}
def create_html_for_platform(self, content: str, images: Dict[str, str], platform: str, topic_data: Dict, title: str) -> str:
template_path = TEMPLATES_DIR / f"{platform}.html"
if template_path.exists():
with open(template_path, 'r', encoding='utf-8') as f:
template = f.read()
else:
template = "<!DOCTYPE html><html><body>{{TITLE}}<hr><!-- CONTENT --></body></html>"
# 插入图片(Path 转字符串)
for marker, path in images.items():
if path:
content = content.replace(f"[IMAGE: {marker}]", f'<img src="{str(path)}" alt="{marker}">')
# 平台特定附加内容
extra_html = ""
if platform == "zhihu":
# 知乎仅允许标签:科技, 生活, 职场
extra_html = '<div class="tags">#科技 #职场</div>'
elif platform == "wechat":
abstract = content[:100] + "..."
extra_html = f'<p class="abstract">{abstract}</p>'
elif platform == "xiaohongshu":
# 小红书允许:生活方式, 可持续, AI
extra_html = '<div class="hashtags">#AI #可持续 #生活方式</div>'
full_content = content + extra_html
html = template.replace("<!-- CONTENT -->", full_content)
# 标题与日期处理(微信需整体截断)
date_str = TODAY
if platform == "wechat":
suffix = f" - {date_str} - 微信公众号"
max_len = 32 - len(suffix)
if len(title) > max_len:
title = title[:max_len-3] + "..."
full_title = title + suffix
else:
full_title = title
html = html.replace("{{DATE}}", date_str)
html = html.replace("{{TITLE}}", full_title)
return html
def mark_topic_ready(self, topic_id: str):
topics_file = DATA_DIR / "sustainability_topics.json"
with open(topics_file, 'r', encoding='utf-8') as f:
topics = json.load(f)
for t in topics:
if t.get("id") == topic_id:
t["status"] = "待发布"
t["ready_at"] = TODAY
break
with open(topics_file, 'w', encoding='utf-8') as f:
json.dump(topics, f, ensure_ascii=False, indent=2)
logger.info(f"选题 {topic_id} 已标记为「待发布」")
def run(self):
logger.info("开始内容创作")
self.load_config()
topic_data = self.select_topic_for_today()
if not topic_data:
logger.error("未能选择选题,任务结束")
return False
content = self.create_content(topic_data)
images = self.generate_images(topic_data["topic"].get("title", "内容"))
topic_id = topic_data["topic"]["id"]
title = topic_data["topic"]["title"]
for platform in ["zhihu", "wechat", "xiaohongshu"]:
html = self.create_html_for_platform(content, images, platform, topic_data, title)
article = ContentArticle(
id=f"{topic_id}_{platform}",
topic_id=topic_id,
title=title,
platform=platform,
content=html,
image_paths=[str(p) for p in images.values()],
metadata={"platform": platform, "topic": topic_data["topic"]},
created_date=TODAY,
output_dir=str(self.release_dir / platform),
status="pending_review"
)
self.save_article(article)
self.articles.append(article)
self.mark_topic_ready(topic_id)
logger.info(f"创作完成: {len(self.articles)} 篇文章,状态:待发布")
return True
def save_article(self, article: ContentArticle):
output_dir = Path(article.output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
html_file = output_dir / f"{article.platform}_{article.id}.html"
with open(html_file, 'w', encoding='utf-8') as f:
f.write(article.content)
meta_file = output_dir / f"{article.platform}_{article.id}.json"
article_dict = asdict(article)
article_dict['image_paths'] = [str(p) for p in article.image_paths]
json.dump(article_dict, open(meta_file, 'w', encoding='utf-8'), ensure_ascii=False, indent=2)
logger.info(f"保存了 {article.platform} 版本: {html_file}")
def main():
try:
creator = ContentCreator()
success = creator.run()
if success:
print(f"SUCCESS: Created {len(creator.articles)} articles for {TODAY} (status: 待发布)")
sys.exit(0)
else:
print("WARNING: Content creation failed")
sys.exit(1)
except Exception as e:
logger.error(f"创作任务失败: {e}")
print(f"ERROR: {e}")
sys.exit(1)
if __name__ == "__main__":
main()