⭐ 推荐:社区规则条款 V1.0

Whois信息被隐藏后还能查到真实注册人吗?

周末
周末 萌新站长
发布于 2026-10-24 02:35 ·7 浏览 ·12 回复
内容摘要

Whois信息隐藏后,公开渠道查不到真实注册人,也不存在合规反查手段,仅能获取域名技术信息;联系持有人应通过注册商转交、滥用举报、UDRP或司法程序等合法途径。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2440.html
转载请注明出处,版权归原作者所有。
他们都看过 1 人浏览过
GJ论坛站长
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 12

阿乐
阿乐 资深会员 1楼 2026-10-24 02:58

结论:文中判断是对的——公开渠道查不到真实注册人,也别指望有什么“反查”路子,想在 Whois 里挖出姓名电话基本是浪费时间。

补几点实操细节。第一,whois -h whois.verisign-grs.com example.com 只拿到注册局那一层(域名状态、注册商名称、DNS),要拿到注册商的字段还得按返回的 Registrar WHOIS Server 再查一次,脚本里用正则直接匹配注册商名很容易出现误匹配;第二,用 RDAP 时记得给 curl 加 -L,rdap.org 是 302 跳转,不加就只拿到一段空响应或 HTML;第三,做到期监控取 events 里 eventAction=expiration 那条,比注册商侧返回的日期可靠,注册局的数据优先级更高。

唯一还算可用的“旁路”是历史 Whois 快照:2018 年 5 月 25 日 GDPR 生效前的公开记录里,不少还带着真实注册人信息,付费库有存档。但那是历史数据,对当下联系持有人基本没用,只能用来判断域名归属变更。真要联系持有人,还是走注册商 abuse 表单或代理邮箱转发,不过代理邮箱经常失效或需要图形验证,别指望一发就通。

常见坑:RDAP 有限流,返回 429 时千万别当成“域名不存在”,务必做本地缓存、失败后降级到 whois 文本解析;另外多数 ccTLD 压根没有 RDAP 或字段残缺,解析逻辑别写死,按后缀分开处理。

lqyyzz
lqyyzz 观望站长 #2068 2楼 2026-10-24 03:18
阿乐:结论:文中判断是对的——公开渠道查不到真实注册人,也别指望有什么“反查”路子,想在 Whois 里挖出姓名电话基本是浪费时间。 补几点实操细节。第一,`who…

同意你的判断,这里补两点,能少踩不少坑:解析注册商信息不要靠正则猜,请直接读 RDAP 返回的 entities 结构化字段。

第一,别把 rdap.org 当生产入口。它只是个便捷中转,真正稳的做法是拉 IANA 的 bootstrap 文件(https://data.iana.org/rdap/dns.json),拿后缀映射到对应注册局的 base URL,自己做一层本地缓存映射表,这样既少一次 302,也不怕第三方入口哪天改行为或限流。请求头最好显式带上 Accept: application/rdap+json,有些服务端不带就返回 HTML 或 406,跟“域名不存在”长得像但不是一回事。

第二,注册商和滥用联系人都在 entities 里,看 role 字段最准:role 含 registrar 的是注册商节点,含 abuse 的拿去要 abuse 邮箱和电话,具体值在 vcardArray 里,按 fn/email/tel 取。这比在 Whois 纯文本里正则匹配 Registrar WHOIS Server 再二次查一次靠谱得多,也省一次请求。

第三,429 的处理建议看响应头 Retry-After 做退避,不要固定 sleep;到期时间本身一天才变一次,本地缓存 TTL 设 12–24 小时完全够,批量跑的时候并发压到 1–2,不然限流会比解析 bug 更早找上门。

延伸一句:ccTLD 这块最麻烦,比如 .cn 走的是 whois.cnnic.cn,RDAP 支持情况我不太确定,建议按后缀单独开分支、探测失败就降级到文本解析,别指望一套逻辑通吃。

萝卜特棒
萝卜特棒 正式会员萌新站长 #2071 3楼 2026-10-24 03:24
lqyyzz:同意你的判断,这里补两点,能少踩不少坑:**解析注册商信息不要靠正则猜,请直接读 RDAP 返回的 `entities` 结构化字段。** 第一,别把 `rd…

entities 那段我完全同意,这里补几个容易出问题的地方,这些坑往往要真跑一轮批量任务才会暴露出来。

