OpenCode 实践课:4 分钟用 Python 写出一个命令行 DNS 记录批量查询工具

项目介绍

有时候排查域名解析问题时,我们需要反复在终端和网页 DNS 工具之间切换,效率很低。今天用 OpenCode 写一个命令行 DNS 记录批量查询工具,支持 A、AAAA、MX、CNAME、TXT 等多种记录类型,还能批量查询多个域名,输出 JSON 格式方便集成到脚本中。

  • 语言:Python 3
  • 依赖:dnspython
  • 代码行数:约 80 行

准备工作

确保本地安装了 Python 3.8+ 和 OpenCode。新建项目目录后,只需一个命令启动:

pip install dnspython

然后在终端打开项目目录,直接用自然语言跟 OpenCode 对话即可。

实践过程

第一步:创建基础 DNS 查询脚本

我对 OpenCode 说:

> 帮我用 Python 写一个命令行 DNS 查询工具,接收域名参数,查询 A 记录并打印结果。使用 dnspython 库。

OpenCode 立即生成了初版脚本,导入了 dns.resolver,用 argparse 处理命令行参数,核心查询逻辑不到 20 行。

import argparse
import dns.resolver

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("domain")
    args = parser.parse_args()
    answers = dns.resolver.resolve(args.domain, "A")
    for a in answers:
        print(a.to_text())

if __name__ == "__main__":
    main()

关键要点:OpenCode 对标准库非常熟悉,argparse 和异常处理的用法都是惯用的 Python 写法。

第二步:添加多种记录类型支持

一个 DNS 查询工具不能只查 A 记录。我继续追加需求:

我对 OpenCode 说:

> 加一个 -t 参数支持指定记录类型,比如 A、AAAA、MX、CNAME、TXT、NS。再添加 --all 参数一键查询所有常见类型。

OpenCode 更新了代码,定

义了 RECORD_TYPES 列表,将 resolve() 的第二个参数改为动态传入,同时在 argparse 中新增了 --type--all 两个参数。这是典型的"增量迭代"——每加一个功能只需一句话。

第三步:添加批量查询和 JSON 输出

单域名查询还不够,排查问题时经常需要对比多个域名。

我对 OpenCode 说:

> 支持一次传多个域名,比如 python dns.py baidu.com google.com。再加一个 --json 参数,输出 JSON 格式的结果。

OpenCode 将 domains 改为 nargs="+" 接收多个参数,新增了 query_domain() 函数统一处理每个域名的查询,返回结构化字典。JSON 输出直接用 json.dumps 一行搞定,TTL 信息也顺手带上了。

results = [query_domain(d, types, args.nameserver) for d in args.domains]

if args.json:
    print(json.dumps(results, ensure_ascii=False, indent=2))

关键要点:OpenCode 善于把握"最小改动原则"——它不会重写整个文件,而是在现有代码基础上精准修改。这点在多人协作或维护老项目时非常重要。

第四步:完善容错和输出格式

最后一步是提升健壮性——超时、域名不存在、网络异常都要有中文提示。

我对 OpenCode 说:

> 加上超时处理和错误提示,域名不存在时提示"域名不存在",超时时提示"查询超时"。美化一下输出格式,加上分隔线和 TTL 显示。

OpenCode 给 Resolver 设置了 5 秒超时,用 try/except 分别捕获 NXDOMAINTimeout 等异常,输出带颜色的分隔线。还贴心地添加了 epilog 示例帮助,用户执行 python dns.py -h 就能看到用法。

整个过程四轮对话、四分钟完成,无需手写一行代码。

完整代码

#!/usr/bin/env python3
import argparse
import json
import dns.resolver

RECORD_TYPES = ["A", "AAAA", "MX", "CNAME", "TXT", "NS", "SOA", "CAA"]


def resolve_dns(domain: str, record_type: str, nameserver: str = None):
    resolver = dns.resolver.Resolver()
    resolver.timeout = 5
    resolver.lifetime = 5
    if nameserver:
        resolver.nameservers = [nameserver]

    answers = resolver.resolve(domain, record_type)
    return [{"value": str(a).replace('"', ''), "ttl": answers.rrset.ttl} for a in answers]


def query_domain(domain: str, types: list, nameserver: str = None):
    result = {"domain": domain, "records": {}}
    for rt in types:
        try:
            result["records"][rt] = resolve_dns(domain, rt, nameserver)
        except dns.resolver.NoAnswer:
            result["records"][rt] = []
        except dns.resolver.NXDOMAIN:
            result["records"][rt] = [{"value": f"域名 {domain} 不存在", "ttl": 0}]
        except dns.resolver.Timeout:
            result["records"][rt] = [{"value": "查询超时", "ttl": 0}]
        except Exception as e:
            result["records"][rt] = [{"value": f"查询失败: {e}", "ttl": 0}]
    return result


def display_result(result: dict):
    print(f"\n{'='*60}")
    print(f"域名: {result['domain']}")
    print(f"{'='*60}")

    for rt, records in result["records"].items():
        print(f"\n[{rt} 记录]")
        if not records:
            print("  (无记录)")
            continue
        for r in records:
            ttl = f"  [TTL: {r['ttl']}s]" if r['ttl'] > 0 else ""
            print(f"  → {r['value']}{ttl}")


def main():
    parser = argparse.ArgumentParser(
        description="DNS 记录查询工具 — 命令行版 dig",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
示例:
  %(prog)s baidu.com
  %(prog)s baidu.com -t MX
  %(prog)s baidu.com --all -n 8.8.8.8
  %(prog)s baidu.com google.com --json
        """
    )
    parser.add_argument("domains", nargs="+", help="要查询的域名(支持多个)")
    parser.add_argument("-t", "--type", default="A", help="记录类型 (默认: A)")
    parser.add_argument("-n", "--nameserver", help="指定 DNS 服务器")
    parser.add_argument("-a", "--all", action="store_true", help="查询所有常见记录")
    parser.add_argument("--json", action="store_true", help="JSON 格式输出")

    args = parser.parse_args()
    types = RECORD_TYPES if args.all else [args.type.upper()]
    results = [query_domain(d, types, args.nameserver) for d in args.domains]

    if args.json:
        print(json.dumps(results, ensure_ascii=False, indent=2))
    else:
        for r in results:
            display_result(r)
        print()


if __name__ == "__main__":
    main()

运行示例:

$ python dns.py baidu.com -t A

============================================================
域名: baidu.com
============================================================

[A 记录]
  → 110.242.68.66  [TTL: 600s]
  → 39.156.66.10  [TTL: 600s]

小结

用 OpenCode 开发这个小工具的感受可以概括为三个字:说就行

不用查 dns.resolver 的 API 文档,不用翻 argparsenargs 怎么用,也不用纠结 argparse 的排版格式——直接告诉 OpenCode 你要什么,它生成代码,你验证结果。四轮对话下来,一个功能完善、带容错、支持 JSON 输出的 DNS 查询工具就完成了。

传统方式你可能需要花 20 分钟查文档、调参数、处理边界情况;用 OpenCode 只需要 4 分钟,你把精力放在"想清楚需求"上,而不是"记住 API"上。这就是 AI 编程助手带来的效率跃迁。