Files
wehub-resource-sync 1d6fd29cda
tests / test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:36:27 +08:00

406 lines
15 KiB
Python
Raw Permalink 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
"""
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()