ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

微信域名批量检测工具v8.0:短链探针与凭证池自动化巡检实践

微信域名批量检测工具v8.0:短链探针与凭证池自动化巡检实践 简介微信对站外链接有着严格的访问控制域名能否在微信内置浏览器中正常打开直接关系到依赖微信流量的业务转化。这份草根系列工具的v8.0版本面向网站管理员、互联网运营者及微信生态从业者用于批量检测多个域名在微信环境下的可用状态。工具支持扫描登录微信、导入网址清单并模拟微信内核逐一验证检测完成后可导出结果文件便于用户根据结果快速定位被微信策略拦截的域名。资源压缩包共64个文件整体约12.17MB主要包括exe主程序、CefSharp相关dll运行库、53个pak内核资源文件以及htm使用文档、txt源码获取说明等辅助材料涵盖操作指引、问题排查与源码获取路径便于普通用户快速上手也方便开发者研究检测逻辑或做二次定制。目前已有365人浏览学习。1. 草根微信域名批量检测工具 v8.0别等用户截图才知道链接被拦做微信生态里的投放页、短链和落地页最怕的不是没人点而是链接刚发出去用户那边就弹出一句「该网页已停止访问」。域名到底什么时候被微信侧的拦截策略盯上的、拦了整站还是只拦了某个路径、解封后又多久能恢复——这些在公众号后台根本看不到。草根微信域名批量检测工具 v8.0就是把这个黑匣子撬开一条缝的东西把候选域名批量丢给微信官方接口做探针几十秒换回一份「哪个还能用、哪个已经被拦」的清单。它不需要企业级安全平台不需要按次付费的检测 API一台小机器加几个公众号账号的凭证就能自己跑起来。适合做站群、做投放、维护多个落地页的运营和技术也适合刚接手域名巡检的同学把它当自动化练手项目。2. 微信域名检测的三条路为什么 v8.0 把「长链转短链」当主力探针2.1 路径一长链转短链接口用官方报错当检测信号微信公众平台开放了一个「长链接转短链接」的接口正常情况下是用来生成短链的但开发者们早就发现它能当域名探针用。原因是微信在生成短链之前会先对提交的 long_url 做一轮校验域名是否备案、URL 格式是否合法、域名是否在微信的风控名单里。校验不通过短链就不会生成接口直接返回错误码。这个特性让检测变得非常干净调用接口时如果返回 errcode 为 0 并且带上了 short_url说明这个域名在微信侧是「被认可」的如果返回的是域名类错误码说明域名在微信侧不可用。相比自己去模拟微信浏览器访问落地页、再去比对拦截页特征这个方式拿到的是微信官方接口的直接判定结果可信度高而且单次请求毫秒级返回天然适合批量。这里要强调一个关键点微信并没有开放「域名批量检测」这种专用接口所谓检测工具本质上都是在消费 shorturl 这类公开接口的副产物逻辑。v8.0 选择它当主力探针是因为它兼顾了准确性和批量能力。代价是它需要一个公众号的 access_token才能发起请求。所以这类工具的架构里账号凭证管理永远是第一优先级后面第三章会展开讲。2.2 路径二微信内置浏览器行为模拟适合小批量复核第二种常见做法是完全模拟用户在微信里打开链接的行为。用带微信内置浏览器 UA 的请求甚至用 Playwright 这类无头浏览器去访问目标 URL然后看响应内容里有没有腾讯拦截页的特征片段。这个方案的优点是真实验证了「用户看到什么」尤其能发现接口检测不出来的细节比如落地页触发了分享拦截、或者被投诉后提示文案变化。但缺点头部也很明显无头浏览器启动和渲染开销大一条链接少说要两三秒微信风控对高频同 UA 访问有反爬特征批次一大容易被临时限流而且拦截页特征经常调整脚本要跟着维护特征库。所以它适合在 v8.0 的流程里做第二道复核不适合当批量主力。2.3 路径三第三方安全检测接口与人工兜底市面上也有第三方厂商的 URL 安全检测接口能返回域名是否被标记为风险。这类服务的优点是不用自己维护微信侧的判定逻辑拿来即用缺点是维度偏「全网安全」和「微信内是否可访问」不是一回事。一个域名在第三方检测里干干净净不代表微信侧不拦。反过来被判风险的域名在微信里可能也只是单项提示。粒度对不上就只能当参考。人工复核是最后一道兜底做法很朴素把检测结果里标记为「待复核」的域名生成一个临时短链用手机微信真实打开一次看最终落到哪个页面。v8.0 这类工具通常会把人工复核设计成一个独立队列而不是混在自动检测里因为人工的成本高只该用来处理机器判不准的那一小撮。三种路径的取舍可以简单对比检测路径判定依据单条耗时批量能力误报风险长链转短链接口官方短链是否生成毫秒级强适合并发未备案域名会被误判微信 UA 行为模拟响应是否含拦截特征秒级弱容易触发风控特征库过期导致漏报第三方安全接口全网风险库秒级中与微信侧口径不一致人工复核真机打开结果分钟级极弱最准确但成本高2.4 v8.0 为什么是「多账号凭证池」而不是代理 IP 池很多老一代检测工具会把力气花在维护代理 IP 池上因为它们的检测方式是高频访问腾讯的拦截页IP 一频繁就被封。但 v8.0 的思路是换赛道既然主力探针走了官方接口那频率限制就是按 access_token 维度计的一个公众号每天能调用的短链接口次数是固定的量大之后瓶颈在 token 而不是 IP。所以 v8.0 的典型骨架里都有一个凭证池组件把多个公众号的 appid 和 secret 放进去轮询。这样做还有个附带好处检测来源看起来是多个不同主体基本不会被微信侧标记为「同一账号疯狂试域名」。这个设计和子域名收集工具搭配起来特别好用——子域名收集跑出一批域名后正好丢给 v8.0 批量过一遍微信侧的状态确认哪些子域名还能在微信生态里正常访问。3. 搭一个能跑的 v8.0Python 批量检测脚本与多账号凭据池3.1 最小可用版本单个 token 循环检测先不看花哨功能一个最小可用的检测脚本只需要三件事拿 access_token、循环调 shorturl 接口、把结果打出来。需要一个已认证的公众号或者测试号也行前提是接口权限里已经把「长链接转短链接」开出来。import requests import time APPID wxYOUR_APPID SECRET your_app_secret BASE https://api.weixin.qq.com/cgi-bin def get_token(appid, secret): url f{BASE}/token params { grant_type: client_credential, appid: appid, secret: secret, } resp requests.get(url, paramsparams, timeout10).json() if access_token not in resp: raise RuntimeError(ftoken 获取失败: {resp}) return resp[access_token] def check_domain(token, long_url): url f{BASE}/shorturl params {access_token: token} payload {action: long2short, long_url: long_url} resp requests.post(url, paramsparams, jsonpayload, timeout10).json() return resp if __name__ __main__: token get_token(APPID, SECRET) domains [ https://a.example.com/page, https://b.example.com/landing, ] for d in domains: result check_domain(token, d) print(d, errcode:, result.get(errcode), short_url:, result.get(short_url)) time.sleep(0.5)这段代码的逻辑很直白get_token 拿到的 access_token 有效期是 7200 秒建议在内存里缓存复用没必要每个域名都调一次 token 接口。check_domain 里把 token 放在 URL query 上请求体只放 action 和 long_url这是微信文档的标准姿势。timeout10 指的是连接加读取的总超时批量场景下不要设太长否则某个域名卡住会拖慢整轮。这里要说一个参数细节long_url 必须是带协议头的完整 URL比如https://a.example.com/page不能只写a.example.com。如果 URL 里带中文参数建议先用 urllib.parse.quote 编码以后拼接否则接口可能因为 URL 解析问题返回非预期错误码。sleep(0.5) 是最粗暴的限速手段单账号小批量没问题量大以后要靠并发控制这是第四章的内容。3.2 凭据池多个公众号轮询突破单账号频控单账号跑着跑着就会撞上 45009意思是接口调用超出频率限制。解决思路不是去等它恢复而是准备多个账号一个被限流就切下一个。v8.0 的常见做法是把账号信息做成一个池子按轮询顺序取 token过期了自动刷新。import threading import time class TokenPool: def __init__(self, accounts): self.accounts accounts # 形如 [(appid, secret), (appid, secret)] self.tokens {} self.idx 0 self.lock threading.Lock() def _refresh(self, appid, secret): token get_token(appid, secret) self.tokens[appid] { token: token, expire_at: time.time() 7000, # 官方 7200 秒留 200 秒余量 } return token def get(self): with self.lock: for _ in range(len(self.accounts)): appid, secret self.accounts[self.idx % len(self.accounts)] self.idx 1 info self.tokens.get(appid) if info and info[expire_at] time.time(): return appid, info[token] token self._refresh(appid, secret) return appid, token raise RuntimeError(所有公众号账号均不可用)这个池子的核心是 idx 自增取模保证每次调用都换一个账号负载尽量均匀。锁是必须的因为多线程环境下两个线程同时刷新同一个 appid 的 token 会互相覆盖导致其中一个请求带着旧 token 出去。过期时间留 200 秒余量是为了避免请求发出时 token 刚好过期那种边界问题排查起来很费劲。要注意的是凭证池不是越多越好。每个公众号都能看到调用记录突然出现大量短链生成请求本身就容易引起注意。我一般建议池子容量控制在 5 到 10 个账号之间配合后面的并发限速使用而不是一味堆账号。3.3 增量检测与结果落盘别每天重复测同一个域名如果不加任何缓存策略每次运行都全量测一遍额度会很快耗尽。v8.0 的典型做法是维护一份历史状态表检测前先读表只有新域名或者上次结果异常的域名才进这次的任务队列。import csv import os from datetime import datetime STATE_FILE domain_state.csv FIELDS [domain, errcode, short_url, checked_at] def load_state(): state {} if os.path.exists(STATE_FILE): with open(STATE_FILE, newline, encodingutf-8) as f: for row in csv.DictReader(f): state[row[domain]] row return state def save_result(domain, errcode, short_url): now datetime.now().strftime(%Y-%m-%d %H:%M:%S) with open(STATE_FILE, a, newline, encodingutf-8) as f: writer csv.DictWriter(f, fieldnamesFIELDS) if os.path.getsize(STATE_FILE) 0: writer.writeheader() writer.writerow({ domain: domain, errcode: errcode, short_url: short_url, checked_at: now, }) def build_task_list(candidates): state load_state() tasks [] for d in candidates: old state.get(d) if not old: tasks.append(d) # 新域名必须测 elif old[errcode] 40048: tasks.append(d) # 上次不可用这次复检 return tasks增量逻辑的关键在 build_task_list已经测过且当时可用的域名默认跳过只有新出现或上次被判不可用的域名才重新检测。这里有一个经验点被判 40048 的域名一定要放进每次的复检队列因为解封是真实存在的场景域名可能今天不可用、下周就恢复了不自动复检的话这条记录就会一直烂在表里。另一个细节是 CSV 的追加写入。文件不存在时先写 header之后只追加行。这个方案在单机场景下够用不推荐引入数据库因为这工具本来就该保持轻量。4. 并发与重试参数把 v8.0 调稳而不是调快4.1 ThreadPoolExecutor 并发检测用信号量锁住 QPS批量检测最自然的优化是上线程池。但这里有个很常见的翻车点线程数开太大微信接口侧 QPS 超限明明域名是好的接口返回 45009检测结果整批作废。所以 v8.0 的并发设计里线程池只是载体真正限速靠的是信号量。from concurrent.futures import ThreadPoolExecutor, as_completed import threading semaphore threading.Semaphore(3) # 同一时刻最多 3 个请求在飞 def bounded_check(token, domain): with semaphore: result check_domain(token, domain) return domain, result def run_batch(token, domains, max_workers8): results [] with ThreadPoolExecutor(max_workersmax_workers) as pool: future_map { pool.submit(bounded_check, token, d): d for d in domains } for future in as_completed(future_map): domain future_map[future] try: domain, result future.result() results.append((domain, result)) except Exception as exc: results.append((domain, {errcode: EXC, msg: str(exc)})) return results这里两个参数要分开理解max_workers 是线程池的大小决定了同时有多少个任务在排队等待Semaphore(3) 决定了真正能同时发出去的 HTTP 请求数。我一般把 max_workers 设成 8信号量设成 3等效 QPS 大概是 3 左右跑几百个域名也就几分钟但不会触发接口频控。实际配置时要根据账号数量调整账号多可以适当把信号量放大到 5账号少老老实实维持 2 到 3。有一个判断信号量是否合适的土办法跑一轮看日志里 45009 的出现频率如果连续出现就把信号量减半再试。4.2 重试与退避不同错误码不同重试策略无脑重试是批量工具的大忌。一个域名的错误码是永久的业务判定重试十次结果也一样而网络超时和频控是临时状态重试才有效。所以重试策略必须按错误类型分开。下面的重试逻辑只处理两类情况网络层异常走指数退避45009 走换 token 重试。其他错误码直接返回结果不做任何重试。import time def check_with_retry(pool, domain, max_retries3): last_exc None for attempt in range(max_retries): try: appid, token pool.get() result check_domain(token, domain) errcode result.get(errcode) if errcode 45009: # 当前账号频控池子内部已经切到下一个账号继续下一轮 time.sleep(1 attempt) continue if errcode 40001: # token 失效强制刷新后重试一次 pool.tokens.pop(appid, None) continue return result except requests.Timeout: last_exc timeout time.sleep(2 ** attempt) # 1s, 2s, 4s 指数退避 except requests.RequestException as exc: last_exc str(exc) time.sleep(1) return {errcode: EXC, msg: last_exc, domain: domain}这段代码的核心原则是业务错误码 40048 这类直接返回不重试45009 表示当前账号限流换下一个账号继续40001 表示 token 失效把池子里缓存清掉强制刷新。网络超时用指数退避第一次等 1 秒、第二次 2 秒、第三次 4 秒避免雪崩式重试把接口打挂。这里有一条血泪经验不要把 45009 和网络超时混在一个重试分支里。45009 频繁出现说明整体 QPS 太高应该降速而不是重试重试只会让频控更严重。换句话说45009 是一个需要「后退」的信号而不是「再试一次」的信号。4.3 检测结果四级分档让下游知道该干嘛原始接口返回只有 errcode 和 short_url但运营需要的是「下一步动作」。v8.0 的常见做法是在检测层做一次结果分档把原始结果映射成四个状态状态判定条件下游动作可用errcode0 且拿到 short_url继续投放进入白名单不可用errcode40048 等域名类错误撤链、准备申诉待复核错误码不在已知集合进人工复核队列未知网络异常且重试失败下一轮补测分档逻辑看起来简单但有一个小坑40048 不代表一定是被封也可能是域名没备案或 URL 格式不合法。所以在分档时一定要结合域名的备案信息来判。常见做法是把备案状态存进域名的配置表里只有「已备案 返回 40048」才判不可用否则归到待复核。5. v8.0 落地避坑5 个让检测结果失真的典型问题5.1 未备案域名误报为「被封」现象一批新域名测完全部返回 40048工具直接判成「不可用」。运营慌了以为整批域名进了微信黑名单准备全部撤链。原因shorturl 接口的域名校验里包含备案检查。未备案域名或备案信息还没同步到微信侧的域名在接口看来就是「不合法域名」返回的错误码和真被封禁的域名一样都是 40048。工具没有区分这两类场景直接一刀切了。解决域名入库时单独维护一个备案状态字段。检测逻辑里加上前置判断只有备案状态为「已备案」的域名40048 才判为不可用未备案域名统一归到「待复核」提示运营先去确认备案状态。5.2 access_token 过期后整批检测全挂现象某天定时任务跑完几百个域名全部标记为待复核错误码清一色 40001。原因access_token 的缓存时间写死成 7200 秒但网络时钟偏差、或者公众号后台重置了 secret都会导致 token 提前失效。失效后脚本没有自动刷新带着死 token 跑完了整批任务。解决TokenPool 里的过期时间留余量是第一步更关键的是在检测循环里对 40001 做响应式处理遇到 40001 就把对应 appid 的缓存清掉强制刷新后重试一次。不要等整批跑完再统一处理那样浪费一整轮的额度。5.3 并发调大后触发接口频控整批结果掺水现象线程数从 8 调到 16检测速度快了一倍但结果里开始混入大量 45009。这些域名被分档逻辑归到「待复核」人工复核一看全是好的。原因接口的 QPS 限制是硬性的线程池开再大也没用超发的请求直接被限流。更隐蔽的是45009 出现后如果立即重试会进一步加重限流形成恶性循环。解决按账号数量控制信号量3 个账号以内信号量设为 25 到 10 个账号信号量设为 3 到 5。同时把 45009 作为「降速信号」处理连续出现三次以上全局 sleep 5 秒再继续。5.4 HTTPS 证书异常和重定向干扰检测结果现象一个用 http 访问会 301 跳到别处的域名检测结果忽好忽坏有时返回短链有时报错。原因shorturl 接口在解析 long_url 时会去拉取目标 URL 的响应头做校验。如果域名的证书过期、或者请求被重定向到微信侧不可识别的地址接口会判定 URL 异常返回的就不是标准错误码而是随风飘的异常信息。解决入库前先统一域名协议头尽量用 https 并保证证书有效。对带重定向的域名检测前先用 requests 的 allow_redirectsFalse 看一眼跳转目标确认跳转后的域名也在检测列表里避免微信接口拿到的是一个「中间商」地址。5.5 「接口可用」不等于「用户端一定能打开」现象工具显示某个域名「可用」但投放群里用户反馈在微信里手动输入这个域名打不开出现「已停止访问该网页」的提示。原因shorturl 接口校验的是「域名是否具备生成短链的资格」不等价于「用户在聊天窗口点链接的实时拦截状态」。微信的拦截策略是分场景的接口侧和用户端有时会有一段时间不同步尤其是域名刚被投诉或刚解封的时候。解决把自动检测当成第一道筛子筛完以后对「可用」名单做小比例抽检。抽检方式就是第二章说的微信 UA 行为模拟每天抽 5% 到 10%用真实微信 UA 访问一遍确认没有跳转到拦截页。这套组合拳打下来误判率能压到比较低的水平。6. 定时任务与告警把 v8.0 接进日常巡检闭环6.1 crontab 定时检测别在高峰期跑检测任务适合放在业务低峰期比如凌晨 4 点。这个时间段微信侧接口压力小token 额度也更经用。crontab 里直接把检测脚本串起来结果追加到 CSV同时把运行日志输出到独立文件方便排错。0 4 * * * cd /opt/domain_check /usr/bin/python3 check.py logs/check.log 21日志文件要定期轮转不然半年能攒好几个 GB。用 logrotate 按周切割就行保留最近 4 周。6.2 故障告警由「检测到封禁」直接推到企业微信群里定时任务跑出来的结果如果没人看等于白跑。v8.0 收尾阶段最常见的做法是接一个 webhook 告警检测到「不可用」状态就拼一条消息推到运维群里。企业微信群机器人的 webhook 接口是直连的代码很简单。import requests def send_alert(msg): webhook https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyYOUR_KEY payload {msgtype: text, text: {content: msg}} requests.post(webhook, jsonpayload, timeout10)告警一定要做去重同一个域名连续三天判不可用只发第一天那条就够了不然每天早上群里刷屏大家很快就会把告警通知静音。去重的简单做法是在状态表里记一个 alert_sent 字段域名状态没变化就不重复发。6.3 解封自动复检与申诉记录对「不可用」域名不要手动盯着而是让工具每天自动复检一次。状态表里 40048 的域名本来就在增量检测的任务队列里恢复可用后再发一条「域名已恢复」的告警这样运营就知道可以重新启用量了。我个人的习惯是申诉和检测分开记录域名被封后去微信公众平台提交申诉把申诉单号记录在状态表旁边复检恢复后自动把单号标记为已完成。当初我接手这套工具时就是懒得留申诉记录结果同一个域名被封了三次每次都要重新查历史记录确认申诉有没有提交过折腾了两周才把流程理顺。后来养成的规矩是所有自动化跑出来的状态变化都留一条可追溯的记录哪怕是 CSV 文件里的三行日志也比人的记忆可靠。这套工具体系折腾下来最大的收获不是省了多少人工而是把一个原本需要「等人反馈」的黑匣子变成了每天自动刷新的一张表。希望这篇笔记能帮你少走几条弯路。本文还有配套的精品资源点击获取
返回列表