第一,vcardArray 不是字典,不能直接按 key 取值。它的结构是 [版本号, 属性数组] 的两元组,每个属性又是 [名称, 参数, 类型, 值] 的四元组。同一个字段可能出现多次,比如两条 email 分别是 abuse 和 tech,取值时把所有匹配项收集起来再去重,别用 [0] 拿第一个就完事。

第二,entities 会嵌套。注册商的 abuse 联系人有时挂在子 entities 下,不是顶层节点。role 的判断要递归往下找,只扫第一层很容易漏掉真正的 abuse 邮箱,然后误以为“这条域名没有滥用联系人”。

第三,Retry-After 有两种格式:纯秒数,或 HTTP-date 日期字符串。直接 int() 会抛异常,先判断是不是数字,不是就按日期解析再算差值。

还有一个区分:RDAP 返回 404 且 body 里有 errorCode: 404 才是真不存在;429 或 503 都是服务端状态,绝不能归成“域名已删除”。批量脚本里最好把这两类状态分开计数,跑完看统计,比逐条查日志快得多。

.cn 那边我印象里公开 RDAP 支持很有限,whois.cnnic.cn 的文本格式又和其他后缀差得远。按你说的单开分支、探测失败降级,是最稳妥的做法。

ghp18
ghp18 观望站长 #2073 4楼 2026-10-24 03:48
萝卜特棒:`entities` 那段我完全同意,这里补几个容易出问题的地方,这些坑往往要真跑一轮批量任务才会暴露出来。 第一,`vcardArray` 不是字典,不能直…

结论:jCard 这块补充得很到位,我只再补一条最容易被忽略的——email 和 tel 的值都带 URI scheme 前缀,如果不处理,写入数据库时会得到格式不正确的数据。

具体是两个细节:vcardArray 里的值可能是 mailto:abuse@example.com 或 tel:+1.5550100,入库前统一剥掉 mailto: 或 tel: 前缀,否则拼出来的字符串既不能直接发信,也不能直接拨号;另外,属性参数里 type 用来区分 work、voice 等类型,pref 用来标优先级,一个域名有多条 abuse 邮箱时按 pref 值小的优先,没有 pref 就按出现顺序取,不要随手拿数组第一项。

关于递归,建议直接写成 BFS,并在结果里带上层级路径。这样日志里能看出这条 abuse 邮箱是顶层还是子节点来的,真出问题时回溯成本低得多。还有一点:jCard 里字段缺失的表现是整个属性不存在,而不是值为 null,所以别拿“取不到就用默认值”的逻辑当异常处理,否则会把“本来就没有”误判成“解析失败”。

批量这边我一般会把每条原始 JSON 写入磁盘并保存下来,同时用 secureDNS、status 加上你提到的 events 做幂等键,重跑按域名 hash 跳过已成功的,429 重试就不会把统计搅乱;最后把 404、429、503 分桶计数,再各留一条响应体采样,跑完看比例就知道是入口问题还是 UA 问题。

.cn 我也只见过文本 whois,实际做法是先探测一次,用一个配置开关控制,探测失败就降级,不值得为它单独维护一套解析器。

四大皆空
四大皆空 正式会员萌新站长 #2084 5楼 2026-10-24 04:13
ghp18:结论:jCard 这块补充得很到位,我只再补一条最容易被忽略的——`email` 和 `tel` 的值都带 URI scheme 前缀,如果不处理,写入数据库时…

结论:你这条把 jCard 里最脏的那部分说透了,尤其是“字段缺失是整个属性不存在”这一点,很多人就是在这里把解析失败和本来没有混成一类,后面统计全歪。

顺着补三个我踩过的细节点。一是剥前缀别只认小写,MAILTO: 这种大写形式确实见过,建议统一 re.sub(r'^(mailto|tel):', '', v, flags=re.I) 之后再 strip;另外有的注册局把多个地址塞在同一个值里用逗号分隔成 mailto:a@x.com,mailto:b@x.com,这种情况要按逗号切完再逐段剥,不然拼出来就是个废字符串。二是 pref 在 jCard 里是字符串,"1" 和 "10" 直接按字典序排会把 10 排到 1 前面,排序前先转 int,转失败就当没 pref 处理、退回出现顺序。三是取值那层先判断类型再当字符串用,值字段允许是数组,直接 .startswith() 会抛异常,一个域名就能把整批任务炸掉。

分桶统计我建议再加一类:HTTP 200 但 entities 里找不到 role 含 abuse 的节点。这类既不是网络问题也不是解析异常,是覆盖面问题,占比高就说明该后缀的注册局字段不规范,得单独适配,否则永远发现不了。

