406 lines
15 KiB
Python
406 lines
15 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
simp-skill · Social Media Parser
|
||
扫描社交媒体内容目录(朋友圈截图、微博、小红书等),
|
||
提取文字内容,生成心上人社交画像报告。
|
||
|
||
支持内容:
|
||
- 图片:jpg / jpeg / png / gif / webp / bmp / heic
|
||
- 文字导出:txt / md / json / csv
|
||
|
||
用法:
|
||
python3 social_parser.py --dir crushes/xiaomei/memories/social --output crushes/xiaomei/memories/social/report.md
|
||
python3 social_parser.py --dir ./social_screenshots --target 小美
|
||
"""
|
||
|
||
import os
|
||
import re
|
||
import json
|
||
import argparse
|
||
from pathlib import Path
|
||
from datetime import datetime
|
||
from collections import Counter
|
||
|
||
|
||
# ─────────────────────────────────────────────
|
||
# 常量
|
||
# ─────────────────────────────────────────────
|
||
|
||
IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".heic", ".heif"}
|
||
TEXT_EXTS = {".txt", ".md", ".json", ".csv"}
|
||
|
||
# 平台关键词(用于从文件名推测来源平台)
|
||
PLATFORM_HINTS = {
|
||
"weibo": ["微博", "weibo", "wb_"],
|
||
"xiaohongshu": ["小红书", "红书", "xhs", "xiaohongshu", "rednote"],
|
||
"moments": ["朋友圈", "moments", "wechat"],
|
||
"douyin": ["抖音", "douyin", "dy_"],
|
||
"instagram": ["instagram", "ig_"],
|
||
"twitter": ["twitter", "tweet"],
|
||
"bilibili": ["bilibili", "bili", "b站"],
|
||
}
|
||
|
||
# 情感信号关键词(用于文字内容的信号扫描)
|
||
SIGNAL_KEYWORDS = {
|
||
"积极信号": [
|
||
"喜欢", "开心", "快乐", "幸福", "期待", "想你", "念你", "陪伴",
|
||
"一起", "约", "见面", "等你", "好想", "最近怎么样", "你在吗",
|
||
"miss", "happy", "love", "together", "date",
|
||
],
|
||
"情绪低落": [
|
||
"难过", "伤心", "哭", "失眠", "想太多", "孤独", "一个人",
|
||
"失落", "心情不好", "烦", "累", "sad", "lonely", "cry",
|
||
],
|
||
"感情相关": [
|
||
"喜欢一个人", "暗恋", "表白", "心动", "心跳", "脸红",
|
||
"好看", "好温柔", "好厉害", "崇拜",
|
||
"crush", "like someone", "confession",
|
||
],
|
||
}
|
||
|
||
|
||
# ─────────────────────────────────────────────
|
||
# 扫描目录
|
||
# ─────────────────────────────────────────────
|
||
|
||
def scan_directory(directory: str) -> dict:
|
||
"""递归扫描目录,按类型分类文件"""
|
||
result = {"images": [], "texts": [], "others": []}
|
||
base = Path(directory)
|
||
|
||
if not base.exists():
|
||
print(f"⚠️ 目录不存在:{directory}")
|
||
return result
|
||
|
||
for path in sorted(base.rglob("*")):
|
||
if not path.is_file():
|
||
continue
|
||
ext = path.suffix.lower()
|
||
rel = str(path.relative_to(base))
|
||
|
||
if ext in IMAGE_EXTS:
|
||
result["images"].append({"path": str(path), "rel": rel, "name": path.name, "size": path.stat().st_size})
|
||
elif ext in TEXT_EXTS:
|
||
result["texts"].append({"path": str(path), "rel": rel, "name": path.name, "size": path.stat().st_size})
|
||
elif path.name.startswith(".") or path.name == "report.md":
|
||
continue # 跳过隐藏文件和已生成的报告
|
||
else:
|
||
result["others"].append({"path": str(path), "rel": rel, "name": path.name})
|
||
|
||
return result
|
||
|
||
|
||
# ─────────────────────────────────────────────
|
||
# 平台识别
|
||
# ─────────────────────────────────────────────
|
||
|
||
def detect_platform(filename: str) -> str:
|
||
"""从文件名推测社交平台来源"""
|
||
name_lower = filename.lower()
|
||
for platform, hints in PLATFORM_HINTS.items():
|
||
for hint in hints:
|
||
if hint.lower() in name_lower:
|
||
return platform
|
||
return "未知平台"
|
||
|
||
|
||
def platform_display(platform: str) -> str:
|
||
display = {
|
||
"weibo": "微博",
|
||
"xiaohongshu": "小红书",
|
||
"moments": "微信朋友圈",
|
||
"douyin": "抖音",
|
||
"instagram": "Instagram",
|
||
"twitter": "Twitter / X",
|
||
"bilibili": "哔哩哔哩",
|
||
"未知平台": "未知来源",
|
||
}
|
||
return display.get(platform, platform)
|
||
|
||
|
||
# ─────────────────────────────────────────────
|
||
# 文字内容提取与分析
|
||
# ─────────────────────────────────────────────
|
||
|
||
def read_text_file(filepath: str, max_chars: int = 5000) -> str:
|
||
"""读取文本文件,限制长度"""
|
||
try:
|
||
with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
|
||
content = f.read(max_chars)
|
||
if len(content) == max_chars:
|
||
content += "\n\n[... 内容过长,已截断 ...]"
|
||
return content.strip()
|
||
except Exception as e:
|
||
return f"[读取失败:{e}]"
|
||
|
||
|
||
def parse_json_export(filepath: str) -> list:
|
||
"""尝试解析 JSON 导出(微博/小红书等平台的数据导出)"""
|
||
try:
|
||
with open(filepath, "r", encoding="utf-8") as f:
|
||
data = json.load(f)
|
||
|
||
posts = []
|
||
# 尝试常见的 JSON 结构
|
||
items = (
|
||
data if isinstance(data, list)
|
||
else data.get("data", data.get("posts", data.get("items", [])))
|
||
)
|
||
|
||
for item in items[:50]: # 最多取50条
|
||
text = (
|
||
item.get("text") or item.get("content") or
|
||
item.get("description") or item.get("body") or ""
|
||
)
|
||
created = (
|
||
item.get("created_at") or item.get("time") or
|
||
item.get("timestamp") or item.get("date") or ""
|
||
)
|
||
posts.append({"text": str(text).strip(), "time": str(created)})
|
||
|
||
return [p for p in posts if p["text"]]
|
||
except Exception:
|
||
return []
|
||
|
||
|
||
def scan_signals(text: str) -> dict:
|
||
"""扫描文字内容中的情感信号关键词"""
|
||
found = {}
|
||
text_lower = text.lower()
|
||
for category, keywords in SIGNAL_KEYWORDS.items():
|
||
hits = [kw for kw in keywords if kw in text_lower]
|
||
if hits:
|
||
found[category] = hits
|
||
return found
|
||
|
||
|
||
# ─────────────────────────────────────────────
|
||
# 报告生成
|
||
# ─────────────────────────────────────────────
|
||
|
||
def generate_report(directory: str, target_name: str, output_path: str = None) -> str:
|
||
"""生成社交媒体内容分析报告"""
|
||
files = scan_directory(directory)
|
||
now = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||
|
||
images = files["images"]
|
||
texts = files["texts"]
|
||
|
||
lines = [
|
||
f"# 📱 社交媒体内容报告",
|
||
f"",
|
||
f"> 心上人:**{target_name}** | 分析时间:{now}",
|
||
f"> 来源目录:`{directory}`",
|
||
f"",
|
||
f"---",
|
||
f"",
|
||
f"## 📊 内容概览",
|
||
f"",
|
||
f"| 类型 | 数量 |",
|
||
f"|------|------|",
|
||
f"| 图片 | {len(images)} 张 |",
|
||
f"| 文字文件 | {len(texts)} 个 |",
|
||
f"| 合计 | {len(images) + len(texts)} 个文件 |",
|
||
f"",
|
||
]
|
||
|
||
# ── 图片清单 ──────────────────────────────
|
||
if images:
|
||
# 按平台分组
|
||
platform_groups: dict = {}
|
||
for img in images:
|
||
plat = detect_platform(img["name"])
|
||
platform_groups.setdefault(plat, []).append(img)
|
||
|
||
lines += [
|
||
f"---",
|
||
f"",
|
||
f"## 🖼️ 图片清单({len(images)} 张)",
|
||
f"",
|
||
f"> 图片需要通过 Claude 的视觉能力分析,",
|
||
f"> 可将图片路径告诉 Claude,让其直接读取图片内容。",
|
||
f"",
|
||
]
|
||
|
||
for plat, imgs in sorted(platform_groups.items()):
|
||
lines.append(f"### {platform_display(plat)}({len(imgs)} 张)")
|
||
lines.append(f"")
|
||
for img in imgs:
|
||
size_kb = round(img["size"] / 1024, 1)
|
||
lines.append(f"- `{img['rel']}`({size_kb} KB)")
|
||
lines.append(f"")
|
||
|
||
lines += [
|
||
f"**使用建议**:",
|
||
f"将以上图片路径逐一告诉 Claude,配合以下提示词分析:",
|
||
f"",
|
||
f"```",
|
||
f"请分析这张图片,告诉我:",
|
||
f"1. 图片内容是什么?(文字/场景/情绪)",
|
||
f"2. 有没有关于 {target_name} 性格或生活状态的信息?",
|
||
f"3. 有没有可以用于定制情话的细节?",
|
||
f"```",
|
||
f"",
|
||
]
|
||
|
||
# ── 文字内容 ──────────────────────────────
|
||
if texts:
|
||
lines += [
|
||
f"---",
|
||
f"",
|
||
f"## 📝 文字内容({len(texts)} 个文件)",
|
||
f"",
|
||
]
|
||
|
||
all_signals: dict = {}
|
||
|
||
for tf in texts:
|
||
filepath = tf["path"]
|
||
filename = tf["name"]
|
||
ext = Path(filename).suffix.lower()
|
||
plat = detect_platform(filename)
|
||
|
||
lines += [
|
||
f"### 📄 {tf['rel']}",
|
||
f"",
|
||
f"**来源平台**:{platform_display(plat)}",
|
||
f"",
|
||
]
|
||
|
||
# JSON 导出特殊处理
|
||
if ext == ".json":
|
||
posts = parse_json_export(filepath)
|
||
if posts:
|
||
lines.append(f"**解析到 {len(posts)} 条内容**:")
|
||
lines.append(f"")
|
||
for i, post in enumerate(posts[:10], 1):
|
||
time_str = f"({post['time']})" if post["time"] else ""
|
||
lines.append(f"{i}. {time_str}{post['text'][:200]}")
|
||
if len(posts) > 10:
|
||
lines.append(f"... 共 {len(posts)} 条,仅展示前10条")
|
||
lines.append(f"")
|
||
# 合并所有文字做信号扫描
|
||
combined = " ".join(p["text"] for p in posts)
|
||
signals = scan_signals(combined)
|
||
else:
|
||
content = read_text_file(filepath)
|
||
lines.append(f"```")
|
||
lines.append(content)
|
||
lines.append(f"```")
|
||
lines.append(f"")
|
||
signals = scan_signals(content)
|
||
else:
|
||
content = read_text_file(filepath)
|
||
lines.append(f"```")
|
||
lines.append(content)
|
||
lines.append(f"```")
|
||
lines.append(f"")
|
||
signals = scan_signals(content)
|
||
|
||
# 信号标注
|
||
if signals:
|
||
lines.append(f"**🔍 检测到的情感关键词**:")
|
||
for cat, kws in signals.items():
|
||
lines.append(f"- {cat}:{', '.join(f'`{k}`' for k in kws)}")
|
||
lines.append(f"")
|
||
# 累计
|
||
for cat, kws in signals.items():
|
||
all_signals.setdefault(cat, []).extend(kws)
|
||
|
||
# 全局信号汇总
|
||
if all_signals:
|
||
lines += [
|
||
f"---",
|
||
f"",
|
||
f"## 🎯 社交内容信号汇总",
|
||
f"",
|
||
f"从所有文字内容中检测到以下情感关键词:",
|
||
f"",
|
||
]
|
||
for cat, kws in all_signals.items():
|
||
freq = Counter(kws).most_common(5)
|
||
lines.append(f"**{cat}**:{', '.join(f'{k}×{c}' if c > 1 else k for k, c in freq)}")
|
||
lines += [
|
||
f"",
|
||
f"> 💡 这些关键词可以帮助你了解 {target_name} 近期的情绪状态和关注点,",
|
||
f"> 用于定制更贴近ta当下心情的情话。",
|
||
f"",
|
||
]
|
||
|
||
# ── 空目录提示 ────────────────────────────
|
||
if not images and not texts:
|
||
lines += [
|
||
f"",
|
||
f"⚠️ 目录中没有找到可分析的文件。",
|
||
f"",
|
||
f"**如何获取社交媒体内容**:",
|
||
f"",
|
||
f"| 平台 | 方法 |",
|
||
f"|------|------|",
|
||
f"| 微信朋友圈 | 截图保存为图片 |",
|
||
f"| 微博 | 截图 或 使用数据导出工具 |",
|
||
f"| 小红书 | 截图 或 复制文字粘贴为 .txt |",
|
||
f"| 抖音 | 截图视频封面 |",
|
||
f"",
|
||
f"将文件放入 `{directory}/` 后重新运行本工具。",
|
||
]
|
||
|
||
lines += [
|
||
f"---",
|
||
f"",
|
||
f"## 📌 后续建议",
|
||
f"",
|
||
f"1. 将图片路径告诉 Claude 进行视觉分析,补充 `profile.md` 中的画像细节",
|
||
f"2. 运行 `/simp analyze` 结合聊天记录与社交内容进行综合信号评估",
|
||
f"3. 社交内容揭示的情绪关键词可以作为情话的切入点",
|
||
f"",
|
||
f"---",
|
||
f"",
|
||
f"*由 simp-skill · 追爱军师 生成*",
|
||
]
|
||
|
||
report = "\n".join(lines)
|
||
|
||
if output_path:
|
||
Path(output_path).parent.mkdir(parents=True, exist_ok=True)
|
||
with open(output_path, "w", encoding="utf-8") as f:
|
||
f.write(report)
|
||
print(f"✅ 报告已保存到 {output_path}")
|
||
|
||
return report
|
||
|
||
|
||
# ─────────────────────────────────────────────
|
||
# 主程序
|
||
# ─────────────────────────────────────────────
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(
|
||
description="simp-skill · 社交媒体内容解析器",
|
||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||
epilog="""
|
||
示例:
|
||
python3 social_parser.py --dir crushes/xiaomei/memories/social
|
||
python3 social_parser.py --dir ./screenshots --target 小美 --output report.md
|
||
""",
|
||
)
|
||
parser.add_argument("--dir", required=True, help="社交媒体内容目录路径")
|
||
parser.add_argument("--target", default="心上人", help="心上人的名字(默认:心上人)")
|
||
parser.add_argument("--output", "-o", help="输出报告路径(默认:打印到控制台)")
|
||
|
||
args = parser.parse_args()
|
||
|
||
print(f"💝 simp-skill · 社交内容解析器")
|
||
print(f"📂 扫描目录:{args.dir}")
|
||
print(f"🎯 心上人:{args.target}")
|
||
print()
|
||
|
||
report = generate_report(args.dir, args.target, args.output)
|
||
|
||
if not args.output:
|
||
print(report)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|