news 2026/10/20 20:20:44

用JSON和Python命令行构建个人技能管理工具:从数据建模到实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用JSON和Python命令行构建个人技能管理工具:从数据建模到实践

1. 为什么需要技能管理工具:需求拆解与设计取舍

上周末想更新简历,结果发现我根本说不清“现在到底会什么”。十几项技能分散在旧简历、脑图、收藏夹和一摞笔记本里,有的早已过期,有的只停留在“看过教程”的阶段,还有的写出来自己都心虚。于是我决定花一个周末做一个本地命令行工具,项目名就叫skills,专职维护个人技能清单和成长进度。

这个工具不打算做成知识管理库,也不做课程打卡,只解决三个问题:我拥有哪些技能?每项技能到了什么程度?下一步该学什么?最小可用版本就是“一个JSON文件加一个Python脚本”,但它要能支撑我未来几个月持续更新。如果你也经常被问到“你会什么”却答不上来,或者想系统规划学习路线,这篇文章的完整实现思路应该能给你不少启发。

1.1 痛点:技能信息散落各处,无法回答“我会什么”

先描述一个真实场景。多数人的技能信息至少存在四个地方:简历里写过“精通某种语言”,被追问后发现只是会写基础语法;笔记应用里收藏了几十个教程,但没记录学到哪一章节;个人网站的标签页很久没更新;项目仓库的README提到过技术栈,但已经是半年前的状态。当需要评估自己的竞争力或规划下一步时,只能靠记忆拼凑一个模糊答案。

更麻烦的是,技能之间有依赖关系。比如想学Spring Boot,应该先掌握Java基础和构建工具,可分散的记录根本不会表达这种关系。我经常跳级学习,结果效率极低。那时候我需要的不是又一个大而全的笔记应用,而是一个能记录“熟练程度”和“前置依赖”的轻量级清单,并且要足够快,让我愿意在每天结束时顺手更新几笔。

1.2 为什么不用电子表格、脑图或在线笔记

很多人第一反应是用电子表格。电子表格确实适合统计,但录入技能的体验很糟糕:新增一条要填很多列,输入熟练度时还会纠结填“高/中/低”还是数字。脑图适合展示层级和依赖关系,但每次更新都要拖动节点,而且条件查询非常费劲。在线笔记工具虽然方便分享,但打开慢、格式封闭,导出后数据很难被其他程序使用。

我最终选型是“JSON文件+Python命令行”。理由很直接:数据是纯文本,能放进版本管理工具里做每次修改的diff;命令行交互比打开应用快一个数量级;所有代码加起来不到两百行,完全可控。个人技能管理场景的数据量撑死几百条,用数据库反而小题大做。JSON作为中间格式的最大优点,是未来换任何语言都能无缝读取。

1.3 核心需求收敛:最小功能集就够了

为了避免过度设计,我只留了五个核心需求。一是新增技能时能记录名称、分类、标签、熟练度、目标熟练度、学习进度和前置依赖;二是能查看所有技能,并支持按分类和标签过滤;三是能更新技能状态,比如今天学了两小时就把进度从50改成65;四是能删除废弃技能;五是能输出技能树和统计信息,用来判断学习优先级。

其他需求全部砍掉,包括多用户、云同步、日历提醒、富文本笔记。这些功能加进来会显著拖慢开发,而且我在个人场景里根本用不到。一条最重要的原则是:工具越简单,坚持使用的概率越高。与其画一个功能满满的饼,不如先跑起一个每天愿意打开十次的命令行。

方案录入效率依赖关系表达条件查询数据迁移适合程度
电子表格中等弱中等一般偏低
脑图低强弱弱偏低
在线笔记低弱弱弱低
JSON+CLI高强高高高

2. 技能数据建模:用JSON撑起一个足够灵活的结构

确定选型之后,最关键的事就是设计数据结构。这套结构决定了工具后续能不能扩展。如果字段设计不好,每加一个功能都要改格式和数据迁移;如果设计得当,以后换语言、换界面,数据本身依然可以复用。我在动手前花了半天专门做这一步。

2.1 技能条目的字段设计

我的skills.json顶层只有两个字段:version和skills。skills是一个对象,键是技能ID,值是完整的技能条目。用对象而不是数组,是为了让查找和更新更快。一个技能条目大概长这样:

