用Flask搭建个人博客:从零到Docker部署完整实战
发布时间:2026/9/3 22:22:11来源:尧图网络
简介一份基于Python Flask框架实现的个人博客网站源码与配套说明面向Flask初学者也适合希望快速搭建轻量级CMS内容管理系统的开发者参考。资源完整演示了从环境配置、项目结构划分、数据模型定义到路由视图、模板渲染与用户登录的整个开发流程涵盖了SQLAlchemy数据库操作和表单处理等关键技术能够帮助读者理解Flask项目的基本组织方式与常用扩展。 压缩包共19个文件以12个HTML页面模板作为前端展示层2个Python脚本承担应用逻辑配合JavaScript交互、CSS样式、依赖清单及说明文档整体仅71KB结构简洁适合直接下载解压后对照学习。 目前已有5261人学习下载。压缩包内提供了可运行的代码片段与清晰的目录架构整体思路清晰读者可据此启动一个基础博客站点并延伸实现文章发布、评论、分类、搜索等功能是快速进入Flask Web开发的一条清晰路径。 想自己搭一个博客又不想被WordPress这种重型系统绑死也不想用Hexo这种静态生成器绕来绕去那Flask绝对是我最推荐的上手方案。Python配上Flask几十行代码就能把文章列表、详情页、Markdown渲染跑起来整个过程你会清楚知道每一个页面是怎么出来的每一个请求走到了哪里。这篇文章就是我自己动手用Flask搭建个人博客的完整记录从选型、环境、数据模型、路由到Markdown渲染、模板样式的打磨再到踩坑和Docker部署全部整理在这里。适合刚学完Python语法、想接触Web开发的人也适合已经在用其他框架、想回头看看Flask思路的朋友。1. 为什么是Flask而不是Django、Hexo或WordPress先说实话我当初并不是没考虑过Django。Django确实强大自带Admin后台、ORM、认证体系拿来做个博客可以说是杀鸡用牛刀。但也正因为东西多新手很容易陷入“不知道哪个模块在干活”的迷茫。你刚点开Django的目录结构看到settings.py、urls.py、views.py、models.py外加一堆中间件配置大脑基本就开始过载了。Flask不一样它的核心请求逻辑就是“装饰器定义路由函数返回内容”十几分钟就能跑起一个Hello World这种正反馈在学Web开发时特别重要。再对比Hexo和WordPress。Hexo这类静态博客生成器主打的是写Markdown然后自动生成静态文件部署是简单但你基本碰不到服务端逻辑数据库、请求处理、表单交互这些东西一辈子也学不到。WordPress更不用说了PHP生态主题插件点一点就完事博客跑起来的那一刻你其实还是个“使用者”不是“开发者”。我自己的理解是如果你只是想有个博客那用现成方案没问题但如果你想要的是一次“完整掌握Web应用原理”的锻炼Flask博客就是最合适的练手项目。Flask还有一个优点它是个“毛坯房”水电点位都给你留好了但墙怎么刷、家具怎么摆全看你自己。比如我想用SQLite存文章就加一个Flask-SQLAlchemy想用Markdown写文章就装一个markdown库想渲染代码高亮就用Pygments。每一块都是按需引入清清楚楚。如果你后面想转FastAPIFlask打下的路由、请求、模板这套思路也能直接迁移过去。所以无论从学习成本还是技术收益来看Flask搭博客都是一个性价比极高的选择。2. 搭骨架项目目录、虚拟环境与依赖清单2.1 项目目录结构我这次采用的是“小而完整”的目录结构不搞工厂模式那么重但也没把所有代码塞进一个文件。整体如下blog/ ├── app.py # Flask应用入口 ├── models.py # 数据库模型 ├── requirements.txt # 依赖清单 ├── templates/ # HTML模板 │ ├── base.html │ ├── index.html │ ├── post.html │ └── 404.html ├── static/ │ └── css/ │ └── style.css └── venv/ # 虚拟环境你可以看到这个结构非常简单一个应用入口负责路由一个模型文件负责数据库模板和静态文件各自归位。对博客这种体量来说过度封装反而是负担。等你做到评论、分类、标签这些功能后再拆成blueprints也不迟。2.2 虚拟环境与依赖虚拟环境是个老生常谈的事但我还是要强调因为我在Windows上踩过“pip装到了系统环境”的坑。先创建虚拟环境python -m venv venvWindows下激活venv\Scripts\activatemacOS/Linux下激活source venv/bin/activate然后安装依赖。我的requirements.txt内容如下Flask3.0.3 Flask-SQLAlchemy3.1.1 Markdown3.6 Pygments2.18.0 gunicorn22.0.0gunicorn在Windows上装不上也没关系它是部署到Linux服务器上用的本地开发用flask run即可。这里特别说一下Flask 3.x系列已经内置了flask run的开发服务器不用再装flask-script这类第三方启动工具。2.3 最小可运行应用写一个最小的app.py验证环境没问题from flask import Flask app Flask(__name__) app.route(/) def index(): return Hello, Flask Blog!然后运行flask --app app run --debug看到Running on http://127.0.0.1:5000浏览器打开出现文字就说明整个链路已经通了。这里加了--debug参数代码修改后服务会自动重启省去了手动重启的烦恼。3. 数据模型与路由用SQLite存储并打通文章链路3.1 Post模型设计个人博客的核心数据就是文章一句话概括一篇文章要有标题、固定的URL别名、正文内容、发布时间。基于这个思路我的models.py设计如下from datetime import datetime from flask_sqlalchemy import SQLAlchemy db SQLAlchemy() class Post(db.Model): __tablename__ posts id db.Column(db.Integer, primary_keyTrue) title db.Column(db.String(120), nullableFalse) slug db.Column(db.String(140), uniqueTrue, nullableFalse, indexTrue) content db.Column(db.Text, nullableFalse) created_at db.Column(db.DateTime, defaultdatetime.utcnow) def __repr__(self): return fPost {self.title}为什么用slug而不是直接用id作为详情页的标识原因很简单/post/3这种URL既不好看也不利于SEO而/post/hello-flask一眼就能看出文章主题。slug字段设置uniqueTrue从数据库层面杜绝了URL冲突的可能性。这里说明一点datetime.utcnow存储的是UTC时间展示的时候再做时区转换。如果你不想在展示时换算可以改用datetime.now但更规范的做法是存储UTC模板显示时再格式化。3.2 数据库初始化与路由接下来在app.py中完成数据库初始化和核心路由。首次启动时需要初始化数据库表可以直接在Flask命令行注册一个init-db命令import os import markdown from flask import Flask, render_template from models import db, Post app Flask(__name__) # SQLite数据库文件放在instance目录下 basedir os.path.abspath(os.path.dirname(__file__)) os.makedirs(app.instance_path, exist_okTrue) app.config[SQLALCHEMY_DATABASE_URI] sqlite:/// os.path.join(app.instance_path, blog.db) app.config[SQLALCHEMY_TRACK_MODIFICATIONS] False db.init_app(app) app.cli.command(init-db) def init_db(): db.create_all() sample Post( title你好我的第一篇博客, slughello-flask, content# 欢迎\n\n这是我的Flask博客第一篇测试文章。 ) db.session.add(sample) db.session.commit() print(数据库初始化完成已创建示例文章。) app.route(/) def index(): posts Post.query.order_by(Post.created_at.desc()).all() return render_template(index.html, postsposts) app.route(/post/slug) def post_detail(slug): post Post.query.filter_by(slugslug).first_or_404() return render_template(post.html, postpost) app.errorhandler(404) def not_found(e): return render_template(404.html), 404我在热词里看到很多人搜“flask 后台服务工程目录”其实就是想搞清楚这个层次。现在这个设计已经明确把“路由层”和“数据层”分开了app.py只管HTTP请求models.py只管数据库后续扩展会非常舒服。首次使用依次执行flask --app app init-db flask --app app run --debugfirst_or_404()这个方法强烈推荐它会自动帮我们处理“文章不存在”的情况省去手动判断if post is None的啰嗦代码。数据库层遇到的问题比如表没有创建、字段对不上绝大多数都可以先删掉instance/blog.db重新init-db解决。4. Markdown写作体验从.md到HTML页面的渲染管线4.1 为什么用Markdown写文章博客文章如果在后台HTML编辑器里写要不停敲p标签、手动加样式效率低而且容易出错。Markdown的最大优势是“纯文本易读、语法轻量”写标题用#写列表用-写代码块用三个反引号任何编辑器都能直接打开并阅读源码。Flask博客里加Markdown支持本质就是给文章内容加一条“转换管线”从数据库读到的字符串 → Markdown解析器 → HTML字符串 → 渲染进模板。4.2 Markdown解析与代码高亮我用的是markdown库并且启用了两个扩展fenced_code支持三反引号围栏代码块codehilite负责代码语法高亮。在app.py里把Markdown转换逻辑封装好app.route(/post/slug) def post_detail(slug): post Post.query.filter_by(slugslug).first_or_404() post.html_content markdown.markdown( post.content, extensions[fenced_code, codehilite] ) return render_template(post.html, postpost)我选择在路由中直接给post对象挂一个html_content属性而不是修改原始content字段。这样做的原因是数据库里始终保留Markdown原文将来你想换渲染器、改样式或者生成RSS摘要都有原文可用。codehilite扩展产出的HTML会带上highlight之类的CSS类名你只需要引入一份Pygments生成的CSS文件代码块就有高亮效果了。想省事的话可以在命令行生成CSSpygmentize -S default -f html static/css/pygments.css然后在base.html里引入。没有这一步代码高亮会产生类名但没有任何样式看起来就是一堆普通字符。模板中渲染时要注意必须用|safe过滤器让Flask不要对HTML做转义div classmarkdown-body {{ post.html_content|safe }} /div这里我要多说一句安全方面的事。这个|safe之所以能放心用是因为内容是你自己写的Markdown。如果以后加了评论区用户提交的内容直接|safe输出那就是典型的XSS漏洞会被人注入脚本。多人协作或者有评论功能时Markdown渲染结果必须经过清洗比如使用bleach白名单过滤标签这是个人博客进阶的必修课。5. 模板继承与页面美化让博客看起来像个作品5.1 母版模板base.htmlFlask默认使用Jinja2模板引擎模板继承是它的灵魂。我先把公共结构抽到base.html里包括head、顶部导航、页面底部再留出content和title两个块!doctype html html langzh head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1 title{% block title %}我的博客{% endblock %}/title link relstylesheet href{{ url_for(static, filenamecss/style.css) }} link relstylesheet href{{ url_for(static, filenamecss/pygments.css) }} /head body header classsite-header a href{{ url_for(index) }}我的博客/a nav a href{{ url_for(index) }}首页/a a href/about关于/a /nav /header main classcontainer {% block content %}{% endblock %} /main footer classsite-footer pPowered by Flask/p /footer /body /htmlurl_for(index)比写死/更好因为以后如果你把博客挂到子路径下所有链接会自动适配不用改代码。static文件同样通过url_for(static, filenamecss/style.css)引用这样不管开发环境还是生产环境的绝对路径Flask都会帮你处理好。5.2 首页与详情页模板首页模板遍历所有文章按时间倒序展示标题和日期{% extends base.html %} {% block title %}首页 · 我的博客{% endblock %} {% block content %} h1最新文章/h1 {% for post in posts %} article classpost-item h2a href{{ url_for(post_detail, slugpost.slug) }}{{ post.title }}/a/h2 time{{ post.created_at.strftime(%Y-%m-%d) }}/time /article {% else %} p还没有文章先写一篇吧。/p {% endfor %} {% endblock %}写模板时记得用{% else %}处理空列表的情况不然数据库里没文章时页面会显得很突兀。详情页模板则把整个文章内容展示出来就按上一章的方式用{{ post.html_content|safe }}输出。到这里一个博客的核心浏览链路已经通了首页看文章列表点标题进详情页详情页展示Markdown渲染后的正文。接下来就是花点心思把CSS样式写好我直接手写了一份极简风格的CSS主要做了三件事把正文最大宽度限制在720px左右保证阅读舒适度给代码块加上深色背景和圆角用Flexbox让导航和底部分布整齐。6. 实际开发中踩过的坑PyCharm社区版、路径与编码问题6.1 PyCharm社区版没有Flask模板怎么办热词里有人搜“pycharm社区版不能使用flask”这个问题我深有体会。PyCharm社区版是免费的但新建项目时没有Flask选项很多新手在这一步就卡住了。解决办法其实很简单手动创建虚拟环境、手动安装Flask、手动创建目录结构。不要因为社区版界面没给你“Flask项目”的快捷按钮就以为它不能用它只是少了模板不影响你写Flask代码。运行时也不需要社区版专门的“Flask run”配置直接在终端里flask --app app run --debug一样能跑。VSCode也是同理先建.venv再在终端里激活并安装依赖然后flask run就完事。真正决定能不能跑起来的永远是终端命令不是编辑器的某个按钮。6.2 Windows下python命令与pip安装的坑第一次在Windows上搭环境最容易遇到“不是内部或外部命令”的报错。这个大概率是Python没有加入环境变量。解决办法是重装Python时勾选“Add Python to PATH”或者在安装目录里找到python.exe所在的路径手动加到系统环境变量的Path里。另一类常见问题是激活虚拟环境后pip install提示安装成功但运行flask命令时却提示找不到。这个几乎可以确定是虚拟环境没激活或者激活后又在另一个终端窗口执行了命令。记住一个原则每个新终端窗口都要重新激活当前项目的虚拟环境。6.3 中文乱码问题个人博客全是中文内容乱码问题格外致命。出现乱码的根源几乎都是文件编码不统一。我的做法是所有.py源文件存为UTF-8模板文件在head里显式声明meta charsetutf-8Markdown源文件同样用UTF-8保存。Python 3默认源码就是UTF-8只要你的编辑器没有把文件改成GBK基本不会有问题。如果从某处复制了文章内容粘贴到代码里乱码先确认粘贴源的编码再用“以UTF-8重开”的方式转换一下。6.4 静态文件404页面样式不生效、图片打不开先看浏览器开发者工具里的请求路径。404的常见原因有两种一是模板里直接写了style.css这种相对路径而当前页面URL层级变深后浏览器解析路径出错二是忘了创建static/css目录Flask启动时并不会自动帮你创建空目录。用url_for(static, filenamecss/style.css)可以彻底避免第一类问题。7. 部署上线与后续扩展Docker打包和进阶思路7.1 用Docker打包Flask博客热词里我看到“flask 博客 docker 部署”说明大家都希望把博客部署到服务器上。Flask自带的开发服务器绝对不能用于生产环境我这里选择用gunicorn作为生产级WSGI服务器再通过Docker打包成镜像一键部署。Dockerfile内容如下FROM python:3.11-slim WORKDIR /app ENV FLASK_APPapp.py ENV PYTHONUNBUFFERED1 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 5000 CMD [gunicorn, -w, 2, -b, 0.0.0.0:5000, app:app]gunicorn最后的app:app表示“从app.py中导入名为app的Flask实例”。为了让SQLite数据在容器重建时不丢失我建议用docker-compose把数据目录挂载到宿主机services: blog: build: . ports: - 5000:5000 volumes: - ./instance:/app/instance构建并启动docker-compose up -d --build因为数据库文件放在了app.instance_path下也就是/app/instance所以挂载这个目录后文章数据就是持久化的容器删掉重建也不丢。这一步是我实际部署中反复调整才确定的心得。7.2 数据库变更管理博客人少的时候改表结构直接删库重建无所谓。但当你开始写了几十篇文章后随便删库就太心疼了。给博客引入Flask-Migrate是非常值得的一件事它基于Alembic做数据库迁移每次模型变更后执行flask db migrate -m add tag to post flask db upgrade就能保留原有数据的同时更新表结构。代码上只需要在app.py里初始化一下Migrate(app, db)几乎没有学习成本。7.3 功能扩展方向这个博客跑起来之后后续可加的方向其实很多。加标签和分类就是在Post模型里加一个关联表首页按标签筛选加站内搜索可以用SQLite的LIKE查询标题和正文加RSS订阅用feedgenerator生成XML想更工程化一点可以改用蓝图Blueprint把文章、关于页、后台管理拆成不同模块。也有人用这套类似的模型做个人记账系统把Post换成账单记录加一个统计页面就变成另一套应用了。说到底Flask博客这个项目真正的价值不在于博客本身而是给你一条贯穿“数据模型 → 路由 → 模板 → 部署”的完整链路。你做完之后再去看任何Web框架的文档都能迅速找到自己熟悉的部分这就是最好的入门方式。本文还有配套的精品资源点击获取
网站建设高端定制企业官网