Files
nanocode/skills/xiaohongshu_integration/xiaohongshu_publisher.py
T
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

463 lines
17 KiB
Python
Executable File
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.
#!/usr/bin/env python3
"""
小红书自动发布器
集成到IT资讯自动化系统
"""
import json
import os
import sys
import subprocess
from datetime import datetime
import time
from pathlib import Path
class XiaohongshuPublisher:
"""小红书自动发布器"""
def __init__(self):
self.cookie_manager = None
self.content_converter = None
# 初始化组件
self._init_components()
def _init_components(self):
"""初始化组件"""
try:
# 导入Cookie管理器
sys.path.append(os.path.dirname(__file__))
from cookie_manager import XiaohongshuCookieManager
self.cookie_manager = XiaohongshuCookieManager()
# 导入内容转换器
from content_converter import ContentConverter, Article
self.content_converter = ContentConverter()
print("✅ 组件初始化完成")
except Exception as e:
print(f"⚠️ 组件初始化警告: {e}")
# 即使某些组件失败,仍可继续
def check_prerequisites(self):
"""检查前置条件"""
print("🔍 检查前置条件...")
# 检查xhs命令
try:
result = subprocess.run(['which', 'xhs'], capture_output=True, text=True)
if result.returncode != 0:
print("❌ xhs命令未找到")
return False
print("✅ xhs命令可用")
except:
print("❌ 无法检查xhs命令")
return False
# 检查Cookie
if self.cookie_manager:
if not self.cookie_manager.check_cookie_validity():
print("❌ Cookie无效")
return False
print("✅ Cookie有效")
print("✅ 所有前置条件满足")
return True
def create_tech_image(self, title, source, style="modern"):
"""创建技术资讯图片(使用ImageMagick)"""
print(f"🎨 创建技术资讯图片 (风格: {style})...")
try:
# 使用高级图片生成器
from advanced_image_generator import AdvancedImageGenerator
generator = AdvancedImageGenerator()
image_path = generator.create_tech_news_image(title, source, style=style)
if image_path:
print(f"✅ 图片创建成功: {image_path}")
return image_path
else:
print("❌ 高级图片生成失败,使用回退方法")
return self._create_fallback_image(title, source)
except Exception as e:
print(f"❌ 高级图片生成器异常: {e}")
return self._create_fallback_image(title, source)
def _create_fallback_image(self, title, source):
"""回退图片生成方法"""
try:
# 使用ImageMagick创建简单图片
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
image_dir = "/tmp/xiaohongshu_images"
os.makedirs(image_dir, exist_ok=True)
image_path = os.path.join(image_dir, f"fallback_{timestamp}.jpg")
# 创建渐变背景
cmd = [
'convert', '-size', '800x600',
'gradient:blue-red',
'-fill', 'white',
'-pointsize', '30',
'-gravity', 'Center',
'-draw', f'text 0,-50 "技术资讯"',
'-pointsize', '24',
'-draw', f'text 0,0 "{title[:40]}"',
'-pointsize', '18',
'-draw', f'text 0,100 "来源: {source}"',
'-pointsize', '16',
'-draw', f'text 0,150 "{datetime.now().strftime("%Y-%m-%d %H:%M")}"',
image_path
]
subprocess.run(cmd, check=True)
print(f"✅ 回退图片创建成功: {image_path}")
return image_path
except Exception as e:
print(f"❌ 回退图片也失败: {e}")
return None
def _wrap_text(self, text, font, max_width):
"""文本换行"""
lines = []
words = text.split()
current_line = []
for word in words:
test_line = ' '.join(current_line + [word])
# 简单估算宽度(字符数 * 20)
test_width = len(test_line) * 20
if test_width <= max_width:
current_line.append(word)
else:
if current_line:
lines.append(' '.join(current_line))
current_line = [word]
if current_line:
lines.append(' '.join(current_line))
return lines
def publish_article(self, article_data, image_path=None, image_style="auto"):
"""发布文章到小红书(支持A/B测试)"""
print(f"🚀 发布文章到小红书: {article_data.get('title', '')[:50]}...")
try:
# 准备内容
title = article_data.get('title', '技术资讯分享')
content = article_data.get('content', '')
source = article_data.get('source', 'IT资讯')
tags = article_data.get('tags', [])
# 构建小红书内容
xhs_content = self._format_xiaohongshu_content(title, content, source, tags)
# 选择图片风格
if image_style == "auto":
# 使用A/B测试选择最佳风格
try:
from image_ab_test import ImageABTest
ab_test = ImageABTest()
selected_style = ab_test.select_best_style(title, source)
print(f"🎯 A/B测试选择风格: {selected_style}")
image_style = selected_style
except Exception as e:
print(f"⚠️ A/B测试失败,使用默认风格: {e}")
image_style = "modern"
# 创建图片(如果没有提供)
if not image_path or not os.path.exists(image_path):
image_path = self.create_tech_image(title, source, style=image_style)
if not image_path:
print("❌ 无法创建图片,无法发布")
return False
# 执行发布命令
print(f"📤 执行发布...")
print(f" 标题: {title[:30]}...")
print(f" 内容长度: {len(xhs_content)} 字符")
print(f" 图片: {image_path}")
# 截断标题(小红书可能有长度限制)
short_title = title[:30]
cmd = [
'xhs', 'post',
'--title', short_title,
'--body', xhs_content,
'--images', image_path,
'--json'
]
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=60
)
print(f"返回码: {result.returncode}")
if result.returncode == 0:
try:
data = json.loads(result.stdout)
if data.get('ok'):
note_id = data.get('data', {}).get('id', '未知')
print(f"🎉 发布成功! 笔记ID: {note_id}")
# 保存发布记录
self._save_publish_record(article_data, note_id, image_path)
# 记录A/B测试
try:
from image_ab_test import ImageABTest
ab_test = ImageABTest()
test_id = ab_test.record_test(
title,
source,
image_style,
note_id
)
print(f"📝 A/B测试记录已创建 (ID: {test_id})")
except Exception as e:
print(f"⚠️ A/B测试记录失败: {e}")
return {
'success': True,
'note_id': note_id,
'data': data,
'image_style': image_style
}
else:
print(f"❌ 发布失败: {data}")
return {'success': False, 'error': 'API返回错误'}
except json.JSONDecodeError:
print(f"❌ JSON解析失败")
return {'success': False, 'error': 'JSON解析失败'}
else:
print(f"❌ 命令执行失败")
if result.stderr:
print(f"错误信息: {result.stderr[:200]}")
return {'success': False, 'error': '命令执行失败'}
except subprocess.TimeoutExpired:
print("⏰ 发布超时")
return {'success': False, 'error': '超时'}
except Exception as e:
print(f"❌ 发布异常: {e}")
import traceback
traceback.print_exc()
return {'success': False, 'error': str(e)}
def _format_xiaohongshu_content(self, title, content, source, tags):
"""格式化为小红书内容(优化版)"""
from content_converter import ContentConverter, Article
# 创建文章对象
article = Article(
title=title,
content=content,
source=source,
url="",
images=[],
publish_date=datetime.now().strftime("%Y-%m-%d"),
author="IT资讯自动化",
category="技术"
)
# 使用内容转换器
converter = ContentConverter({
'max_title_length': 40,
'max_content_length': 1200,
'auto_generate_tags': True,
'add_source_info': True,
'add_emoji': True,
'max_tags': 8
})
# 生成小红书内容
xhs_content = converter.to_xiaohongshu(article)
# 添加自定义标签
if tags:
tag_text = " ".join([f"#{tag}" for tag in tags[:3]])
xhs_content += f"\n\n{tag_text}"
# 添加互动引导
xhs_content += "\n\n💬 你对这个话题有什么看法?欢迎在评论区讨论!"
xhs_content += "\n⭐ 如果觉得有用,记得点赞收藏哦~"
return xhs_content
def _save_publish_record(self, article_data, note_id, image_path):
"""保存发布记录"""
record_dir = "/home/ubuntu/.nanobot/workspace/xiaohongshu_publish_records"
os.makedirs(record_dir, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
record_file = os.path.join(record_dir, f"publish_{timestamp}.json")
record = {
'timestamp': timestamp,
'note_id': note_id,
'article': {
'title': article_data.get('title'),
'source': article_data.get('source'),
'url': article_data.get('url')
},
'image_path': image_path,
'publish_time': datetime.now().isoformat()
}
with open(record_file, 'w', encoding='utf-8') as f:
json.dump(record, f, indent=2, ensure_ascii=False)
print(f"📝 发布记录已保存: {record_file}")
def publish_from_markdown(self, markdown_file):
"""从Markdown文件发布"""
print(f"📄 从Markdown文件发布: {markdown_file}")
try:
# 读取Markdown文件
with open(markdown_file, 'r', encoding='utf-8') as f:
content = f.read()
# 解析文章信息
article_data = self._parse_markdown_article(content)
if not article_data:
print("❌ 无法解析文章")
return False
# 发布文章
return self.publish_article(article_data)
except Exception as e:
print(f"❌ 发布失败: {e}")
return False
def _parse_markdown_article(self, markdown_content):
"""从Markdown内容解析文章信息"""
lines = markdown_content.split('\n')
article_data = {
'title': '',
'content': '',
'source': '',
'url': '',
'tags': ['技术资讯', '科技新闻', '技术']
}
# 提取标题
for line in lines:
if line.startswith('# ') and not line.startswith('# 📋'):
article_data['title'] = line[2:].strip()
break
# 提取文章信息
in_info_section = False
for line in lines:
if line.startswith('## 📋 文章信息'):
in_info_section = True
continue
elif line.startswith('---') and in_info_section:
break
elif in_info_section:
if line.startswith('- **来源**:'):
article_data['source'] = line.replace('- **来源**:', '').strip()
elif line.startswith('- **原文链接**:'):
article_data['url'] = line.replace('- **原文链接**:', '').strip()
# 提取中文内容
in_chinese_content = False
chinese_lines = []
for line in lines:
if '【中文翻译】' in line or '中文翻译' in line:
in_chinese_content = True
continue
elif '---' in line and in_chinese_content:
break
elif in_chinese_content and line.strip():
chinese_lines.append(line.strip())
article_data['content'] = '\n'.join(chinese_lines)
# 根据内容添加标签
content_lower = article_data['content'].lower()
if 'ai' in content_lower or '人工智能' in content_lower:
article_data['tags'].append('AI')
if '数据库' in content_lower:
article_data['tags'].append('数据库')
if '云计算' in content_lower:
article_data['tags'].append('云计算')
if '安全' in content_lower:
article_data['tags'].append('网络安全')
return article_data
def main():
"""主函数"""
import argparse
parser = argparse.ArgumentParser(description='小红书自动发布器')
subparsers = parser.add_subparsers(dest='command', help='命令')
# publish命令
publish_parser = subparsers.add_parser('publish', help='发布文章')
publish_parser.add_argument('--markdown', help='Markdown文件路径')
publish_parser.add_argument('--title', help='文章标题')
publish_parser.add_argument('--content', help='文章内容')
publish_parser.add_argument('--source', default='IT资讯', help='文章来源')
# check命令
subparsers.add_parser('check', help='检查系统状态')
# test命令
subparsers.add_parser('test', help='测试发布')
args = parser.parse_args()
publisher = XiaohongshuPublisher()
if args.command == 'check':
publisher.check_prerequisites()
elif args.command == 'test':
print("🧪 测试发布...")
test_article = {
'title': '测试文章 - 小红书自动化发布',
'content': '这是一个测试内容,用于验证小红书自动化发布功能。技术资讯自动化系统已成功集成小红书发布能力。',
'source': '测试',
'tags': ['测试', '自动化', '技术']
}
result = publisher.publish_article(test_article)
print(f"测试结果: {result}")
elif args.command == 'publish':
if args.markdown:
publisher.publish_from_markdown(args.markdown)
elif args.title and args.content:
article_data = {
'title': args.title,
'content': args.content,
'source': args.source,
'tags': ['技术资讯']
}
publisher.publish_article(article_data)
else:
print("❌ 需要提供--markdown或--title和--content参数")
else:
parser.print_help()
if __name__ == "__main__":
main()