OpenCode 实践课:4 分钟用 Python 写一个 Markdown 链接检查器

项目介绍

写博客、维护文档时,最怕什么?死链。Markdown 文件里的外部链接说挂就挂,手动一个个点开检查又太费劲。今天我们用 OpenCode 花 4 分钟写一个命令行 Markdown 链接检查器——自动扫描 .md 文件中的所有链接,检测死链并输出报告。

  • 技术栈:Python 3.x(仅用标准库)
  • 最终代码:约 100 行
  • 功能:扫描目录下所有 Markdown 文件,提取 HTTP(S) 链接,并发检测可达性,生成检查报告

准备工作

确保本地已安装 Python 3.8+ 和 OpenCode。在终端进入项目目录后,用 opencode 命令启动交互会话即可。

> 提示:如果你还不了解 OpenCode 的安装与基本用法,可以参考我之前的《OpenCode 完全指南》系列。

实践过程

第一步:描述需求,让 OpenCode 生成初始代码

打开终端,进入项目目录,我对 OpenCode 说:

> 帮我写一个 Python 命令行工具,扫描当前目录下所有 .md 文件,提取其中的 HTTP/HTTPS 链接,逐个检测链接是否可达(返回 200 才算有效),最后输出检测报告。要求只用标准库,代码控制在 100 行左右。

OpenCode 先后执行了 ls 查看目录结构,然后用 Write 工具创建了 linkchecker.py,核心骨架如下:

import re
import sys
import urllib.request
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path

它自动选用了 urllib.request 发请求、concurrent.futures 做并发检测、pathlib 遍历文件——全程不需要我指定具体库名,OpenCode 自己做了技术选型。

关键要点:描述需求时把你的"约束条件"说清楚(只用标准库、100 行左右、输出报告),OpenCode 会在约束内做最优选择。

第二步:发现 bug,自然语言修复

代码跑起来了,但我发现一个小问题——每次检查都要等很久。我对 OpenCode 说:

> 检查太慢了,能不能加个超时时间?另外有些网站返回 301/302 重定向,也算它有效吧。

OpenCode 立刻定位到 urllib.request.urlopen 的调用处,加了 timeout=5 参数,并把状态码判断从 == 200 改为 200 <= code < 400

def check_link(url, timeout=5):
    try:
        req = urllib.request.Request(url, method='HEAD')
        req.add_header('User-Agent', 'LinkChecker/1.0')
        resp = urllib.request.urlopen(req, timeout=timeout)
        return url, resp.status, None
    except Exception as e:
        return url, None, str(e)

它甚至贴心地加上了 User-Agent 头,避免被某些网站拒绝访问——这就是 AI 编程的好处,它会顺便帮你补上你没想到的细节。

关键要点:遇到问题不要自己翻代码,直接用自然语言描述现象和期望,OpenCode 会精准定位修改点。

第三步:美化输出,一条指令搞定

功能跑通了,但输出就是一坨文本。我对 OpenCode 说:

> 输出报告太丑了,用表格格式排版,把有效链接和无效链接分开,最后加个统计摘要。

OpenCode 随即重写了输出部分,加入了终端表格边框和 emoji 状态标识:

def print_report(results):
    ok_links = [(url, code) for url, code, err in results if err is None]
    bad_links = [(url, err) for url, code, err in results if err is not None]

    print("\n" + "=" * 60)
    print("  Markdown 链接检查报告")
    print("=" * 60)

    for url, code in ok_links:
        print(f"  [OK] {code}  {url}")
    for url, err in bad_links:
        print(f"  [FAIL] {url}")
        print(f"         原因: {err}")

    total = len(ok_links) + len(bad_links)
    print(f"\n总计: {total}  有效: {len(ok_links)}  无效: {len(bad_links)}")

关键要点:OpenCode 理解"用表格格式排版"这种模糊需求,会自动转化成具体的代码实现。你不需要给出 CSS 级别的精确指令。

第四步(可选):加命令行参数

我对 OpenCode 说:

> 加个 --dir 参数指定扫描目录,默认用当前目录。再支持 --timeout 参数自定义超时秒数。

OpenCode 在顶部补了 import argparse,生成了标准的参数解析逻辑,同时更新了 main() 函数来接收这些参数。整个过程不到 30 秒。

