Python Flask个人博客开发实战:从环境搭建到部署上线

发布时间:2026/10/4 2:40:16
Python Flask个人博客开发实战:从环境搭建到部署上线
1. 动手前先把需求拆清楚个人博客到底在做什么经常有学Python的朋友问我语法学了一轮列表、字典、循环、函数都能写但真让我独立做个东西出来完全没有头绪。我的建议一直很简单——做一个基于Python的个人博客网站。它不是什么高并发、分布式的宏大项目却能把Python里最常用的东西全部串一遍环境管理、Web框架、数据库、模板渲染最后还能实实在在部署上线让别人通过网址访问到你写的文章。不过动手写代码之前我想先带着你把“个人博客网站”这件事拆开看。很多人一听到“网页设计”就以为要做一堆花哨的CSS动画其实不是这样。一个博客网站的核心不是“页面”而是“系统”。1.1 第一步是拆需求个人博客不是“一个网页”而是“一个系统”你可以做个最简单的实验打开浏览器访问一篇博客文章时你看到的是一张排版好的网页。但这张网页背后发生了什么浏览器向服务器发送的请求本质上是“把ID等于3的那篇文章给我”然后服务器从数据库里查数据、套进模板、生成HTML最后返回给浏览器。这件事拆开来就是一个最小的博客系统必须有的几块一个数据库用来持久化存储文章的标题、正文、发布时间和标签。一个后端程序处理浏览器发来的请求按需查询数据库把数据交给模板。一套模板引擎把后端传过来的数据渲染成真正的HTML页面。一个写作后台你总不能每次写文章都去命令行里敲SQL所以需要一个登录界面和文章编辑页面。为什么要用Python来做这一整套东西而不是直接用现成的WordPress或者纯HTML静态页面因为有取舍。用纯HTML写博客服务器上存的就是一堆写死的页面每次更新文章都要手工改文件WordPress确实强但它把一切封装好了你看不到内部是“怎么跑起来”的。而用Python从零搭一遍你能亲眼看到请求、路由、ORM、模板这些概念的完整链路这才是这个练手项目真正的价值。1.2 框架怎么选Flask、Django、FastAPI的取舍确定用Python之后第二个问题就是选哪个Web框架。市面上主流的是三个我用一张表给你说明白框架特点适合的场景踩坑点Flask微框架核心只有路由和模板其余全靠扩展个人项目、教学练手、中小型站点需要自己组装组件Django全家桶自带ORM、Admin后台、表单系统快速做产品、内容管理系统概念多、体积重、学习曲线陡FastAPI异步高性能天然适合API接口前后端分离、接口服务做网页需要额外配模板教程偏接口向个人博客网站这种项目最优解几乎就是Flask。原因很实在它解决的恰好就是“把文章数据渲染成页面”这件事不加多余的东西。Django自带Admin后台确实诱人但你还没弄懂ORM是怎么回事就钻进了一堆“中间件”“迁移”“信号量”的概念里很容易失去耐心。FastAPI是好东西但它的主战场是JSON API服务端渲染HTML不是它的强项。我带的每一个新手都是从Flask入门的。它在Flask 3.x时代依然非常轻量装两个包就能跑起来而且文档质量高、中文教程多遇到问题一搜就能解决。下面我就直接按平时带人的流程走一遍你跟着操作就行。2. 环境准备与项目初始化10分钟跑起骨架很多人在这一步就放弃了原因是环境问题。Python版本不对、pip找不到、虚拟环境没激活、装包装到了全局环境里——这些乱七八糟的问题占掉了一大半的排错时间。这一节我尽量说得细一点把这些坑提前填平。2.1 检查Python版本和虚拟环境新手最容易漏的一步首先确认你的电脑上有Python。在命令行里执行python --version看到类似Python 3.10、3.11、3.12这样的输出就说明Python装好了。如果提示“python不是内部或外部命令”说明安装时没有勾选“Add Python to PATH”去Python官网重新下载安装包安装时把这个选项勾上基本就能解决。我建议直接用3.10以上的版本Flask 3.x对现代Python支持得很好没必要给自己设置老版本兼容的难题。接下来是虚拟环境。这一步新手常常会跳过但跳过之后基本都会被坑。虚拟环境解决的问题是不同项目依赖不同版本的包隔离在一个独立目录里互不污染。生活化一点说就是你每个项目都有一个自己的独立工具箱装什么都不会影响到别的工作台。创建和激活的命令如下python -m venv venvWindows系统激活venv\Scripts\activatemacOS或Linux系统激活source venv/bin/activate激活之后命令行前面会出现(venv)的提示符这就代表你已经在虚拟环境里了。然后安装我们的两个核心依赖pip install flask flask-sqlalchemy如果你装了VSCode打开项目文件夹它通常会自动识别这个虚拟环境写代码时Python相关提示也会自动匹配到这个环境里省心很多。这里的flask-sqlalchemy是Flask的ORM扩展后面定义数据模型时会非常方便现在先不深究装上再说。2.2 用Flask创建最小项目骨架从hello world开始跑通流程依赖装好之后先在某个目录下新建一个项目文件夹比如叫blog。目录结构先按下面这样来不用多够用就行blog/ ├── app.py ├── templates/ └── static/templates目录放HTML模板文件static目录放CSS、JS、图片这类静态资源。Flask的默认约定就是这两个目录你不需要额外配置框架会自动找到它们。然后打开app.py写一个最小应用from flask import Flask, render_template app Flask(__name__) app.route(/) def index(): return render_template(index.html) if __name__ __main__: app.run(debugTrue)这里Flask(name)里的__name__很重要它让框架知道你的应用从哪个文件启动从而定位templates和static目录的相对位置。在templates目录下建一个最简单的index.html随便写点内容然后运行python app.py浏览器访问http://127.0.0.1:5000如果能看到你的页面恭喜整个骨架已经通了。debugTrue意味着开发模式下代码改动会自动重载并且报错时会显示详细错误页面。这里有一个非常重要但新手经常忽略的事情debugTrue只有在开发阶段才能用。线上环境如果开着debug等于把你的错误信息甚至交互式调试器直接暴露给了任何人这是非常危险的操作。后面部署的时候第一件事就是把它关掉。3. 让文章出现在页面上数据、路由、模板三步走骨架跑通之后接下来就是博客最核心的部分让文章数据真正显示在网页上。这一节是整篇文章里含金量最高的一段我会把数据模型、路由、模板三个环节串起来讲你跟着走完就能看到一个有真实文章的博客首页。3.1 文章数据怎么存SQLite表结构设计有人可能会问文章为什么不直接写在HTML里如果你只有一两篇文章直接写当然没问题。但当你有几十篇、上百篇的时候你需要的是“按时间排序”“按标签筛选”“全文搜索”这些能力纯HTML完全处理不了。这时候就得引入数据库。个人博客最合适的起步方案是SQLite。它是文件型数据库不需要安装独立的数据库服务整个数据库就是一个后缀为.db的文件备份和迁移都非常简单。你的博客写到一千万篇之前SQLite的性能完全不是瓶颈。将来真要换到MySQLFlask-SQLAlchemy的ORM层会帮你把迁移成本降到最低。我们定义文章的数据模型。在app.py里扩展一下from datetime import datetime from flask_sqlalchemy import SQLAlchemy app.config[SQLALCHEMY_DATABASE_URI] sqlite:///blog.db db SQLAlchemy(app) 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) tags db.Column(db.String(200), default) def __repr__(self): return fPost {self.title}这个Post类对应数据库中的一张post表每个类属性就是表里的一个字段。id是自增主键title是文章标题content是正文created_at是发布时间tags用来存标签。关于tags字段我想多说一句。正规的做法是另建一张标签表再用一张关联表做多对多关系。但以我实际带新人的经验来说项目一开始就上多对多会把你的注意力从“博客的主流程”拉到“表关系设计”上。最务实的方案是先把标签存成逗号分隔的字符串比如“教程,随笔,日记”等文章真正多起来、标签需要单独管理的时候再拆表。这种“先让系统跑起来结构上留好重构空间”的思路在工作中非常常见。模型定义好之后需要真正建表。在命令行里进入Python交互环境执行python from app import app, db with app.app_context(): ... db.create_all() ...这里必须用app.app_context()包一层初学者几乎都会在这个位置栽跟头报一个“RuntimeError: Working outside of application context”。原因在于数据库操作需要应用上下文环境你不用去背这个底层机制只要记住“在命令行里操作数据库之前先写with app.app_context():这一句”就够了。然后用同样的方式插入第一条测试文章 from app import Post with app.app_context(): ... p Post(title我的第一篇博客, content这里是正文内容, tags随笔) ... db.session.add(p) ... db.session.commit()3.2 首页和详情页视图函数打通数据到页面的桥现在数据库里已经有数据了接下来要做的是把数据查出来并展示出去。这是Web框架里“视图函数”要做的事。先写首页视图app.route(/) def index(): posts Post.query.order_by(Post.created_at.desc()).all() return render_template(index.html, postsposts)这里Post.query.order_by(Post.created_at.desc()).all()的逻辑是把数据库里的文章全部查出来按照created_at字段倒序排列最新的在最前面。查出来的结果作为posts变量传给模板。这一行代码背后其实帮你做了“拼接SQL语句、执行查询、把结果转换成Python对象”三件事这就是ORM的意义。再看详情页app.route(/post/int:post_id) def post_detail(post_id): post Post.query.get_or_404(post_id) return render_template(post.html, postpost)路由里的 int:post_id 是一个动态规则它会把URL中post/后面的数字解析为post_id传给函数。比如访问/post/1post_id就是1。Post.query.get_or_404(post_id)的用法很值得注意它先尝试按id去数据库里找文章找到就返回对象找不到就直接返回404页面。这里有一个真实的开发场景你可以想象一下有人复制了一篇旧文章的链接发给朋友但文章后来被删了朋友点进去应该看到“页面不存在”而不是一个错误500。get_or_404就是为这种场景准备的。之后你还可以自定义404页面让它在出错时也保持博客的整体风格。3.3 Jinja2模板渲染循环、条件、过滤器一次学会数据查出来了最后一步是渲染HTML。Flask内置的模板引擎叫Jinja2它的核心能力是在HTML里写类Python的语法。我先改造base.html把它做成整个博客的公共骨架!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, filenamestyle.css) }} /head body nav a href{{ url_for(index) }}首页/a /nav main {% for message in get_flashed_messages() %} div classflash{{ message }}/div {% endfor %} {% block content %}{% endblock %} /main /body /html模板继承是这里的关键。base.html定义了所有页面共有的结构子页面只需要写属于自己的那块内容。这就好比你做了一版带公司Logo的Word模板每个新文档只需要往里填正文其他都是现成的。index.html继承base并且循环渲染文章列表{% extends base.html %} {% block content %} {% for post in posts %} article h2a href{{ url_for(post_detail, post_idpost.id) }}{{ post.title }}/a/h2 p classmeta{{ post.created_at.strftime(%Y-%m-%d) }} 标签{{ post.tags }}/p p{{ post.content[:80] }}{% if post.content|length 80 %}...{% endif %}/p /article {% else %} p还没有文章去后台发布一篇吧。/p {% endfor %} {% endblock %}这里面有三件新手最容易懵的事。第一{{ post.title }}是Jinja2里“输出变量”的语法它会自动把Post.title字段的值打印到HTML里。第二{{ post.created_at.strftime(%Y-%m-%d) }}是Python的日期格式化方法把datetime对象格式化成2025-04-01这种形式你在模板里可以直接调用Python对象的方法。第三{{ post.content[:80] }}是对字符串切片取前80个字符这是Python的切片语法用来生成文章摘要。Jinja2和Python语法高度一致这就是用Python做Web开发的一个隐性优势。详情页post.html更简单{% extends base.html %} {% block title %}{{ post.title }}{% endblock %} {% block content %} article h1{{ post.title }}/h1 p classmeta{{ post.created_at.strftime(%Y-%m-%d) }}/p div{{ post.content | safe }}/div /article {% endblock %}这里有个非常重要的安全点要说清楚模板默认会对变量输出做HTML转义也就是说如果文章正文里写了