{ "version": 1, "skills": { "python-basics": { "name": "Python编程基础", "category": "编程语言", "tags": ["python", "基础"], "level": 4, "target_level": 5, "progress": 80, "status": "active", "depends_on": [], "resources": [], "note": "", "updated_at": "2025-01-15" }, "flask-web": { "name": "Flask Web开发", "category": "Web框架", "tags": ["python", "web"], "level": 3, "target_level": 4, "progress": 60, "status": "active", "depends_on": ["python-basics"], "resources": ["flask官方文档"], "note": "重点补一补RESTful设计", "updated_at": "2025-01-14" } } }

字段看起来多,但每个都有明确价值。id是机器可读的唯一标识,用短横线命名,比如python-basics。name是展示用的完整名称,随时可以改。category是大的分类,比如编程语言、Web框架、DevOps、软技能。tags数组用于跨分类筛选,比如python这个标签会同时命中Python基础和Flask开发。level和target_level记录当前熟练度和目标熟练度,progress是学习进度百分比。depends_on保存前置技能的ID列表,这是生成技能树和学习路径的关键。note字段留给临时想法,比如“记得补RESTful设计”。

2.2 熟练度分级与进度分离:为什么不用百分比表示level

熟练度我坚持用0到5的整数等级,而不是百分比。原因很简单:熟练度是等级概念,不是精确度量。我给五级分别定了一个可对照的行为锚点:0代表完全不会,1代表只听说过或看过,2代表能照着教程做,3代表能独立完成常见任务,4代表能灵活解决复杂问题,5代表能给别人讲清楚、可以输出教学级内容。打分时只要对照这些描述,几秒钟就能完成,不会因为今天心情好就乱打高分。

progress字段则是“当前学习进行到哪里”。比如我正在学Flask,基础入门部分已经完成60%,但熟练度可能只有2,因为还没做完整项目。这两个维度各有用途:level反映“我现在的能力是多少”,progress反映“我计划中的学习路径完成了多少”。分开存储后,统计模块就能回答两类问题:一类是“我的熟练度分布是否均衡”,另一类是“手头进行中的学习计划平均完成度如何”。如果只用百分比,后一类问题根本算不出来。

2.3 用id做对象键与依赖关系表达

我花了一点时间纠结skills到底用数组还是对象。数组的好处是顺序自然,但删除和改名时会很麻烦:删掉一个元素,后面所有索引都会变;按名字查找要遍历整个列表。对象的好处是key唯一,直接data['skills']['flask-web']就能定位到目标。最终我当然选了对象。

依赖关系依赖depends_on字段。这个字段里存的是前置技能的ID,而不是名字。为什么不用名字?因为名字可以改,ID才是稳定标识。比如我把“Flask Web开发”改名为“基于Flask的RESTful服务开发”,所有依赖它的技能依然能通过flask-web找到它。在输出技能树时,不是简单从上往下打印,而是先构建一个“反向依赖表”,从没有依赖的根技能开始,一层层列出依赖它的后续技能。这其实就是一个简化版拓扑遍历,但代码量很少,却解决了“先学什么后学什么”的核心问题。

2.4 数据校验:防止脏数据进入

既然数据文件用手工维护,那就一定会产生脏数据。我在load_data之后会跑一个validate_data函数,专门检查几个硬规则:level必须在0到5之间,progress必须在0到100之间,target_level不能小于level,depends_on里引用的ID必须真实存在。如果违反规则,工具会给出明确提示并拒绝保存。

这些校验一开始没有,直到有一天我把level写成了6,统计图里直接出现了一个异常柱状条。后来我意识到,个人工具虽然小,但也要在入口处设一道防线。校验逻辑不复杂,却能让未来一个月的数据都是干净的。如果确实存在循环依赖,比如A依赖B、B依赖A,tree命令也能通过visited集合避免无限递归。

3. 从零实现一个skills命令行工具

现在开始最令人兴奋的部分:动手写代码。整个项目只有两个文件:一个Python脚本skills.py,一个数据目录。不需要第三方依赖,只用Python标准库里的argparse、json、datetime和pathlib。只要机器上有Python 3.8以上就能跑。下面完整展示实现思路和关键代码。

3.1 项目结构与存储初始化

项目目录保持极简:

skills/ skills.py data/ skills.json backup/