完整代码

import re
import sys
import argparse
import urllib.request
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path

LINK_PATTERN = re.compile(r'\[([^\]]*)\]\(((?:https?://)[^\)]+)\)')


def find_md_files(directory):
    return list(Path(directory).rglob("*.md"))


def extract_links(filepath):
    links = []
    text = filepath.read_text(encoding="utf-8", errors="ignore")
    for match in LINK_PATTERN.finditer(text):
        links.append(match.group(2))
    return links


def check_link(url, timeout=5):
    try:
        req = urllib.request.Request(url, method='HEAD')
        req.add_header('User-Agent', 'LinkChecker/1.0')
        resp = urllib.request.urlopen(req, timeout=timeout)
        code = resp.status
        return (url, code, None) if 200 <= code < 400 else (url, code, f"状态码 {code}")
    except Exception as e:
        return (url, None, str(e))


def print_report(results):
    ok_links = [(u, c) for u, c, e in results if e is None]
    bad_links = [(u, e) for u, c, e in results if e is not None]

    print("\n" + "=" * 60)
    print("  Markdown 链接检查报告")
    print("=" * 60)

    if ok_links:
        print("\n  ✅ 有效链接:\n")
        for url, code in ok_links:
            print(f"  [{code}] {url}")

    if bad_links:
        print("\n  ❌ 无效链接:\n")
        for url, err in bad_links:
            print(f"  {url}")
            print(f"  └─ {err}\n")

    total = len(ok_links) + len(bad_links)
    print("-" * 60)
    print(f"  总计: {total}  |  有效: {len(ok_links)}  |  无效: {len(bad_links)}")
    print("=" * 60 + "\n")


def main():
    parser = argparse.ArgumentParser(description="Markdown 链接检查器")
    parser.add_argument("--dir", default=".", help="扫描目录 (默认: 当前目录)")
    parser.add_argument("--timeout", type=int, default=5, help="请求超时秒数 (默认: 5)")
    args = parser.parse_args()

    md_files = find_md_files(args.dir)
    if not md_files:
        print("未找到 Markdown 文件。")
        sys.exit(0)

    print(f"找到 {len(md_files)} 个 Markdown 文件,正在提取链接...")

    all_links = []
    for f in md_files:
        links = extract_links(f)
        if links:
            print(f"  {f} -> {len(links)} 个链接")
            all_links.extend(links)

    unique_links = list(set(all_links))
    print(f"\n去重后共 {len(unique_links)} 个链接,开始检测...\n")

    results = []
    with ThreadPoolExecutor(max_workers=10) as executor:
        futures = {executor.submit(check_link, url, args.timeout): url for url in unique_links}
        for future in as_completed(futures):
            results.append(future.result())

    print_report(results)


if __name__ == "__main__":
    main()

运行方式:

python linkchecker.py --dir ./my-blog --timeout 3

如果没有指定 --dir,默认扫描当前目录下所有 .md 文件。输出会清晰列出有效 / 无效链接及失败原因。

小结

用 OpenCode 开发这个链接检查器,我全程没有写一行 Python 代码。四轮对话,每次用自然语言描述需求或问题,OpenCode 负责:

| 环节 | 我做的 | OpenCode 做的 |
|------|--------|---------------|
| 选型 | 说"只用标准库" | 选 urllib + concurrent.futures |
| 核心逻辑 | 说"提取链接并检测" | 写正则 + 并发 HTTP 检测 |
| Bug 修复 | 说"太慢了,加超时" | 精准定位改代码 |
| 美化输出 | 说"用表格排版" | 重写报告格式化 |
| 命令行参数 | 说"加 --dir 和 --timeout" | 补 argparse 逻辑 |

对比传统开发方式:自己写可能需要翻文档查 urllib 用法(我之前确实没用过 urllib.request.Requestmethod='HEAD')、手动调试正则表达式、还要考虑异常处理的各种边界情况。用 OpenCode 之后,我的角色从"编码者"变成了"产品经理"——只管提需求、验证结果,具体实现细节交给 AI。

如果你也想试试这种开发方式,挑一个日常小需求,用 OpenCode 聊上几轮,你会感受到这种"自然语言驱动开发"的效率提升。