116 lines
6.6 KiB
Python
116 lines
6.6 KiB
Python
"""孕妈悄悄话置顶笔记的 100 条模拟祝福评论(11-07 用户:「评论里按正常评论放上 100 个」)。
|
||
lines.txt 一行一条,「||」后面是楼中楼回复(其他孕妈/宝妈)。人设从 cm.json 的 1000 人里挑:主评论用孕期人设、回复混宝妈;
|
||
尽量挑在别的帖子下出现少的人。确定性(固定种子),重跑结果不变;只追加/覆盖 c[TARGET],不动其他帖子。"""
|
||
import json, random
|
||
from collections import Counter
|
||
TARGET = 'wh-20260928'
|
||
P = 'guangchang/cm.json'
|
||
d = json.load(open(P, encoding='utf-8'))
|
||
used = Counter(c[0] for k, L in d['c'].items() if k != TARGET for c in L)
|
||
rnd = random.Random(20261107)
|
||
import re
|
||
def wk(p):
|
||
m = re.match(r'孕 ?(\d+)', p[2]); return int(m.group(1)) if m else 0
|
||
# 内容多是胎动、产检、待产(孕中晚期的事)→ 主评论只用孕 24 周以上的人设,免得「孕 6 周」说「头朝下了」
|
||
preg = sorted([i for i, p in enumerate(d['p']) if wk(p) >= 24], key=lambda i: (used[i], i))
|
||
anyp = sorted(range(len(d['p'])), key=lambda i: (used[i], i))
|
||
# 11-07 用户看评论:名字套路太像(4 个「在挑名字」、4 个「今天也开心」、3 个「水獭」、虎虎的老母亲/虎虎妈妈…)→ 同一帖里前两个字、后两个字都不撞
|
||
SIG = set()
|
||
def fresh(i):
|
||
n = d['p'][i][0]; ks = {'h:' + n[:2], 't:' + n[-2:]}
|
||
return not (ks & SIG)
|
||
def take(pool, k):
|
||
out = []
|
||
for i in pool:
|
||
if len(out) >= k: break
|
||
if fresh(i):
|
||
n = d['p'][i][0]; SIG.update({'h:' + n[:2], 't:' + n[-2:]}); out.append(i)
|
||
for i in pool: # 不够就只要求后两个字不撞
|
||
if len(out) >= k: break
|
||
n = d['p'][i][0]
|
||
if i not in out and 't:' + n[-2:] not in SIG: SIG.add('t:' + n[-2:]); out.append(i)
|
||
for i in pool: # 还不够就随它
|
||
if len(out) >= k: break
|
||
if i not in out: out.append(i)
|
||
return out
|
||
preg = take(preg, 160)
|
||
mains = preg[:200]; rnd.shuffle(mains)
|
||
anyp = [i for i in anyp if i not in set(preg) and not re.match(r'准爸爸', d['p'][i][2])] # 回复里不放「准爸爸」(还没娃,说不了打针)
|
||
reps = take(anyp, 31); rnd.shuffle(reps)
|
||
lines = [l.strip() for l in open('tools/whcm/lines.txt', encoding='utf-8') if l.strip()]
|
||
assert len(lines) == 100, len(lines)
|
||
out, ri = [], 0
|
||
# 内容要和孕周对得上:按关键词给每条定孕周范围,从打乱后的人设里挑第一个落在范围内的
|
||
RULES = [(r'糖耐', 24, 28), (r'预产期在月底', 37, 39), (r'过年', 25, 28),
|
||
(r'待产包|入盆|无痛|胎位|头朝下|小床|产假|月子餐|育儿嫂|报纸|满月|儿科医生|插座|标签|绘本|包被', 32, 39),
|
||
(r'踢的位置越来越低|孕妇照|羊水|吸奶器|数胎动|数够十下|产褥垫|剪指甲|第一批小衣服|婴儿车|疫苗时间表', 28, 39), (r'四维', 24, 30)]
|
||
def rng(t):
|
||
for pat, lo, hi in RULES:
|
||
if re.search(pat, t): return lo, hi
|
||
return 24, 39
|
||
left = list(mains)
|
||
def pick(t):
|
||
lo, hi = rng(t)
|
||
for i in left:
|
||
if lo <= wk(d['p'][i]) <= hi: left.remove(i); return i
|
||
raise SystemExit('没有合适人设:' + t)
|
||
for k, l in enumerate(lines):
|
||
parts = l.split('||')
|
||
likes = max(3, int(520 * (0.965 ** k)) + rnd.randint(-18, 18))
|
||
rr = []
|
||
for t in parts[1:]:
|
||
rr.append([reps[ri], t]); ri += 1
|
||
out.append([pick(parts[0]), parts[0], likes, rr])
|
||
# 11-07 用户:「评论里也加一些孕早期的孕妈,看到别人有胎动也有期待,也有自己的话对孩子说;都要真人感」
|
||
# early.txt = 孕 6~13 周人设的留言(回复用孕中晚期/宝妈人设,像过来人);均匀插进前 100 条中间,不全堆在后面
|
||
# early_rep.txt = 「原留言开头|早孕妈妈的回复」,挂在已有留言楼里(「看你们说胎动好羡慕」这类)
|
||
early_p = take(sorted([i for i, p in enumerate(d['p']) if 6 <= wk(p) <= 13 and i not in set(reps) and not re.search(r'待产|宝妈|老母亲|养娃|学抱娃|拼尿布|麻麻|大宝', p[0])], key=lambda i: (used[i], i)), 51)
|
||
er = random.Random(20261108); er.shuffle(early_p)
|
||
ei = 0
|
||
for t in [l.strip() for l in open('tools/whcm/early_rep.txt', encoding='utf-8') if l.strip()]:
|
||
head, txt = t.split('|', 1)
|
||
hit = [c for c in out if c[1].startswith(head)]
|
||
assert len(hit) == 1, head
|
||
hit[0][3].append([early_p[ei], txt]); ei += 1
|
||
ERULES = [(r'两道杠|还没告诉老公|验孕棒|小黑圈|胎心胎芽', 6, 8), (r'孕酮|查血|见了一点血', 6, 10), (r'约了NT', 10, 12),
|
||
(r'满三个月', 8, 12), (r'才两个月', 8, 9), (r'葡萄|裤子有点紧|肚子还平', 10, 13)]
|
||
eleft = early_p[ei:]
|
||
def epick(t):
|
||
lo, hi = next(((a, b) for pat, a, b in ERULES if re.search(pat, t)), (6, 13))
|
||
for i in eleft:
|
||
if lo <= wk(d['p'][i]) <= hi: eleft.remove(i); return i
|
||
raise SystemExit('没有合适早孕人设:' + t)
|
||
early = [l.strip() for l in open('tools/whcm/early.txt', encoding='utf-8') if l.strip()]
|
||
eo = []
|
||
for k, l in enumerate(early):
|
||
parts = l.split('||')
|
||
rr = []
|
||
for t in parts[1:]:
|
||
rr.append([reps[ri], t]); ri += 1
|
||
eo.append([epick(parts[0]), parts[0], 0, rr])
|
||
# 定赞数:从第 3 条起每隔约 2~3 条放一条早孕的,赞数取前后两条之间
|
||
merged = []
|
||
step = len(out) / len(eo)
|
||
j = 0
|
||
for k, c in enumerate(out):
|
||
merged.append(c)
|
||
while j < len(eo) and k >= 2 + j * step * .96:
|
||
e = eo[j]; nxt = out[k + 1][2] if k + 1 < len(out) else 3
|
||
e[2] = max(3, (c[2] + nxt) // 2 + rnd.randint(-4, 4)); merged.append(e); j += 1
|
||
for e in eo[j:]: e[2] = 3
|
||
# 追加在最后(模拟留言的 key 是「帖子#下标」,插在中间会让原来那 100 条下标错位,真实回复/本机点赞挂错楼);
|
||
# 赞数已按插入位置取好,「最热」排序时自然穿插在中间
|
||
out = out + eo
|
||
# 11-07 用户定(方案 A):「被赞 3 次变星星」要和赞数对得上 → 赞数最高的 26 条标成已变星(第 5 位 = 1),
|
||
# 赞数压到几十~个位(不再几百);其余 0~5 个赞(门槛 6),看着就是「还差一点」,有人帮着点一下就能变星(前端本机算)
|
||
order = sorted(range(len(out)), key=lambda k: -out[k][2])
|
||
NS = 26
|
||
for r, k in enumerate(order):
|
||
if r < NS:
|
||
out[k][2] = max(6, int(168 * (0.88 ** r)) + rnd.randint(-5, 5)); out[k].append(1)
|
||
else: # 11-07 用户:门槛改 6 个赞 → 没变星的 0~5 个
|
||
out[k][2] = rnd.choice([0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 4, 5])
|
||
d['c'][TARGET] = out
|
||
json.dump(d, open(P, 'w', encoding='utf-8'), ensure_ascii=False, separators=(',', ':'))
|
||
print('ok', len(out), 'replies', ri, 'early', len(eo), 'early-replies', ei)
|