data/skills.json在首次运行时自动创建,不需要手动编辑。我在skills.py里定义两个辅助函数load_data和save_data。load_data会检查文件是否存在,不存在就返回初始结构;save_data会在写入前自动把现有skills.json复制成带时间戳的备份文件。这个设计后来真的救了我,第4部分会详细说。

初始化相关代码是这样的:

import json, argparse, datetime, re, sys from pathlib import Path BASE_DIR = Path(__file__).resolve().parent DATA_DIR = BASE_DIR / 'data' SKILLS_FILE = DATA_DIR / 'skills.json' BACKUP_DIR = DATA_DIR / 'backup' def init_storage(): DATA_DIR.mkdir(exist_ok=True) BACKUP_DIR.mkdir(exist_ok=True) if not SKILLS_FILE.exists(): save_data({'version': 1, 'skills': {}}) def load_data(): if not SKILLS_FILE.exists(): init_storage() with open(SKILLS_FILE, 'r', encoding='utf-8') as f: return json.load(f) def save_data(data): init_storage() ts = datetime.datetime.now().strftime('%Y%m%d_%H%M%S') backup_name = BACKUP_DIR / f'skills_{ts}.json' if SKILLS_FILE.exists(): with open(SKILLS_FILE, 'r', encoding='utf-8') as src: with open(backup_name, 'w', encoding='utf-8') as dst: dst.write(src.read()) with open(SKILLS_FILE, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=2)

save_data每次写入都先备份当前文件,虽然会留下许多备份文件,但个人使用完全无所谓。这段代码的价值在于:万一改坏了数据,只要把backup目录里最近一份文件拷贝回来,就立刻恢复。相信我,这个习惯能避免很多崩溃时刻。

3.2 核心命令:add、list、update、rm

命令解析用argparse,标准库自带,能自动生成帮助信息。main函数里定义四个子命令,并在调用前加参数校验。先看add命令的实现:

def make_id(text): return re.sub(r'[^a-z0-9]+', '-', text.strip().lower()).strip('-') def normalize_tags(tag_str): if not tag_str: return [] tags = [] for t in tag_str.split(','): t = t.strip().lower() if t: tags.append(TAG_ALIASES.get(t, t)) return tags def cmd_add(args): data = load_data() skill_id = args.id or make_id(args.name) if not skill_id: print('错误:无法根据名称生成id,请用--id参数指定') return if skill_id in data['skills']: print(f'技能已存在: {skill_id},请用update命令修改') return data['skills'][skill_id] = { 'name': args.name, 'category': args.category or '未分类', 'tags': normalize_tags(args.tags), 'level': int(args.level), 'target_level': int(args.target_level) if args.target_level else 5, 'progress': int(args.progress) if args.progress else 0, 'status': 'active', 'depends_on': [x.strip() for x in args.depends_on.split(',') if x.strip()] if args.depends_on else [], 'note': args.note or '', 'updated_at': datetime.date.today().isoformat() } save_data(data) print(f'已添加技能: {args.name}')

make_id函数会把英文名转成短横线形式,如果是纯中文名则可能生成空字符串,所以我允许用户用--id手动指定。中文技能的id建议用英文或拼音缩写,比如“Flask Web开发”的id是flask-web,这样在命令行里不会出现编码问题。

使用方法示例:

python skills.py add \ --name "Flask Web开发" \ --id flask-web \ --category "Web框架" \ --tags python,web \ --level 3 \ --target 4 \ --progress 60 \ --depends-on python-basics

list命令默认展示全部技能,支持按分类、标签、状态过滤,并按level从高到低排序。输出用表格形式,但不用第三方库,直接做字符串格式化。代码核心就一句列表推导加格式化输出,这里不展开。update命令支持部分字段更新,用户传哪个字段就改哪个字段。rm命令按ID删除,但删除前会检查是否有其他技能依赖它,如果有,会给出警告并要求输入--force确认。

3.3 进阶能力:搜索、统计与依赖树输出

工具做到这里已经能用,但“好用”是从加了search和stats开始的。search命令在name、category、tags、note四个字段里做不区分大小写的子串匹配,结果按level排序。stats命令会输出总技能数、分类分布、熟练度分布、进行中学习项和平均学习进度。

stats的实际输出大概长这样:

技能总数: 23 分类分布: 编程语言: 8 Web框架: 5 DevOps: 4 软技能: 6 熟练度分布: level 0: 1 level 1: 3 level 2: 5 level 3: 8 level 4: 4 level 5: 2 进行中的学习项: 5 平均学习进度: 47%

