ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用Flask从零搭建个人博客:开发部署全流程实战指南

用Flask从零搭建个人博客:开发部署全流程实战指南 简介这是一份面向Python初学者与Flask入门者的轻量级CMS实践项目以搭建个人博客为主线系统演示从环境配置、项目结构规划到路由视图、数据模型、模板渲染与用户登录的完整流程。资源共19个文件压缩包仅71KB其中包含12个HTML模板、2个Python源码文件、2个JavaScript脚本及CSS样式、说明文档等层次分明便于对照学习。项目通过SQLite数据库与Flask-SQLAlchemy实现文章存储并集成Flask-WTF与登录认证适合想快速掌握Flask核心用法的读者边改边练。目前已吸引5261人学习下载。借助源码中的目录划分和注释用户可以清晰理解models、routes、templates等模块的协作关系并在此基础上扩展评论、分类、搜索等功能将小型博客逐步完善为功能更丰富的内容管理系统。 先回答最开始那个问题为什么我要用 Flask 自己搭一个个人博客而不是直接去装 WordPress 或者某个现成的静态站点生成器原因其实很朴素。我一直做 Python 方向的开发写了不少脚本和工具但一直没正儿八经地做过一个 Web 项目。Flask 是我接触过的最轻量、最容易上手的 Python Web 框架用它搭博客等于是把 Python 爬虫、数据清洗、后端接口这套东西全部串了一遍。博客本身功能不复杂但“导航页、详情页、后台管理、发布文章、数据库读写、模板渲染、生产部署”这一条链路走下来对 Web 开发的理解会完全不一样。这篇文章就把我这次用 Flask 搭个人博客网站的完整过程、踩过的坑和最终上线方案整理出来给同样想动手的人参考。写的时候我会把每一步“为什么这么做”讲清楚不光是贴代码。适合的读者包括学过 Python 基础但没做过完整项目的人、想低成本拥有一个可控博客的人、以及准备转 Web 开发方向的初学者。1. 整体设计与技术选型思路1.1 为什么选 Flask 而不是 Django博客网站看起来就是个“文章列表 文章详情”的展示站但真正动手以后你会发现还有后台、评论、标签、搜索这些边边角角的需求。Django 当然能做而且自带 Admin 后台、ORM、迁移工具但副作用是它把所有东西都准备好了新手顺着教程跑起来很容易但很多东西“不知道为什么能用”。Flask 不同它默认只给你一个极简内核路由怎么写、数据库怎么连、表单怎么校验、密码怎么存全都是你自己选型、自己拼装。这个过程会让人多走很多弯路但也正是这些弯路让你真正理解一个 Web 项目是怎么运转的。我的选择是核心框架用 Flask 2.x数据库用 SQLite 起步ORM 用 SQLAlchemy模板用 Jinja2表单校验用 WTForms上线后用 Gunicorn 加 Nginx 对外提供服务。这套组合在中小流量场景下非常稳定而且每个组件都足够轻出问题的时候你能够快速定位。1.2 功能边界怎么控制保证项目不膨胀个人博客最容易犯的错是一开始就想要“全功能”——评论、私信、主题皮肤、多用户、统计后台、自动摘要全塞进去以后项目复杂度直线上升最后要么烂尾要么变成一个写不动的大型工程。我把这次的范围严格控制在四件事上文章列表、文章详情、后台管理、基础样式。需要说明的是这是基于个人博客最典型需求的合理裁剪。评论功能我暂时用第三方服务代替标签页和归档页留到第二期再做。这样做的最大好处是核心链路可以尽快闭环——从写完代码到部署上线一周内就能看到一个能访问的真实网站。对于学习项目来说“跑通”比“做全”重要得多。2. 项目结构与开发环境准备2.1 创建虚拟环境并安装依赖Python 项目的环境隔离是必须做的一步。我见过不少人在全局环境里直接 pip install装完一个项目另一个项目就崩了。博客这种小项目用 Python 自带的 venv 就足够。mkdir myblog cd myblog python3 -m venv venv source venv/bin/activate pip install flask flask-sqlalchemy flask-wtf依赖就这三个。Flask-WTF 主要是拿来处理表单和 CSRF 防护的Flask-SQLAlchemy 是数据库操作的高层封装。装完之后把依赖冻结到 requirements.txt方便日后部署复现pip freeze requirements.txt我习惯把项目名起成 myblog目录结构保持简单直观方便后续扩展和维护。2.2 项目目录结构与各文件职责myblog/ ├── app.py # 应用入口路由和核心逻辑 ├── models.py # 数据库模型定义 ├── requirements.txt # Python 依赖清单 ├── templates/ # Jinja2 模板文件 │ ├── base.html # 公共布局导航栏、页脚 │ ├── index.html # 文章列表页 │ ├── post.html # 文章详情页 │ ├── about.html # 关于页 │ ├── admin.html # 后台管理列表 │ ├── login.html # 登录页 │ └── edit.html # 新建/编辑文章页 ├── static/ │ ├── css/style.css # 全站样式 │ └── images/ # 图片资源 └── blog.db # SQLite 数据库文件运行后生成这个结构没有选择再拆成蓝色经典的 Flask 工厂模式比如 application 包加 config.py 那种而是用单文件 app.py 跑主业务。原因很简单项目规模摆在那里拆出十几个包反而增加理解成本。我也明确告诉大家如果后面要加用户系统、评论系统、或者哪天要把后台从 SQLite 迁到 MySQL那就必须重构把配置、扩展、路由分别模块化。到那个阶段这个结构不够用但做第一个版本足够。3. 核心功能实现与关键代码解析3.1 数据库模型设计先定义文章的数据结构。我是这么设计的每篇文章有 id、标题、正文内容、发布时间三个核心字段。分类和标签我的初版先不建表因为那会增加一对多关系和多对多关系分析起来复杂度提升不少。真正的个人博客第一批文章通常只有几十篇标签功能后续用 Redis 里的 Set 结构也能做没必要一开始就让数据库变复杂。from flask_sqlalchemy import SQLAlchemy from datetime import datetime db SQLAlchemy() class Post(db.Model): id db.Column(db.Integer, primary_keyTrue) title db.Column(db.String(200), nullableFalse) content db.Column(db.Text, nullableFalse) created_at db.Column(db.DateTime, defaultdatetime.now) def __repr__(self): return fPost {self.title}关于数据库字段有一点提醒一下标题字段我留了 200 字符没必要和某些论坛帖子标题一样留 255 的上限中文占位本来就多200 足够。正文用 Text 类型而不限定长度后续如果要支持 Markdown 源码长度扩展不会成为一个问题。3.2 路由设计与页面流程一个博客最核心的访问路径就三条打开首页看文章列表、点进某一篇看正文、进后台写文章。对应的路由拆得很清楚。from flask import Flask, render_template, request, redirect, url_for, session from models import db, Post app Flask(__name__) app.config[SQLALCHEMY_DATABASE_URI] sqlite:///blog.db app.config[SECRET_KEY] your-secret-key db.init_app(app) app.route(/) def index(): posts Post.query.order_by(Post.created_at.desc()).all() return render_template(index.html, postsposts) app.route(/post/int:post_id) def detail(post_id): post Post.query.get_or_404(post_id) return render_template(post.html, postpost) app.route(/about) def about(): return render_template(about.html)首页的路由我直接用order_by(Post.created_at.desc())把文章按发布时间倒序排最新的排在最前面。详情页用get_or_404文章不存在的时候自动返回 404 页面而不是让用户看到一个报错堆栈。这两点非常基础但很多新手写出来是前端直接循环或者 id 不存在就报 500 错误体验差很多。3.3 模板继承base.html 的价值Jinja2 模板引擎里最值得说的就是模板继承。没有它每个页面都要重复写导航栏、CSS 引用、页脚。有它之后公共部分只维护一次所有页面共用。!doctype html html langzh-CN head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1 title{% block title %}个人博客{% endblock %}/title link relstylesheet href{{ url_for(static, filenamecss/style.css) }} /head body nav a href{{ url_for(index) }}首页/a a href{{ url_for(about) }}关于/a a href{{ url_for(admin) }}后台/a /nav main {% block content %}{% endblock %} /main /body /html子模板只需要重写 content 块就行。这里有两个小经验一是 static 文件路径强烈建议用url_for(static, filename...)生成不要写死/static/css/style.css不然部署到子目录或者用 CDN 时非常痛苦二是导航栏里我把后台链接也放进去了这对个人博客来说省事但对多用户系统来说绝对不行——下一条就讲权限控制。3.4 后台管理与权限控制的取舍后台这部分是博客项目里最容易出安全问题的位置。我的初版方案是管理页面全部套一层登录校验未登录直接跳转到登录页。登录验证我用了 Flask 自带的 session 机制没有引入 JWT 或复杂的权限框架因为只有我一个人写文章只需要一个管理员账号。from werkzeug.security import generate_password_hash, check_password_hash ADMIN_USER admin ADMIN_PASSWORD_HASH generate_password_hash(your-password) app.route(/login, methods[GET, POST]) def login(): if request.method POST: username request.form[username] password request.form[password] if username ADMIN_USER and check_password_hash(ADMIN_PASSWORD_HASH, password): session[logged_in] True return redirect(url_for(admin)) else: return render_template(login.html, error账号或密码错误) return render_template(login.html) def login_required(view): from functools import wraps wraps(view) def wrapped_view(*args, **kwargs): if not session.get(logged_in): return redirect(url_for(login)) return view(*args, **kwargs) return wrapped_view密码我用 Werkzeug 自带的generate_password_hash存哈希绝不存明文。这个细节非常关键很多小项目把管理员密码写死在代码里或者数据库明文存放一旦服务器文件泄露就等于后台沦陷。还要提醒一句SECRET_KEY上线前一定要改掉并且不要用 GitHub 上教程里的默认值否则任何人都可以伪造 session 登录你的后台。写文章和删文章的路由都加上login_required装饰器。删文章我做了二次确认页面防止点错按钮把整篇文章干掉。4. 完整开发流程与本地调试4.1 从零到可用开发顺序建议新手最容易犯的错是一上来就把所有代码写在一个大文件里写完再一起来调。我的建议是按下面的顺序分阶段推进每一步都能看到“可运行的结果”先实现空项目跑通 Flask 的 hello world。建好 SQLite 数据库和 Post 模型写一个往数据库插入假数据的临时脚本。实现首页列表和详情页的展示打开浏览器能看到数据这个正反馈很重要。实现后台登录和发布、编辑、删除功能。把 base.html 和样式调整好让每个页面都好看。本地全部测试一遍后再进入部署环节。每个阶段的结束标准都是“自己能操作一遍且不报错”。这样持续有前进感不容易在做项目的第 3 天就放弃。4.2 本地运行与调试技巧开发阶段直接用 Flask 内置服务器开启 debug 模式代码改动后会自动重载export FLASK_APPapp.py export FLASK_ENVdevelopment flask run --host0.0.0.0 --port5000注意FLASK_ENVdevelopment是 Flask 1.x 时代的老写法新版本用--debug或环境变量FLASK_DEBUG1更合适。打开 debug 模式后会有一个浏览器端调试器模板渲染报错时能把堆栈信息展示出来开发效率高很多。但我必须强调** 生产环境一定要关掉 debug**否则任何人都能看到你的报错信息甚至代码片段非常危险。调试过程中我用 Flask-SQLAlchemy 的时候遇到过一个坑修改模型字段以后SQLite 数据库并不会自动加列需要手动迁移或者重新建表。开发初期的处理方式是直接删除 blog.db 文件再重新 build_all但在生产环境绝对不要这么干。另外我习惯在本地 WSL 里开发、Windows 浏览器访问因为--host0.0.0.0可以让虚拟机里的服务在宿主机访问到方便在手机上也能预览页面效果。4.3 用代码插入测试数据开发阶段反复往数据库里塞数据是家常便饭写一个临时脚本from app import app from models import db, Post with app.app_context(): db.create_all() p Post(title我的第一篇博客, content## 你好Flask \n\n 这是正文内容。) db.session.add(p) db.session.commit()这里必须用app.app_context()包裹数据库操作因为 Flask-SQLAlchemy 需要应用上下文才能访问数据库。很多新手在这里卡住报RuntimeError: Working outside of application context就是没理解上下文这个概念。简单理解应用上下文相当于告诉 SQLAlchemy “我们现在是在这个 Flask 应用的环境里工作”。5. 服务器的部署与上线方案5.1 本地开发服务器不适合生产环境Flask 自带的开发服务器是单进程单线程的性能差且不稳定而且也没有经过生产级的安全加固。上线的时候我用 GunicornLinux 环境下常见且稳定的 Python WSGI 服务器来跑 Flask 应用gunicorn -w 2 -b 127.0.0.1:8000 app:app这条命令的意思是启动 2 个 worker 进程监听本机的 8000 端口。为什么不直接监听 80 端口因为 80 端口是留给 Nginx 的。Nginx 负责接收外部请求、处理静态文件、把动态请求反代给 Gunicorn。这个架构是业内最常见的 Linux 下 Flask 部署架构稳定性和安全性都有保障。5.2 Nginx 反向代理配置要点我的 Nginx 配置片段大致如下server { listen 80; server_name yourdomain.com; location /static/ { alias /var/www/myblog/static/; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }静态文件交给 Nginx 处理而不是让 Flask 自己返回这个点很重要能极大减少 Python 进程的压力。以后如果要做 HTTPS也是在这一层申请证书并配置跳转不需要改 Flask 代码。我在这个阶段踩过一个印象很深的坑上线后首页样式全丢检查后发现是服务器上 Nginx 配置的静态路径和实际部署路径不符排查半天。所以上线前先单独访问一下http://你的域名/static/css/style.css如果返回 404说明静态文件配置有问题不用急着看 Flask 日志。5.3 Docker 部署的另一种思路我最终是通过 Docker 部署的因为服务器环境不太干净用容器隔离省心很多。我的 Dockerfile 大致是这样FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple COPY . . EXPOSE 8000 CMD [gunicorn, -w, 2, -b, 0.0.0.0:8000, app:app]需要提一句的是Docker 容器内的 Gunicorn 要监听0.0.0.0:8000而不是127.0.0.1:8000否则容器外访问不到。博客数据存 SQLite所以容器删除后数据会丢我用了 volume 来持久化 blog.db 这个文件docker run -d -p 8000:8000 -v /var/www/myblog/data:/app/data myblog:latest注意把数据库路径配置到 data 目录下而不是容器默认的工作目录。这个细节救过我一次之前升级容器时忘记挂载数据库被初始化成空表还好文章数量不多手动恢复了。现在我会提前确认数据库文件、上传文件都放在持久化卷里。5.4 后端加一个简单的分页文章多了以后首页把所有文章一次性查出来并不是好做法。我用 Flask-SQLAlchemy 的分页方法改了一下from flask import request PER_PAGE 10 app.route(/) def index(): page request.args.get(page, 1, typeint) pagination Post.query.order_by(Post.created_at.desc()).paginate(pagepage, per_pagePER_PAGE, error_outFalse) posts pagination.items return render_template(index.html, postsposts, paginationpagination)模板里加上前一页、后一页的链接这个功能对阅读体验的提升非常直观。error_outFalse表示页码超出范围时返回空列表而不是报 404对爬虫和误操作都比较友好。6. 常见问题与排查技巧实录这一节把开发过程中遇到的典型问题整理成表格每一条都是我自己实际踩过坑后摸清楚的按出现频率从高到低排列。问题现象排查思路解决方法模板渲染报 500 错误页面炸了打开 debug 看堆栈多半是 Jinja2 语法写错检查{% %}和{{ }}是否配对过滤器名是否写错静态文件 404检查 Flask 的 static 目录路径和 Nginx location 配置先直接访问/static/css/style.css确认静态资源是否存在中文乱码HTTP 头或 HTML 没声明 UTF-8meta charsetutf-8加在 head 里Flask 侧app.config[JSON_AS_ASCII] False处理 JSON 场景端口被占用lsof -i:8000或netstat -ano看占用进程换端口或 kill 掉旧进程SQLite 报 database is locked并发写入了SQLite 不适合高并发博客场景几乎遇不到真遇到就换 PostgreSQLdebug 模式下改代码不生效Flask 自动重载没触发或浏览器缓存刷新浏览器缓存确认FLASK_DEBUG1检查文件是否保存部署后所有链接带不上静态资源Nginx alias 路径配置错用绝对路径方式location /static/配合 alias 指向绝对目录还有一个新手绕不开的问题登录后跳转回后台时 session 失效。排查方法是在浏览器开发者工具里看 Cookie确认SECRET_KEY是否在生产环境被修改过。如果改了 key所有旧 session 都会失效用户会被迫重新登录。这也是安全性和用户体验之间要权衡的点我最后选择了保留同一个 key只是在代码外引入环境变量。再有就是 Flask-WTF 的 CSRF 防护。给表单加上{{ form.hidden_tag() }}之后所有 POST 请求都会校验 token。第一次跑通之后我特意试了一下用 curl 直接 POST 表单数据返回 400说明防护生效了。使用时要确认未登录页面也包含 CSRF token否则某些页面在登录后会因为 token 过期而报错。7. 发布后的运维与后续扩展上线并不是终点博客的长期维护才是真正考验项目结构的部分。我持续观察访问日志发现几个平时不会注意的问题搜索引擎爬虫会频繁请求站点地图移动端访问的 CSS 适配不够偶尔有陌生人探测后台地址。针对这些我做了几件事加了一个简洁的 sitemap.xml、把后台路径从/admin改成了/manage这种不明显的路径、用 Nginx 的访问日志来自动封禁反复试探的 IP。这些都是在不引入额外依赖的前提下完成的成本很低但收益明显。后续如果要加功能我建议优先考虑这几个方向文章标签系统和按标签筛选、Markdown 编辑界面、每篇文章的浏览计数、通过 Webhook 或者邮件通知被评论。这些功能的优先级都是基于真实使用体验排出来的不是因为技术好看。最后再分享一个小技巧每次发布新版本前我都先在本地用虚拟环境完整跑一遍python -m pytest哪怕只有一个简单的冒烟测试也要跑确认数据库迁移脚本没破坏已有数据再上线。以前我都是“改完直接推到服务器”结果有一回把生产库的表结构改崩了恢复数据花了一晚上自此再也不敢裸更新。个人博客这个项目我的体会是它麻雀虽小但把前端、后端、数据库、部署、运维全流程都串起来了。哪怕你最后写出来的功能很朴素只要它能稳定访问、能持续更新文章就已经赢过了市面上 90% 的“跑完教程就吃灰”的项目。建议你也动手搭一个不必一上来就追求大而全先让第一个版本跑起来再看日志、迭代功能这会是你 Python 学习道路上非常扎实的一步。本文还有配套的精品资源点击获取
返回列表