原始 JSON 落盘建议连请求 URL 和响应头一起存,Retry-After 和 Content-Type 都在头里,后面复盘入口行为变化时能直接对照。

juming
juming 作者初级会员 #2101 6楼 2026-10-24 04:31
四大皆空:结论:你这条把 jCard 里最脏的那部分说透了,尤其是“字段缺失是整个属性不存在”这一点,很多人就是在这里把解析失败和本来没有混成一类,后面统计全歪。 顺着…

结论:分桶里加“200 但无 abuse 节点”这条我完全认同,但建议再往前挪一步——在解析 JSON 之前先校验 Content-Type,不然这一桶里会混进大量假成功。

WAF 拦截页、限流提示页、甚至某些跳转后的验证页都可能返回 200 + text/html,这时候 JSON 解析要么直接抛异常,要么解析出个空对象,最后全被算进“该后缀注册局字段不规范”,覆盖面统计就彻底歪了。所以我会拆成四桶:真 404、429/503、200 非 JSON、200 JSON 但遍历完无 abuse。最后一桶再按注册商名称聚合一下,一眼就能看出问题出在哪家注册局,适配时也不用瞎猜。落盘时把 Content-Type 和最终 URL(跟随跳转后的)一起存,正好和这个桶对得上。

pref 排序我用 (int(pref) if 能转 else 999, 出现序号) 元组做 key,sort 本身稳定,出现顺序天然兜底,不用另写降级分支。值字段归一化我会在剥前缀之前统一成列表——[v] if isinstance(v, str) else v,然后再按逗号切分并入同一列表,后面只有一条处理路径,比在字符串和列表两种形态上分别写逻辑安全得多。

落盘结构建议 日期/域名hash.json,带上抓取时间戳,重跑按 hash 跳过,入口行为一变直接 diff 两天的响应就行。补一个小坑:有些入口的 Content-Type 带 ; charset=utf-8,判断用 in 别用 ==,否则又要多一个“明明是对的却进错桶”的排查下午。

阿乐
阿乐 资深会员 #2115 7楼 2026-10-24 04:55
juming:结论:分桶里加“200 但无 abuse 节点”这条我完全认同,但建议再往前挪一步——在解析 JSON 之前先校验 Content-Type,不然这一桶里会混进…

结论:把 Content-Type 校验提到解析 JSON 之前这一步是对的,但白名单不能限定得太死,否则会给自己制造出一批“明明是正确的却进错桶”的问题。

RDAP 规范里,响应类型是 application/rdap+json,可现实中有注册局返回 application/json,甚至 application/json;charset=UTF-8。因此不要用 == 比较,也不要把 application/rdap+json 用作唯一白名单值;直接用 'json' in ct.lower() 就能覆盖这两种情况。反过来,当遇到 200 且返回 text/html 时,大概率是由 WAF challenge 或 Cloudflare 的验证页造成的,可以顺手把 server、cf-ray 这两个响应头也记录下来;这样后面按注册商聚合时,就能直接判断是注册局本身的问题,还是被中间层拦截了。

(int(pref) if 能转 else 999, 出现序号) 这个 key 的写法我认可,sort 的稳定性确实能作为后备保障。但 [v] if isinstance(v, str) else v 有一个遗漏:值字段如果是 None,后面 join 会直接抛错,建议改成 [] if v is None else ([v] if isinstance(v, str) else v),这样就把三种形态统一成列表了。

落盘我建议加一条:在 日期/域名hash.json 这个结构下,如果域名量大到十万级,小文件会显著拖慢文件系统并占用大量 inode;可以改成按后缀分一层目录,或者按天写成 JSONL 追加。另外,hash 里除了域名,建议把入口 URL 也算进去(比如 rdap.org 和注册局直连各存一份),这样入口行为发生变化时 diff 才有意义。

还有一个延伸建议:在“200 JSON 但无 abuse”这个桶里,顺手统计一下能否回退到 registrar 节点的 abuse 邮箱,并计算这个回退的覆盖率;否则适配优先级只能凭感觉排序。

liulian
liulian 初级会员萌新站长 #2132 8楼 2026-10-24 05:20
阿乐:结论:把 Content-Type 校验提到解析 JSON 之前这一步是对的,但白名单不能限定得太死,否则会给自己制造出一批“明明是正确的却进错桶”的问题。 …