我再加了一个很实用的判断:如果某个技能level小于3,且它依赖的前置技能里有任何一项level也小于3,就把这个技能标记为“前置未达标”。在训练自己的学习规划时,这个标记帮我及时发现跳级问题。比如我发现“RESTful API设计”被标成前置未达标,原因就是它的前置技能“Flask Web开发”只有level 2。于是我把学习顺序调整到先巩固Flask基础,再深入RESTful设计。

tree命令则生成Markdown格式的技能树。核心思路是先构建反向依赖表,从没有任何前置技能的根节点开始递归输出。代码里用visited集合防止循环依赖死循环。输出结果可以直接贴进个人网站或周报:

- Python编程基础 (level 4, 进度 80%) - Flask Web开发 (level 3, 进度 60%) - RESTful API设计 (level 2, 进度 20%) - 数据分析基础 (level 3, 进度 50%)

有了这个输出,每周复盘就变成了一次命令调用。我通常会在周五跑一遍skills tree,然后结合stats决定下周重点补什么。

3.4 参数校验、错误处理与效率技巧

参数校验在命令行工具里很容易被忽略,但我吃过亏。为了让level不能超过5,progress不能超过100,我写了一个简单的validate_args函数。如果校验失败,立即打印错误并退出,不会把坏数据写进JSON。

def validate_skill_args(level, target_level, progress): level = int(level) target_level = int(target_level) progress = int(progress) if not (0 <= level <= 5): sys.exit('level必须在0到5之间') if target_level < level: sys.exit('目标熟练度不能低于当前熟练度') if not (0 <= progress <= 100): sys.exit('progress必须在0到100之间') return level, target_level, progress

使用上也有一个提高效率的小技巧:为常用命令设置shell alias。比如在.zshrc里加:

alias skills='python3 ~/tools/skills/skills.py' alias skills_add='python3 ~/tools/skills/skills.py add' alias skills_tree='python3 ~/tools/skills/skills.py tree'

这样我每天更新学习进度时,只需要敲skills update --id flask-web --progress 65,一个命令十秒钟就完成。工具的价值在于“它是你愿意用的工具”,而不是“功能最多的工具”。

4. 真实使用中的翻车记录与排查思路

工具写出来不难,难的是连续使用三个月后还能保持数据可靠。这一段我整理了自己踩过的五个真实坑,每一个都值得后来者注意。我会把问题和解决过程写清楚,方便你直接避坑。

4.1 数据文件被误删之后:备份策略是必需品

有一次我重命名了data目录,脚本还在引用旧路径,save_data直接新建了一个空的skills.json。等我发现时,原来的技能清单已经变成版本1的空文件。还好save_data每次写入前都会备份到backup目录,我通过最近一份备份恢复,整个过程不到两分钟。从那以后,我把“任何修改前必须备份”这句话刻在了脑子里。

个人工具的容灾设计很容易被忽略,因为你觉得“只有我一个人用,不会出问题”。实际上,一个rm -rf、一次路径写错,就足以让几个月的数据瞬间蒸发。我的方案是双保险:脚本自动做时间戳备份,同时整个data目录放进版本管理工具,每次修改后手动提交一次。

4.2 标签大小写和同义词混乱

tags字段用了几天就开始失控。同一门技术会在不同时间被写成Python、python和PYTHON;同义词也乱七八糟,web、website、后端、DevOps混在一起。后来我在normalize_tags函数里统一做小写、去空格处理,并维护一个标准标签别名表:

TAG_ALIASES = { 'website': 'web', '后端': 'back-end', 'devops': 'devops', }

add和update写入tags前都会先过一遍别名映射。这个改造让按标签筛选变得可靠。数据治理不只是在数据库设计阶段考虑,个人工具同样需要。输入天然是混乱的,最好在入口处就做好规范,而不是事后清洗。

4.3 熟练度标准漂移:从“熟练”到“精通”的口径统一

初版工具的熟练度定义写在代码注释里,但实际操作时总会忘记“3级到底是什么水平”。有一阵我信心爆棚,给某项技能打了4分,过一周再看发现连基础原理都说不清,又改回3分。这种前后不一致会直接误导学习计划。

