☰
用Python Flask从零搭建个人博客网站完整教程
2026/9/26 12:06:26 网站建设 项目流程

2024年我把Python基础语法过完一遍之后,第一个想到的练手项目就是做一个个人博客网站。当时想法特别简单:不想用WordPress,也不想用现成博客平台,就想自己从零搭一个能把文章展示出来的网站。

在这个过程中我发现,Python做网页真的比想象中容易得多——不需要写一堆复杂的原生后端代码,用Flask这个轻量级框架,配合最基础的HTML、CSS、JavaScript,两三天就能把一个能看、能点、能写文章的个人博客跑起来。这篇博文就把我从环境配置、项目搭建、页面设计到常见坑排查的完整过程写出来,尽量用大白话讲清楚每一步为什么这么做,帮你少走弯路。

这篇文章的核心思路是:用Python的Flask框架做后端,用HTML5+CSS3+JavaScript做前端,用JSON文件当临时数据库。这套组合特别适合刚学完Python语法、想做网页设计作业、或者想给自己搭一个个人展示页的朋友。你不用先学Django那种重型框架,也不用碰数据库,跟着一步步走,就能拥有一个属于自己的、部署在本地浏览器里的个人博客网站。

1. 项目整体设计与方案选型

1.1 为什么选择Flask而不是Django

很多刚接触Python网页开发的人,第一个问题就是:到底学Flask还是Django?我当时也纠结了很久。Django功能全,自带后台管理、数据库ORM、用户认证,但它的学习曲线偏陡,而且很多功能对于“做一个个人博客”来说根本用不上。

Flask的哲学是“微框架”——它只提供最基本的路由和模板渲染能力,其他功能你按需自己装。这种设计对新手非常友好,因为你能清楚地看到每一个环节是怎么运作的:浏览器发请求,Flask接住请求,返回一个渲染好的HTML页面。整个过程没有黑魔法,每一步都明明白白。

用Flask做个人博客,还有一个很实际的好处:代码量小。我最终写的app.py文件,核心逻辑不超过80行。对比Django项目默认生成的那一堆配置文件和目录结构,Flask项目一眼就能看明白哪个文件是干什么的。对于网页设计课的作业、程序设计的课程项目、或者个人练手作品来说,Flask的控制力是最合适的——你既要交一段能体现逻辑的Python代码,又不需要为了框架本身去学一堆无关概念。

1.2 为什么网页设计还需要HTML、CSS和JavaScript

Python负责的是“后台逻辑”:用户访问哪个网址、应该给他返回什么内容、文章数据从哪里读取。但用户在浏览器里实际看到的东西——页面的布局、字体、颜色、按钮的点击效果——这些全都靠前端三件套。

做个不太严谨但很好懂的类比:如果把博客网站比作一家餐厅,Python(Flask)是后厨和店长,负责接单、备菜、出餐;HTML是菜品的摆盘结构,决定了盘子里先放什么、后放什么;CSS是餐厅的装修风格,决定了灯光、色调和桌椅摆放;JavaScript则是服务员——顾客举手时能立刻回应,按钮按下去有反应。

所以你千万别觉得“用Python做网页”就不用碰前端了。恰恰相反,个人博客网站的核心价值在于“展示”,一个再好的后端逻辑,配合一个简陋的页面,整体体验也是很差的。我的建议是:Python写后端逻辑,HTML搭骨架,CSS负责颜值,JavaScript做点缀交互,这四样加在一起才是一个完整的个人博客网站。

1.3 个人博客网站的功能清单与页面规划

在做任何代码之前,第一件事是规划好网站有哪些页面、每个页面上有什么。我给自己定的需求清单很简单,一共四个页面:

  • 首页:展示博客标题、一句个人简介、文章列表(每篇显示标题、日期、摘要)。
  • 文章详情页:根据文章的ID,展示完整正文内容。
  • 关于页:博主自我介绍,放一些联系方式或社交链接。
  • 404错误页:访问不存在的网址时,显示一个友好的提示页面。

这个清单看起来不起眼,但它覆盖了网页设计里最核心的几个操作:列表展示、详情跳转、动态路由、模板复用。以后你想加分类、加标签、加搜索,都是在这些基础上扩展而已。

