curate.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. """
  2. v2: 基于用户标注 (保留 7 条) + 新一轮搜索补充, 按"真实感 / 棚拍 / 写真"三方向分类。
  3. 读取两批 cache (旧的在 /Users/sunlit/Profile/.cache, 新的在 ./.cache),
  4. 合并图片到 images/, 输出 result.json。
  5. 注意: 重跑会覆盖外部追加的字段 (例如 method / index), 如要保留请改成 merge 模式。
  6. """
  7. import json
  8. import re
  9. from pathlib import Path
  10. import httpx
  11. OLD_CACHE = Path('/Users/sunlit/Profile/.cache/content_search')
  12. NEW_CACHE = Path('/Users/sunlit/Profile/analysis/ai-portrait-realism/.cache/content_search')
  13. OUT_DIR = Path('/Users/sunlit/Profile/analysis/ai-portrait-realism')
  14. IMG_DIR = OUT_DIR / 'images'
  15. IMG_DIR.mkdir(parents=True, exist_ok=True)
  16. MAX_IMAGES_PER_ITEM = 6
  17. # (cache_dir, file, platform, index, slug, category, user_kept, user_comment, description)
  18. # user_kept: True 表示用户在 v1 标注里明确保留, False 表示 v2 新增
  19. # user_comment: 用户在 v1 标注里给该条的备注 (无则空字符串)
  20. PICKS = [
  21. # ═══════════════════════════════════════════════════════════
  22. # 真实感 (网感) — iPhone 自拍 / 闪光灯 / 主动加瑕疵 / 平庸感
  23. # ═══════════════════════════════════════════════════════════
  24. (OLD_CACHE, 'apr-xhs-jimeng.json', 'xhs', 8, 'xhs-jimeng-iphone-selfie',
  25. 'realism', True, '',
  26. '⭐ 核心"网感" prompt: "iPhone 后置自拍 + 闪光灯 + 运动模糊 + 构图随意 + 角度尴尬 + 平凡无奇";'
  27. '把所有"完美/讲究"指令删掉, 主动加入瑕疵。'),
  28. (OLD_CACHE, 'apr-xhs-jimeng.json', 'xhs', 9, 'xhs-jimeng-real-beauty-prompt',
  29. 'realism', True, '',
  30. '与同款"日常自拍"风格 prompt 的另一版本(场景换为酒店房间), 验证模板可迁移到不同场景。'),
  31. (OLD_CACHE, 'apr-xhs-quaiwei.json', 'xhs', 10, 'xhs-anti-ai-influencer-formula',
  32. 'realism', True, '',
  33. '"消除 AI 感"配方: 网红/素人感博主 + 浴室/寝室/咖啡厅 + 前置摄像头/自然光/真实感 + 高冷+放荡不羁的矛盾感。'),
  34. (NEW_CACHE, 'apr-gzh-banana2.json', 'gzh', 7, 'gzh-banana2-rainy-girl',
  35. 'realism', False, '',
  36. 'Nano Banana 2 雨天少女 prompt: 完整结构化 prompt(主体/五官/姿势/服装/构图/光线), '
  37. '"梦幻雨天 + 透过湿润伞面 + 略带忧郁" 的情绪+真实感混合。属于真实感里的"故事性瞬间"派。'),
  38. # ═══════════════════════════════════════════════════════════
  39. # 棚拍 — 影棚 / 干净光线 / 商业肖像 / 影楼级精致
  40. # ═══════════════════════════════════════════════════════════
  41. (OLD_CACHE, 'apr-xhs-realhuman.json', 'xhs', 4, 'xhs-nanobanana-studio-cream',
  42. 'studio', True, '',
  43. 'Nano Banana 奶白棚拍 prompt: 含构图/主体/动作/环境/光线/镜头/质感全套描述; '
  44. '"低姿态 + 干净光线", 强调"加入 8-12% 胶片颗粒/避免塑料皮"。'),
  45. (OLD_CACHE, 'apr-xhs-realhuman.json', 'xhs', 7, 'xhs-orchid-white-moonlight',
  46. 'studio', True, '也是棚拍类型的',
  47. '"白月光兰花人像"完整指令: 妆容 + 姿态 + 构图 + 道具 + 灯光分层描述, '
  48. '低饱和度复古橄榄绿背景墙的棚拍布景, 东方氛围感。'),
  49. (NEW_CACHE, 'apr-gzh-banana2.json', 'gzh', 1, 'gzh-banana2-7-styles',
  50. 'studio', False, '',
  51. '实测 Nano Banana 7 种高级人物摄影写真模板 (黑白影棚/美式高管/韩系轻透 等), '
  52. '每种风格都给完整 prompt, 是棚拍方向的最全模板库之一。'),
  53. (NEW_CACHE, 'apr-gzh-banana2.json', 'gzh', 2, 'gzh-banana2-dark-cinematic',
  54. 'studio', False, '',
  55. 'Nano Banana 2 高质感暗调电影感东亚女性人像: 4900+ 字超长结构化 prompt'
  56. '(核心主体/发型/皮肤/体态/服装/摄影参数全维度), 是"高级感+真实感"棚拍的标杆样本。'),
  57. # ═══════════════════════════════════════════════════════════
  58. # 写真 — 摄影师作品级, 有审美/艺术气息/真实感, 棚拍与自拍之间
  59. # ═══════════════════════════════════════════════════════════
  60. (OLD_CACHE, 'apr-xhs-daily.json', 'xhs', 5, 'xhs-banana-pro-realhuman-pack',
  61. 'portrait_art', True, 'prompt在图片里,要注意提取',
  62. 'Banana Pro 真实感人像 prompt 包(10 张例图), 多风格/多场景的成片美学参考图库。'),
  63. (OLD_CACHE, 'apr-zhihu-realistic.json', 'zhihu', 18, 'zh-6-scenes-light-skin',
  64. 'portrait_art', True, '',
  65. '6 组场景的"光影适配 + 法医级皮肤纹理"复刻技巧, 用 Nano Banana Pro 出片;'
  66. '场景含日式庭院/复古客厅/画室侧窗等, 把"场景适配光影"这个被忽视的维度讲清楚。'),
  67. (NEW_CACHE, 'apr-gzh-jimeng.json', 'gzh', 5, 'gzh-jimeng-zimei-template-pack',
  68. 'portrait_art', False, '',
  69. '《我用AI给自己拍了套写真!》⭐ 607 likes 标杆型公众号文章, '
  70. '14 张样片 + 万能提示词, 是"普通人 vs 影楼写真"的对照组, 写真方向最具传播力的样本之一。'),
  71. (NEW_CACHE, 'apr-gzh-jimeng.json', 'gzh', 8, 'gzh-jimeng-10-studio-styles',
  72. 'portrait_art', False, '',
  73. '"10 大影楼质感" — Nano Banana + 即梦, 5200+ 字, 75 张样片;'
  74. '横跨 10 种风格(高级摄影棚/复古港风/婚纱/古风等), 每种风格都给独立 prompt, 区分男女版本, 适合直接抄。'),
  75. (NEW_CACHE, 'apr-gzh-jimeng.json', 'gzh', 3, 'gzh-jimeng-mood-portrait',
  76. 'portrait_art', False, '',
  77. '即梦 4.0 高级人物肖像照 (附提示词), 摄影棚风格脸部特写 + 极简灰色渐变背景 + '
  78. '黑白胶片颗粒 + 非居中构图 — 是"棚拍但有写真气质"的过渡范式。'),
  79. (NEW_CACHE, 'apr-gzh-banana2.json', 'gzh', 12, 'gzh-banana2-godlike-portrait',
  80. 'portrait_art', False, '',
  81. 'NanoBanana 神级人物摄影 ⭐ 771 likes 高人气, "顶级摄影肖像照"评测;'
  82. '生活照 → 高 bigger 肖像照, 是"作家级人像"思路的入门级范例。'),
  83. (NEW_CACHE, 'apr-gzh-banana2.json', 'gzh', 4, 'gzh-banana2-10-goddess-prompts',
  84. 'portrait_art', False, '',
  85. 'Nano Banana 10 组女神专属玩法 (高反差影棚人像/宅男卧室电脑等), '
  86. '每组覆盖一个流行美学风格, 是"小红书爆款写真"的反向工程图鉴。'),
  87. (NEW_CACHE, 'apr-xhs-korean.json', 'xhs', 1, 'xhs-korean-tianmei-paobao',
  88. 'portrait_art', False, '',
  89. '韩系画报少女写真 ⭐ 1024 likes, 完整 prompt(发型/妆容/服装/道具/背景/姿势);'
  90. '"画报感"恰好是写真的核心 — 比手机自拍精致, 比影棚自然。'),
  91. (NEW_CACHE, 'apr-xhs-korean.json', 'xhs', 4, 'xhs-korean-8k-glass-skin',
  92. 'portrait_art', False, '',
  93. '8k 商拍超真实半身写真 ⭐ 1409 likes, K-Beauty 美学;'
  94. '"玻璃肌 + 自然雀斑 + 柔和工作室灯光" — 干净通透但保留真实瑕疵, 是用户描述的"有审美+真实感"的精确实例。'),
  95. (NEW_CACHE, 'apr-xhs-korean.json', 'xhs', 7, 'xhs-korean-white-oxygen',
  96. 'portrait_art', False, '',
  97. '⭐⭐ Nano Banana 白色氧气感写真 397 likes, '
  98. '"干净又温柔 / 通透 / 亲近感" — 完整 prompt 描述了一字肩白色针织毛衣 + 微侧站立 + 柔光自然光线;'
  99. '最贴近用户对"写真"的描述: 不影楼/不随手自拍/有艺术气息。'),
  100. (NEW_CACHE, 'apr-xhs-korean.json', 'xhs', 10, 'xhs-korean-cool-aesthetic',
  101. 'portrait_art', False, '',
  102. '清冷氛围感韩系肖像写真 316 likes, "低饱和低对比的高调人像 + 冷暖反差(冷灰白调+蜜桃粉)";'
  103. '日韩系空气感的标准做法, 适合做静谧/诗意/略带脆弱的氛围写真。'),
  104. (NEW_CACHE, 'apr-xhs-travel.json', 'xhs', 7, 'xhs-travel-korean-wedding',
  105. 'portrait_art', False, '',
  106. 'Seedream 韩式海景婚纱写真大师指令 ⭐ 553 likes, 18 张样片;'
  107. '冷蓝调 + 电影质感 + 海面背景, 多人 + 单人构图全 cover, 旅拍/婚纱型写真模板。'),
  108. (NEW_CACHE, 'apr-xhs-outdoor.json', 'xhs', 5, 'xhs-outdoor-summer-film',
  109. 'portrait_art', False, '',
  110. '⭐ 0 成本逆光梦中情照 3683 likes, "夏日清新胶片写真" — '
  111. '法式庭院 + 藤编椅 + 绣球花丛 + 午后侧逆光 + 暖调胶片颗粒, 9 张不同动作;'
  112. '是"户外/旅拍/胶片感"写真的标杆配方。'),
  113. (NEW_CACHE, 'apr-xhs-literary.json', 'xhs', 9, 'xhs-literary-vintage-film',
  114. 'portrait_art', False, '',
  115. '复古胶片氛围感少女 1520 likes, "旧报刊亭/橘粉花丛/虚焦背景拖影/重影/胶片颗粒纹理";'
  116. '画面带"故事感", 平衡"妖冶与甜美", 同时具备"网红网感"和"作品感" — 是写真方向最有"叙事性"的样本。'),
  117. (NEW_CACHE, 'apr-xhs-mood.json', 'xhs', 3, 'xhs-mood-eastern-classical',
  118. 'portrait_art', False, '',
  119. '岁序更迭: 东方古韵 AI 提示词技巧 ⭐⭐ 63410 likes 大热门; '
  120. '不是直接给 prompt, 而是讲方法论 — Agfacolor 胶片色彩 / 高调影棚柔光 / 微缩布景手绘接景;'
  121. '适合做"东方写真"美学定调的参考。'),
  122. # ── 第 3 轮追加 (关键词: AI 故事感 / AI 怎么写真 / AI 氛围感 / AI ins风) ──
  123. (NEW_CACHE, 'apr-xhs-story.json', 'xhs', 5, 'xhs-story-iwai-shunji',
  124. 'portrait_art', False, '',
  125. '⭐⭐ 岩井俊二美学 AI 生图 prompt 1884 likes — "All About Lily Chou-Chou" 二创, '
  126. '完整英文 prompt: 35mm film grain, soft focus, atmospheric haze, cool cyan-blue / warm golden hour backlight, '
  127. 'strong lens flare, muted desaturated color palette, low angle shot, Japanese youth aesthetic. '
  128. '是"日系作家级故事感写真"的标杆样本, 比一般氛围感写真更有作品感与情绪深度。'),
  129. (NEW_CACHE, 'apr-xhs-story.json', 'xhs', 10, 'xhs-story-hongkong-cafe',
  130. 'portrait_art', False, '',
  131. '⭐ 港风茶餐厅生活感写真口令 1366 likes — 完整 prompt(发型/妆容/服装/背景/光影) + 9 种动作姿势模板. '
  132. '"复古胶片 + 港风 + 生活烟火气" 的精确 prompt, 是写真方向"生活感叙事"派的最强样本之一. '
  133. '场景具象: 香港老式茶餐厅 / 旧海报 / 绿色铁皮餐桌 / 暖黄色调 / 明显胶片颗粒.'),
  134. (NEW_CACHE, 'apr-xhs-story.json', 'xhs', 3, 'xhs-story-karin-reference',
  135. 'portrait_art', False, '',
  136. 'AI 胶片摄影师 Karin. 的作品参考帖 2829 likes — body 短, 但提到了"日式胶片复古人像独特故事感"作为审美锚点. '
  137. '可作为"作家级写真"赛道里的一个真实存在的 IP 案例(Karin), 用于对标 / 反向研究其 prompt 路径.'),
  138. (NEW_CACHE, 'apr-xhs-story.json', 'xhs', 9, 'xhs-story-grass-portrait',
  139. 'portrait_art', False, '',
  140. 'AI 户外草地写真样片 1815 likes — body 仅一句 "图片为 ai 创作", 但 8 张 AI 户外写真成片可作为美学参考. '
  141. '配合岩井俊二/港风/Karin 一起看, 形成"故事感写真"成片库。'),
  142. ]
  143. # 去重检查 (按 cache_file + cache_index)
  144. def _check_dedup():
  145. seen = {}
  146. for p in PICKS:
  147. key = (p[1], p[2], p[3]) # (cache_file, platform, index)
  148. if key in seen:
  149. raise ValueError(f'Duplicate pick: {key} -> {p[4]} clashes with {seen[key]}')
  150. seen[key] = p[4]
  151. _check_dedup()
  152. def safe_filename(s):
  153. return re.sub(r'[^\w\-.]', '_', s)[:80]
  154. def download(url, dest, client):
  155. if dest.exists():
  156. return True
  157. try:
  158. r = client.get(url, follow_redirects=True, timeout=30.0)
  159. r.raise_for_status()
  160. dest.write_bytes(r.content)
  161. return True
  162. except Exception as e:
  163. print(f' [X] {url[:80]}... -> {e}')
  164. return False
  165. def ext_from_url(url):
  166. m = re.search(r'\.(jpg|jpeg|png|webp|gif)(\?|$)', url, re.IGNORECASE)
  167. return ('.' + m.group(1).lower()) if m else '.jpg'
  168. def main():
  169. final = []
  170. with httpx.Client(headers={
  171. 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
  172. }) as client:
  173. for cache_dir, cache_file, platform, index, slug, category, user_kept, user_comment, description in PICKS:
  174. cache_path = cache_dir / cache_file
  175. if not cache_path.exists():
  176. print(f'[!] missing {cache_path}')
  177. continue
  178. d = json.load(cache_path.open())
  179. entry = d.get(f'search:{platform}')
  180. if not entry:
  181. continue
  182. posts = entry['posts']
  183. if index < 1 or index > len(posts):
  184. continue
  185. post = posts[index - 1]
  186. title = post.get('title') or ''
  187. body = post.get('body_text') or ''
  188. url = post.get('link') or ''
  189. author = {'xhs': '小红书博主', 'zhihu': '知乎作者',
  190. 'gzh': '公众号作者', 'bili': 'B 站 UP'}.get(platform, platform)
  191. img_urls = [u for u in (post.get('images') or []) if u]
  192. local_paths = []
  193. for i, img_url in enumerate(img_urls[:MAX_IMAGES_PER_ITEM], 1):
  194. ext = ext_from_url(img_url)
  195. fname = safe_filename(f'{slug}-{i:02d}{ext}')
  196. dest = IMG_DIR / fname
  197. if download(img_url, dest, client):
  198. local_paths.append(f'images/{fname}')
  199. print(f' [OK] {fname}')
  200. cover = local_paths[0] if local_paths else ''
  201. final.append({
  202. 'category': category,
  203. 'user_kept': user_kept,
  204. 'user_comment': user_comment,
  205. 'description': description,
  206. 'cover': cover,
  207. 'title': title,
  208. 'author': author,
  209. 'body': body,
  210. 'images': local_paths,
  211. 'url': url,
  212. 'note': f'platform={platform} | cache_file={cache_file} | cache_index={index} | likes={post.get("like_count")}',
  213. })
  214. tag = '[KEPT]' if user_kept else '[NEW] '
  215. print(f'[done] {tag} [{category}] {slug}: {len(local_paths)} imgs')
  216. out_path = OUT_DIR / 'result.json'
  217. out_path.write_text(
  218. json.dumps(final, ensure_ascii=False, indent=2),
  219. encoding='utf-8',
  220. )
  221. # 分类统计
  222. from collections import Counter
  223. cat_counts = Counter(item['category'] for item in final)
  224. kept_count = sum(1 for it in final if it['user_kept'])
  225. new_count = len(final) - kept_count
  226. print(f'\n=== Wrote {len(final)} items to {out_path}')
  227. print(f' user_kept: {kept_count} | new: {new_count}')
  228. for cat, cnt in cat_counts.items():
  229. kept_in_cat = sum(1 for it in final if it['category'] == cat and it['user_kept'])
  230. print(f' {cat}: {cnt} (kept={kept_in_cat}, new={cnt - kept_in_cat})')
  231. if __name__ == '__main__':
  232. main()