我的解法是把熟练度标准单独放在项目根目录的SKILL_LEVEL_GUIDE.md里,每次打分前扫一眼。现在我的标准是:能独立完成常见任务就是3级,能灵活应对复杂问题才是4级,能整理成教程教会别人才是5级。把标准写下来,是控制主观漂移最有效的手段。工具只能帮我们记录数据,定义好尺子的人始终是我们自己。

4.4 多设备同步冲突的本地解法

skills没有云同步,但我希望在公司电脑和家里电脑之间保持一致。最初我把skills.json放进同步盘,结果两台电脑同时修改时产生了冲突副本,搞得数据出现分叉。后来改成“单写主设备”策略:所有更新都在一台设备上完成,再通过同步盘把最新版本推送到其他设备。这个限制对个人项目完全可接受。

如果你确实需要多端修改,也可以给每个操作打时间戳,同步时按“后改获胜”规则合并。但合并逻辑会变得复杂,不建议在最小工具里实现。对我来说,单写主设备加时间戳备份已经足够。

4.5 常见问题速查表

最后把高频问题整理成一张速查表,方便对照解决:

问题可能原因解决方案
技能重复出现name或id不符合预期用list查看id,用rm删除后重新add
标签筛选不准标签大小写、别名不统一检查normalize_tags和TAG_ALIASES配置
level数字越界手动编辑JSON时写错运行stats看分布,手动改回0到5区间
progress累计超过100频繁update且不检查原值在update里增加上限校验
tree命令出现死循环存在循环依赖检查depends_on字段,删除形成环的引用
backup目录越来越大每次保存都生成备份写一个小脚本,保留最近30份即可

这套工具用到现在已经进入第四个月,唯一一次数据回滚就是因为备份机制,其余问题基本都靠入口校验解决了。如果你也要做类似的项目,我的建议是:从最简单的add和list开始,不要一上来就做花哨的网页和图表。技能管理的本质不是“记录工具有多强”,而是“你能多频繁地掏出它更新一行状态”。数据结构和备份策略看起来不起眼,却比任何炫酷功能更值得用心设计。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 20:16:43

Composer predictions 深度解析:AI代码预测如何提升开发效率

1. 这个功能到底解决了什么问题写代码的人大概都有过这种体验&#xff1a;脑子里已经想好了下一步要写什么&#xff0c;手指还没跟上&#xff0c;编辑器就已经把整行补全了。这种“心有灵犀”的感觉&#xff0c;就是代码补全工具一直在追求的目标。而这次面向所有 Pro 用户开放…

作者头像 李华
网站建设 2026/10/20 20:16:41

用宏智树AI把课程论文从凑字变高分的完整流程

说实话&#xff0c;看到“课程论文”这四个字&#xff0c;我脑子里立刻浮现出自己当年对着Word文档发呆、把一句话反复换着花样说、就为了凑够那几千字要求的画面。那时候要是有人告诉我&#xff0c;AI能把这事儿变成一套标准化流程&#xff0c;我绝对会以为他在开玩笑。但现在…

作者头像 李华
网站建设 2026/10/20 20:16:37

Transformer异构加速实战:算子融合与异步调度优化

长期以来&#xff0c;Transformer 结构的计算性能都是各大加速平台优化团队的重点攻坚对象。这个架构里的算子虽然看起来不复杂&#xff0c;但放到异构计算处理器上跑&#xff0c;出来的性能往往跟理论上限差得远。我自己在调试各类 AI 加速芯片上的推理引擎时&#xff0c;就反…

作者头像 李华
网站建设 2026/10/20 20:10:19

Hive实现树形结构展开:三种方案对比与工程实践

1. 树形结构为什么要展开&#xff1a;从组织架构到评论盖楼的实际场景很多数仓同学第一次看到“hive实现树形结构展开”这个需求时&#xff0c;心里想的都是&#xff1a;这不就是递归查询吗&#xff1f;但真正动手就会发现&#xff0c;Hive 在这方面比常见的关系型数据库麻烦得…

作者头像 李华
网站建设 2026/10/20 20:09:47

C# TcpClient 发送接收实战:粘包处理、心跳检测与断线重连

简介&#xff1a;一份面向C#初学者的TCP客户端通信示例资源&#xff0c;围绕System.Net.Sockets命名空间下的TcpClient类&#xff0c;演示从创建连接、发送数据到接收响应、关闭连接的完整流程。适合正在学习.NET网络编程、希望掌握同步与异步Socket通信基础的开发者参考。资源…

作者头像 李华