用 GitHub Pages 搭一个小组周报网站:周报用 Markdown 写在仓库里,推送到 main 后 GitHub Actions 自动构建成 HTML、检查站内断链并发布。
先看清成品
build.py已在本机运行:第一次构建发现 1 个断链并以退出码 1 失败(见调试示例),修复后构建 2 页,并用本地 HTTP 服务器确认页面标题、导航和表格正确。pages.yml已通过 actionlint 和 GitHub workflow schema 校验。没有推送到 GitHub 实际部署 Pages。
注意:遵守 GitHub 服务条款。不要在仓库、日志或 issue 里提交密钥和个人数据;不要用 Actions 挖矿或做与仓库无关的大量计算;不要共享账号。
Pages 内容规范:GitHub Pages 不能用于商业交易站点或违法内容,具体以 GitHub Pages 使用限制为准。
做完之后你会得到什么?
| 成果 | 说明 |
|---|---|
| 周报站 | https://<用户名>.github.io/<仓库名>/ |
| 构建脚本 | Markdown → HTML,自动生成导航 |
| 断链检查 | 有断链就不发布 |
| 部署 workflow | 构建与部署分两个任务 |
需要准备哪些环境?
| 项目 | 要求 | 说明 |
|---|---|---|
| GitHub 仓库 | 公开仓库可免费用 Pages | 私有仓库用 Pages 需要付费计划,以 GitHub 文档为准 |
| Python | 3.11 以上 | 本地预览 |
| Python-Markdown | 3.11.2 | 写在 requirements.txt |
文件结构
19-github-B-src/
├── build.py
├── requirements.txt
├── content/
│ ├── index.md
│ └── 2026-w42.md
└── .github/workflows/pages.yml
第 1 步:写内容
content/index.md:
# 首页
本站每周发布一次小组周报。最新一期见 [第 42 周](2026-w42.md)。
| 周次 | 主题 |
| :--- | :--- |
| 42 | 发布流程迁移到 GitHub Actions |
content/2026-w42.md:
# 2026 年第 42 周
- 文档站改为 Actions 部署
- 下周计划:整理部署文档
[返回首页](index.md)
写作时站内链接写 xxx.md,在 GitHub 上直接浏览也能点;构建时自动改成 .html。
第 2 步:构建脚本
build.py:
"""把 content/*.md 构建成 _site/*.html,并检查站内链接。"""
import html, re, sys
from pathlib import Path
import markdown
SRC, OUT = Path("content"), Path("_site")
TPL = """<!doctype html>
<html lang="zh-CN"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>{title} · 小组周报</title>
<style>body{{max-width:46rem;margin:2rem auto;padding:0 1rem;font:16px/1.7 system-ui,sans-serif}}
nav a{{margin-right:1rem}}</style></head>
<body><nav>{nav}</nav><main>{body}</main></body></html>
"""
def title_of(text, fallback):
m = re.search(r"^# (.+)$", text, re.M)
return m.group(1).strip() if m else fallback
def build():
OUT.mkdir(exist_ok=True)
pages = sorted(SRC.glob("*.md"))
nav = " ".join(f'<a href="{p.stem}.html">{html.escape(title_of(p.read_text(encoding="utf-8"), p.stem))}</a>' for p in pages)
for p in pages:
text = p.read_text(encoding="utf-8")
# 写作时链接写 xxx.md,构建时改成 xxx.html
body = markdown.markdown(re.sub(r"\]\(([\w-]+)\.md\)", r"](\1.html)", text), extensions=["tables"])
(OUT / f"{p.stem}.html").write_text(TPL.format(title=html.escape(title_of(text, p.stem)), nav=nav, body=body), encoding="utf-8")
(OUT / ".nojekyll").write_text("")
return pages
def check_links():
bad = []
for f in OUT.glob("*.html"):
for href in re.findall(r'href="([^"#:]+)"', f.read_text(encoding="utf-8")):
if not (OUT / href).exists():
bad.append(f"{f.name} -> {href}")
return bad
if __name__ == "__main__":
pages = build()
bad = check_links()
print(f"构建 {len(pages)} 页到 {OUT}/")
for b in bad: print("✗ 断链:", b)
sys.exit(1 if bad else 0)
.nojekyll 告诉 Pages 不要再用 Jekyll 处理;所有链接都是相对路径,所以放在 /仓库名/ 子路径下也能用。
第 3 步:本地构建预览
pip install -r requirements.txt
python build.py
cd _site && python -m http.server 8000
检查结果:终端输出 构建 2 页到 _site/;浏览器打开 http://localhost:8000/index.html,能看到导航和表格。
第 4 步:部署 workflow
.github/workflows/pages.yml:
name: Deploy Pages
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.13"
cache: pip
- run: pip install -r requirements.txt
- name: 构建并检查链接
run: python build.py
- uses: actions/configure-pages@v6
- uses: actions/upload-pages-artifact@v5
with:
path: _site
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v5
deploy-pages 所在任务必须有 pages: write 和 id-token: write,并部署到 github-pages 环境。
第 5 步:打开 Pages 并发布
- 仓库 Settings → Pages,Source 选 GitHub Actions。
- 推送到 main。
- 在 Actions 里看到 build、deploy 两个任务都成功后,deploy 任务上会显示站点地址。
调试示例
最初 2026-w42.md 里写了指向尚不存在的第 43 周的链接,本机实际输出:
构建 2 页到 _site/
✗ 断链: 2026-w42.html -> 2026-w43.html
退出码为 1,在 CI 里 build 任务会失败,deploy 不会执行,线上站点保持旧版本。删掉链接(或先写好第 43 周)后通过。
出错了怎么办?
| 现象 | 原因 | 解决 |
|---|---|---|
| deploy 报权限错误 | 缺 pages: write / id-token: write |
按本文 permissions 写 |
| 站点 404 | Settings → Pages 的 Source 没选 GitHub Actions | 改成 GitHub Actions |
| 样式或链接丢失 | 用了 /xxx.html 绝对路径 |
改成相对路径 |
| 部署被环境规则拒绝 | github-pages 环境只允许特定分支 | 在 Settings → Environments 检查分支规则 |
| 中文乱码 | 文件不是 UTF-8 | 统一 UTF-8 保存 |
模板规则
- 一期周报一个 Markdown 文件,文件名
YYYY-wNN.md。 - 断链检查不通过不发布。
- 依赖版本固定在 requirements.txt。
项目资源
| 用途 | 地址 |
|---|---|
| 用自定义 workflow 发布 Pages | https://docs.github.com/en/pages/getting-started-with-github-pages/using-custom-workflows-with-github-pages |
| actions/deploy-pages | https://github.com/actions/deploy-pages |
| Python-Markdown | https://python-markdown.github.io/ |
| actionlint | https://github.com/rhysd/actionlint |
这个站点只有一个构建脚本和一个 workflow,没有引入框架。周报写完推送就上线,构建出错会挡住发布,线上版本始终是能用的。