要说明白这件事,得先亮个底:我自己有一套用Python写的疗愈日志分析脚本,它不提供诊断、不替代心理咨询,只做一件事——把我记录下来的情绪文本,转换成可以回看的趋势图、关键词和时间线。这套代码我自己断续用了一年多,期间改了很多版,从最初单纯的词频统计,到后来加入情绪打分、滑窗趋势判断,一点点变成了现在这个样子。今天把它整理出来分享,主要是给两类人看:一类是正在用文字记录自己情绪状态的人,想借代码帮自己更客观地回看变化;另一类是刚接触文本分析的开发者,想找一个轻量、能跑通、又能迁移到其他场景的情感分析模板。
先说好,这套东西不是玄学,也不是诊断工具。它能做的是把你写的字变成数据,再把数据变成图。至于怎么理解那些图,主动权——也应该——始终在你自己手里。
1. 为什么用Python做疗愈日志分析:思路与数据边界
1.1 疗愈日志到底能量化什么:三个可靠维度
很多人觉得情绪是没法量化的东西,其实这是个误解。文字记录里天然藏着三个可以量化的维度:情感倾向、主题分布、时间节奏。
情感倾向是最好入手的。你写的每一句话都带有情绪色彩,比如“今天睡了个好觉,醒过来觉得轻松了些”,这句话的得分应该是偏积极;而“下午又开始胡思乱想,怎么也停不下来”,这句话明显偏消极。用代码做这件事的本质,就是建立一个情绪词表,再设计一套规则,把文本里出现的正负词映射成分数。
主题分布更偏向“这段时间我到底在反复想什么”。通过分词、过滤掉无意义的虚词之后,剩下的高频名词和动词,能很直观地反映你当前关注的焦点。比如连续两周的高频词都指向“工作”“会议”“方案”,那说明压力源大概率在职场上;如果高频词变成了“睡眠”“疲惫”“没力气”,那重心就转向了身体状态和精力管理。
第三个维度是时间节奏。疗愈从来不是一条直线,它有起伏、有反复、有看似倒退但实际在整合的阶段。用滑窗统计的方式画出情绪得分随时间变化的曲线,能看见自己在一段较长周期里是整体波动向上,还是持续走低,这种宏观视角是单靠记忆把握不住的。
1.2 工具选型:为什么用轻量脚本而不是现成App
市面上并不缺情绪记录类的App,但我最后还是选择写一套本地脚本,核心原因有三个。
第一是数据私密性。情绪日志是非常个人的数据,放到第三方服务器上我始终不踏实。本地脚本配合本地文件,所有分析都在自己电脑上完成,数据从不离开硬盘,这带来的安全感是云端服务替代不了的。
第二是分析透明度。现成App给一个“心情指数87分”,你根本不知道分是怎么来的。自己写的脚本,每个词的权重、每道规则都摊在眼前,清楚知道自己到底是怎么被“打分”的。这种可控感在情绪探索场景里尤其重要——你在分析自己,不是被一个黑盒分析。
第三是扩展性。App的功能是固定的,而我对自己的需求是不断变化的。最初我只想做词频统计,后来想加情绪得分,再后来想按月对比主题变化。自己写代码,需求变化只需要改几行函数的事。
提示:这套脚本适合“辅助自我理解”,不适合也不应该替代专业人员。如果你的情绪状态已经影响睡眠、食欲或正常生活超过两周,请优先寻求正规心理服务,代码帮不了这个忙。
2. 日志数据怎么设计才能让代码有效:字段、词典与打分原理
2.1 一份能长期坚持的记录格式:日期、标签与正文
数据是整个项目的地基。我踩过最大的坑就是一开始没有定义清晰的格式,想到什么写什么,结果后面做分析时光清洗数据就花了一半时间。
我自己最终固定下来的格式非常简单,每行一条记录,用英文逗号分隔,长这样:
2025-01-12,睡眠,昨晚只睡了四个小时,中间醒了好几次,早上起来头很重。 2025-01-14,情绪,傍晚出门散步,风吹过来的时候突然觉得胸口没那么堵了。 2025-01-15,工作,开完会整个人发懵,坐在工位上盯着屏幕发呆,效率很低。三个字段分别是日期、标签、正文。日期用来做时间序列分析;标签是一条记录的大类,可以按自己的情况定义——我常用的是“情绪”“睡眠”“工作”“身体”“人际”这几类;正文就是完整的情绪叙述,分词和情感打分都是在这一栏上进行的。
这个格式最大的好处是兼容性好。记事本能写,Excel能开,Python用一行代码就能读进来。不要用复杂格式,越简单越容易坚持。
2.2 情感打分原理:词典、否定词与程度副词
情绪打分是整个脚本的核心,它的逻辑其实不复杂:准备一个带有权重的正负情绪词表,在文本里找到这些词,根据上下文调整分值,最后加总。
正负词典要自己维护。我目前的版本里大概收了一两百个常用词,权重按情绪强度来定。举个例子:
positive_words = { "平静": 2.0, "轻松": 2.0, "开心": 3.0, "安心": 2.0, "释然": 3.0, "期待": 1.5, "温暖": 2.5, "恢复": 1.0, "踏实": 2.0, "愉悦": 2.5, "欣慰": 2.0, "放松": 1.5 } negative_words = { "烦躁": 2.0, "低落": 2.0, "崩溃": 4.0, "恐慌": 4.0, "无力": 2.0, "压抑": 3.0, "焦虑": 2.5, "疲惫": 1.5, "失眠": 2.0, "痛苦": 3.5, "委屈": 2.5, "麻木": 2.0 }但只有词表远远不够。中文里“不平静”和“平静”完全相反,如果只看字面,会把意思算反。所以必须有否定词处理——“不”“没”“没有”“无法”“难以”出现时,情感词的分数要取反。程度副词也要考虑,“很崩溃”和“有点崩溃”强度显然不一样,我用倍数来处理,比如“非常”“极度”乘1.5倍,“有点”“稍微”乘0.7倍。
打分的最小单位是句子而不是整段文本,因为一段话里可能前半句低落、后半句出现转机,拆开算能保留更多细节。
2.3 关键词与主题轨迹:停用词过滤与高频对比
有了情感得分,你知道了“好不好”,但还不知道“在想什么”。关键词分析解决的就是这个问题。
中文和英文不同,没有天然空格分词,需要先拆词。完整引入大模型分词库会让项目复杂很多,一个更轻量的方案是用jieba这个库,它体积小、部署快,几行代码就能完成分词。如果你连第三方库都不想装,也有一个更朴素的降级方案:自己维护一个自定义词典,用正则在文本里逐个匹配。这个方法召回率不高,但做日常主题参考够用。
分词后一定要做停用词过滤。把“的”“了”“就是”“还是”“因为”这类纯语法功能的词删掉,剩下的词才是真正携带语义的。每次分析时我会输出两个榜单:一个是全时间段的关键词Top 20,另一个是本月对比上月的“新增高频词”,后者能发现议题的迁移。
3. 核心代码实现:从情感打分到趋势图表的完整脚本
3.1 完整脚本:可以直接跑起来的版本
我尽量把代码写得可以独立运行。下面这个版本做了三件事:读取日志、逐句情感打分、绘制趋势图。如果你还没有真实数据文件,脚本会自动用内置的两周示例数据演示效果。
import os import re from collections import Counter from datetime import datetime import matplotlib.pyplot as plt # ---------- 1. 配置 ---------- # 正负情感词典 positive_words = { "平静": 2.0, "轻松": 2.0, "开心": 3.0, "安心": 2.0, "释然": 3.0, "期待": 1.5, "温暖": 2.5, "恢复": 1.0, "踏实": 2.0, "愉悦": 2.5, "欣慰": 2.0, "放松": 1.5 } negative_words = { "烦躁": 2.0, "低落": 2.0, "崩溃": 4.0, "恐慌": 4.0, "无力": 2.0, "压抑": 3.0, "焦虑": 2.5, "疲惫": 1.5, "失眠": 2.0, "痛苦": 3.5, "委屈": 2.5, "麻木": 2.0 } negation_words = ["不", "没", "没有", "无", "无法", "难以", "别", "非"] adverb_scales = { "非常": 1.5, "极度": 1.5, "特别": 1.5, "很": 1.3, "有点": 0.7, "稍微": 0.7, "有些": 0.8 } stopwords = {"的", "了", "是", "我", "在", "和", "就", "都", "也", "又", "才", "但", "而", "跟", "被", "把", "让", "这", "那"} # ---------- 2. 文本预处理与句子打分 ---------- def split_sentences(text: str) -> list: """按常见标点把一段文字拆成句子列表""" parts = re.split(r"[。!?;\n]", text) return [p.strip() for p in parts if p.strip()] def score_sentence(sentence: str) -> float: """对单个句子打分,返回正负分合计""" score = 0.0 tokens = re.findall(r"[\u4e00-\u9fa5]+|[a-zA-Z]+", sentence) i = 0 while i < len(tokens): token = tokens[i] # 判断前面是否有否定词 negation = False if i > 0 and tokens[i-1] in negation_words: negation = True # 判断是否有程度副词修饰 adverb_scale = 1.0 if i > 0 and tokens[i-1] in adverb_scales: adverb_scale = adverb_scales[tokens[i-1]] if token in positive_words: base = positive_words[token] * adverb_scale score -= base if negation else base elif token in negative_words: base = negative_words[token] * adverb_scale score += base if not negation else -base i += 1 return score def score_text(text: str) -> float: """对整条日志打分:各句子分数求和""" total = 0.0 for sent in split_sentences(text): total += score_sentence(sent) return total # ---------- 3. 加载数据 ---------- def load_logs(path: str = "healing_log.txt") -> list: """读取日志文件,返回字典列表。文件不存在时返回内置演示数据""" if not os.path.exists(path): print(f"[提示] 未找到 {path},使用内置示例数据") return [ {"date": "2025-01-05", "tag": "情绪", "text": "今天睡了个好觉,醒过来觉得轻松了些。"}, {"date": "2025-01-07", "tag": "情绪", "text": "下午又开始胡思乱想,心里烦躁得坐不住。"}, {"date": "2025-01-09", "tag": "睡眠", "text": "昨晚睡得不错,很久没有这种踏实的感觉。"}, {"date": "2025-01-11", "tag": "工作", "text": "会议开完很疲惫,感觉脑子里一团浆糊。"}, {"date": "2025-01-13", "tag": "人际", "text": "和朋友聊了很久,说完之后胸口没那么堵了。"}, {"date": "2025-01-15", "tag": "情绪", "text": "傍晚散步时风吹过来,突然觉得很平静。"}, ] rows = [] with open(path, "r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue parts = line.split(",", maxsplit=2) if len(parts) < 3: continue rows.append({"date": parts[0].strip(), "tag": parts[1].strip(), "text": parts[2].strip()}) return rows # ---------- 4. 关键词提取(简化版) ---------- def extract_keywords(texts: list, topk: int = 10) -> list: """简易分词 + 停用词过滤 + 高频统计""" custom_words = list(positive_words.keys()) + list(negative_words.keys()) counter = Counter() for text in texts: # 先把词典词和中文连续片段都找出来 for w in custom_words: if w in text: counter[w] += 1 # 2字节以上的汉字片段作为候选词 for seq in re.findall(r"[\u4e00-\u9fa5]{2,6}", text): if seq not in stopwords and len(seq) >= 2: counter[seq] += 1 return counter.most_common(topk) # ---------- 5. 趋势可视化 ---------- def plot_trend(rows, scores): dates = [datetime.strptime(r["date"], "%Y-%m-%d") for r in rows] plt.figure(figsize=(10, 4)) plt.plot(dates, scores, marker="o", linestyle="-", linewidth=1.5) plt.axhline(0, color="gray", linestyle="--", linewidth=0.8) plt.title("情绪得分趋势:正值为偏积极,负值为偏消极") plt.xlabel("日期") plt.ylabel("情绪得分") plt.grid(True, alpha=0.3) # 3点移动平均线更平滑 if len(scores) >= 3: ma = [] for i in range(len(scores)): window = scores[max(0, i-1): i+2] ma.append(sum(window) / len(window)) plt.plot(dates, ma, color="orange", linewidth=2, label="3期移动平均") plt.legend() # 中文显示调整 plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei", "PingFang SC", "Noto Sans CJK SC"] plt.rcParams["axes.unicode_minus"] = False plt.tight_layout() plt.show() plt.close() # ---------- 6. 汇总主流程 ---------- def main(): rows = load_logs() if not rows: print("没有找到有效数据,请检查日志文件格式") return scores = [score_text(r["text"]) for r in rows] print("===== 日志情绪得分明细 =====") for r, s in zip(rows, scores): print(f"{r['date']} [{r['tag']}] 得分 {s:+.2f} {r['text'][:20]}") print("\n===== 关键词 Top 10 =====") for word, cnt in extract_keywords([r["text"] for r in rows], topk=10): print(f"{word}: {cnt}") plot_trend(rows, scores) if __name__ == "__main__": main()3.2 打分逻辑里那两个“小聪明”:否定取反与副词修饰
很多第一次接触情感分析的人会问:为什么句子里没有词表里的词,分数还是不对?问题通常出在否定和程度修饰上。我在脚本里处理这两类问题时用了相对轻量的方案,在跑通的基础上保证准确率。
否定取反的规则是:如果情感词的前一个词是否定词,那么情感分值就要取反。比如“不烦躁”这句,“烦躁”原本是+2.0,经过否定后变成-2.0。注意这里有个尖角,“不烦躁”是一个偏积极的状态,分数为负值是正确的,因为我的规则是“正值为偏消极,负值为偏积极”。为了统一性,正文里我主动把正向得分定义为“更平静/更积极”,负向得分定义为“更低落/更消极”——这个方向你自己定义就行,但全脚本必须保持一致。
程度副词的处理更简单,取前一个词查表乘系数。“很崩溃”的“很”是1.3倍,最终是4.0×1.3=5.2;“稍微平静了一点”里的“稍微”是0.7倍,总分2.0×0.7=1.4。这个线性乘法当然粗糙,但做趋势分析足够稳定,比不加修饰准确性提高一个档次。
3.3 从分数到结论:怎么读你的趋势图
图跑出来之后,重点看三类信息。第一是整体斜率:把情绪得分按周做平均,看趋势线是抬升还是下降。我自己的记录里经常出现“某几天得分特别低,但周平均在持续走高”的情况,这说明整体在往好的方向挪,短期波动不必恐慌。第二是波动幅度:疗愈过程中情绪反弹是正常的,模板里“3期移动平均”这条橙线就是为了削弱单日波动的干扰,让你把注意力放在中期节奏上。第三是标签分布:统计各标签的平均得分和记录频率,能定位主要情绪负担来自哪个生活领域。
4. 常见问题、避坑清单与心理边界
4.1 代码跑不起来?十有八九是字体和编码问题
这个脚本对Python版本要求不高,3.8以上都能跑,依赖只有matplotlib。最常见的两类报错都很好解决。
一是中文显示成方块。这是matplotlib默认字体不包含中文导致的,Windows上把SimHei放到最前面基本能解决,macOS用PingFang SC,Linux用Noto Sans CJK SC。如果改了还不行,可以检查系统是否安装了对应字体,或者干脆指定一个绝对存在的字体文件路径。
二是读取日志报告UnicodeDecodeError。这通常是Windows记事本默认存成了GBK编码,而脚本按UTF-8读取。解决办法是在open时加一个encoding="utf-8-sig"参数,这个变体会自动吃掉BOM头,同时兼容常见的中文编码。或者更简单:保存文件时在记事本右下角把编码改成UTF-8。
4.2 词表不够用,打分偏低怎么办
情感词典是永远填不满的。我的经验是:与其一开始就追求大词表,不如每次跑完看结果,发现有明显的情绪词没被识别,就随手加进字典。词表是在使用中越长越大的,别追求一步到位。
另外可以考虑用极简加权法来扩展:把词典词分成“核心词”和“参考词”,参考词权重减半,这样既扩大了覆盖范围,又不会让不熟悉的高权重词主导结果。比如“失望”我觉得强度中等,就给2.5;“遗憾”强度较弱,给1.0。权重的调整完全靠个人感觉,这正是自用工具的优势——不需要迎合别人的标注标准。
4.3 最重要的一条:别让代码成为新的负担
用这套系统最大的风险不是技术问题,而是心理上的过度依赖。我踩过的坑是:有段时间每天写日志、跑分析、看曲线,结果情绪反而被那根橙色的移动平均线控制住了。后来我调整了节奏,不是每天都跑,而是固定每周日复盘一次,只对比本周和上周的周均值、看新增关键词,其他时间只是记录,不分析。
另外务必记住:分数波动是一个信号,不是判决。某天得分-4.5不代表你“倒退了”,它可能只是说明你在某个议题上的情绪能量还是在释放。真正的疗愈曲线绝不是一根向上的直线,而是带锯齿、有反复的斜向上的带状区间。代码存在的意义,是让你看见这个区间的边界,而不是成为评判自己的新标准。
4.4 后续可以怎么扩展
这套脚本留了几个明显的扩展点。如果做文本分题有需求,可以在读取时增加“月份”字段,按月输出主题词差异;如果希望自动识别情绪周期,可以用滑窗标准差检测“波动增大的区间”,作为有重要议题触发的提示;如果开始对整段文本做回顾,可以增加一个“回看模式”,随机抽取三个月前的同一天记录,配合当时的得分曲线做对照。
我在实际使用中的体会是,工具的价值不在代码写完那一刻,而在你坚持记录两个月、三个月之后,回看第一周数据和最近一周数据的对比时才真正显现。那些你以为早就翻篇的情绪,其实都在曲线里留下了痕迹。这个脚本帮助你面对的不是别人,正是过去某一刻的你自己,而这一步往往是疗愈真正开始的地方。