Python域名探测最实用的组合是dnspython库加上多线程/异步并发,它能应对从单个解析到百万级域名筛选的绝大多数场景。 如果你只想做一次简单的域名解析,自带的socket模块就够了;但要批量探测、定制记录类型、控制超时,dnspython是绕不开的利器。
Python域名探测工具有哪些?这5个够用了
先聊聊工具体系,很多朋友问我“域名探测工具哪个好用”,我的答案是:没必要装一堆重型工具,Python库足以覆盖90%的需求,下面是我常用的五个核心工具,按使用频率排序。
- socket:Python自带的标准库,
gethostbyname()和getaddrinfo()足够做基础A记录查询,零依赖。 - dnspython:功能最全的DNS解析库,支持A、AAAA、CNAME、MX、TXT等几乎所有记录类型,还能自定义DNS服务器。
- pycares:c-ares的Python封装,底层原生支持异步DNS解析,适合追求极致性能的选手。
- concurrent.futures:这不是DNS库,但配合上面任意一个解析函数,就能实现线程池并发,把单线程改为多线程。
- asyncio:Python3.4+内置的异步框架,配合
loop.run_in_executor()或asyncio.DNSError等机制,可以做到非阻塞批量查询。
行业共识认为:选工具要结合场景,如果你只查几十个域名,socket就够了;一旦需要扫描整个IP段的反向域名,就得让dnspython搭配公共DNS服务商的自建解析器。
下表直观对比这几款工具的核心差异:
| 工具 | 记录类型支持 | 并发能力 | 依赖复杂度 | 适用场景 |
|---|---|---|---|---|
| socket | A/AAAA | 需自己处理 | 零依赖 | 简单查询 |
| dnspython | 几乎全部 | 需配合线程 | 单库 | 常规批量解析 |
| pycares | 有限 | 原生异步 | 需编译 | 高性能任务 |
| concurrent.futures | 无(是并发框架) | 线程池 | 标准库 | 配合解析库提速 |
| asyncio | 无(是事件循环) | 协程 | 标准库 | 高并发+非阻塞需求 |
实际项目里,我用得最多的是dnspython,因为它能设置timeout和lifetime,避免卡死,很多“域名探测脚本”跑不动,不是因为库不好,而是没处理好异常和超时。
Python域名探测代码怎么写?从单线程到多线程实战
接下来上硬菜,我会从一个最简单的解析脚本开始,一步步加并发达成批量探测。
基础版:socket实现单点查询
先看最基础的代码片段,这一版适合临时验证,比如想知道某个域名当前解析到哪个IP。
import socket
try:
result = socket.getaddrinfo('www.example.com', None, socket.AF_INET)
ip = result[0][4][0]
print(f'{ip}')
except socket.gaierror:
print('解析失败')
这段代码能跑通,但有两个痛点:一是只支持A记录(IPv4),二是遇到超时或DNS污染会直接抛异常,如果目标域名不存在,getaddrinfo会阻塞很久,默认超时时间偏长。
升级版:dnspython定制解析逻辑
为了解决上面的问题,建议切换为dnspython,它允许你显式指定DNS服务器,比如使用公共DNS或者内网自建的。
import dns.resolver
def query_a(domain, dns_server='223.5.5.5'):
my_resolver = dns.resolver.Resolver()
my_resolver.nameservers = [dns_server]
my_resolver.timeout = 1
my_resolver.lifetime = 2
try:
answers = my_resolver.resolve(domain, 'A')
return [r.address for r in answers]
except dns.resolver.NXDOMAIN:
return None # 域名不存在
except dns.resolver.NoAnswer:
return [] # 无记录
except dns.exception.Timeout:
return None # 超时
这个版本已经能处理大多数异常了,需要注意的是,timeout控制每次向上游发起请求的等待时间,lifetime控制整个解析过程的最大耗时,很多业务场景下,设置lifetime=3就能让脚本快速跳过不响应的服务器。
提速版:多线程池并发探测
当域名列表超过一百个,用单循环会非常浪费时间,我一般用concurrent.futures.ThreadPoolExecutor把解析任务分发到多个线程,下面是一个实战片段,应对“批量域名探测”的需求。
import dns.resolver
from concurrent.futures import ThreadPoolExecutor, as_completed
def check_domain(domain):
resolver = dns.resolver.Resolver()
resolver.nameservers = ['223.5.5.5', '8.8.8.8']
resolver.timeout = 0.5
resolver.lifetime = 1
try:
ip_list = [str(ip) for ip in resolver.resolve(domain, 'A')]
return domain, ip_list
except Exception:
return domain, None
domains = open('domains.txt').read().splitlines()
results = {}
with ThreadPoolExecutor(max_workers=50) as executor:
future_map = {executor.submit(check_domain, d): d for d in domains}
for future in as_completed(future_map):
domain, ip_list = future.result()
results[domain] = ip_list
这里设置了max_workers=50,多数情况下已经能跑满带宽和CPU,但注意,线程数不是越大越好,超过200后系统切换线程的开销会明显增加,而且公共DNS服务也会对并发请求做限制。
进阶版:异步IO与超时控制
如果你想在单进程内处理上万个域名,asyncio是更好的选择,不过asyncio直接支持DNS解析的函数很少,通常需要把dnspython的解析丢到事件循环里,或者用asyncio.open_connection()配合loop.create_dns_query(),这里给你一个基于to_thread的简单写法,它适合部署在高并发服务器上。
import asyncio
import dns.resolver
async def resolve_one(domain):
loop = asyncio.get_running_loop()
return await loop.run_in_executor(None, check_domain, domain)
async def main():
tasks = [resolve_one(d) for d in domains]
results = await asyncio.gather(tasks)
# 处理results
用这段代码时要注意,run_in_executor本质还是多线程,并没有摆脱GIL,但相比手动管理线程池更简洁,真正的异步解析可以用pycares,它基于c-ares实现了原生的非阻塞调用,只是需要额外安装,代码风格也不像dnspython那么常规。
如何提升Python域名探测速度与准确性?
前面代码已经展示了基本提速思路,但在实际部署时,我们还会遇到更多细节问题,这里整理了几个关键技巧,都是我踩坑后的总结。
优先使用并发而不是单循环
单线程串行解析一个域名一般需要几十到几百毫秒,如果有一万个域名,最坏情况要等一个小时,用50线程并发后,这个时间能缩短到一分钟左右,多数情况下,普通电脑跑50~100并发是安全的。
显式指定公共DNS服务器
本地DNS往往有缓存,可能导致结果过期或遭受污染,我一般优先使用阿里DNS(223.5.5.5)或腾讯DNS(119.29.29.29),备用一个国际DNS,某些情况下,比如从海外服务器探测国内域名,就要切换DNS服务器来验证一致性。
对结果做去重和过滤
重复查询同一CNAME转发的域名没有意义,建议在代码里用集合记录已处理的域名,同时过滤掉内网IP和保留地址,减少后续步骤的噪声。
import ipaddress
def is_public_ip(ip_str):
try:
ip = ipaddress.ip_address(ip_str)
return ip.is_global and not ip.is_private
except ValueError:
return False
利用TTL信息做缓存
域名记录的TTL值决定了你能缓存多久,如果你在同一段时间内需要反复查询同一域名,可以自己做一个简单的字典缓存,带上过期时间,这能显著降低上游DNS服务器的压力。
Python域名探测的常见坑与绕行方案
除了速度和准确性,异常处理同样重要,很多朋友写的脚本一开始没问题,跑半小时后就卡死或崩掉,基本都是栽在这几个坑里。
- DNS服务器无响应:不要依赖单一DNS,多备几个并做好轮询。
- 异常类型混乱:
dns.resolver.NXDOMAIN、NoAnswer、LifetimeTimeout分别代表不同情况,捕获时要分开处理。 - 线程间共享DNS Resolver对象:多个线程共用一个resolver实例可能导致协程冲突,我建议在每个线程内新建resolver,或者用全局变量加锁。
- 域名列表有重复或格式错误:先清理,比如去掉
http://、https://前缀,去掉空行。 - 超时设置太保守:如果使用默认超时,单个域名可能需要等十几秒才会放弃,请务必显式设置
lifetime和timeout。
用我上面的代码骨架,再配合一个合理的错误处理函数,你的“Python域名探测脚本”就能稳定运行在绝大多数生产环境中。
Python域名探测的核心竞争力在于用最少的依赖换取最多的功能,dnspython负责解析,多线程或异步负责并发,再配合超时和异常处理,这套方案足以应对常规的域名收集、资产梳理和DNS排查任务,如果你有更极端的性能需求,不妨研究一下pycares或者直接用massdns,但那些是另一个话题了。
关于Python域名探测的常见疑问
Q1:Python域名探测脚本运行很慢,怎么排查?
首先确认是否使用了并发,单线程写循环一定会慢,其次用日志记录每个域名的解析耗时,定位是超时还是被限速,最后检查DNS服务器响应速度,尝试换成阿里DNS或腾讯DNS,并把超时参数调小到1秒左右。
Q2:如何用Python批量查询域名解析记录并保存到文件?
核心思路是使用dnspython并发解析,把结果写入CSV或JSON,比如读取domains.txt,用ThreadPoolExecutor并发查询A记录和CNAME,最后用csv.writer写入result.csv,字段建议包括域名、IP列表、记录类型、解析耗时、状态。
Q3:探测内网域名和外网域名有什么不同?
内网域名通常由企业内部的DNS服务器解析,需要把resolver的nameservers指向内网IP,外网域名受DNS污染影响较大,可以多备几个公共DNS交叉验证,外网解析对IP地址的准确性要求更高,建议过滤掉私有地址后再做后续判断。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/614646.html





