Files
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

313 lines
11 KiB
Python

#!/usr/bin/env python3
"""
小红书工作流 - 从IT资讯自动化数据库获取数据源
与IT资讯自动化流程整合:
- IT资讯自动化:抓取 → 翻译/总结 → 保存到数据库 → 发邮件
- 小红书工作流:从同一数据库读取 → 挑选文章 → 发布小红书
"""
import os
import sys
import json
import sqlite3
import argparse
from datetime import datetime
from typing import Optional, Dict, List
# 导入发布模块
sys.path.insert(0, os.path.dirname(__file__))
from publisher_adapter import XHSPublisherAdapter
class XHSFromITDBWorkflow:
"""
小红书工作流 - IT资讯数据库版
数据源: /home/ubuntu/nanocode/it_automation_enhanced/data/articles.db
"""
# IT资讯数据库路径
IT_DB_PATH = "/home/ubuntu/nanocode/it_automation_enhanced/data/articles.db"
IT_OUTPUT_DIR = "/home/ubuntu/nanocode/it_automation_enhanced/output"
def __init__(self, config_path: str = None):
self.config = self._load_config(config_path)
self.publisher = XHSPublisherAdapter()
# 路径
self.log_dir = self.config.get('paths', {}).get('log_dir', './logs')
self.output_dir = self.config.get('paths', {}).get('output_dir', './output')
os.makedirs(self.log_dir, exist_ok=True)
os.makedirs(self.output_dir, exist_ok=True)
def _load_config(self, config_path: str) -> dict:
"""加载配置"""
if not config_path:
config_path = os.path.join(
os.path.dirname(__file__), '..', 'config', 'config.json'
)
try:
with open(config_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
print(f"配置加载失败: {e},使用默认配置")
return {
'workflow': {'content_style': 'tech'},
'paths': {'log_dir': './logs', 'output_dir': './output'}
}
def _log(self, message: str):
"""记录日志"""
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
log_message = f"[{timestamp}] {message}"
print(log_message)
log_file = os.path.join(
self.log_dir,
f"xhs_from_itdb_{datetime.now().strftime('%Y%m%d')}.log"
)
with open(log_file, 'a', encoding='utf-8') as f:
f.write(log_message + '\n')
def get_processed_articles(self, limit: int = 10) -> List[Dict]:
"""从IT资讯数据库获取已处理但未发小红书的文章"""
articles = []
if not os.path.exists(self.IT_DB_PATH):
self._log(f"❌ IT资讯数据库不存在: {self.IT_DB_PATH}")
return articles
try:
conn = sqlite3.connect(self.IT_DB_PATH)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
# 查询已处理但未发小红书的文章
cursor.execute("""
SELECT id, title, url, source, category,
publish_date, word_count, created_at, xhs_status
FROM articles
WHERE processed = 1
AND (xhs_status = 0 OR xhs_status IS NULL)
ORDER BY created_at DESC
LIMIT ?
""", (limit,))
rows = cursor.fetchall()
for row in rows:
articles.append({
'id': row['id'],
'title': row['title'],
'url': row['url'],
'source': row['source'],
'category': row['category'],
'publish_date': row['publish_date'],
'word_count': row['word_count'],
'created_at': row['created_at'],
'xhs_status': row['xhs_status'] or 0
})
conn.close()
self._log(f"📊 找到 {len(articles)} 篇已处理但未发小红书的文章")
except Exception as e:
self._log(f"❌ 数据库查询失败: {e}")
return articles
def load_processed_content(self, article_id: int) -> Optional[Dict]:
"""加载IT资讯自动化的处理结果"""
processed_file = os.path.join(
self.IT_OUTPUT_DIR,
f"article_{article_id}_processed.json"
)
if not os.path.exists(processed_file):
self._log(f"⚠️ 处理结果文件不存在: {processed_file}")
return None
try:
with open(processed_file, 'r', encoding='utf-8') as f:
data = json.load(f)
# 构建小红书内容
content = {
'article_id': article_id,
'title': data.get('translated_title', data.get('original_title', '')),
'body': data.get('summary', data.get('translated_content', ''))[:900],
'tags': self._generate_tags(data),
'source': data.get('source', '技术'),
'url': data.get('url', ''),
'original_title': data.get('original_title', ''),
}
return content
except Exception as e:
self._log(f"❌ 加载处理结果失败: {e}")
return None
def _generate_tags(self, data: Dict) -> List[str]:
"""根据文章数据生成标签"""
source = data.get('source', '技术')
category = data.get('category', '科技')
# 基础标签
base_tags = ['科技资讯', '技术分享']
# 根据来源添加标签
source_tags = {
'InfoQ': 'InfoQ',
'Wired': 'Wired',
'TechCrunch': 'TechCrunch',
}
if source in source_tags:
base_tags.append(source_tags[source])
# 根据分类添加标签
if category and category not in base_tags:
base_tags.append(category)
# 确保5个标签
while len(base_tags) < 5:
base_tags.append('程序员')
return base_tags[:5]
def mark_as_published(self, article_id: int) -> bool:
"""标记文章为已发布到小红书"""
try:
conn = sqlite3.connect(self.IT_DB_PATH)
cursor = conn.cursor()
cursor.execute("""
UPDATE articles
SET xhs_status = 1, updated_at = datetime('now')
WHERE id = ?
""", (article_id,))
conn.commit()
conn.close()
self._log(f"✅ 文章 {article_id} 已标记为已发布")
return True
except Exception as e:
self._log(f"❌ 标记发布状态失败: {e}")
return False
def run(self, article_id: int = None, dry_run: bool = False) -> bool:
"""
执行小红书发布工作流
Args:
article_id: 指定文章ID(可选,不指定则自动选择)
dry_run: 试运行模式
Returns:
是否成功
"""
self._log("="*60)
self._log("🚀 小红书工作流启动 (IT资讯数据源)")
self._log("="*60)
# 步骤1: 获取文章
if article_id:
# 指定ID
self._log(f"\n📄 步骤1: 使用指定文章 ID={article_id}")
articles = self.get_processed_articles(limit=50)
article = next((a for a in articles if a['id'] == article_id), None)
if not article:
# 尝试从数据库直接查询
self._log(f"⚠️ 文章 {article_id} 不在未发布列表中,尝试直接加载...")
content = self.load_processed_content(article_id)
if content:
content['article_id'] = article_id
else:
self._log(f"❌ 无法加载文章 {article_id}")
return False
else:
content = self.load_processed_content(article_id)
else:
# 自动选择
self._log("\n📄 步骤1: 从IT资讯数据库获取文章...")
articles = self.get_processed_articles(limit=5)
if not articles:
self._log("❌ 没有可发布的文章(IT资讯可能尚未运行或文章都已发布)")
self._log("💡 提示: IT资讯自动化每天16:00运行,请等待或手动运行")
return False
# 选择第一篇
article = articles[0]
self._log(f"✅ 选中文章: {article['title'][:50]}...")
self._log(f" 来源: {article['source']} | 字数: {article['word_count']}")
content = self.load_processed_content(article['id'])
if not content:
self._log("❌ 无法加载文章内容")
return False
# 步骤2: 显示内容预览
self._log("\n📝 步骤2: 内容预览")
self._log(f"标题: {content['title'][:60]}...")
self._log(f"正文: {len(content['body'])} 字符")
self._log(f"标签: {', '.join(content['tags'])}")
# 保存到本地
output_file = os.path.join(
self.output_dir,
f"xhs_content_{content['article_id']}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
)
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(content, f, ensure_ascii=False, indent=2)
self._log(f"\n💾 内容已保存: {output_file}")
# 步骤3: 发布
if dry_run:
self._log("\n🧪 试运行模式,跳过发布")
self._log("="*60)
return True
self._log("\n📱 步骤3: 发布到小红书...")
success = self.publisher.publish(
title=content['title'],
content=content['body'],
tags=content['tags'],
style=self.config.get('workflow', {}).get('content_style', 'tech')
)
if success:
self._log("🎉 发布成功!")
# 标记为已发布
self.mark_as_published(content['article_id'])
else:
self._log("❌ 发布失败")
self._log("="*60)
return success
def main():
parser = argparse.ArgumentParser(description='小红书工作流 - IT资讯数据源')
parser.add_argument('--article-id', type=int, help='指定文章ID')
parser.add_argument('--dry-run', action='store_true', help='试运行不发布')
parser.add_argument('--config', help='配置文件路径')
args = parser.parse_args()
workflow = XHSFromITDBWorkflow(config_path=args.config)
success = workflow.run(
article_id=args.article_id,
dry_run=args.dry_run
)
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()