数据结构方面,我用了一个Python列表,里面放字典,每个字典代表一篇文章。比如:

articles = [ { "id": 1, "title": "我的第一篇博客", "date": "2024-06-01", "content": "这里是正文内容……" }, { "id": 2, "title": "Python学习笔记:列表与字典", "date": "2024-06-10", "content": "另一个正文……" } ]

文章数量少的时候,这种硬编码的方式完全够用。后期文章多了,再把数据迁移到SQLite数据库或者单独的JSON文件里去,我先不说那么远,第一阶段先把流程跑通。

2. 开发环境准备:Python、VS Code与虚拟环境配置

2.1 Python的安装:不同系统的操作区别

如果你还没有装Python,这一步是整个项目开始的第一个必修课。我建议装Python 3.10以上的版本,太老的版本会对Flask的兼容性造成不必要的麻烦。

Windows用户直接去Python官网下载Windows安装包,安装时有一个特别重要的勾选选项:Add python.exe to PATH。这个是必须勾上的,否则在命令行里输入python会提示“不是内部或外部命令”。安装完成后,打开命令行,输入:

python --version

能输出版本号就说明成功。如果你输入的是py --version才能显示版本,说明你的系统配置了Python的启动器方式,后续命令把python替换成py执行就行了,效果一样的。

macOS和Linux用户一般系统自带Python 3,但版本可能比较旧。建议用包管理器装新版,macOS用户用Homebrew:

brew install python

Ubuntu/Debian用户用:

sudo apt update sudo apt install python3 python3-pip python3-venv

装好之后同样验证一下版本。不同系统的安装细节有差异,但根本目的就一个:能在终端里顺利执行python命令。

2.2 VS Code环境配置:不再报红、不装错解释器

编辑器方面,我用的是VS Code,免费且对Python支持成熟。装好VS Code后,打开扩展市场,安装微软官方的Python扩展。安装完成后,打开项目文件夹,按Ctrl+Shift+P(macOS是Cmd+Shift+P),输入Python: Select Interpreter,选择你刚安装的那个Python版本。

这一步非常关键。很多新手在VS Code里写Python代码,明明电脑上装了Python,却总提示ModuleNotFoundError: No module named 'flask',多半就是因为VS Code选错了解释器——比如系统里同时存在多个Python版本,VS Code默认选了一个你没有安装Flask的解释器。我踩过一次这个坑之后,每次新建项目的第一件事就是检查右下角的解释器版本。

2.3 虚拟环境:为什么必须做、怎么创建

虚拟环境是Python项目开发里最容易忽略但极其重要的一个步骤。它的作用,简单来说,就是给每个项目单独划分一个“依赖小仓库”,不同项目的第三方库互不干扰。

举个例子:你项目A需要Flask 2.2版本,项目B需要Flask 3.0版本,如果没有虚拟环境,两个项目共用一套Python环境,装新版本会把旧版本覆盖掉,项目A就崩了。有了虚拟环境,各用各的,互不影响。

在项目根目录下执行:

python -m venv venv

这条命令会创建一个名为venv的文件夹,里面是独立的Python解释器和pip工具。接下来激活虚拟环境。Windows下:

venv\Scripts\activate

macOS/Linux下:

source venv/bin/activate

激活成功后,命令行提示符前面会出现(venv)字样。这之后再用pip装的包,就都装在这个虚拟环境里了。

然后安装Flask:

pip install flask

想确认是否安装成功,可以在Python交互环境里执行import flask,不报错就是妥了。

3. Flask后端核心:路由、模板引擎与动态网页

3.1 最小Flask应用:一个永不熄灭的服务器

现在开始写第一个代码文件。在项目目录下新建app.py,输入下面这段代码:

from flask import Flask, render_template app = Flask(__name__) @app.route("/") def home(): return "Hello, my blog!" if __name__ == "__main__": app.run(debug=True)

在终端运行python app.py,终端会输出一行地址,通常是http://127.0.0.1:5000。用浏览器打开这个地址,看到“Hello, my blog!”就说明你的Python后端已经活过来了。

