用Python+Flask+SQLite从零搭建个人知识库系统
发布时间:2026/8/30 17:46:18 作者:尧图编辑部 阅读量:1,286

刚整理完手头积压的工作笔记突然发现知识都散在各处微信收藏里躺着一堆技术文章本地文件夹里有几个 Markdown 文件浏览器书签存了不少教程链接还有几个文档躺在网盘里吃灰。真到需要复用的时候往往要翻半天才能找到自己想要的那段内容。干脆自己动手做一个轻量级的个人知识库系统把散落的知识集中管理起来。这篇文章会从一个纯小白的角度完整演示如何用 Python Flask SQLite 搭建自己的个人知识库系统。你不需要有很强的编程基础只要跟着步骤走一步一步操作大约十分钟就能跑起来一个具备新增、搜索、查看、删除功能的知识库系统并且能理解每个环节背后的设计思路。这个项目做完之后你掌握的不仅是“复制粘贴能跑”的代码还会理解数据库表结构怎么设计、后端路由怎么写、前端页面怎么渲染数据以及后续怎么扩展成能够接入大模型的智能知识库。无论你是刚入门编程还是想快速搭建一个内部工具这套方案都足够直接。1. 个人知识库系统是什么为什么要自己动手搭建1.1 知识库系统到底解决什么问题个人知识库系统简单来说就是给自己搭建一个集中存储、管理和检索知识的平台。它可以记录技术笔记、项目文档、读书笔记、会议纪要、代码片段、问题排查记录等各种内容。很多人一边用微信收藏一边用备忘录一边用网盘知识分散在不同平台里搜索起来非常痛苦。时间一长就会陷入“明明收藏过却找不到”的困境。个人知识库系统要解决的核心问题有三个存储统一、检索高效、沉淀复用。存储统一指的是所有知识条目都放进同一个库里不用再在不同平台之间切换检索高效指的是可以通过关键词快速定位到想要的笔记甚至后续可以升级成全文本检索、语义检索沉淀复用指的是把零散的信息整理成结构化的知识后续写文章、查资料、做项目的时候能够直接调取。从技术角度看个人知识库系统并不需要很复杂。单用户场景下数据量通常只有几万条甚至几千条一台普通电脑就能轻松承载。这也是为什么我们可以用轻量级方案来搭而不必一上来就引入重型的知识库平台。1.2 自己搭建和现成笔记软件有什么不同市面上的知识管理工具很多Notion、Obsidian、语雀、飞书文档都可以用来管理个人知识为什么还要自己动手“手搓”一个这里有一个关键区别现成的笔记软件解决的是“笔记编辑和排版”的问题而程序员的个人知识库系统往往还要解决“自动化、可定制、可扩展、数据私有化”的问题。自己搭建的系统表结构由自己定义功能由自己开发数据完全存放在自己的机器上隐私可控后续想加自动分类、全文搜索、AI 问答这些功能也都是在自己的代码上进行扩展不会被平台的规则限制死。自己动手搭建的过程本身也是一次很好的技术练习。你会接触到后端 Web 框架、数据库设计、前端页面渲染、接口调试等基础知识。哪怕这个知识库系统最终只用了几天中间学到的技术也完全值回投入的时间。2. 技术选型为什么是 Python Flask SQLite2.1 适合小白的轻量方案个人知识库系统可以有很多技术实现方案但如果你想要一个“安装简单、代码量少、容易理解、后续扩展空间大”的方案Python Flask SQLite 是很合适的选择。先看 Python。Python 语法简单适合快速开发生态丰富初学者也能很快上手。再看 Flask。Flask 是一个轻量级的 Python Web 框架核心功能简洁不做过度的封装一个文件就能写一个 Web 应用对理解 Web 请求处理流程非常有帮助。再用 SQLite 做数据库。SQLite 是 Python 内置的轻量级关系型数据库不需要单独安装数据库服务数据保存在一个本地文件里零配置文件对单用户个人知识库来说完全够用。这个组合还有一个好处依赖极少。整个项目只需要安装 Flask 一个第三方库其他全是 Python 标准库提供的功能大大降低了环境配置失败的几率。2.2 本文的功能范围为了让教程足够聚焦我们的个人知识库系统规划以下核心功能新增知识条目支持标题、正文内容和标签。知识列表在首页展示所有已保存的知识条目。关键词搜索通过标题、内容、标签进行模糊搜索。详情页点击标题查看单条知识的完整内容。删除功能删除不需要的知识条目。这些功能已经能构成一个完整可用的最小知识库系统。后续如果要升级成智能知识库比如接入全文检索、标签面板、AI 问答都可以在这套代码的基础上扩展。2.3 项目目录结构先整体看一下项目目录规划这样后面写代码的时候心里会更有数。my_knowledge_base/ ├── app.py # Flask 主程序负责路由和业务逻辑 ├── init_db.py # 数据库初始化脚本创建数据表 ├── requirements.txt # 项目依赖文件 ├── knowledge.db # SQLite 数据库文件首次初始化后自动生成 └── templates/ ├── index.html # 首页模板展示列表、搜索框、新增表单 └── note.html # 详情页模板展示单条知识内容app.py是后端入口处理浏览器发过来的请求init_db.py负责初始化数据库templates目录存放 Jinja2 模板文件Flask 会自动从这个目录加载页面模板。3. 环境准备与项目初始化3.1 安装 Python 与创建虚拟环境首先确认你的电脑上已经安装了 Python。如果没有安装去 Python 官网下载对应系统的安装包安装过程中记得勾选Add Python to PATH。建议使用 Python 3.8 及以上版本具体版本号不强制新版 Python 对 Flask 和 SQLite 的支持都没有问题。安装好 Python 之后我们在命令行里创建一个项目文件夹并进入该目录mkdir my_knowledge_base cd my_knowledge_base接着创建虚拟环境。虚拟环境可以为当前项目单独隔离一套 Python 依赖环境避免不同项目之间依赖冲突。Windows 系统执行python -m venv venv venv\Scripts\activatemacOS / Linux 系统执行python3 -m venv venv source venv/bin/activate激活成功后命令行前面会出现(venv)标记说明当前已经进入项目的独立 Python 环境。3.2 安装 Flask在虚拟环境中安装 Flaskpip install flask也可以先把依赖写入requirements.txt文件再用pip install -r requirements.txt安装。requirements.txt内容如下flask安装完成后可以用下面命令确认 Flask 是否安装成功python -c import flask; print(flask.__version__)如果输出一个版本号说明安装正常。版本号可能会因为安装时间不同而不同不影响本文代码运行。3.3 创建项目文件在项目目录下创建templates子目录mkdir templates到这里目录结构已经建立好接下来先从数据库设计开始写代码。4. 数据库设计与初始化4.1 数据表设计思路数据库是知识库系统的核心底座。知识库系统最核心的数据实体是“知识条目”所以设计一张notes表就足够了。这张表需要记录哪些字段先想一下知识条目的使用场景每条知识必须有一个标题方便识别和列表展示。正文内容是知识的主体需要完整保存。标签用于分类和检索标签之间的分隔符可以约定为英文逗号。创建时间记录这条知识是什么时候入库的方便后续排序和回顾。一个自增主键id用于唯一标识每条知识。对应的建表 SQL 语句如下。为了让created_at字段在插入数据时自动填充当前时间这里使用了DEFAULT (datetime(now, localtime))。CREATE TABLE IF NOT EXISTS notes ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, content TEXT NOT NULL, tags TEXT DEFAULT , created_at TEXT NOT NULL DEFAULT (datetime(now, localtime)) );这里几个字段类型需要说明一下INTEGER PRIMARY KEY AUTOINCREMENT表示自增主键每次插入新记录时自动加一。TEXT类型适合存储文本内容SQLite 对 TEXT 类型的存储长度没有严格限制。NOT NULL约束保证标题和内容不能为空这是业务层面的基本要求。DEFAULT指定默认值。4.2 编写数据库初始化脚本在项目根目录创建init_db.py文件写入以下代码# 文件路径init_db.py import sqlite3 import os DB_PATH os.path.join(os.path.dirname(__file__), knowledge.db) def init_db(): conn sqlite3.connect(DB_PATH) cursor conn.cursor() cursor.execute( CREATE TABLE IF NOT EXISTS notes ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, content TEXT NOT NULL, tags TEXT DEFAULT , created_at TEXT NOT NULL DEFAULT (datetime(now, localtime)) ) ) conn.commit() conn.close() if __name__ __main__: init_db() print(数据库初始化完成文件位置, DB_PATH)这段代码的核心逻辑很简单先连接 SQLite 数据库文件如果knowledge.db不存在会自动创建然后执行 CREATE TABLE 语句创建notes表最后提交事务并关闭连接。整个脚本可以重复执行因为IF NOT EXISTS保证了表已经存在时不会重复创建避免报错。4.3 执行初始化在项目根目录执行python init_db.py运行成功后项目目录下会生成一个knowledge.db文件。这个文件就是整个知识库系统的数据存储文件后续所有知识条目都会写在里面。想备份知识库直接复制这个文件就可以了。5. 核心功能实现5.1 首页展示全部知识条目现在开始写 Flask 主程序。首先需要一个路由函数处理首页请求从数据库读取全部知识条目并传递给模板渲染。数据库连接操作比较高频所以封装一个get_db()函数每个请求处理时获取连接请求处理结束后关闭连接。这里有一个细节值得注意设置conn.row_factory sqlite3.Row这样查询返回的每一行都可以像字典一样用字段名访问比如row[title]比用数字下标访问更清晰。首页路由处理逻辑如下app.route(/) def index(): q request.args.get(q, ).strip() conn get_db() if q: cursor conn.execute( SELECT id, title, tags, created_at FROM notes WHERE title LIKE ? OR content LIKE ? OR tags LIKE ? ORDER BY id DESC , (f%{q}%, f%{q}%, f%{q}%)) else: cursor conn.execute( SELECT id, title, tags, created_at FROM notes ORDER BY id DESC ) notes cursor.fetchall() conn.close() return render_template(index.html, notesnotes, qq)首页同时承担了搜索功能如果 URL 中带有q参数就按关键词模糊查询否则查询全部。f%{q}%是 SQL 模糊匹配语法表示包含该关键词的内容都会被匹配到。这里必须提醒一个非常重要的数据库操作习惯查询条件中的值通过?占位符传入而不是直接拼接 SQL 字符串。这样做可以有效防止 SQL 注入攻击。即使是个人使用的知识库系统也应该从一开始就养成参数化查询的习惯。5.2 新增知识条目新增知识条目的数据来源是前端表单的 POST 请求。路由函数需要从request.form中获取标题、内容、标签三个字段校验非空后写入数据库。app.route(/add, methods[POST]) def add_note(): title request.form.get(title, ).strip() content request.form.get(content, ).strip() tags request.form.get(tags, ).strip() if not title or not content: return 标题和内容不能为空, 400 conn get_db() conn.execute( INSERT INTO notes (title, content, tags) VALUES (?, ?, ?), (title, content, tags) ) conn.commit() conn.close() return redirect(url_for(index))其中.strip()用于去掉字符串首尾空白字符避免用户不小心输入多余空格。如果标题和内容为空返回 400 状态码提示参数错误。插入完成后页面重定向到首页让新添加的知识条目立即显示在列表中。5.3 详情页与删除功能详情页根据note_id查询单条知识并展示app.route(/note/int:note_id) def note_detail(note_id): conn get_db() cursor conn.execute(SELECT * FROM notes WHERE id ?, (note_id,)) note cursor.fetchone() conn.close() if note is None: return 知识条目不存在, 404 return render_template(note.html, notenote)删除功能的实现要注意风险控制。删除操作不可逆所以在路由中只允许 POST 请求触发删除同时前端加一个二次确认按钮防止误点。app.route(/delete/int:note_id, methods[POST]) def delete_note(note_id): conn get_db() conn.execute(DELETE FROM notes WHERE id ?, (note_id,)) conn.commit() conn.close() return redirect(url_for(index))实际生产项目中删除操作还需要考虑权限校验、操作审计、软删除等安全措施这里作为个人知识库的 MVP 版本先演示核心流程。5.4 后端主程序汇总把上述路由函数整合到app.py中代码如下# 文件路径app.py import sqlite3 import os from flask import Flask, render_template, request, redirect, url_for BASE_DIR os.path.dirname(__file__) DB_PATH os.path.join(BASE_DIR, knowledge.db) app Flask(__name__) def get_db(): conn sqlite3.connect(DB_PATH) conn.row_factory sqlite3.Row return conn app.route(/) def index(): q request.args.get(q, ).strip() conn get_db() if q: cursor conn.execute( SELECT id, title, tags, created_at FROM notes WHERE title LIKE ? OR content LIKE ? OR tags LIKE ? ORDER BY id DESC , (f%{q}%, f%{q}%, f%{q}%)) else: cursor conn.execute( SELECT id, title, tags, created_at FROM notes ORDER BY id DESC ) notes cursor.fetchall() conn.close() return render_template(index.html, notesnotes, qq) app.route(/note/int:note_id) def note_detail(note_id): conn get_db() cursor conn.execute(SELECT * FROM notes WHERE id ?, (note_id,)) note cursor.fetchone() conn.close() if note is None: return 知识条目不存在, 404 return render_template(note.html, notenote) app.route(/add, methods[POST]) def add_note(): title request.form.get(title, ).strip() content request.form.get(content, ).strip() tags request.form.get(tags, ).strip() if not title or not content: return 标题和内容不能为空, 400 conn get_db() conn.execute( INSERT INTO notes (title, content, tags) VALUES (?, ?, ?), (title, content, tags) ) conn.commit() conn.close() return redirect(url_for(index)) app.route(/delete/int:note_id, methods[POST]) def delete_note(note_id): conn get_db() conn.execute(DELETE FROM notes WHERE id ?, (note_id,)) conn.commit() conn.close() return redirect(url_for(index)) if __name__ __main__: app.run(debugTrue, host127.0.0.1, port5000)这段代码已经覆盖了知识库系统的全部后端接口。debugTrue方便本地调试代码改动后服务会自动重载也能显示更详细的报错信息。真正部署到公网环境时必须关闭 debug 模式否则会带来严重的安全风险。6. 前端页面编写6.1 首页模板Flask 默认使用 Jinja2 模板引擎在templates/index.html中我们可以用{% for %}循环渲染知识列表用{{ }}输出变量值。页面需要包含几个区块标题区域、搜索框、新增知识表单、知识列表、删除按钮。这里用一个简洁的页面把功能串起来!-- 文件路径templates/index.html -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 title个人知识库系统/title style body { font-family: PingFang SC, Microsoft YaHei, sans-serif; max-width: 900px; margin: 40px auto; padding: 0 20px; color: #333; } h1 { color: #1a73e8; } .card { border: 1px solid #e0e0e0; border-radius: 8px; padding: 16px; margin-bottom: 16px; } .card h3 { margin: 0 0 8px; } .tags { color: #1a73e8; font-size: 14px; } .time { color: #999; font-size: 12px; } form { margin: 20px 0; } input, textarea { width: 100%; padding: 8px; margin-bottom: 12px; border: 1px solid #ddd; border-radius: 4px; } button { background: #1a73e8; color: #fff; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; } button:hover { background: #1762c8; } .search-box form { display: flex; gap: 8px; } .search-box input { margin-bottom: 0; } /style /head body h1个人知识库系统/h1 div classsearch-box form action/ methodget input typetext nameq placeholder搜索标题、内容或标签 value{{ q }} button typesubmit搜索/button /form /div h2新增知识条目/h2 form action/add methodpost input typetext nametitle placeholder标题 required textarea namecontent rows5 placeholder内容 required/textarea input typetext nametags placeholder标签用逗号分隔例如Python,后端,笔记 button typesubmit保存/button /form h2知识条目列表/h2 {% if notes %} {% for note in notes %} div classcard h3a href/note/{{ note[id] }}{{ note[title] }}/a/h3 div classtags标签{{ note[tags] or 未设置 }}/div div classtime创建时间{{ note[created_at] }}/div form action/delete/{{ note[id] }} methodpost onsubmitreturn confirm(确定删除该知识条目吗); button typesubmit stylebackground: #d9534f; padding: 6px 12px; font-size: 14px;删除/button /form /div {% endfor %} {% else %} p还没有知识条目赶紧写上第一条吧。/p {% endif %} /body /html这个页面的逻辑很清楚搜索表单用 GET 方法提交关键词会出现在 URL 参数q中新增表单用 POST 方法提交到/add每条知识卡片里的删除按钮同样用 POST 方法提交到对应 id 的删除接口并带有确认提示弹窗。6.2 详情页模板详情页展示单条知识的完整内容并提供一个返回列表的链接。为了让正文中的换行能正常显示代码里按换行符拆分内容再逐行渲染!-- 文件路径templates/note.html -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 title{{ note[title] }}/title style body { font-family: PingFang SC, Microsoft YaHei, sans-serif; max-width: 900px; margin: 40px auto; padding: 0 20px; color: #333; } .meta { color: #999; font-size: 14px; margin: 12px 0; } .content { line-height: 1.8; } a { color: #1a73e8; } /style /head body a href/返回列表/a h1{{ note[title] }}/h1 div classmeta标签{{ note[tags] or 未设置 }} | 创建时间{{ note[created_at] }}/div div classcontent {% for line in note[content].split(\n) %} p{{ line }}/p {% endfor %} /div /body /html需要注意Jinja2 模板默认会自动对变量进行 HTML 转义也就是说即使知识内容中包含了script这样的字符串也只会被当作普通文本展示不会被浏览器执行。这个默认机制有效防止了存储型 XSS 攻击个人系统也不能忽略这一点。6.3 页面样式的设计思路页面样式没有引入任何前端框架直接写了一点内联 CSS。这样做的目的是降低依赖让整个项目保持简洁。后续如果界面要做成卡片式知识库、添加侧边栏分类、支持 Markdown 渲染可以再引入前端组件库或 Markdown 解析库不会影响现有代码结构。7. 运行与验证7.1 启动服务在项目根目录执行python app.py看到类似下面的日志输出说明服务启动成功* Running on http://127.0.0.1:5000打开浏览器访问http://127.0.0.1:5000就能看到个人知识库系统的首页。7.2 功能验证流程推荐按下面的顺序做一轮完整测试在“新增知识条目”表单中输入标题、内容和标签点击保存。首页列表中应立即出现刚才新增的知识条目。点击知识标题进入详情页确认内容展示正常。返回首页在搜索框输入一个关键词确认能搜索到对应条目。点击某条知识卡片上的删除按钮在弹出的确认框中点击确定确认该条知识被删除。在搜索框输入一个不存在的关键词确认页面显示“还没有知识条目”的提示。7.3 启动报错排查问题现象常见原因解决思路ModuleNotFoundError: No module named flask没有在虚拟环境中安装 Flask执行pip install flask并确认虚拟环境已激活Address already in use5000 端口被占用修改app.run(port5001)或者关闭占用端口的进程页面提示TemplateNotFound模板文件不在templates目录检查项目目录结构确保index.html和note.html都放在templates文件夹中Failed to open database file数据库路径不对确认DB_PATH指向项目根目录下的knowledge.db文件中文乱码终端编码问题Windows 终端可以执行chcp 65001切换为 UTF-8 编码8. 进阶方向从轻量知识库走向智能知识库8.1 全文检索升级当前系统使用的是 SQLite 的LIKE模糊查询。数据量只有几百条时性能完全够用但知识条目增长到几千条甚至几万条时LIKE查询会逐渐变慢而且不支持中文分词搜索体验有限。SQLite 自带的 FTS5 全文检索扩展可以解决这个问题。FTS5 在 SQLite 中创建一个全文索引表通过分词和倒排索引实现快速全文搜索。对中文场景建议使用 FTS5 提供的unicode61tokenizer或者配合simpletokenizer 做基础切分。实际效果还需要根据你的数据样本进行测试因为 SQLite 版本不同FTS5 的可用性也可能不同。8.2 标签与分类体系目前标签只是简单的字符串字段用逗号分隔。后续可以增加独立的标签表以及知识条目和标签的关联表形成多对多的关系结构。这样就能实现标签面板、标签筛选、标签统计等功能。更进一步还可以支持多级分类把知识条目组织成树形结构让知识库更接近成熟产品的使用体验。8.3 接入大模型和 RAG当知识库中的内容增多之后很多人希望实现“直接用对话问知识库”的效果。这就涉及到 RAG检索增强生成技术。RAG 的基本思路是先把知识内容做向量化把文本转换成向量表示存入向量数据库用户提问时将问题向量化在向量库中检索最相关的知识片段最后把检索到的知识片段和用户问题一起提交给大模型由大模型结合知识片段生成回答。实现 RAG 的常见方案有两种。一种是自己编写一套流程嵌入诸如 Sentence Transformers、Faiss、Chroma 这样的工具库另一种是直接使用现成的大模型知识库平台比如 Dify、FastGPT、RAGFlow 等把本项目的桌面端知识库系统与这些平台对接。后者在上手难度上更友好适合想要快速落地智能问答的场景。8.4 多用户与权限管理个人知识库系统默认是单机、单用户使用的。如果需要部署到团队内部让多人同时使用就需要考虑用户登录、权限控制、操作审计等安全机制。Flask 生态中有 Flask-Login、Flask-Security 等扩展可以辅助实现认证授权数据库层面还需要增加用户表和权限表在每次读写操作时校验当前用户的访问范围。9. 最佳实践与工程建议9.1 数据安全与备份SQLite 数据库是一个单文件备份非常方便。最简单的备份方式就是定期复制knowledge.db文件到其他目录、U 盘或者网盘。业务逻辑运行中直接复制数据库文件可能会有不一致风险更稳妥的做法是使用 SQLite 的在线备份 API或者通过 Python 的sqlite3连接执行VACUUM INTO语法生成备份文件。删除功能虽然好用但风险也高。个人知识库可以增加“回收站”机制删除时只把记录标记为已删除而不是物理删除定期确认后再真正清理。这个设计能避免很多误操作带来的损失。9.2 代码结构与可维护性当功能逐渐增加把所有路由函数堆在app.py里会越来越难维护。建议后续把项目改造成 Flask Blueprint 结构按功能模块拆分成独立文件比如routes_knowledge.py专门负责知识库增删改查routes_search.py专门负责搜索models.py负责数据库操作。数据库访问也可以从 Flask 应用中分离出来用独立的工具模块统一管理连接。这样代码的职责边界会清晰很多。9.3 生产环境部署建议当前代码中app.run(debugTrue)只适合本地开发。如果要把知识库系统部署到服务器上必须做以下几项调整关闭debug模式设置secret_key。使用生产级 WSGI 服务器比如waitressWindows 下通用或gunicornLinux 下常用。在前端增加登录认证至少是简单的密码访问控制。如果服务暴露到公网必须配置 HTTPS 证书。对写入接口增加频率限制防止恶意请求刷库。定期备份数据库文件。部署环境差异比较大不同操作系统的安装命令也不一样具体操作要按自己的服务器环境调整不要盲目照搬网上的部署命令。10. 总结与下一步到这里一个完整的个人知识库系统就搭建完成了。你实现了知识条目的新增、展示、搜索、删除功能理解了 Flask 路由的请求处理流程掌握了 SQLite 数据库的连接、建表和参数化查询也学会了用 Jinja2 模板在页面中渲染数据。这些基础能力可以迁移到很多 Web 项目中。如果电脑上还没有跑起来建议现在动手把项目代码复制下来按步骤运行一遍。先完成最小功能再根据需求做界面美化或者功能扩展比一开始就追求大而全要容易得多。接下来可以尝试给系统加上 Markdown 编辑器、分类筛选、数据导入导出、FTS5 全文检索等功能。如果你对 RAG 和大模型应用感兴趣也可以把知识库系统的数据导出成文本接入 Dify 或 FastGPT搭建一个能回答知识库问题的 AI 助手。从一个简单的 CRUD 应用到智能知识库中间的路都是可以一步一步走出来的。