项目文件夹

文件
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 行
15 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
#!/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()