简介:数据库-周公解梦数据集将传统梦境文化与结构化数据存储相结合,收录约7261条梦境解析记录,面向数据分析初学者、传统文化研究者及解梦类应用开发者,解决梦境释义数据零散、难以直接利用的问题。资源压缩包共4个文件,整体仅3.84MB,分别提供JSON、SQL、CSV、XLSX四种格式:JSON便于前端交互与Web服务调用,SQL适合关系型数据库查询统计,CSV兼容各类分析工具,XLSX则可直接进行排序、过滤与图表可视化。已有612人学习该资源,数据覆盖常见梦境主题与对应解释,可用于探索梦境心理学、统计分析高频梦境意象,或作为小程序、问答机器人的语料基础。无论是执行复杂SQL查询,还是快速导入Excel制作报告,都能按需选用,帮助用户快速搭建属于自己的解梦知识库。
1. 周公解梦数据集:一份能直接落库的结构化词条包
“周公解梦数据集”最实用的地方,是它把几千条“梦见XX”的文化语料整理成了可检索的结构化数据,而不是散在各处的网页文本。它通常以 SQL 脚本为主包,同时分发 JSON、CSV 两个版本,字段统一抽象为词条、类别和解梦文本三类,拿过来就能建表,也能直接被 Python 读取。我最早用它是给一个传统文化类小程序写查询接口,起初想自己爬数据,结果发现网页里的排版噪音太多,清洗成本远高于直接落库。这套数据让我跳过了最脏的一步,半天就把模糊查询接口跑通了。它适合两类人:一类是急着做解梦类小工具、公众号后端或 API 的开发者,另一类是手里缺真实中文词条语料、需要做分词和意图识别测试的研究型读者。
2. 拆表结构:词条、分类、解梦文本的三种格式映射
先别急着导数据,拿到这种打包好的数据集,第一件事永远是看“数据到底长什么样”。这套数据集的核心结构非常简单,本质就是一个三列的词典表:看到什么(keyword)、属于什么大类(category)、怎么解释(interpretation)。只要你把这三个字段想清楚,SQL、JSON、CSV 三种格式之间的映射关系就一目了然。
2.1 字段设计与三种格式的对应关系
我拿到的这个版本,核心字段基本是这样映射的:
| SQL 列名 | JSON 键名 | CSV 列头 | 类型 | 含义与样例 |
|---|---|---|---|---|
| id | id | id | INT | 自增主键,仅作排序用 |
| keyword | keyword | keyword | VARCHAR(100) | 梦象词条,如“梦见掉牙”“梦见被狗追” |
| category | category | category | VARCHAR(50) | 分类,如“动物”“人物”“自然”“器物” |
| interpretation | interpretation | interpretation | TEXT | 解梦文本,通常是一段 30~200 字的文化释义 |
有些版本还会附带一个source或sort_no字段,标注出处或排序权重,导入时可以忽略,也可以在应用层作为一个弱排序依据。要注意的是,keyword虽然叫“词条”,实际内容是短句而不是单词,和常见的英文词典数据集不一样,做 NLP 时不能按空格分词,需要走中文分词流程。
设计上我最认可的一点是它没有强行做多级分类。只有一层category,好处是后续做统计和筛选非常省事;代价是同一词条可能归属多个分类,比如“梦见蛇”既可能出现在“动物”,也可能出现在“自然”。如果你后续要用分类做筛选,最好先跑一遍分布检查,看看哪些分类有重叠,再决定要不要加一张多对多的关联表。常见做法是保持原有单分类不动,只在应用层加关键词映射,这比改数据要稳妥。解梦文本本身也保留了原始网站的口语化风格,个别条目里能看到“主吉”“主凶”这类旧式说法,作为文化语料没问题,但如果你要面向年轻用户,输出前最好把这种措辞降噪。
2.2 先跑一遍数据体检
导入之前,我习惯先用 CSV 版本做一次数据质量初检,因为 CSV 可以直接用 pandas 打开,成本最低。不管你最终要用 SQL 还是 JSON,这一步都能帮你对后面的坑提前有数。
import pandas as pd df = pd.read_csv("dream.csv", encoding="utf-8") print(df.info()) print("完全重复词条:", df["keyword"].duplicated().sum()) print("空分类条数:", df["category"].isna().sum()) print("词条长度描述:") print(df["keyword"].str.len().describe())这段代码做了四件事:用info()看总行数和各列的空值情况;用duplicated().sum()查完全重复的keyword数量;用isna().sum()查空分类;用str.len().describe()看词条长度的均值、最值和分位数。这套数据常见的检查结果是:总词条数千条左右,完全重复的一般是个位数,空分类占比不高但一定存在,词条长度集中在 2~6 字,极少数长句能到 12 字以上。
这个体检结果直接决定了后面建表时要不要加唯一索引,也决定了第 5 章避坑记录里清理的重点。如果重复词条数量上百,说明这个版本经过了多次拼接,导入时的冲突处理逻辑要预留;如果词条长度分布异常,比如出现大量 20 字以上的“词条”,那多半是原网站把整段描述塞进了标题字段,需要回到原始 CSV 再决定是否保留。经验之谈,这种免费流传的数据集,源头大多是从旧版解梦网站批量抓取后再打包的,质量参差是常态,千万别默认它干净。
2.3 三种格式怎么选:别盲目全都要
数据包同时给了三种格式,但不代表每个场景都用得上。我的选择逻辑如下:
| 消费场景 | 推荐格式 | 理由 |
|---|---|---|
| 快速试跑、数值统计 | CSV | pandas 直接读,所见即所得 |
| 后端接口、嵌入式脚本 | JSON | 结构清晰,无需处理引号和换行转义 |
| 正式服务端落库 | SQL | 保留索引和字段类型,导入即用 |
实际操作中建议以 SQL 版本为基准,因为 SQL 里的字段类型最完整。CSV 主要用来做第 2.2 节的数据体检,JSON 则直接交给应用层消费。有一点容易忽略:三种格式可能来自同一次导出,但版本不完全一致,偶尔会遇到 JSON 版比 CSV 版多了几个别名词条的情况。遇到这种差异,以词条数最多的那份为准,再单独补数据,别让下游系统各用各的文件,否则两边查询结果对不上时会非常难排查。
3. 把 SQL 数据落进 MySQL:建库、导入、验证一条线
在动手之前先回答一个我经常被问的问题:为什么用 MySQL,不用 SQLite?SQLite 当然更适合这种词典型数据,单文件、零运维、Python 内置支持。我在这里选 MySQL,是因为大多数读者的业务环境里已经跑着 MySQL 服务,解梦查询接口通常要挂在现有后端上,直接落进 MySQL 比另起一个 SQLite 文件更好维护。如果你只是本地做实验,把下面的建表语句换成 SQLite 的CREATE TABLE也完全可以,字段类型和唯一索引的逻辑不变。
落库的第一步不是执行,而是先看 SQL 文件里到底有什么。很多人拿到数据包直接source,结果遇到编码或表结构冲突,再回来改就麻烦了。
3.1 建库建表:为什么要用 utf8mb4 和唯一索引
先建库和表。如果 SQL 文件里本身带了完整的CREATE TABLE,你可以直接用文件里的;但为了后续查询顺手,我通常重新定义一遍,只保留核心字段。
CREATE DATABASE IF NOT EXISTS dream_db DEFAULT CHARSET utf8mb4; USE dream_db; DROP TABLE IF EXISTS dream; CREATE TABLE dream ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, keyword VARCHAR(100) NOT NULL COMMENT '梦象词条', category VARCHAR(50) NOT NULL DEFAULT '' COMMENT '分类', interpretation TEXT COMMENT '解梦文本', UNIQUE KEY uk_keyword (keyword) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='周公解梦词条表';这里有两个值得说明的选择。字符集必须用utf8mb4,不能用utf8,因为utf8mb4才能完整存下中文生僻字和特殊符号。解梦文本里经常出现“囍”“々”这类边缘字符,用utf8在 MySQL 5.7 及以下版本会直接报Incorrect string value,或者静默变成问号。UNIQUE KEY uk_keyword的作用是挡住重复词条,避免同一个“梦见掉牙”在库里出现多行,查询结果乱七八糟。如果你的 SQL 文件里已经建过表,且字段名和我这里不一样,最简单的方式是把keyword、category、interpretation三个字段单独SELECT出来,重建一张干净表,而不是硬改原表结构。改表永远比重建表麻烦,这是数据库操作里的铁律。
提示:如果 SQL 文件里自带建表语句且字段名不同,先
grep -i "create table" dream.sql看清表名,再决定是换库名还是改表结构。别让导进去的数据落在一个意料之外的库里。
3.2 导入命令与导入后的三句验证 SQL
建好表之后执行导入,最直接的命令是:
mysql -u root -p dream_db < dream.sql这里的前提是:dream.sql文件里要么没有CREATE DATABASE,要么它创建的库名正好是dream_db。如果文件里带了USE some_other_db;,导进去之后库就不对了,所以导入前先看一眼文件的库名声明。这个习惯能帮你避免大量莫名其妙的“表不存在”报错。
导入完成后,三句验证 SQL 基本够用:
SELECT COUNT(*) AS total FROM dream; SHOW WARNINGS; SELECT category, COUNT(*) AS cnt FROM dream GROUP BY category ORDER BY cnt DESC LIMIT 8;COUNT(*)看全表是否导入成功;SHOW WARNINGS看导入过程中的编码或截断警告,这一步最容易暴露第 5 章里的第一个坑;分类分布则是验证category字段能否聚合成有意义的类别。如果分类分布里出现一个占比特别高的“其他”或空字符串,说明原始分类本来就不均匀,后面做统计时要单独处理。顺便说一句,验证时如果发现总行数和 CSV 体检时的行数对不上,优先查是不是有重复词条被唯一索引挡掉了一部分,这是数据包版本差异最常见的结果。
3.3 按分类导出子集:INTO OUTFILE 与它的权限限制
落库之后,最常见的需求是导出一个子集给别的系统用。比如只要“动物”分类:
SELECT keyword, category, interpretation INTO OUTFILE '/tmp/dream_animal.csv' FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY '\n' FROM dream WHERE category = '动物';INTO OUTFILE是 MySQL 服务端直接写文件,所以路径是 MySQL 所在机器的路径,不是当前客户端的路径。这里容易翻车的是secure_file_priv参数:MySQL 8.0 默认只允许向配置指定目录写文件,如果你的/tmp不在白名单里,会直接报ERROR 1290 (HY000)。常见做法是先把secure_file_priv改成空字符串再重启服务,或者把导出目录挪到白名单内。如果不想动 MySQL 配置,更轻的方式是用mysql -e配合管道输出:
mysql -u root -p dream_db -e "SELECT keyword, category, interpretation FROM dream WHERE category='动物'" > animal.tsv这种导出默认是\t分隔,适合临时给人开数据,不太适合直接喂给下游系统。我一般只在应急时用,正式的导出还是要走INTO OUTFILE,因为它能精确控制引号和转义,导出的 CSV 不会被 Excel 误读。如果你需要经常导出,建议单独配一个只读账号,权限限制在SELECT,避免在业务库上直接写文件。
4. 换种姿势消费数据:用 Python 读 JSON/CSV 做模糊查询
数据库落好了,但如果只是做一个解梦查询工具,很多时候根本不需要 MySQL。几千条数据加载进内存也就几 MB,Python 直接读 JSON 或 CSV,匹配速度毫秒级,部署时不用依赖数据库服务,拿到文件就能跑。这一章讲的是怎么在文件层面把同一份数据用起来。
4.1 为什么文件版更适合轻量应用
解梦这种应用,特征是“数据量小、查询路径单一、关键词匹配为主”。它不需要复杂 SQL,也基本不存在并发写操作,用数据库管理反而多背了一个运维负担。所以我在本地原型和小型 API 场景下,更喜欢直接用 JSON 文件。CSV 适合给人看、给 Excel 打开,但处理中文长文本时容易踩到引号和换行符;JSON 的结构化程度更高,直接json.load()就能用。如果你最后的目标是部署一个轻量服务,JSON 是三种格式里最合适的消费格式。
4.2 核心匹配函数:包含匹配与排序策略
先写一个加载函数和一个查询函数:
import json def load_dream(path="dream.json"): with open(path, encoding="utf-8") as f: return json.load(f) def search_dream(keyword, data, limit=10): kw = keyword.strip() hits = [] for item in data: hay = item.get("keyword", "") if kw in hay or hay in kw: hits.append(item) hits.sort(key=lambda x: (x["keyword"] != kw, len(x["keyword"]))) return hits[:limit]if kw in hay or hay in kw这一句是模糊匹配的核心:用户输入“掉牙”能命中“梦见掉牙”,用户输入“梦见掉了一个牙”也能通过hay in kw命中完整的“梦见掉牙”。排序时,词条与输入完全相等的排最前,其次按词条长度升序,这样“梦见掉牙”会排在“梦见掉牙出血”前面,让精确结果优先展示。limit参数限制返回条数,避免长尾结果把接口响应拖慢。
这个函数在实用性上有两个明显短板。第一,它只匹配keyword字段,不匹配interpretation正文,如果用户输入的是解释里的关键词就查不到,比如解释里出现“财富”但词条里没有。第二,它对同义词无能为力,“梦见蛇”和“梦见蟒蛇”会被视作不同结果。这两点属于短文本匹配的天然边界,不是数据集的问题。要做全量语义检索,就得引入分词或向量召回,但这对解梦场景来说属于过度设计。
4.3 用别名表补同义词和近义词
处理同义词,我的做法是加一张极简别名映射表,不做分词也不做向量相似度,够用就行:
ALIAS = { "蛇": ["蟒蛇", "小青蛇", "大蛇"], "掉牙": ["掉牙齿", "牙齿掉落", "牙掉了"], } def expand_keyword(kw): keywords = {kw} for base, aliases in ALIAS.items(): if base in kw: keywords.update(a for a in aliases if a not in kw) return list(keywords)查询时先expand_keyword,把展开后的每个词都跑一遍search_dream,再做一次去重合并。这套方案的优点是零依赖、可解释性强;缺点是别名表需要人工维护。如果你手里的数据集版本足够大,也可以反过来从数据里自动抽取:把所有keyword里同时出现某个核心词的词条归成一组,做成动态别名。但那种结果会比较粗糙,需要人工再筛一遍。对于这种文化语料型数据集,我建议人工维护 20~30 条别名就够了,高频方向无非是“蛇”“狗”“掉牙”“头发”“死人”这几个,把高频词条匹配稳,比系统性解决所有语义问题更有性价比。
4.4 不想装 pandas 时的 CSV 读取姿势
如果你运行环境里没有 pandas,CSV 也可以直接用标准库读,而且能规避不少手工切分的问题:
import csv def load_dream_csv(path="dream.csv"): with open(path, encoding="utf-8-sig") as f: return list(csv.DictReader(f))这里用utf-8-sig而不是utf-8,是因为部分版本在导出时会给文件加上 BOM 头,普通utf-8会把 BOM 当成一个看不见的字符拼进第一列列名,导致item["keyword"]直接KeyError。csv.DictReader会按第一行表头自动映射字段,并且正确处理字段内嵌的引号和换行。这个函数返回的列表结构和json.load出来的基本一样,可以直接喂给search_dream。如果你准备在服务里同时支持 JSON 和 CSV 两种来源,让两个加载函数都返回同样的 list[dict] 结构,后面的查询逻辑就不用关心数据到底来自哪个文件了。
5. 避坑:导入与查询的五条踩坑记录
这一部分全是我实际跑这套数据时踩过的坑,每条都按“现象、原因、解决”的顺序写。有的是 SQL 导入阶段的,有的是查询阶段的,建议对照自查。
5.1 中文全变成问号或“锟斤拷”
现象:导入 SQL 后执行SELECT,所有中文变成“????”或“锟斤拷”,分类列完全不可读。
原因:SQL 文件本身是 UTF-8,但 MySQL 客户端的连接字符集默认不是 UTF-8。执行导入时,MySQL 按连接层字符集解读文件里的字节,UTF-8 字节被错误解码后写入,就成了乱码。这类问题在SHOW WARNINGS里通常没有明显报错,最隐蔽。
解决:导入前设置连接字符集。
mysql -u root -p --default-character-set=utf8mb4 dream_db < dream.sql或者建表后先执行SET NAMES utf8mb4;再source。如果文件带 BOM,字符集声明可能失效,文件开头三个字节会被当成内容写进第一条记录,解决方法是把文件另存为 UTF-8 无 BOM。从那以后我拿到任何 SQL 文件,第一件事都是检查文件编码和 BOM,这个习惯救过我好几次。
5.2 导入时唯一索引报错,重复词条打断流程
现象:执行source到一半报ERROR 1062 (23000): Duplicate entry,导入流程中断,不知道导进去了多少条。
原因:这个数据集在不同网站之间反复流传,同一个词条可能出现多次,或者“梦见掉牙”和“梦见掉牙 ”(带空格)被算成两个词条,加唯一索引后冲突立刻暴露。翻录次数越多的版本,重复率越高。
解决:先确认冲突规模。如果只有十几条,可以用下面这类语句跳过重复项重新导入;如果上百条,说明这份数据源拼接严重,得先清洗。
INSERT IGNORE INTO dream (keyword, category, interpretation) SELECT * FROM dream_tmp;我的习惯是先导入临时表,再去重合并到正式表,保留一份原始数据做追溯。对于日常查询场景,重复词条影响不大;但做统计时条数会虚高,所以宁可丢重复数据,也别让脏数据长期霸占主键。
5.3 词条里带全角空格,模糊查询匹配不到
现象:用户搜“梦见掉牙”查不到,但数据库里明明存在这一行。
原因:原始词条可能是“梦见 掉牙”或“梦见掉牙 ”,词条里混入了全角/半角空格。应用层输入时虽然做了strip(),但只去掉了两端,查询词与数据中间的空格位置对不上,匹配就失败了。
解决:导入前统一清洗数据中的空格:
UPDATE dream SET keyword = REPLACE(REPLACE(keyword, ' ', ''), ' ', '');两个REPLACE分别清半角空格和全角空格。查询入口也要做同样处理,否则数据是干净的,查询词还带着空格,结果还是两头落空。这个坑看似小,实则是“数据包里能用”和“接口里能查”之间最常见的断层。
5.4 interpretation 里残留 HTML 标签,接口输出脏文本
现象:SELECT interpretation看着正常,但通过 API 返回后,前端页面上出现<p>、 和网址链接。
原因:这份数据最早是从旧版解梦网页抓取的,解释文本没有做标签剥离,原样保存进了数据集。这种脏数据在 CSV 里最隐蔽,因为 Excel 打开时标签会被当成普通文本。
解决:在查询函数返回前做一次清理,用正则剥离常见标签:
import re def clean_text(s): s = re.sub(r"<[^>]+>", "", s) s = s.replace(" ", " ").replace("&", "&") return re.sub(r"\s+", " ", s).strip()注意,不要在导入时直接改interpretation的原始值,保留原始内容用于追溯,清理放到输出层。这也是我坚持数据文件不改动、只在应用层做清洗的原因。如果你准备把清洗后的数据回写数据库,建议新建一个字段如interpretation_clean,而不是覆盖原字段。
5.5 CSV 里内嵌换行,pandas 读出来的行数翻倍
现象:用pd.read_csv读dream.csv,打印info()发现行数比 SQL 里的COUNT(*)多出不少,解释文本也被拦腰截断。
原因:CSV 字段用引号包裹时,字段内部可以包含换行符,这是合法的 CSV。但某些老式清洗脚本或文本编辑器没有按引号语义切分,把字段内的换行当成了行分隔符。pandas 默认能正确处理标准 CSV 引号,但如果你先用open().readlines()再手工split(','),就会翻车。
解决:坚持用 pandas 或标准库csv.DictReader读取,不要手工切分。读入后对比行数与 SQL 总行数,正常应一致。如果行数一致但文本仍断开,通常是导出时ENCLOSED BY参数没控制好,回到第 3.3 节用INTO OUTFILE重新导出,别在人工清洗上恋战。
6. 进阶:用分类统计把数据集盘成一个轻量 API
如果只是查询词条,这个数据集的价值只发挥了一半。它还有一个值得挖的点:分类统计。把category聚合起来,就能回答“用户最常梦见什么”这类问题,这在运营侧很好用。
我习惯把查询和统计合在同一个轻量 API 里。前提是复用第 4.2 节的加载函数,然后加两个路由:
from flask import Flask, jsonify, request from collections import Counter import json app = Flask(__name__) data = load_dream("dream.json") @app.route("/api/search") def api_search(): kw = request.args.get("q", "").strip() if not kw: return jsonify({"error": "missing q"}), 400 return jsonify({"items": search_dream(kw, data)}) @app.route("/api/stats") def api_stats(): c = Counter(item.get("category", "未分类") for item in data) return jsonify(c.most_common(15))跑起来之后,用两个命令验证:
curl "http://127.0.0.1:5000/api/search?q=掉牙" curl "http://127.0.0.1:5000/api/stats"/api/search复用第 4 章的模糊匹配函数,/api/stats用Counter把全量词条按分类聚合并取前 15 个分类,接口返回可以直接接图表。这一步把数据集从一个死字典变成了带观测视角的小服务:哪些分类词条量大、哪些词条长期无人查询,都能在日志和数据里看出来。
如果你要部署到公网,记得给/api/stats加一层简单缓存,因为分类统计的结果不会经常变,没必要每次请求都全表扫一遍。几千条数据用Counter很快,但接口被频繁刷时依然浪费;用字典缓存或者放到 Redis 里,一分钟过期就够用了。
我现在的习惯是,拿到任何数据集都要强制走一遍“编码、重复、空值”三件套检查,再决定是否写业务代码。这条规矩就是从这套解梦数据上吃出来的教训——当时急着出接口,跳过检查直接导入,结果乱码和重复词条来回折腾了一个下午。后来花十分钟做预检,反而再没返过工。数据仓库、语料包、demo 数据库都一样,导入前的十分钟体检,永远是性价比最高的操作。希望帮到你。
本文还有配套的精品资源,点击获取