简介海风域名查询工具1.0版是一套面向Linux主机环境的域名信息检索源码程序主要服务于需要自主搭建域名查询平台的个人站长、运维人员以及PHP学习开发者。工具将安装引导、后台管理与数据库配置整合在一起部署者可使用默认管理员账号快速进入管理界面完成站点初始化与文件权限设置。资源以RAR压缩包发布整体大小约372KB包内主要为源码文件、站点配置文件及数据表安装文件便于在服务器上直接部署或二次修改。目前已有118人学习下载。通过这套源码读者可以获得可运行的域名查询程序骨架学习项目在主机环境下的文件权限配置思路、安装脚本编写方法以及数据表导入和后台登录机制的具体实现内置的站点地图与配置模块也为后续扩展查询接口、适配不同业务场景提供直观参考对掌握完整的上线维护流程具有实操帮助。1. 海风域名查询工具 v1.0域名查询工具到底在解决什么问题做域名投资、网站运维或者安全分析的人几乎每天都要跟域名打交道。手头有个域名想知道它还活着没有、注册人是谁、什么时候到期、有没有被抢注挨个去注册商后台点一遍是低效的而且不同后缀的查询入口都不一样。所谓域名查询工具就是把 WHOIS、RDAP、DNS 探测这些分散的查询能力统一收口做成一个命令行或 Web 工具输入一个或一批域名批量拿回注册信息、到期时间、NS 记录和可用性状态。海风域名查询工具 v1.0 这个名字里的“海风”在我看来更像是一个内部项目代号——它的价值不在这几个字而在“查询工具”这四个字背后一整套技术选型、协议适配和数据清洗逻辑。这篇文章会顺着这个方向把一个域名查询工具从协议选型、模块拆分、参数调优到踩坑排错完整讲透适合想自建域名查询能力的后端开发、运维工程师和域名投资从业者参考。我知道冲着这个标题来的人多半是已经在网上找过一轮工具发现要么是网页版只能单条查要么是命令行工具对某些后缀支持很差。与其继续凑合不如自己搭一个 v1.0。下面从协议层开始讲一步步落到可复现的代码。2. WHOIS 与 RDAP 选型域名查询工具的协议地基2.1 为什么 WHOIS 依然是域名查询的主干协议域名查询工具最核心的数据来源是 WHOIS 协议。它走 TCP 43 端口客户端往服务器发一个域名服务器把该域名的注册信息按文本格式返回。这个协议从 1982 年用到今天虽然老但所有顶级域TLD的注册局仍然强制提供 WHOIS 服务对于 .com、.net 这类后缀注册局 Verisign 运营统一的 WHOIS 服务器查询端口固定为 43。对于国家顶级域ccTLD如 .cn、.jp各自注册局托管不同的 WHOIS 服务器但这些服务器地址通常能在 IANA 的根区数据库里查到。所以做域名查询工具第一步不是写代码而是搞清楚每个后缀该连哪台服务器。一个常见做法是内置一张“后缀到服务器”的映射表.com/.net 查 whois.verisign-grs.com.org 查 whois.pir.org.io 查 whois.nic.io其他冷门后缀统一从 IANA 拉取列表动态更新。v1.0 不需要覆盖全量后缀把最常查的 30 个主流后缀写死在配置里就够百分之八十的使用场景了。# 手动验证 WHOIS 服务器连通性与返回格式 nc whois.verisign-grs.com 43 baidu.com上面的命令用 netcat 向 Verisign 的 WHOIS 服务器发送查询返回的第一行通常是 Domain Name 和 Registry Domain ID。手动验证的意义在于先用最简单的方式确认网络通路和服务器行为再开始写代码避免程序写完了才发现连接超时或者端口被防火墙拦截。在工具设计上WHOIS 查询模块需要包含三个要素服务器地址、超时时间、重试策略。服务器地址决定查询哪份数据超时时间决定用户体验重试策略决定网络抖动时要不要自动补偿。超时时间我会设成 10 秒因为某些冷门注册局的服务器响应非常慢甚至超过 30 秒但主流后缀通常 2 秒内能返回。重试策略采用最多 2 次、退避间隔 1 秒的机制超过 3 秒还没返回就标记为查询失败。2.2 RDAP 是域名查询的下一步但 v1.0 不能只押一边WHOIS 有个天生缺陷返回的数据是纯文本没有统一的结构化格式。不同注册局返回字段名五花八门比如 .com 用 Registrar某些国别域用 Registrant Organization有的用 sponsoring registrar解析时全靠正则与关键词匹配。RDAPRegistration Data Access Protocol是 ICANN 推动的替代方案它基于 HTTP 和 JSON 返回数据字段结构有统一的 RFC 7480 规范约束还天然支持国际化地址和 HTTP 状态码。RDAP 的优点非常明确解析稳定、天然结构化、支持指定数据版本。但现实骨感——目前仍有大量国别域没有部署 RDAP 服务.cn 虽然有 RDAP 端点但某些老注册局的返回字段和 RFC 7484 规范并不严格一致。所以 v1.0 的设计原则是双通道主流后缀优先用 WHOIS 查全量信息支持 RDAP 的后缀.com、.net、.org 等做一个备用通道当 WHOIS 返回异常或需要拿 JSON 结构做展示时自动切换到 RDAP。与其纠结选哪个协议不如做成故障转移。# RDAP 查询模块设计优先 RDAP失败回退 WHOIS import json import urllib.request def query_rdap(domain: str, timeout: int 10) - dict | None: # RDAP 的公开端点格式https://rdap.org/domain/{全域名} url fhttps://rdap.org/domain/{domain} req urllib.request.Request(url, headers{User-Agent: HaifengDomainTool/1.0}) try: with urllib.request.urlopen(req, timeouttimeout) as resp: if resp.status ! 200: return None return json.loads(resp.read().decode(utf-8)) except Exception: return None上面的代码用 rdap.org 作为 RDAP 查询入口它会根据域名后缀自动跳转到对应的注册局 RDAP 服务器。返回的 JSON 里有 handle、ldhName、status、entities 等字段entities 里嵌套着注册人、管理联系人、技术联系人的完整信息。注意 User-Agent 需要自定义部分注册局对默认 urllib UA 会直接拒绝。RDAP 的回退逻辑放在工具内部这样实现先尝试 RDAP拿不到非空且包含 status 字段的 JSON 就转 WHOIS。这样对用户来说感知到的只是结果展示形式不同查询入口对他们是透明的。v1.0 没必要强上 RDAP——它只解决了解析问题没解决数据缺失问题大量域名在隐私保护政策下返回的注册人信息本来就是空的。2.3 模块拆分查询、解析、缓存三层各干各的一个能称之为“工具”的程序模块边界要清楚。我通用的拆分方式是三层架构查询层负责发起网络请求接收原始文本或 JSON解析层负责把响应转成统一的内部数据结构服务层负责缓存、并发调度和结果输出。查询层只关心网络不关心字段含义解析层只关心字段不关心网络状态服务层把两者粘起来。以域名查询工具 v1.0 的目标范围来说核心模块就是 WHOIS 客户端、RDAP 客户端、响应解析器、域名可用性判断器和批量调度器。WHOIS 客户端做成 socket 直连不依赖第三方库因为 Python 标准库的 socket 模块完全够用减少依赖就是减少后续维护成本。RDAP 客户端基于 urllib 写同样不引外部库。唯一值得引入第三方库的是并发调度因为 asyncio 的写法更适合高频批量查询场景。# 项目结构v1.0 建议的最小目录 haifeng_domain/ ├── cli.py # 命令行入口支持单查/批量查 ├── whois_client.py # WHOIS socket 查询 ├── rdap_client.py # RDAP HTTP 查询 ├── parser.py # whois 文本解析 rdap json 整理 ├── availability.py # 可用性判断逻辑 ├── batch.py # 并发批量调度 ├── cache.py # 查询结果本地缓存 └── config.yaml # 后缀-服务器映射、超时、并发参数上面这个结构里cli.py 是唯一面向用户的入口其他模块都不感知用户参数。config.yaml 集中放置所有可调参数避免参数散落在代码里。这样做的直接好处是排查问题时先看配置文件再逐层往里查代码调整并发数或超时时间不用改动任何一行逻辑代码。3. 用 Python 在本地跑通 WHOIS 查询的最小实现3.1 socket 直连 WHOIS 服务器完整代码与参数说明理解了协议后写一个最小 WHOIS 客户端只需要十几行代码。它做的事很简单创建 socket 连接发送域名加换行符接收直到服务端关闭连接的响应。这个流程是 RFC 3912 定义的原始交互模式兼容所有公开 WHOIS 服务器。import socket from typing import Optional def query_whois(domain: str, server: str, port: int 43, timeout: float 10.0) - Optional[str]: 直连 WHOIS 服务器查询原始数据 sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) try: sock.connect((server, port)) sock.send(f{domain}\r\n.encode(utf-8)) chunks [] while True: data sock.recv(4096) if not data: break chunks.append(data) return b.join(chunks).decode(utf-8, errorsreplace) except socket.timeout: print(f[WARN] {domain} 查询超时 ({server})) return None except socket.error as exc: print(f[ERROR] {domain} 连接失败: {exc}) return None finally: sock.close()代码里 key 点有两个一是发送的查询字符串必须以 \r\n 结尾很多服务器把 \n 结尾的请求当无效包处理直接断开不给数据二是 recv 要循环调用直到返回空字节因为服务器响应可能大于单次缓冲区。解码时用 errorsreplace 防止非 UTF-8 编码的中文注册信息抛异常比如某些国别域名注册人字段用的是 GBK 编码replace 处理后虽然部分乱码但不会让整个程序崩掉。这里有个在实际查询中很容易犯的错把域名拼成 www.baidu.com 去查。WHOIS 服务器只认注册域名 baidu.com带 www 前缀不仅查不到数据还可能被服务器判定为非法查询而限流。代码内部做好归一化遇到 www、http:// 前缀一律剥掉再发请求。3.2 解析 WHOIS 文本从原始响应当中提取结构化字段WHOIS 响应是纯文本但字段格式相对有规律。拿 .com 域名的返回做例子常见字段名有 Domain Name、Registry Domain ID、Registrar WHOIS Server、Updated Date、Creation Date、Registry Expiry Date、Registrar、Name Server 等。解析策略从早年的正则逐步演变为现在的规则匹配加正则兜底先用全等匹配查找带冒号的行再对特殊格式的日期字段做二次清洗。import re from typing import Dict, List def parse_whois_text(raw: str) - Dict[str, List[str]]: parsed: Dict[str, List[str]] {} # 兼容不同服务器返回的行分隔符 for line in raw.splitlines(): if not line.strip(): continue # 匹配 字段名: 值 格式字段名允许包含空格 match re.match(r^(.*?):\s*(.*)$, line, re.IGNORECASE) if not match: continue key match.group(1).strip().lower().replace( , _) value match.group(2).strip() if key in parsed: parsed[key].append(value) else: parsed[key] [value] return parsed这段代码把原始文本变成字典字段名统一为小写加下划线值保留为列表。用列表而非字符串的原因一个域名可能有多个 Name Server例如 ns1.example.com 和 ns2.example.com 会返回两行相同字段名列表天然支持多值场景。日期字段注册商返回常见格式有 2023-01-15T04:28:13Z 与 2023.01.15 两种统一转换成时间戳为后续到期提醒做准备。解析层的核心矛盾是兼容性。verisign 返回的字段名和 GoDaddy 的私有 WHOIS 返回字段名不一致是常态解析器要宁可解析出空值也不抛异常。v1.0 阶段只要把字段名映射表做到主流字段全覆盖其余字段原样塞进 raw 字段即可不必追求全量结构化。3.3 域名可用性判断注册信息不存在不等于就能注册域名查询工具的一个高频需求是查域名是否可注册。很多人以为 WHOIS 查不到注册信息就等于可以注册这个判断有严重漏洞。不同注册局返回“查无此域”的行为各不相同有的返回一行 No Data Found有的返回空响应还有的返回一条带 Name 字段但 Status 为 pendingDelete 的记录。可用性判断器要做三态判定可注册、不可注册、状态待定。def judge_availability(parsed: Dict[str, List[str]]) - str: 三态判定registered / available / pending if not parsed: return available joined .join(v for values in parsed.values() for v in values).lower() no_data_markers [no data found, not found, no match, no entries] for marker in no_data_markers: if marker in joined: return available status .join(parsed.get(status, [])).lower() pending_markers [pendingdelete, pending delete, redemptionperiod] if any(marker in status for marker in pending_markers): return pending return registered注意第一行判断逻辑parsed 为空可能是真的没有注册信息也可能是服务器限流返回了空响应。所以 availability.py 里要结合 http 查询里记录的错误状态来使用仅当网络请求正常返回且 parsed 为空时才把域名判为 available。pending 状态意味着域名处于赎回期或等待删除期此时不能注册等释放后才能抢注。这个三态设计能避免用户误把“即将释放”的域名当成可注册域名。4. 批量查询与性能参数并发数、超时与限速的取值经验4.1 同步改 asyncio并发查询的代码骨架单条查询的代码跑通后立刻要面对批量场景。域名投资人手里清单动辄几百几千个域名同步一个个查效率太低一条查询 2 秒500 条要 16 分钟用户早跑了。用 asyncio 把 socket 查询改成异步可以同时发起几十个查询请求总耗时可降到十几秒。注意socket 模块本身不支持异步要改用 asyncio 的事件循环包装Python 3.8 以上推荐 loop.sock_connect 和 loop.sock_recv。import asyncio async def query_whois_async( domain: str, server: str, timeout: float 10.0 ) - str | None: loop asyncio.get_event_loop() sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setblocking(False) try: await asyncio.wait_for( loop.sock_connect(sock, (server, 43)), timeouttimeout ) await loop.sock_sendall(sock, f{domain}\r\n.encode(utf-8)) data b while True: chunk await asyncio.wait_for(loop.sock_recv(sock, 4096), timeouttimeout) if not chunk: break data chunk return data.decode(utf-8, errorsreplace) except asyncio.TimeoutError: print(f[WARN] {domain} 异步查询超时) return None finally: sock.close()区别于阻塞版的地方在 setblocking(False)socket 复习非阻塞模式后由事件循环驱动。wait_for 给每个网络操作都设了独立的超时保护避免某个查询卡死整个并发组。批量查询入口用 asyncio.gather 收集结果返回的结果列表顺序与输入域名列表顺序一致方便后续按序输出报告。4.2 并发参数的工程取值别再傻乎乎设 100并发数、超时、限速这三个参数是域名查询工具批量场景的全部学问。并发开小了速度上不去开大了会被注册局的速率限制封 IP。这些限制不会写在文档里只有实测才知道各注册局的行为差异非常大Verisign 对同一 IP 每秒超过 10 个请求就会短暂封禁某些小注册局 5 个并发就会开始丢包。我常用的参数组合是默认并发 8、单请求超时 10 秒、每个 IP 每秒最大请求数不超过 5。这组参数安全但不算激进适合 v1.0 默认值。如果想要更快提供三档预设——safe / balanced / aggressive分别对应并发 4/8/16。关键点在于限速不是代码里写死的而是从配置文件读取这样上线后可以根据实际封禁情况现场调整。# config.yaml 里与性能相关的配置 concurrency: default: 8 max: 16 timeout: connect: 5 read: 10 rate_limit: qps_per_ip: 5 burst: 10 retry: max_attempts: 2 backoff_seconds: 1.0配置项里 connect 和 read 分开设置是有讲究的。实际网络故障中连不上的情况远多于连上但读不到数据的情况connect 设短一点能迅速失败并释放并发槽位read 长一点是为了宽容那些响应慢的注册局。qps_per_ip 指单个出口 IP 每秒最多 5 个查询burst 允许 10 个突发请求这样既保证平均速率可控又不会因为微小的调度抖动导致请求被拒。4.3 结果缓存与增量更新查询工具一定要有后悔药批量查询最怕的是重复跑同一批域名。500 个域名查一遍要几分钟查完发现了连不上的修复后又得从头查。缓存机制就是后悔药按域名做 key缓存 WHOIS 原始响应正文和查询时间TTL 按数据时效性分成两类——注册信息和 NS 记录缓存 24 小时可用性判断缓存 6 小时。为什么不缓存更久WHOIS 的到期日虽然不变但域名可能在一天内从正常状态变成欠费赎回状态缓存太久会误导用户。import os import json import hashlib from datetime import datetime, timedelta class DomainCache: def __init__(self, cache_dir: str .cache, ttl_hours: int 24): self.cache_dir cache_dir self.ttl_hours ttl_hours os.makedirs(cache_dir, exist_okTrue) def _path(self, domain: str) - str: digest hashlib.md5(domain.encode()).hexdigest() return os.path.join(self.cache_dir, f{digest}.json) def get(self, domain: str) - dict | None: path self._path(domain) if not os.path.exists(path): return None with open(path, r, encodingutf-8) as fp: record json.load(fp) cached_at datetime.fromisoformat(record[cached_at]) if datetime.now() - cached_at timedelta(hoursself.ttl_hours): os.remove(path) return None return record[data] def set(self, domain: str, data: dict) - None: record {domain: domain, cached_at: datetime.now().isoformat(), data: data} with open(self._path(domain), w, encodingutf-8) as fp: json.dump(record, fp, ensure_asciiFalse, indent2)缓存文件名用 MD5 做散列可以避免域名字符串做文件名时出现特殊字符或路径穿越问题。注意 .com 与 .com.cn 是两个不同域名缓存 key 必须区分主域名与子域名只缓存注册级域名即裸域名不带 www 和协议前缀。存 JSON 是 v1.0 最简单的选择等到数据量超过十万条再考虑 SQLite。5. 海风域名查询工具避坑与排查5 个典型翻车现场5.1 批次查询批量超时根源是没做后缀到服务器的闭环现象100 个域名混在一起批量查询跑到一半开始大面积超时单独查其中几个域名却能正常返回。原因不同后缀的 WHOIS 服务器不同有些注册局对域名后缀与服务器不匹配的连接直接丢弃请求。比如 .cn 的域名发到 Verisign 的服务器服务器不会报错但会挂起直到超时。解决在批量调度器里按后缀做分组每组域名下发到各自的 WHOIS 服务器。先从顶层映射表拿到每个域名的服务器再按服务器聚合查询最后按原顺序合并结果。这一步是批量查询速度和成功率的分水岭。5.2 WHOIS 里的状态码字段被忽略可用性误判现象一个被注册局锁定clientHold的域名WHOIS 记录里没有 No Data Found但域名事实上无法正常使用。工具把它判为 registered用户实际访问时发现网站打不开白白浪费了投资判断。原因状态字段包含了域名当前的生命周期状态是判断可用性的关键信号但我早期版本只看有没有记录不看状态码含义。解决把状态码解析成可读含义例如 clienthold 表示注册商暂停解析redemptionperiod 表示赎回期。对 redemptionperiod 的状态单独标记为即将释放并在输出报告里用红色警告标注。状态码转译表在 ICANN 官网可以拿到不要自己瞎猜。5.3 中文域名查询结果乱码编码转换踩坑现象查询 .cn 或 .中国 后缀的域名返回的注册人信息出现大量乱码比如曾经的注册单位字段显示成 系统。原因部分老注册局返回的是 GBK 编码的文本程序按 UTF-8 解码导致乱码。解决解析前用 chardet 库快速嗅探编码或者对 .cn 后缀优先用 GBK 解码尝试。乱码字段不影响注册信息结构时可以用 errorsreplace 兜底。做一个编码检测函数针对 .cn/.hk/.tw 这些国别域单独指定解码优先级。5.4 RDAP 返回 404 却被当成空结果现象对某些国别域用 RDAP 查询时服务器返回 HTTP 404程序按“无数据”处理判定为可注册事实上域名的注册信息在 WHOIS 通道里存在。原因RFC 7480 规定 RDAP 对不存在的资源返回 404而很多注册局的 RDAP 服务还没有覆盖全部域名字段404 是服务端能力缺失不是域名不存在。解决RDAP 模块的返回状态码要透传到上层404 和 200 必须分开标记不能丢进同一个空结果分支。v1.0 中用 RDAP 时只信任 200 响应其他状态码回退到 WHOIS 重新查询。5.5 并发高了被注册局限流封 IP现象并发调到 16 后前 200 个查询正常后面所有请求返回空响应或者直接被重置连接。原因触发注册局 QPS 限制或短时并发限制IP 被临时拉黑。解决遵守速率限制在调度器里加入令牌桶算法。每个 IP 维护一个每秒 5 次的令牌桶定时填充请求前取令牌取不到就等待。这样平均速率稳定且尽量避免突发带来的封禁。被拉黑后换 IP 最稳妥但更根本的办法是控制速率把速率配置留到配置文件里方便现场调整。6. 把 v1.0 做成工具箱到期监控与多源交叉验证域名查询工具做到能查、能批量查只能算及格。真正让它产生价值的是叠加场景到期提醒、状态变化监控、以及多数据源交叉验证。到期提醒的逻辑是定期拉取域名列表的到期时间和当前日期比对提前 30 天/7 天/1 天三级告警。这一步的时间成本不高但能帮域名投资人避免忘记续费导致域名被抢注的惨剧值得加进 v1.0 的规划里。交叉验证是针对数据篡改和隐私保护的一个技巧。WHOIS 的注册人信息受 GDPR 影响很多字段已经脱敏但 NS 记录和 DNS 解析结果仍然是公开的。验证一个域名是否真的在某个注册商名下可以交叉对比 WHOIS 里的 Registrar 字段与该注册商公共 Whois 服务器返回的信息同时结合 DNS 查询 NS 记录判断解析是否指向注册商默认服务器。两者对不上时自动标红提示用户该域名可能刚转移或处于异常状态。# 验证域名 NS 记录与 WHOIS 返回是否一致多源交叉验证示例 def cross_check_ns(whois_ns: List[str], dns_ns: List[str]) - bool: normalized_whois {ns.lower().rstrip(.) for ns in whois_ns} normalized_dns {ns.lower().rstrip(.) for ns in dns_ns} return normalized_whois normalized_dns这段代码的坑在于 NS 记录格式化WHOIS 返回的 ns1.example.com 可能带结尾点也可能不带DNS 轮询时也要注意顺序变化。集合化处理后忽略顺序差异只比内容是否一致这是最稳妥的比法。实际使用中还发现过一个注册商返回的是 ns1.example.com. 而 DNS 返回 ns1.example.com统一去掉尾点再判断别让格式差异造成误报。最后一说验证方法v1.0 上线之前至少拿 20 个不同类型域名做回归测试覆盖 .com/.net/.org/.cn/.io 五种后缀每种下至少包含一个已注册域名和一个明显未注册域名。断言结果必须五五开并且状态码标记正确。这个回归清单不要写死在代码里用文本文件维护每次调整解析器后跑一遍。我个人的经验是域名查询工具的第一个版本不要贪多。把单查、批量查、可用性判断、缓存、到期提醒这五个点做扎实就已经能覆盖绝大多数日常需求。后面再想加功能也容易新增一个后缀映射、接入一个数据源都是在现有模块上扩展。宁可先做到“简单、可信、不翻车”也别一次性塞入太多华而不实的特性。希望帮到你祝你的海风域名查询工具 v1.0 上线顺利。本文还有配套的精品资源点击获取