这里解释一下发生了什么:@app.route("/")是Flask的路由装饰器,它告诉Flask,“当用户访问根路径/时,执行下面这个函数”。函数的返回值,就是浏览器收到的内容。app.run(debug=True)是启动开发服务器,debug=True的意思是开启调试模式——代码改了之后服务器自动重启,报错了会在页面上显示详细信息。这个参数只在开发时用,做真正的部署之前要关掉。

3.2 模板继承:把重复的页面骨架抽出来

上面的返回字符串只能算“有响应”,还不算“网页设计”。做真正的网页,必须用Flask的render_template函数,把HTML页面交给模板引擎Jinja2去渲染。

看这个改动:

@app.route("/") def home(): return render_template("index.html")

Flask默认会去项目下的templates文件夹找HTML文件。所以目录结构要调整一下:

myblog/ ├── app.py ├── venv/ ├── templates/ │ ├── base.html │ ├── index.html │ ├── article.html │ └── about.html └── static/ ├── css/ │ └── style.css └── js/ └── main.js

templates放网页模板,static放CSS、JS、图片这类静态资源。

这里要重点讲讲模板继承。个人博客网站的每个页面其实有大量相同部分:顶部导航栏、页脚、整个页面的head标签。如果不做模板继承,每个HTML文件都要复制粘贴一份导航栏,后期改一个链接,所有页面全得改一遍。所以我自己习惯的做法是先把公共骨架抽出来,做一个base.html:

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{% block title %}我的博客{% endblock %}</title> <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}"> </head> <body> <header class="site-header"> <nav> <a href="/" class="logo">My Blog</a> <ul> <li><a href="/">首页</a></li> <li><a href="/about">关于</a></li> </ul> </nav> </header> <main> {% block content %}{% endblock %} </main> <footer> <p>© 2024 My Blog. 由 Python + Flask 强力驱动。</p> </footer> <script src="{{ url_for('static', filename='js/main.js') }}"></script> </body> </html>

子模板里,用{% extends "base.html" %}声明继承,然后用{% block content %}往骨架里填自己的内容。比如index.html:

{% extends "base.html" %} {% block content %} <section class="posts-list"> <h1>最新文章</h1> <ul> {% for article in articles %} <li class="post-item"> <h2><a href="/article/{{ article.id }}">{{ article.title }}</a></h2> <p class="post-date">{{ article.date }}</p> <p class="post-summary">{{ article.summary }}</p> </li> {% endfor %} </ul> </section> {% endblock %}

注意看,{{ }}是输出变量,{% %}是写控制逻辑。这里用了一个for循环遍历从后端传来的articles列表,自动生成文章列表。前端HTML里结合后端的循环和数据渲染,这种能力是“动态网页”和“静态网页”的核心区别。

3.3 动态路由:点击文章标题跳转到详情页

首页文章列表的链接写成/article/{{ article.id }},这是一个动态地址——不同文章对应不同ID,地址就不同。后端的路由要这样写:

from flask import Flask, render_template, abort app = Flask(__name__) articles = [ { "id": 1, "title": "我的第一篇博客", "date": "2024-06-01", "summary": "这是一篇关于博客开张的碎碎念。", "content": "大家好,这是我的个人博客第一次发文。\n\n以后会在这里记录一些学习Python的过程和心得。" }, { "id": 2, "title": "Python学习笔记:列表与字典", "date": "2024-06-10", "summary": "用最通俗的语言理解Python里最常用的两种数据结构。", "content": "列表是带顺序的盒子,字典是带标签的盒子。\n\n这篇文章记录了我初学Python时对这两种结构的理解。" } ] @app.route("/") def home(): return render_template("index.html", articles=articles) @app.route("/article/<int:article_id>") def article_detail(article_id): for article in articles: if article["id"] == article_id: return render_template("article.html", article=article) abort(404) @app.route("/about") def about(): return render_template("about.html") @app.errorhandler(404) def page_not_found(e): return render_template("404.html"), 404 if __name__ == "__main__": app.run(debug=True)

