462 lines
16 KiB
Python
Executable File
462 lines
16 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""
|
||
基于 oh-my-writing-skill 的小红书内容转换器
|
||
将通用文章转换为适合小红书平台的格式和风格
|
||
"""
|
||
|
||
import re
|
||
import json
|
||
import random
|
||
from typing import Dict, List, Optional
|
||
from dataclasses import dataclass
|
||
|
||
@dataclass
|
||
class XiaohongshuContent:
|
||
"""小红书内容结构"""
|
||
title: str
|
||
content: str
|
||
cover_suggestions: List[str]
|
||
tags: List[str]
|
||
style: str = "小红书风格"
|
||
|
||
class OhMyWritingConverter:
|
||
"""oh-my-writing-skill 内容转换器"""
|
||
|
||
def __init__(self):
|
||
# 小红书用户画像
|
||
self.user_profile = {
|
||
'main_users': '18-35岁女性为主(男性用户增长中)',
|
||
'reading_habit': '刷图文、看封面、快速浏览',
|
||
'content_preference': '生活分享、实用攻略、颜值内容',
|
||
'interaction_habit': '收藏实用内容、点赞喜欢的博主'
|
||
}
|
||
|
||
# 内容调性
|
||
self.content_style = {
|
||
'亲切感': '像朋友聊天,不是教科书',
|
||
'真实感': '真实体验分享,不是广告',
|
||
'实用性': '能带走干货,有收藏价值',
|
||
'视觉感': '排版清爽,重点突出'
|
||
}
|
||
|
||
# Emoji映射表
|
||
self.emoji_mapping = {
|
||
'技术': '💻',
|
||
'AI': '🤖',
|
||
'编程': '👨💻',
|
||
'效率': '⚡',
|
||
'工具': '🛠️',
|
||
'学习': '📚',
|
||
'分享': '📢',
|
||
'教程': '📝',
|
||
'新闻': '📰',
|
||
'分析': '📊',
|
||
'总结': '📌',
|
||
'推荐': '⭐',
|
||
'提示': '💡',
|
||
'警告': '⚠️',
|
||
'成功': '✅',
|
||
'错误': '❌',
|
||
'问题': '❓',
|
||
'答案': '💬',
|
||
'收藏': '❤️',
|
||
'点赞': '👍',
|
||
'关注': '👀'
|
||
}
|
||
|
||
# 语气转换表
|
||
self.tone_conversion = {
|
||
'本文将介绍': '今天来聊聊',
|
||
'建议用户': '建议大家/姐妹们',
|
||
'该产品性能优秀': '这个真的很好用!',
|
||
'综上所述': '总之就是',
|
||
'首先、其次、最后': '第一、第二、第三',
|
||
'值得注意的是': '划重点!',
|
||
'需要指出的是': '这里要注意',
|
||
'根据研究显示': '我发现',
|
||
'一般来说': '通常来说',
|
||
'具体来说': '具体来说',
|
||
'换句话说': '简单说就是',
|
||
'例如': '比如',
|
||
'此外': '另外',
|
||
'然而': '不过',
|
||
'因此': '所以',
|
||
'最终': '最后'
|
||
}
|
||
|
||
def analyze_content(self, content: str) -> Dict:
|
||
"""分析内容特征"""
|
||
analysis = {
|
||
'length': len(content),
|
||
'paragraphs': len(content.split('\n\n')),
|
||
'has_images': bool(re.search(r'!\[.*?\]\(.*?\)', content)),
|
||
'has_headings': bool(re.search(r'^#+\s+', content, re.MULTILINE)),
|
||
'has_lists': bool(re.search(r'^[\-\*\+]\s+', content, re.MULTILINE)),
|
||
'formal_tone': 0,
|
||
'keywords': []
|
||
}
|
||
|
||
# 检测正式语气
|
||
for formal_word in ['本文将', '建议', '该', '综上所述', '需要指出']:
|
||
if formal_word in content:
|
||
analysis['formal_tone'] += 1
|
||
|
||
# 提取关键词
|
||
words = re.findall(r'\b[\u4e00-\u9fa5]{2,4}\b', content)
|
||
from collections import Counter
|
||
analysis['keywords'] = [word for word, count in Counter(words).most_common(10)]
|
||
|
||
return analysis
|
||
|
||
def optimize_title(self, original_title: str, keywords: List[str]) -> str:
|
||
"""优化标题为小红书风格"""
|
||
# 移除过于正式的前缀
|
||
title = original_title
|
||
formal_prefixes = ['关于', '浅谈', '简析', '探讨', '研究', '分析', '报告']
|
||
for prefix in formal_prefixes:
|
||
if title.startswith(prefix):
|
||
title = title[len(prefix):].strip()
|
||
|
||
# 添加emoji
|
||
emoji_to_add = []
|
||
for keyword in keywords:
|
||
if keyword in self.emoji_mapping:
|
||
emoji_to_add.append(self.emoji_mapping[keyword])
|
||
break
|
||
|
||
# 小红书标题特点:简短有力,制造好奇
|
||
if len(title) > 20:
|
||
# 截断并添加省略号或感叹
|
||
title = title[:18] + '...'
|
||
|
||
# 添加数字/对比增强吸引力
|
||
if any(word in title for word in ['方法', '技巧', '工具', '步骤']):
|
||
if '个' not in title and '种' not in title:
|
||
title = f"这{random.choice(['几', '5', '7', '10'])}个{title}"
|
||
|
||
# 添加emoji(最多2个)
|
||
if emoji_to_add:
|
||
title += ' ' + ' '.join(emoji_to_add[:2])
|
||
|
||
return title
|
||
|
||
def convert_tone(self, text: str) -> str:
|
||
"""转换语气为小红书风格"""
|
||
converted = text
|
||
|
||
# 替换正式表达
|
||
for formal, informal in self.tone_conversion.items():
|
||
converted = converted.replace(formal, informal)
|
||
|
||
# 添加亲切称呼
|
||
if '大家' in converted or '你们' in converted:
|
||
if '姐妹们' not in converted and '家人们' not in converted:
|
||
# 随机选择亲切称呼
|
||
greetings = ['姐妹们', '家人们', '朋友们', '宝子们']
|
||
greeting = random.choice(greetings)
|
||
|
||
# 在开头添加
|
||
lines = converted.split('\n')
|
||
if lines and len(lines[0]) < 50:
|
||
lines[0] = f"{greeting}!{lines[0]}"
|
||
converted = '\n'.join(lines)
|
||
|
||
return converted
|
||
|
||
def format_opening(self, content_analysis: Dict) -> str:
|
||
"""生成小红书风格开头"""
|
||
openings = [
|
||
"姐妹们!是不是经常遇到这个问题",
|
||
"家人们,今天分享一个超实用的技巧",
|
||
"朋友们,我发现了一个宝藏方法",
|
||
"宝子们,这个真的太好用了",
|
||
"亲测有效,分享给你们",
|
||
"用了这个方法,效率直接翻倍",
|
||
"之前一直困扰的问题,终于解决了",
|
||
"无意间发现的,没想到这么好用"
|
||
]
|
||
|
||
opening = random.choice(openings)
|
||
|
||
# 根据内容类型调整
|
||
if content_analysis['formal_tone'] > 3:
|
||
opening = "之前觉得很难,其实掌握了方法很简单"
|
||
|
||
return f"{opening}\n\n往下看 ⬇️\n\n"
|
||
|
||
def format_body(self, content: str) -> str:
|
||
"""格式化正文为小红书风格"""
|
||
# 分割段落
|
||
paragraphs = content.split('\n\n')
|
||
formatted_paragraphs = []
|
||
|
||
for i, para in enumerate(paragraphs):
|
||
if not para.strip():
|
||
continue
|
||
|
||
# 处理标题
|
||
if para.startswith('#'):
|
||
level = len(re.match(r'#+', para).group())
|
||
title_text = re.sub(r'^#+\s+', '', para).strip()
|
||
|
||
if level == 1:
|
||
# 一级标题转换为【要点】
|
||
formatted_para = f"【{title_text}】\n\n"
|
||
elif level == 2:
|
||
# 二级标题转换为加粗
|
||
formatted_para = f"**{title_text}**\n\n"
|
||
else:
|
||
formatted_para = f"{title_text}\n\n"
|
||
|
||
# 处理列表
|
||
elif re.match(r'^[\-\*\+]\s+', para):
|
||
lines = para.split('\n')
|
||
formatted_lines = []
|
||
for line in lines:
|
||
if re.match(r'^[\-\*\+]\s+', line):
|
||
# 替换列表符号
|
||
line = re.sub(r'^[\-\*\+]\s+', '• ', line)
|
||
formatted_lines.append(line)
|
||
else:
|
||
formatted_lines.append(line)
|
||
formatted_para = '\n'.join(formatted_lines) + '\n\n'
|
||
|
||
# 处理普通段落
|
||
else:
|
||
# 缩短长段落
|
||
if len(para) > 150:
|
||
sentences = re.split(r'[。!?]', para)
|
||
para = '。'.join(sentences[:3]) + '。'
|
||
|
||
formatted_para = para + '\n\n'
|
||
|
||
# 转换语气
|
||
formatted_para = self.convert_tone(formatted_para)
|
||
|
||
# 添加分隔符(每隔2-3段)
|
||
if i > 0 and i % random.randint(2, 3) == 0:
|
||
formatted_para = f"—————\n\n{formatted_para}"
|
||
|
||
formatted_paragraphs.append(formatted_para)
|
||
|
||
return ''.join(formatted_paragraphs)
|
||
|
||
def format_ending(self, keywords: List[str]) -> str:
|
||
"""生成小红书风格结尾"""
|
||
endings = [
|
||
"有用的话记得收藏 📌",
|
||
"有问题评论区见~",
|
||
"用过的来聊聊体验",
|
||
"你们有什么好方法也分享下",
|
||
"下期想看什么内容?",
|
||
"关注我,分享更多实用技巧"
|
||
]
|
||
|
||
import random
|
||
ending = random.choice(endings)
|
||
|
||
# 生成标签
|
||
tags = []
|
||
for keyword in keywords[:5]:
|
||
tags.append(f"#{keyword}")
|
||
|
||
# 添加通用标签
|
||
general_tags = ['#干货分享', '#实用技巧', '#自我提升']
|
||
tags.extend(general_tags[:3-len(tags)])
|
||
|
||
return f"\n—————\n\n{ending}\n\n{' '.join(tags)}"
|
||
|
||
def suggest_cover(self, title: str, keywords: List[str]) -> List[str]:
|
||
"""建议封面图元素"""
|
||
suggestions = []
|
||
|
||
# 基本建议
|
||
suggestions.append("3:4 竖版比例,高清不模糊")
|
||
suggestions.append("简洁干净的设计风格")
|
||
|
||
# 根据内容类型建议
|
||
if any(kw in keywords for kw in ['技术', '编程', 'AI']):
|
||
suggestions.append("科技感配色:蓝色/紫色渐变")
|
||
suggestions.append("可加入代码片段或芯片图案")
|
||
elif any(kw in keywords for kw in ['学习', '教程', '方法']):
|
||
suggestions.append("教育感配色:绿色/黄色")
|
||
suggestions.append("可加入书本或笔记元素")
|
||
elif any(kw in keywords for kw in ['工具', '效率', '技巧']):
|
||
suggestions.append("高效感配色:橙色/红色")
|
||
suggestions.append("可加入齿轮或闪电元素")
|
||
|
||
# 文字建议
|
||
short_title = title[:15] + '...' if len(title) > 15 else title
|
||
suggestions.append(f"封面文字:{short_title}")
|
||
|
||
return suggestions
|
||
|
||
def convert_to_xiaohongshu(self, original_content: str, original_title: str = "") -> XiaohongshuContent:
|
||
"""将通用内容转换为小红书格式"""
|
||
|
||
# 分析内容
|
||
analysis = self.analyze_content(original_content)
|
||
|
||
# 优化标题
|
||
if not original_title:
|
||
# 从内容中提取标题
|
||
title_match = re.search(r'^#\s+(.+)$', original_content, re.MULTILINE)
|
||
original_title = title_match.group(1) if title_match else "实用分享"
|
||
|
||
optimized_title = self.optimize_title(original_title, analysis['keywords'])
|
||
|
||
# 构建小红书内容
|
||
xhs_content = ""
|
||
|
||
# 添加开头
|
||
xhs_content += self.format_opening(analysis)
|
||
|
||
# 添加正文
|
||
body_content = self.format_body(original_content)
|
||
xhs_content += body_content
|
||
|
||
# 添加结尾
|
||
xhs_content += self.format_ending(analysis['keywords'])
|
||
|
||
# 建议封面
|
||
cover_suggestions = self.suggest_cover(optimized_title, analysis['keywords'])
|
||
|
||
# 生成标签
|
||
tags = analysis['keywords'][:5]
|
||
|
||
return XiaohongshuContent(
|
||
title=optimized_title,
|
||
content=xhs_content,
|
||
cover_suggestions=cover_suggestions,
|
||
tags=tags
|
||
)
|
||
|
||
def test_conversion(self, test_content: str = None):
|
||
"""测试转换功能"""
|
||
if not test_content:
|
||
test_content = """# AI写作工具使用指南
|
||
|
||
随着人工智能技术的发展,AI写作工具已经成为内容创作者的重要助手。本文将介绍几款主流的AI写作工具及其使用技巧。
|
||
|
||
## 一、Claude
|
||
|
||
Claude是Anthropic公司开发的AI助手,在中文写作方面表现优异。
|
||
|
||
主要特点:
|
||
- 长文写作能力强,不易跑题
|
||
- 改写润色自然,符合中文表达习惯
|
||
- 上下文理解准确
|
||
|
||
使用建议:适合撰写报告、文章、邮件等正式内容。
|
||
|
||
## 二、ChatGPT
|
||
|
||
ChatGPT是OpenAI开发的对话式AI,创意丰富。
|
||
|
||
主要特点:
|
||
- 创意点子多,适合头脑风暴
|
||
- 插件生态丰富,功能扩展性强
|
||
- 多语言支持良好
|
||
|
||
使用建议:适合创意写作、问题解答、代码编写。
|
||
|
||
## 三、Notion AI
|
||
|
||
Notion AI集成在Notion笔记应用中,与工作流结合紧密。
|
||
|
||
主要特点:
|
||
- 与笔记完美结合,整理内容方便
|
||
- 翻译总结快速准确
|
||
- 模板丰富,开箱即用
|
||
|
||
使用建议:适合知识管理、内容整理、团队协作。
|
||
|
||
## 总结
|
||
|
||
不同的AI写作工具各有优势,建议根据具体需求选择合适的工具。对于正式写作,推荐使用Claude;对于创意工作,推荐使用ChatGPT;对于知识管理,推荐使用Notion AI。"""
|
||
|
||
print("🧪 测试内容转换...")
|
||
print("=" * 60)
|
||
|
||
# 执行转换
|
||
result = self.convert_to_xiaohongshu(test_content)
|
||
|
||
print(f"📝 原标题: AI写作工具使用指南")
|
||
print(f"🎯 小红书标题: {result.title}")
|
||
print("\n📱 小红书内容:")
|
||
print("=" * 60)
|
||
print(result.content)
|
||
print("=" * 60)
|
||
|
||
print("\n🖼️ 封面建议:")
|
||
for suggestion in result.cover_suggestions:
|
||
print(f" • {suggestion}")
|
||
|
||
print(f"\n🏷️ 标签: {' '.join([f'#{tag}' for tag in result.tags])}")
|
||
|
||
return result
|
||
|
||
def main():
|
||
"""主函数"""
|
||
import argparse
|
||
|
||
parser = argparse.ArgumentParser(description='oh-my-writing-skill 小红书转换器')
|
||
subparsers = parser.add_subparsers(dest='command', help='命令')
|
||
|
||
# test命令
|
||
subparsers.add_parser('test', help='测试转换功能')
|
||
|
||
# convert命令
|
||
convert_parser = subparsers.add_parser('convert', help='转换内容')
|
||
convert_parser.add_argument('--input', required=True, help='输入文件路径')
|
||
convert_parser.add_argument('--output', help='输出文件路径')
|
||
|
||
# analyze命令
|
||
analyze_parser = subparsers.add_parser('analyze', help='分析内容')
|
||
analyze_parser.add_argument('--input', required=True, help='输入文件路径')
|
||
|
||
args = parser.parse_args()
|
||
|
||
converter = OhMyWritingConverter()
|
||
|
||
if args.command == 'test':
|
||
converter.test_conversion()
|
||
|
||
elif args.command == 'convert':
|
||
with open(args.input, 'r', encoding='utf-8') as f:
|
||
content = f.read()
|
||
|
||
# 提取标题
|
||
title_match = re.search(r'^#\s+(.+)$', content, re.MULTILINE)
|
||
title = title_match.group(1) if title_match else ""
|
||
|
||
result = converter.convert_to_xiaohongshu(content, title)
|
||
|
||
if args.output:
|
||
with open(args.output, 'w', encoding='utf-8') as f:
|
||
f.write(f"# {result.title}\n\n")
|
||
f.write(result.content)
|
||
f.write("\n\n## 封面建议\n")
|
||
for suggestion in result.cover_suggestions:
|
||
f.write(f"- {suggestion}\n")
|
||
f.write(f"\n## 标签\n{' '.join([f'#{tag}' for tag in result.tags])}\n")
|
||
|
||
print(f"✅ 转换完成,已保存到: {args.output}")
|
||
else:
|
||
print(f"🎯 标题: {result.title}")
|
||
print("\n📱 内容:")
|
||
print(result.content)
|
||
|
||
elif args.command == 'analyze':
|
||
with open(args.input, 'r', encoding='utf-8') as f:
|
||
content = f.read()
|
||
|
||
analysis = converter.analyze_content(content)
|
||
print("📊 内容分析结果:")
|
||
print(json.dumps(analysis, indent=2, ensure_ascii=False))
|
||
|
||
else:
|
||
parser.print_help()
|
||
|
||
if __name__ == "__main__":
|
||
main() |