R / Richie全部文章 ↑

Python · 4 分钟阅读

`dnspython`:DNS 处理库

目录


dnspython 是 Python 的 DNS 工具包,几乎支持所有记录类型,可用于查询、
zone 传输、动态更新、TSIG 签名、EDNS、结果校验等。它能在脚本里替代
nslookup / dig
,并和现有平台集成。

详细实战:检查 URL 是否可访问。


1. 安装

pip install -U dnspython
import dns.resolver
print(dns.__version__)   # 检查安装

2. dns.resolver.resolve():核心查询 API

重要:dns.resolver.query() 在 dnspython 2.0 已弃用,3.0 删除了。
请使用 dns.resolver.resolve()。

import dns.resolver

# 语法
dns.resolver.resolve(qname, rdtype, rdclass, raise_on_no_answer=True, ...)
参数 含义
qname 待查的域名
rdtype 资源记录类型(A / MX / NS / CNAME / PTR …)
rdclass 网络类,常用 dns.rdataclass.IN
tcp 是否强制 TCP
source / source_port 自定义查询源
raise_on_no_answer 无应答时是否抛异常
lifetime 超时时间(秒)

3. 常见记录类型

类型 含义
A 域名 → IPv4
AAAA 域名 → IPv6
MX 邮件交换记录
NS 权威域名服务器
CNAME 别名
PTR IP → 域名(反向解析)
TXT SPF / DKIM / 域名验证
SOA 起始授权机构

4. 查询示例

4.1 A 记录

import dns.resolver

domain = "www.google.com"
try:
    answers = dns.resolver.resolve(domain, "A")
    for rdata in answers:
        print(rdata.to_text())
except dns.resolver.NXDOMAIN:
    print("no such domain")
except dns.resolver.NoAnswer:
    print("no A record")
except dns.resolver.Timeout:
    print("dns query timeout")

A 记录只在最终目标是 IP 时才返回;如果域是 CNAME 形式,需要先查
CNAME,或在配置里启用 search。

4.2 MX 记录

answers = dns.resolver.resolve("163.com", "MX")
for rdata in answers:
    print(f"preference={rdata.preference}, exchange={rdata.exchange}")

4.3 NS 记录

answers = dns.resolver.resolve("baidu.com", "NS")
for rdata in answers:
    print(rdata.to_text())

4.4 CNAME

answers = dns.resolver.resolve("www.baidu.com", "CNAME")
for rdata in answers:
    print(rdata.to_text())

4.5 反向解析(PTR)

import dns.reversename

addr = "8.8.8.8"
rev = dns.reversename.from_address(addr)
answers = dns.resolver.resolve(rev, "PTR")
for rdata in answers:
    print(rdata.to_text())

5. 实战:DNS 轮询业务监控

背景:很多网站一个域名对应多个 IP(DNS 轮询),用来做简单负载均衡。
但若某个 IP 不可用,DNS 不会自动剔除。本节写一个“解析 + 主动探测”脚本。

"""DNS 轮询监控:解析 + HTTP 探活。"""
import socket
from dataclasses import dataclass
from typing import Iterable

import dns.resolver
import requests


@dataclass
class HealthResult:
    ip: str
    ok: bool
    status: int | None
    error: str | None = None


def resolve_a(domain: str) -> list[str]:
    """返回 A 记录里的所有 IP。"""
    try:
        answers = dns.resolver.resolve(domain, "A", lifetime=5)
    except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.Timeout) as e:
        raise RuntimeError(f"DNS 解析失败:{e}") from e
    return [r.to_text() for r in answers]


def check_http(ip: str, host: str, port: int = 80, timeout: float = 5.0) -> HealthResult:
    """对单个 IP 做 HTTP 探活。"""
    try:
        resp = requests.get(
            f"http://{ip}:{port}/",
            headers={"Host": host},
            timeout=timeout,
            allow_redirects=False,
        )
        return HealthResult(ip=ip, ok=resp.ok, status=resp.status_code)
    except requests.RequestException as e:
        return HealthResult(ip=ip, ok=False, status=None, error=str(e))


def monitor(domain: str) -> Iterable[HealthResult]:
    ips = resolve_a(domain)
    for ip in ips:
        yield check_http(ip, host=domain)


if __name__ == "__main__":
    for r in monitor("www.example.com"):
        print(r)

工业级监控还要加:告警去重、告警通知(邮件/钉钉)、指标打点、
持续时间窗口判定、IPv6 fallback 等——参见
业务服务监控。


6. 自定义 nameserver

默认走系统 DNS。要走自建的 8.8.8.8 或内网 DNS:

import dns.resolver

resolver = dns.resolver.Resolver()
resolver.nameservers = ["8.8.8.8", "1.1.1.1"]
answers = resolver.resolve("example.com", "A")

或者使用环境变量:

export DNS_RESOLVER=8.8.8.8

7. 常见问题

  • NoAnswer 怎么办? 域名存在但没有这种记录。检查 rdtype 写对没。
  • NXDOMAIN:域名根本不存在。
  • Timeout:网络不通或被防火墙拦截。检查 lifetime / 防火墙 / nameservers。
  • CNAME 链上跑 A 失败? 用 dns.resolver.resolve 配合 search=True(默认),
    库会顺着 CNAME 链走到底再查 A。
  • 需要 EDNS / TCP? dns.resolver.resolve(qname, rdtype, use_edns=0, tcp=True)。
  • API 名称变化:见到老代码里的 query() 请替换为 resolve(),否则
    dnspython 2.0+ 会抛 AttributeError。

8. 小结

  • dns.resolver.resolve() 是核心 API;query 已删除。
  • 支持几乎所有 DNS 记录类型,能直接替代 dig / nslookup。
  • DNS 轮询监控思路:解析所有 IP → 对每个 IP 探活 → 报告异常。
  • 在大型项目里给 Resolver 配好自定义 nameservers 与 lifetime 超时。