<int:article_id>是Flask的路由转换器,int表示这里期望的是一个整数。用户访问/article/1时,article_id就被赋值为1,然后循环查找文章列表,找到对应文章就渲染详情页,找不到就返回404页面。

这里我想特别提一下abort(404)和@app.errorhandler(404)配套的写法。很多新手一开始不写404页面,用户一旦访问一个不存在的文章ID,浏览器会显示默认的报错页面,又丑又让人困惑。自定义一个404页面,不仅让网站看起来更完整,还能在页面里放一个“返回首页”的链接,把流失的访客接回来。这是很多人忽略的小细节,但很值得做。

3.4 文章详情页与关于页模板

文章详情页的模板article.html比较直接,把文章标题、日期、正文展示出来:

{% extends "base.html" %} {% block content %} <article class="post-full"> <h1>{{ article.title }}</h1> <p class="post-date">{{ article.date }}</p> <div class="post-content"> {% for paragraph in article.content.split("\n") %} <p>{{ paragraph }}</p> {% endfor %} </div> <a href="/" class="back-link">← 返回首页</a> </article> {% endblock %}

关于页就是简简单单一个自我介绍:

{% extends "base.html" %} {% block content %} <section class="about-page"> <h1>关于我</h1> <p>你好,我是这个博客的作者。一个正在努力学好Python的初学者。</p> <p>这个博客是用Python Flask制作的,是我第一个完整的网页项目。</p> </section> {% endblock %}

到目前为止,这个网站已经在逻辑上完整了:能显示文章列表、能点进详情页、有关于页、有404页。接下来就差给页面穿上好看的外衣了。

4. 前端页面设计与实现:导航、布局与交互

4.1 CSS基础布局:用Flexbox搞定导航栏和页面骨架

刚做完HTML结构的时候,网页是“白底黑字、竖着排列”的原始状态,不能说不能用,但离“设计”还差得远。CSS就是负责把光秃秃的结构变得顺眼的工具。

我用的布局方案整体思路很清晰:导航栏水平分布,主内容区居中限宽,文章列表用卡片式排列。全部用Flexbox实现,兼容性好,概念简单。

先看style.css的基础部分:

* { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: "PingFang SC", "Microsoft YaHei", sans-serif; background-color: #f9f9f9; color: #333; line-height: 1.8; } .site-header { background: #2c3e50; padding: 15px 0; position: sticky; top: 0; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } .site-header nav { max-width: 900px; margin: 0 auto; padding: 0 20px; display: flex; justify-content: space-between; align-items: center; } .site-header .logo { color: #fff; font-size: 1.4rem; font-weight: bold; text-decoration: none; } .site-header ul { list-style: none; display: flex; gap: 20px; } .site-header a { color: #ddd; text-decoration: none; transition: color 0.2s; } .site-header a:hover { color: #fff; } main { max-width: 900px; margin: 30px auto; padding: 0 20px; min-height: 70vh; }

这里有几个值得说的点。position: sticky配合top: 0,让导航栏在页面滚动的过程中始终固定在顶部,这是很多博客网站的标配效果,一行CSS就搞定了。max-width: 900px加上margin: 0 auto,让主内容在超宽屏上不会无限拉伸,保持在舒适的阅读宽度。:hover过渡动画是画龙点睛的东西,链接悬停时颜色渐变,视觉上更温润。

4.2 卡片式文章列表:间距、阴影和圆角的细节

文章列表如果没有样式,就是一堆竖排文字。加了卡片样式之后,整个页面的“设计感”立刻就不一样了:

.posts-list h1 { margin-bottom: 20px; font-size: 1.8rem; } .post-item { background: #fff; border-radius: 8px; padding: 20px 25px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); transition: transform 0.2s, box-shadow 0.2s; } .post-item:hover { transform: translateY(-3px); box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1); } .post-item h2 { margin-bottom: 5px; } .post-item h2 a { color: #2c3e50; text-decoration: none; } .post-item h2 a:hover { color: #3498db; } .post-date { color: #888; font-size: 0.85rem; margin-bottom: 8px; } .post-summary { color: #666; font-size: 0.95rem; }

卡片式设计为什么好看?其实说穿了就是一种视觉分组——把一篇完整文章的标题、日期、摘要放在同一个白色圆角容器里,分隔得清清楚楚。白色的卡片在浅灰色背景上,通过box-shadow形成轻微浮起感,鼠标悬浮时卡片微微上移,阴影加深,这种反馈感让用户觉得每个卡片都是“可点击的”。

我还喜欢加一个响应式细节:在窄屏下,把卡片的padding改小一点:

@media (max-width: 600px) { .post-item { padding: 15px; } }

三种设备的浏览体验会更统一。

4.3 JavaScript交互:给页面加一点灵性

个人博客的JavaScript不需要多,但至少要有几个“有存在感”的交互。我加的是:导航栏在移动端展开收起、文章页面回到顶部按钮、页脚自动更新年份。

先看导航栏移动端适配。我在base.html里的导航栏加了汉堡按钮:

<button class="nav-toggle" id="navToggle">☰</button>

然后main.js里:

const navToggle = document.getElementById("navToggle"); const navMenu = document.querySelector(".site-header ul"); navToggle.addEventListener("click", function() { navMenu.classList.toggle("show"); });

配合CSS:

.nav-toggle { display: none; background: none; border: none; color: #fff; font-size: 1.5rem; cursor: pointer; } @media (max-width: 600px) { .nav-toggle { display: block; } .site-header ul { display: none; flex-direction: column; width: 100%; background: #2c3e50; position: absolute; top: 60px; left: 0; padding: 10px 20px; } .site-header ul.show { display: flex; } }

这个交互的逻辑是:默认隐藏移动端菜单,点击汉堡按钮时给菜单加上show类,CSS里.show让菜单显示出来。这个模式下用户才能在手机浏览器上正常使用导航。

回到顶部按钮更好做,在base.html的</body>前放一个按钮元素:

<button id="backToTop" class="back-to-top">↑</button>

JS里:

const backToTop = document.getElementById("backToTop"); window.addEventListener("scroll", function() { if (window.scrollY > 400) { backToTop.classList.add("visible"); } else { backToTop.classList.remove("visible"); } }); backToTop.addEventListener("click", function() { window.scrollTo({ top: 0, behavior: "smooth" }); });

CSS里把按钮固定在右下角,默认透明隐藏,滚动了400像素以上才出现:

.back-to-top { position: fixed; right: 20px; bottom: 40px; width: 40px; height: 40px; border-radius: 50%; background: #2c3e50; color: #fff; border: none; cursor: pointer; opacity: 0; visibility: hidden; transition: all 0.3s; } .back-to-top.visible { opacity: 1; visibility: visible; }

这些交互单独拿出来都很简单,但合在一起,网站给人的体验就从“一个静态文档”变成了“一个真正有人维护的站点”。

5. 从JSON文件读取文章:让博客具备扩展性

5.1 为什么要把文章数据从代码里拆出来

上一阶段文章数据是直接写在app.py里的Python列表。这在使用上有个很大的尴尬:每写一篇新文章,都要改Python代码。如果哪次不小心弄丢一个逗号或引号,程序直接报错。而且“内容”和“代码”混在一起,时间一长,代码会越来越臃肿。

我当时决定把文章抽到独立的JSON文件里,这样文章的增删改完全不用碰Python代码,只改一个数据文件就能完成。JSON本身就是JavaScript对象的一种写法,格式友好,容易阅读,也容易写成Python字典。

具体做法是,把文章数据存到data/articles.json:

[ { "id": 1, "title": "我的第一篇博客", "date": "2024-06-01", "summary": "这是一篇关于博客开张的碎碎念。", "content": "大家好,这是我的个人博客第一次发文。\n\n以后会在这里记录一些学习Python的过程和心得。" }, { "id": 2, "title": "Python学习笔记:列表与字典", "date": "2024-06-10", "summary": "用最通俗的语言理解Python里最常用的两种数据结构。", "content": "列表是带顺序的盒子,字典是带标签的盒子。\n\n这篇文章记录了我初学Python时对这两种结构的理解。" } ]

后端读取部分改成:

import json from flask import Flask, render_template, abort app = Flask(__name__) def load_articles(): with open("data/articles.json", "r", encoding="utf-8") as f: return json.load(f) @app.route("/") def home(): articles = load_articles() return render_template("index.html", articles=articles)

json.load会把JSON文件内容解析成Python列表,之后的操作和之前一模一样,不需要改动模板。但现在的收益很明显:以后写新文章,编辑JSON文件即可,代码一行不用动。

5.2 UTF-8编码:彻底根治中文乱码

读取JSON文件时有一个细节必须注意:open()函数里必须带encoding="utf-8"。如果不带这个参数,Windows系统默认会用GBK编码读取文件,而JSON文件是用UTF-8编码保存的,两者不一致就会解码失败,程序直接抛异常。这是新手在本地运行Flask读中文数据时最常见的报错,我当初第一次遇到的时候完全摸不着头脑,排查了很久才发现就是编码参数的问题。

同样的道理,在app.py文件顶部,如果Python代码里用了中文注释,也应该在编辑器里确认文件保存编码是UTF-8。VS Code默认就是UTF-8,但有些编辑器默认可能是GBK,保存之后再运行,中文就变成乱码了。

5.3 文章模板里支持换行:split的妙用

JSON文件里的content字段中,我用了\n\n来表示段落之间的空行。但HTML渲染时,多个空格和换行都会被合并成一个空格,直接输出{{ article.content }}到达页面后,原先的段落分隔就全部消失了,文章会变成一整坨文字。

解决办法是模板里遍历split后的列表,把每段用一个<p>包起来:

<div class="post-content"> {% for paragraph in article.content.split("\n") %} <p>{{ paragraph }}</p> {% endfor %} </div>

这样在JSON文件里写的每个段落,到页面上就是一个独立的HTML段落,段间距和缩进就都正常了。这个小技巧在处理多行文本时非常实用,大家一定要记住。

6. 调试实战与常见问题排查

6.1 页面样式不加载:静态文件路径全都写错了吗

这是我遇到的第一个让人抓狂的问题。后端跑起来,页面内容出来了,但所有CSS样式全没生效,图片全裂。后来才发现是模板里静态文件的路径写法不对。

我在模板里用绝对路径写<link rel="stylesheet" href="/static/css/style.css">,其实也能生效,但Flask官方推荐的方式是用url_for函数动态生成:

<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">

这两种方式在多数情况下效果一样,但url_for更规范的一点是:它会在网站根路径发生变化时自动调整。比如以后你给网站加了前缀路径(比如/myblog),href="/static/css/style.css"就会失效,而url_for生成的链接依然正确。所以,养成用url_for的好习惯,能帮你避开未来很多隐蔽的链接报错。

样式不加载时,还应该打开浏览器开发者工具(F12),切到Network面板,刷新页面看看CSS文件请求是什么状态。如果返回404,就要检查是不是static文件夹的路径层级放错了。Flask要求静态文件夹必须和templates文件夹平级,都在项目根目录下。

6.2 端口被占用:Address already in use

Flask默认跑在5000端口。如果你同时跑了两个Flask实例,或者5000端口被另一个程序占用了,终端就会报OSError: [Errno 98] Address already in use。

解决办法有几种。简单粗暴的方法是找到占用的进程并结束它,Windows下:

netstat -ano | findstr :5000 taskkill /PID 进程号 /F

macOS/Linux下:

lsof -i :5000 kill -9 PID

更省事的方法是给Flask换个端口:

if __name__ == "__main__": app.run(debug=True, port=5001)

我在开发时经常把端口改成5001用,这样可以同时开着另一个占5000端口的服务,互不打扰。

6.3 Jinja2模板语法报错排查

模板报错信息是英文的,对新手不友好,但它透露的信息量很大。最常见的报错之一是:

jinja2.exceptions.TemplateSyntaxError: expected token 'endblock', got 'endblock'

这类问题通常是模板里{% endblock %}写成了{% endblock %}之类的小错误,或者{% if %}没有对应的{% endif %}。Jinja2语法要求每个块控制语句必须闭合。

我的排查经验是:把报错指向的行号附近的代码整体复制出来,仔细检查是不是少了闭合标签。如果模板太复杂,就在HTML代码里注释掉一半再刷新,用二分法定位出错的位置。这个思路对任何编程问题都适用——复杂问题拆小之后再排查,效率最高。

6.4 修改代码后页面没变化:缓存与服务器双重重启

调试模式下修改app.py,Flask会自动重启,让新代码生效。但如果你修改的是CSS、JS等静态文件,会发现刷新页面后样式没有任何变化——尤其是CSS或JS文件被浏览器缓存了。这是很多新手疑惑的地方。

解决办法很简单,刷新时按Ctrl+Shift+R强制刷新,绕过浏览器缓存。也可以用开发者工具在Network面板里勾选Disable cache,让每次请求都从服务器重新拉取文件。

6.5 格式化JSON文件时踩过的坑

写articles.json文件时,如果手动编辑,很容易多一个逗号或少一个引号,导致json.load报错。最典型的就是:最后一项后面多写一个逗号,比如:

[ { "id": 1, "title": "第一篇文章" }, { "id": 2, "title": "第二篇文章" }, ]

这种写法在JavaScript的数组语法里是合法的(trailing comma),但在JSON标准里是非法的,Python的json.load会直接报错。如果你发现程序启动时报Expecting ',' delimiter,百分之百是JSON文件格式有误。

建议写完JSON后在VS Code里用Shift+Alt+F格式化一下,或者直接使用在线JSON校验工具检查。更稳妥的办法是让Python告诉你哪里错了——报错信息会给出具体行号和列号,定位起来很快。

7. 进阶建议与个人拓展方向

到此为止,一个基于Python Flask的个人博客网站就完整可用了。它包含了一个正规博客站应有的骨架:文章列表、文章详情、关于页、404页、移动端适配、优雅的CSS样式和轻量的JS交互。把一个Python零基础、完全没接触过Web开发的自己带到能独立写出这个项目的状态,我是深有体会的——过程中踩的每一个坑,都让我对“网页是如何工作”的理解更深了一层。

如果说接下来你还想继续拓展这个项目,我建议按这样的优先级去走。

先把文章数据换成SQLite数据库。JSON文件存数据适合文章数量少的阶段,但当你写了50篇、100篇文章后就会发现:查询指定文章需要遍历整个列表,效率很低,更不用说筛选分类和标签了。SQLite是Python内置支持的数据库,不需要额外安装数据库服务,用sqlite3模块就能操作。数据结构的迁移从JSON到SQLite,是个人博客从“练手项目”走向“正式应用”的关键一步。

其次是添加文章发布后台。现在新增文章只能通过编辑JSON文件完成,这明显不方便——非技术用户根本不会写JSON。做一个简单的后台管理页面,用HTML表单提交文章,后端接收数据再存到数据库,这个流程涵盖了Web开发里最常见的“增删改查”全链路,含金量很高。

然后是标签、分类和时间归档,这些功能在博客网站里使用频率非常高,做出来以后网站的交互层级会立刻丰富起来,用户的停留时间也会变长。

最后可以把网站部署上线。把代码推到公网让别人访问,会带来一种完全不同的成就感。不过请记住我在第3节里说的,上线前老老实实把debug=True关掉,换成适合生产环境的服务器。

我第一次在浏览器里看到自己亲手设计的博客页面完整渲染出来的时候,那种激动到现在还记得。不是因为实现了什么了不起的技术,而是因为那一刻我意识到:过去那些模模糊糊的“前端”“后端”“框架”“模板”的概念,第一次在我脑子里形成了一个完整的闭环。这个项目的核心价值不在于代码写得有多漂亮,而在于它把一套Web开发的基本功,拆成了一个个你可以亲手触碰、亲手修改、亲手验证的小块,每一个都看得见、摸得着。如果你的Python水平已经能看懂列表和字典,那么现在就可以打开编辑器,从复制这篇文章里的代码开始,一步一步把属于你的个人博客搭起来。相信我,过程中遇到的每一个报错,都是你在通往“能用Python做真正东西”这条路上,留下的一个个脚印。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询