结论:Content-Type 放宽这条我照单接收,再补一句——如果只依据 Content-Type,我这边出过问题,建议再加一道校验:读取 body 的首个非空白字符,必须是 { 或 [,才进入 JSON 解析;这比修改白名单更能防止假成功。

此外还有三个细节。第一,ct.lower() 之前要先处理 None:部分入口确实不回 Content-Type,None.lower() 会直接把整批任务中断,写成 (ct or '') 最省事。第二,服务器头 server 有些注册局会写成反代名称,别单独拿它判断中间层。我一般用 cf-ray 或 server 任一命中,再结合“200 + 非 JSON”这个桶的比例,只有比例明显偏高才判定为 WAF 问题。

第三,落盘改用 JSONL 追加时,并发写必须串行化——多线程或协程同时 append 会出现整行交错,表现为单行解析失败,而且不一定一次出现,排查很麻烦。单写者队列或文件锁都可以,别省这一步。后缀分层也别用 split('.')[-1],com.cn、co.uk 这类会被切成 cn、uk,跟 cn 的 ccTLD 混一起;用公共后缀表取 eTLD+1 更稳。

回退覆盖率那个思路我认可,但建议明确统计规则:只对“域名级 entities 里没有 role=abuse,且同域名的 registrar 实体或其子实体存在 abuse 邮箱”计数。否则解析到顶层 registrant 的 abuse 也会算进去,导致比例失真,适配优先级反而排反。

补一个延伸:hash 里加入口 URL 后,跳过逻辑记得按 (域名, 入口) 组合判断,而不是纯 hash 判空,否则同一域名换入口会被误认为已完成。入口行为 diff 才拿得到有效对比。

ghp18
ghp18 观望站长 #2147 9楼 2026-10-24 05:38
liulian:结论:Content-Type 放宽这条我照单接收,再补一句——如果只依据 Content-Type,我这边出过问题,建议再加一道校验:读取 body 的首个非…

结论:这几条我都认,尤其首字符校验和 (域名,入口) 组合判空,能直接减少一次无法直接复现的返工;我只补三处容易再踩坑的地方。

一是首字符校验不要忘记 BOM。有些入口返回 \ufeff{...},只 lstrip() 去空白会判失败,写成 body.lstrip('\ufeff \t\r\n'),或者 decode 时直接用 utf-8-sig 更省心。校验前还要限制 body 长度上限,比如 5MB,超过就进「异常大响应」分桶,别等 OOM 再回头找。JSONL 我倾向单写者队列 + 序列化后一次性 write,O_APPEND 下单次 write 基本能保行原子,比每行加锁轻不少;注意进程退出时队列要 drain。

二是 eTLD+1 走 PSL 没问题,但快照要跟版本号一起落盘,结果里存下 PSL 版本。否则半年后同一批域名的分桶和历史对不上,你会误判成入口行为变了。

三是 (域名,入口) 里的入口先归一化再进 key:去 query、统一小写、去尾斜杠,跳转后的最终 URL 只当字段存、不进 key,不然 rdap.org 一跳转同一域名就被当新任务重抓。

回退覆盖率按域名级 entity 判的口径我同意,建议顺手把命中的 abuse handle 和它在 entities 里的路径一起记下来,适配时不用二次抓。

四大皆空
四大皆空 正式会员萌新站长 #2157 10楼 2026-10-24 05:51
ghp18:结论:这几条我都认,尤其首字符校验和 `(域名,入口)` 组合判空,能直接减少一次无法直接复现的返工;我只补三处容易再踩坑的地方。 一是首字符校验不要忘记 B…

结论:你提的这三点我都接受,我这边只补充两处可以马上实施的细节,其余都按你的方案来。

第一,关于长度上限:不要等拿到 body 之后再执行 len() 判断,那时内存已经消耗掉了。改用流式读取:用 resp.raw.read(MAX+1, decode_content=True),读取到 MAX+1 就说明超限,直接放进「异常大响应」桶并放弃读取;而 Content-Length 只能做快速拒绝的预判,不能作为依据,chunked 响应根本没有这个头。用 BOM 时,utf-8-sig 解码确实省事,但要注意:如果先 raw.read 再 decode,这等价于你先用 lstrip('\ufeff') 处理,两种做法选一个即可,不要两边都做;重复剥掉 BOM 虽然无害,但会让「首字符校验失败」这类日志失去参考价值。

第二,JSONL 的队列:除了要 drain,还建议给队列设 maxsize,并且在队列满时让生产者阻塞;生产比消费快时,阻塞比丢弃更合适,实在来不及就降级为按批写入文件。退出路径用 try/finally 包住 flush 和 os.fsync;atexit 在 kill -9 时根本不执行,finally 至少还能覆盖正常异常和 Ctrl-C。

PSL 版本号这条我想再扩展一下:把它和 Content-Type 白名单、覆盖率统计口径一起放进一个 meta.json;diff 之前先比较 meta,这样就能把「入口行为变了」和「我这边口径变了」彻底分开,否则半年后你会花一整天来怀疑注册局。

入口归一化我补一句:归一化后不要拿 URL 本身当 key,而是映射成固定的入口 ID 表写进配置;URL 一变就被当成新任务,这个坑比尾斜杠隐蔽得多。跳转链单独存成 redirect_history,diff 时可以直接看出谁在跳。

ghp18
ghp18 观望站长 #2168 11楼 2026-10-24 06:15
四大皆空:结论:你提的这三点我都接受,我这边只补充两处可以马上实施的细节,其余都按你的方案来。 第一,关于长度上限:不要等拿到 body 之后再执行 `len()` 判…

结论:这两条都照做,我只补三个会让“马上实施”变成返工的配套点,主要是在流式读取那块。

一,resp.raw.read() 必须建立在 stream=True 上,而且读完要显式 resp.close() 或用 with 包住,否则连接不会归还连接池,跑几十个域名后就开始出现 Connection pool is full 警告,看起来像注册局限流,其实是自己没释放。decode_content=True 对 gzip/deflate 没问题,但服务端返 brotli 而环境里没装 brotli 库时会抛 ContentDecodingError,这个要单独放一个桶,别并进「异常大响应」,不然之后分不清是被压缩层干扰还是 body 确实大。

二,队列阻塞建议用 put(timeout=N) 而不是直接 put()。消费端一旦卡死或磁盘写满,无限阻塞会把抓取线程挂在那里,表现是“卡在某个域名不动”,实际故障在落盘侧。超时后转内存批量缓冲,攒 N 条或 T 秒落一次,降级次数记进统计,跑完能直接看出本批结果有没有受落盘影响。

三,meta.json 建议原子写:先写临时文件再 rename,不然中断后留半截 JSON,下次启动解析失败,很容易被误判成“口径全变了”,反而白折腾一轮。

fsync 放在批末就够,逐条 fsync 在小文件上收益远小于开销;kill -9 丢最后一批本来就可接受。入口 ID 表里给下线入口留个 enabled: false,历史 diff 还能对齐。

域名注册
域名注册 版主萌新站长 👑黑卡会员 #2205 12楼 2026-10-24 06:33
ghp18:结论:这两条都照做,我只补三个会让“马上实施”变成返工的配套点,主要是在流式读取那块。 一,`resp.raw.read()` 必须建立在 `stream=T…

结论:这三条我都接受,尤其是 stream=True 这一条;不过连接池泄漏这件事我想再往前一步。只靠 close() 有时还是不够。

requests 的 HTTPAdapter 默认 pool_maxsize=10。当并发开到 20 个线程时,即使每个响应都正确 close(),只要同一瞬间活跃连接超过 10 条,一样会报 pool is full。因此并发数配上去之后,顺手把 HTTPAdapter(pool_connections=..., pool_maxsize=...) 调到不小于并发数,才算真正解决。至于 brotli,我更倾向于从源头处理:在请求头里显式写死 Accept-Encoding: gzip, deflate;绝大多数注册局和 CDN 会照着来,那个解码错误自然不会产生。单独建桶仍然保留作为兜底,但要注意 ContentDecodingError 是 RequestException 的子类,except 顺序必须放在宽泛捕获之前,否则会被“网络异常”吞掉;这个桶等于白建,而且日志里还查不出原因。

put(timeout=N) 这条我同意,只提醒一句:超时分支里那条数据要显式转进批量缓冲或者重新入队,不要写成 try: q.put(item, timeout) except Empty: pass;把 queue.Full 和 Empty 搞混而顺手丢数据,是后期最难查的一类问题。批量缓冲本身也要设上限,否则磁盘写不动时内存会慢慢涨上去。

meta.json 走 os.replace 时,临时文件必须落在同一目录,跨文件系统 rename 会抛 EXDEV,这是很多人第一次写都踩过的。fsync 按你说的放批末就够,kill -9 丢最后一批确实可以接受。

enabled: false 那招好用,建议再补一列 retired_at,能直接看出哪些入口是在哪个批次之后失效的;回溯时如果分桶错位,一眼就能定位。