2026-07-03 16:34:00 +08:00
2026-07-03 16:07:30 +08:00
2026-05-10 11:04:30 +08:00
2026-05-10 11:03:45 +08:00

py-blog

像写一份课程作业那样,搭一个属于自己的博客。
Build your own blog the way you'd hand in a class project.

中文 · English


中文文档

为什么要再造一个博客

市面上的博客产品越来越像「企业级 CMS」:装一堆插件、配一套主题、改一坨 YAML, 最后才能写下第一行字。对一个刚入门的同学来说,这个过程不是在写博客, 是在向所有人宣告自己「不会用」。

但是新手也有写博客的需求:

  • 想把课程笔记整理出来;
  • 想把第一份小作品挂到自己的域名下;
  • 想拥有一个写得不漂亮、但完全属于自己的角落。

py-blog 就是为这种需求做的。

核心理念:像项目作业一样构建博客

  • 一个 blog.py 文件就是整个项目,没有 src/,没有十层目录。
  • 看得懂 Python 基本语法,就看得懂这个博客;想改首页样式,直接搜模板字符串。
  • 部署像交作业:装依赖、跑脚本、看终端输出 —— 没有 Webpack,没有 Node。

功能一览

  • 单文件 PythonFastAPI + SQLite + Jinja2一份脚本即一个站点
  • Markdown 写作:上传 .md 即可,保存时预渲染为 HTML 缓存,访问零开销。
  • 数学公式:内置 KaTeX,支持 $...$ / $$...$$ / \(...\) / \[...\]
  • 图片走图床:正文里用外链 URL,省去本地存储与备份烦恼。
  • 树形分组:按目录组织文章,同级按字典序,文章可按时间或手动顺序排列。
  • 单管理员后台:密码登录、CSRF、Session,够用且不繁琐。
  • 备案号挂载:BLOG_ICP_TEXT / BLOG_ICP_URL 环境变量直接生效,国内合规。
  • 反代友好:默认监听 127.0.0.1:8765,套 Nginx 即可上 HTTPS。

30 秒上手

git clone https://github.com/WHC2006/py-blog.git
cd py-blog
pip install -r requirements.txt
python blog.py

打开 http://127.0.0.1:8765,登录入口在 /admin/login。 若没设置 BLOG_ADMIN_PASSWORD首次启动会在终端打印一次随机密码,请立刻收好。

环境变量

变量 默认 说明
BLOG_HOST 127.0.0.1 监听地址,建议保持本机由 Nginx 反代
BLOG_PORT 8765 监听端口
BLOG_DATA_DIR data SQLite 与运行数据目录
BLOG_SITE_TITLE 站点标题
BLOG_ADMIN_PASSWORD 管理员密码;未设置则首次启动随机生成并打印
BLOG_SECRET_KEY Session 签名密钥;未设置则自动生成并持久化
BLOG_ICP_TEXT 页脚备案文案,例如 京ICP备xxxxxx号-1
BLOG_ICP_URL 备案查询链接,例如 https://beian.miit.gov.cn/

Nginx 反代示例

server {
    listen 443 ssl http2;
    server_name your.domain.com;

    # ssl_certificate ...;
    # ssl_certificate_key ...;

    large_client_header_buffers 8 16k;

    location / {
        proxy_pass http://127.0.0.1:8765;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

若使用 EdgeOne / WAF,请为 /admin/* 添加放行规则,避免登录 POST 被拦截。

不打算做的事

  • 不内置富文本编辑器:写 Markdown 用你顺手的本地编辑器即可。
  • 不内置评论系统:需要时挂 Giscus / Disqus / Waline。
  • 不做主题市场:想换样式直接改模板字符串,这才像做作业

协议

MIT。随便用,写得不好别骂得太狠。


English

Why another blog

Most blog platforms today feel like enterprise CMSs: plugins, themes, YAML configs and a build pipeline before you can write a single sentence. For a beginner, that whole process isn't writing a blog — it's announcing to the world that you can't use one.

But beginners also deserve a blog:

  • to publish their class notes;
  • to put their very first project under their own domain;
  • to own a small, imperfect corner of the internet.

py-blog is built for that.

Core idea: build a blog like a class project

  • One file: blog.py is the whole project. No src/, no ten-level tree.
  • If you can read basic Python, you can read this blog. Want to tweak the homepage? Search the template strings inline.
  • Deploying feels like submitting homework: install deps, run the script, read the terminal. No Webpack, no Node, no incantations.

Features

  • Single-file Python: FastAPI + SQLite + Jinja2 — one script, one site.
  • Markdown-first: upload .md files; HTML is pre-rendered on save.
  • Math: KaTeX out of the box ($...$, $$...$$, \(...\), \[...\]).
  • Images via external CDN (image hosts) — no local upload/backup hassle.
  • Tree-structured categories; siblings sorted A→Z; articles sorted by time or by a manual display_order.
  • Single-admin panel: password login, CSRF, sessions. Enough, not more.
  • Built-in ICP footer (BLOG_ICP_TEXT / BLOG_ICP_URL) for hosting in China.
  • Reverse-proxy friendly: binds to 127.0.0.1:8765 by default; put Nginx in front for HTTPS.

Quick start

git clone https://github.com/WHC2006/py-blog.git
cd py-blog
pip install -r requirements.txt
python blog.py

Open http://127.0.0.1:8765. Admin login lives at /admin/login. If BLOG_ADMIN_PASSWORD is unset, a random password is printed once to the terminal on first boot — copy it immediately.

Environment variables

Variable Default Description
BLOG_HOST 127.0.0.1 Bind address; keep local and front with Nginx
BLOG_PORT 8765 Bind port
BLOG_DATA_DIR data Data directory (SQLite, etc.)
BLOG_SITE_TITLE Site title
BLOG_ADMIN_PASSWORD Admin password; auto-generated on first boot if unset
BLOG_SECRET_KEY Session signing key; auto-generated and persisted if unset
BLOG_ICP_TEXT Footer ICP text (China hosting)
BLOG_ICP_URL Footer ICP link

Nginx reverse proxy

server {
    listen 443 ssl http2;
    server_name your.domain.com;

    # ssl_certificate ...;
    # ssl_certificate_key ...;

    large_client_header_buffers 8 16k;

    location / {
        proxy_pass http://127.0.0.1:8765;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

If you sit behind EdgeOne / a WAF, whitelist /admin/* so the login POST isn't silently dropped.

Non-goals

  • No bundled rich-text editor — use your favourite Markdown editor locally.
  • No bundled comment system — drop in Giscus / Disqus / Waline if needed.
  • No theme marketplace — edit the template strings; that's the whole point.

License

MIT. Use it freely; be gentle when judging the code.

S
Description
No description provided
Readme MIT
62 KiB
Languages
Python 93.3%
Shell 6.7%