尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

PHP 实现微信域名拦截检测:原理、代码与实战避坑指南

PHP 实现微信域名拦截检测:原理、代码与实战避坑指南 做微信生态业务的站长大多有过这种经历推广链接刚发出去没多久用户点开就提示“已停止访问该网页”几乎等同于断粮。域名被微信拦截甚至封禁是每一个做公众号、小程序、H5营销、短链接分发的团队绕不过去的坎。这篇文章我打算完整聊聊 PHP 如何做微信域名拦截检测重点把标题里提到的“官方接口”到底是什么、检测原理、可运行的 PHP 实现代码以及我在实际巡检过程中踩过的坑一次性讲清楚。看这篇文章的人大概率是 PHP 开发者、站长或者负责运营投放的同学。你不需要懂很深的前端知识只需要有一台能跑 PHP 的服务器能执行 crontab就能搭一套属于自己的微信域名实时监控系统。我下面写的所有代码都是可以直接抄走改一改就用的不是那种讲完原理就完事的文章。以前大家检测域名是否被封要么人工用手机微信打开一遍要么去买第三方检测 API。人工检测慢而且人容易忘第三方 API 贵还经常不透明。自己用 PHP 做一套自动化检测成本低、可控性强、数据都在自己手里后续还能把检测日志做成趋势报表长期来看非常划算。1. 微信域名拦截的前前后后为什么你需要一套自动检测1.1 一个典型的域名被封场景我之前帮一个做在线课程推广的朋友处理过一个问题。他们在朋友圈投放了一个引流落地页效果不错当天 UV 破万但第二天早上用户反馈“网页打不开”了。我拿手机试了下微信里打开就是白底灰字的“已停止访问该网页”但用普通浏览器访问完全正常。这就是典型的微信域名拦截域名本身没有被墙服务器也正常运行只是微信客户端在用户访问前拦了一道。当时他们是人工发现问题的等运营同事在群里看到用户截图再反馈过来已经过去了五六个小时。推广暂停、线索中断、客服被问炸损失挺大。从那之后我就意识到凡是依赖微信流量的业务必须有一个自动检测域名可用性的机制。哼哧哼哧等到用户来反馈再处理黄花菜都凉了。尤其是投放高峰期域名状态检测应该做到分钟级而不是靠人肉刷新。1.2 标题里的“官方接口”到底指什么标题里提到了“全新官方接口”这里我得先泼一盆冷水微信官方没有开放过一个“查询域名封禁状态”的公共 API。至少到目前为止我没有在微信开放平台和微信公众平台的公开文档里看到类似“传入 URL返回是否被封”的开放接口。那市面上为什么都在喊“官方接口”根据我的实践大家说的“官方”主要分两类一是腾讯云网址安全检测这类能力。腾讯安全开放了一部分网址检测服务输入一个 URL返回它是否命中恶意库、风险等级、漏洞信息等。这类接口确实是官方背景有正规的鉴权、配额和计费体系适合作为域名风控的辅助依据。二是微信客户端本身的拦截行为。当微信内置浏览器判定一个链接有风险时会返回一个官方安全提示页这个页面由微信官方服务器生成包含固定的文案和特征。我们通过模拟微信客户端的请求把这个提示页抓回来再识别它的特征就能反推出当前域名在微信侧的可用状态。严格来说第二种方式不是“接口”而是“客户端行为模拟”。但因为它拿到的判罚结果来自微信官方安全策略页面所以很多工具型产品也把它包装成“官方接口检测”。我在实际项目里也是以第二种方式为主用第一种方式做交叉验证准确率最稳。1.3 方案选型对比我把市面上常见的检测方式整理了一张对比表方便你做技术选型检测方案可靠性成本实现难度适合场景纯 HTTP 状态码检测很低免费低不推荐拦截页经常返回 200关键词特征匹配较高免费中小型业务自用足够微信 UA 模拟 特征匹配高免费中高生产环境主流自建方案腾讯云网址安全检测较高按量计费中适合作为辅助校验第三方付费检测 API不确定高低图省事但数据源不透明结论很简单如果你在乎的是结果可靠、数据自持那就自己用 PHP 实现一套“微信 UA 模拟 拦截特征匹配”的方案再按需接入腾讯云安全接口做二次确认。这套组合下来比买第三方 API 便宜得多而且你能完全控制检测逻辑。2. 检测原理微信拦截一个域名时到底发生了什么2.1 微信内置浏览器的访问链路要理解检测逻辑得先从微信内置浏览器的访问流程说起。用户在微信里点击一个链接请求并不是直接到达业务服务器而是先经过微信安全策略中心做一个风险判断。微信会根据域名的历史信誉、用户举报数据、内容安全扫描结果等因素决定是放行还是拦截。如果判定为风险链接微信会返回一个安全提示页面上来代替原始网页展示给用户。提示页的域名可能是weixin110.qq.com下面的某个路径也可能是mp.weixin.qq.com下的安全提示地址具体取决于命中哪种拦截规则。这里的关键点是微信的提示页是服务端渲染的 HTML里面包含了大量可识别的固定文案。只要我们能拿到这个 HTML就能判断域名是否被拦截。而拿到它的方式就是从服务器发起一个带微信 UA 的 HTTP 请求让微信安全策略认为“这是一个微信客户端访问”。2.2 拦截提示页的典型特征我抓过上百个被拦截的页面把特征归纳成下面几类每一类都可以作为判断依据特征类型典型表现说明最终跳转域名落到weixin110.qq.com、mp.weixin.qq.com/s?__biz...说明请求被微信安全中心接管页面标题“已停止访问该网页”、“安全提醒”微信拦截页独有的标题正文文案“网页包含不安全内容”、“网页包含诱导分享、诱导关注内容”、“网页包含垃圾信息”不同风险类型的固定文案DOM 结构拦截页带有特定的class或id例如weui-msg等微信前端组件特征普通业务页面很难撞车响应行为返回 200 但内容是提示页或 302 跳转到安全域名不能只看状态码这些特征不是静态的微信会不定期调整提示页的样式和文案。所以生产环境里的特征库要能随时更新最好存成独立配置文件或者数据库表方便运营人员在微信改版后快速补充规则。2.3 为什么不能只靠 HTTP 状态码最早我犯过这个错误用curl拿一个 URL看返回码如果不是 200 就认为“被封了”。结果误报率高得离谱。原因很简单微信拦截页在很多情况下返回的 HTTP 状态码就是 200因为页面本身能正常打开只是展示给用户的内容是“已停止访问”而已。另外部分被拦截的域名会被微信 302 到一个安全提示地址如果你没有开启CURLOPT_FOLLOWLOCATION你只会看到一个 302 状态码容易误判为“跳转异常”。更隐蔽的一个坑是有些正常业务页面本身就可能包含“已停止访问”之类的文字比如你在做一篇关于微信封号原理的文章正文里提到了这句话特征匹配就会误杀。所以最终判断逻辑要综合最终 URL、页面标题、正文关键词、特征域名四重维度而不是只靠一个条件。3. 动手实现PHP 版本的微信域名拦截检测3.1 环境准备我假设你的运行环境是 Linux 服务器PHP 版本在 7.4 以上。需要确保装了以下扩展curl、mbstring、json。可以用下面这条命令快速检查php -m | grep -E curl|mbstring|json如果没有 curl 扩展在 Ubuntu/Debian 上安装sudo apt install php-curl php-mbstring不需要引入任何 Composer 包原生 PHP 就能实现这也是我在生产环境喜欢用原生 curl 的原因部署简单服务器上只要有 PHP-CLI 就能跑。3.2 核心请求函数模拟微信 UA 抓取链接第一步是写一个请求函数模拟微信内置浏览器的访问行为。UA 一定要带MicroMessenger和微信版本号这是微信安全策略判断“客户端来源”的关键。我平时用的 UA 模板长这样Mozilla/5.0 (Linux; Android 13; SM-G9810) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/107.0.0.0 Mobile Safari/537.36 MicroMessenger/8.0.32.2600(0x28002053) WeChat/arm64 Weixin NetType/WIFI Language/zh_CN ABI/arm64注意不同的微信版本、设备类型UA 细节会有差异。微信在 Android 和 iPhone 上的 UA 前缀不同iOS 上通常是Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X)同样带有MicroMessenger标记。我建议多准备几个 UA 轮换使用避免单一 UA 被限流。请求函数用 curl 实现关键选项如下function fetchUrl(string $url, string $ua): array { $ch curl_init(); curl_setopt_array($ch, [ CURLOPT_URL $url, CURLOPT_RETURNTRANSFER true, CURLOPT_FOLLOWLOCATION true, CURLOPT_MAXREDIRS 5, CURLOPT_CONNECTTIMEOUT 5, CURLOPT_TIMEOUT 10, CURLOPT_SSL_VERIFYPEER false, CURLOPT_SSL_VERIFYHOST false, CURLOPT_USERAGENT $ua, CURLOPT_HTTPHEADER [ Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9, Cache-Control: no-cache, ], CURLOPT_ENCODING gzip, deflate, CURLOPT_HEADER true, ]); $response curl_exec($ch); $error curl_error($ch); $httpCode (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); $finalUrl curl_getinfo($ch, CURLINFO_EFFECTIVE_URL); $totalTime curl_getinfo($ch, CURLINFO_TOTAL_TIME); curl_close($ch); if ($error ! ) { return [error $error, http_code 0, final_url , body ]; } // 分离响应头和响应体 $headerSize strpos($response, \r\n\r\n); $body substr($response, $headerSize 4); return [ error , http_code $httpCode, final_url $finalUrl, body $body, time round($totalTime, 3), ]; }几个要注意的点CURLOPT_FOLLOWLOCATION必须开启因为微信可能先返回 302 再跳到安全提示页。CURLOPT_ENCODING设置为gzip, deflate否则微信服务器返回压缩内容时你拿到的是一堆乱码。超时时间不要设置太短有些安全检测链路响应慢5 秒连接超时、10 秒总超时比较合适。这里把 SSL 证书验证关了是因为部分服务器环境的 CA 证书不完整会导致请求失败。如果你有安全洁癖也可以把证书路径配好再开启验证但作为巡检工具关闭影响不大。3.3 拦截特征库与匹配逻辑拿到页面 HTML 之后匹配逻辑是整个检测系统的大脑。我建议把特征分成“URL 特征”和“内容特征”两类。URL 特征用于快速判断如果最终跳转地址包含weixin110.qq.com或者mp.weixin.qq.com下的安全提示路径基本可以判定为拦截。内容特征则要抓 HTML 中的title标签和去除标签后的纯文本再匹配关键词。下面是一个可用的特征库结构$features [ url [ weixin110.qq.com, mp.weixin.qq.com/s?__biz, /safe/, ], title [ 已停止访问该网页, 安全提醒, 网页包含不安全内容, 链接已中断, ], keywords [ 已停止访问该网页, 网页包含诱导分享、诱导关注内容, 网页包含垃圾信息, 网页包含恶意营销内容, 已被多人投诉, 链接无法访问, ], ];匹配的时候先用内容特征匹配如果整个页面找不到任何特征再看最终 URL 是否命中微信安全域名两者都没有命中才算“正常”。我给一个完整的判定函数function checkIntercept(string $finalUrl, string $html, array $features): array { $html mb_convert_encoding($html, UTF-8, UTF-8,GBK,GB2312); preg_match(/title(.*?)\/title/is, $html, $titleMatch); $title trim($titleMatch[1] ?? ); $text trim(preg_replace(/\s/u, , strip_tags($html))); foreach ($features[url] as $urlFeature) { if (stripos($finalUrl, $urlFeature) ! false) { return [intercepted true, feature url: . $urlFeature, title $title]; } } foreach ($features[title] as $titleFeature) { if ($title ! mb_strpos($title, $titleFeature) ! false) { return [intercepted true, feature title: . $titleFeature, title $title]; } } foreach ($features[keywords] as $keyword) { if (mb_strpos($text, $keyword) ! false) { return [intercepted true, feature keyword: . $keyword, title $title]; } } return [intercepted false, feature , title $title]; }这里额外做了一步mb_convert_encoding用来处理部分页面返回 GBK 编码的情况避免中文关键词匹配不上。正则取title时用/is模式兼容换行符。3.4 接入官方安全接口做二次校验纯特征匹配已经能解决 90% 的问题但为了降低误报我还会再叠加一个官方安全检测接口做交叉验证。这里以腾讯云安全检测类服务为例说明。首先去腾讯云控制台开通对应的安全检测服务拿到SecretId和SecretKey。然后调用就是一个标准签名请求。腾讯云 API 的签名流程比较繁琐要去拼接参数做 HMAC-SHA256 签名我这里给一个封装好的简单请求框架实际使用时以你控制台最新的 OpenAPI 文档为准function tencentSecurityCheck(string $url, string $secretId, string $secretKey): array { // 1. 构造公共参数 $params [ Action DescribeUrlDetection, Version 2020-01-01, Region ap-guangzhou, Timestamp time(), Nonce rand(1000, 9999), SecretId $secretId, Url $url, ]; // 2. 按腾讯云签名规范生成签名密钥、请求方法、参数排序等 $signature signRequest(POST, $params, $secretKey); // 3. 发起请求携带签名 // ... curl POST 到腾讯云接口地址参数带上 Signature // 返回结果中包含风险状态、风险等级、风险类型等字段 }签名逻辑每个产品大同小异但接口地址、Action 名、字段含义都要以官方文档为准我不在这里写死一个不可能直接跑的完整代码免得误导你。实际部署的时候可以把这一层单独抽成服务类接口字段变了只改一个地方。补充一个思路如果你不想自己写签名可以用腾讯云的官方 SDK。PHP 版 SDK 安装方式在官方文档里有composer require tencentcloud/tencentcloud-sdk-php就行调用方式和参数名都封装好了更方便。3.5 封装检测类与命令行脚本上面几个函数还是散的我建议封装成一个WechatDomainDetector类方便在业务代码里复用class WechatDomainDetector { private array $features; private array $uas; public function __construct(array $features, array $uas) { $this-features $features; $this-uas $uas; } public function check(string $url): array { $ua $this-uas[array_rand($this-uas)]; $result fetchUrl($url, $ua); if (!empty($result[error])) { return [ status error, message $result[error], ]; } $check checkIntercept($result[final_url], $result[body], $this-features); return [ status $check[intercepted] ? blocked : normal, feature $check[feature], page_title $check[title], final_url $result[final_url], http_code $result[http_code], response_time $result[time], checked_at date(Y-m-d H:i:s), ]; } }再写一个 CLI 入口文件check_cli.php方便直接命令行检测?php require WechatDomainDetector.php; $url $argv[1] ?? ; if ($url ) { fwrite(STDERR, Usage: php check_cli.php url\n); exit(1); } $features include features.php; $uas include uas.php; $detector new WechatDomainDetector($features, $uas); $result $detector-check($url); echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), \n;跑一下效果php check_cli.php https://example.com输出{ status: blocked, feature: title:已停止访问该网页, page_title: 已停止访问该网页, final_url: https://weixin110.qq.com/..., http_code: 200, response_time: 0.823, checked_at: 2024-06-18 10:30:22 }到这一步你已经有一套能跑通的检测工具了。接下来就是把它放到真实的运营链路里定时跑、出报表、发告警。4. 实战落地定时任务、队列调度与告警通知4.1 数据表设计与域名库管理检测脚本能跑只是第一步真要落地到业务里得管好域名库和检测日志。我建议至少建两张表一张管域名基础信息一张管检测记录。域名表wechat_domainsCREATE TABLE wechat_domains ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, domain VARCHAR(255) NOT NULL COMMENT 域名, remark VARCHAR(255) DEFAULT COMMENT 备注比如所属业务线, status TINYINT NOT NULL DEFAULT 1 COMMENT 1启用 0停用, last_result TINYINT DEFAULT 0 COMMENT 0正常 1拦截 2检测失败, last_feature VARCHAR(255) DEFAULT COMMENT 命中的拦截特征, last_check_time DATETIME DEFAULT NULL COMMENT 最近一次检测时间, created_at DATETIME NOT NULL, PRIMARY KEY (id), UNIQUE KEY uk_domain (domain) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;检测日志表wechat_domain_check_logsCREATE TABLE wechat_domain_check_logs ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, domain_id INT UNSIGNED NOT NULL, result TINYINT NOT NULL COMMENT 0正常 1拦截 2检测失败, feature VARCHAR(255) DEFAULT , http_code INT DEFAULT 0, response_time DECIMAL(10,3) DEFAULT 0, final_url VARCHAR(500) DEFAULT , created_at DATETIME NOT NULL, PRIMARY KEY (id), KEY idx_domain_time (domain_id, created_at) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;last_feature字段很重要。我第一次做巡检的时候只存了正常/异常后面想分析“为什么被封”完全无从下手。加上特征字段之后如果域名被封你能直接看到命中的是“诱导分享关键词”还是“被投诉”文案方便运营针对性处理。4.2 用 Cron 做批量巡检有了表结构就可以写一个批量巡检脚本check_domains.php逻辑很简单从wechat_domains表里取出状态为启用、到点该检测的域名逐个跑检测把结果写入日志表并更新域名表的last_result和last_check_time。关键点在于控制并发和频率。微信对检测行为实际上也是有一定容忍度但如果你一个脚本瞬间几十个并发请求出去很容易触发安全策略导致一部分请求拿不到真实结果。我给的建议是全局限流每两次检测之间至少间隔 1 秒如果是大几十个域名建议放到队列里慢慢跑。最简单的限流实现就是在循环里加usleep(1000000)也就是 1 秒foreach ($domains as $domain) { $result $detector-check(https:// . $domain[domain]); saveCheckLog($domain[id], $result); updateDomainStatus($domain[id], $result); usleep(1000000); }crontab 配置示例每 5 分钟跑一次*/5 * * * * /usr/bin/php /data/www/wechat-domain-check/check_domains.php /data/logs/wechat_domain_check.log 21实际跑的时候建议把单个域名的检测频率控制在 5 分钟左右一次。太频繁没有意义微信的拦截状态不会秒级变化太慢了又起不到“及时发现问题”的作用。5 分钟是一个比较均衡的区间。4.3 异常自动通知如果发现域名被拦截光记录在数据库里没用运营不可能没事就去看后台。必须主动通知。我用得比较多的是企业微信群机器人因为配置简单只要一个 Webhook URL。通知函数代码如下function sendWebhook(string $webhookUrl, string $content): bool { $data json_encode([ msgtype text, text [content $content], ], JSON_UNESCAPED_UNICODE); $ch curl_init($webhookUrl); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_HTTPHEADER, [Content-Type: application/json]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 5); $response curl_exec($ch); curl_close($ch); return $response ! false; }但这里有个注意点不要第一次发现异常就疯狂告警。微信拦截偶尔会有误判或者瞬时抖动比如你的服务器 IP 被临时限流导致请求超时结果被判定成“检测失败”这种情况并不代表域名真的被封了。我的处理逻辑是连续 2 次检测都命中拦截特征才进入告警流程告警之后如果下一次检测恢复正常再发一条“恢复通知”。这样既不会漏报也不会因为偶然抖动把运维同事的手机炸掉。告警内容别只发“域名被封”四个字要把域名、命中的特征、检测时间、当前状态都带出来方便接收人第一时间判断严重程度。[域名拦截告警] 域名example.com 状态已停止访问该网页 命中特征title:已停止访问该网页 检测时间2024-06-18 10:30:22 请尽快处理5. 常见问题与避坑指南5.1 误报页面内容本身带了“已停止访问”字样这个我前面提过也是最常见的误报来源。如果你的页面是一篇分析微信封号机制的文章里面必然会有“已停止访问该网页”这句话内容匹配就会误判为拦截。解决方案有两个方向第一个方向是提高判断权重。优先看最终 URL 是否跳转到微信安全域名再看页面标题是否命中最后才看正文关键词。因为普通业务页面的标题里出现“已停止访问该网页”的概率极小正文里出现则很常见。第二个方向是引入“连续 N 次确认”机制。单次检测命中不算数连续两次或三次命中才标记为拦截。这个逻辑可以放在定时任务里通过查询历史日志表实现。5.2 检测频率与 IP 限制微信安全策略对高频请求是有感知的。我试过把检测间隔缩短到 0.2 秒跑十几分钟之后请求开始大量超时返回结果几乎全部“检测失败”等于废了。后来把间隔调整到 1 秒以上长时间运行一直稳定。如果你的域名数量特别多比如上千个建议拆分任务用多个服务器 IP 分担或者直接把检测间隔再拉长到 3 秒以上。检测不及时的代价远小于 IP 被限流后的全军覆没。5.3 服务器检测正常用户手机仍然打不开这种情况我遇到不止一次。服务器那边抓回来的页面完全正常但用户手机上还是被拦截。原因通常是以下三个一是地域差异。微信安全策略可能在不同地区返回不同结果你服务器在北京用户用户手机在广东看到的提示页可能不一样。二是页面中的二级资源被拦截。你的主域名是正常的但页面里加载了某个 JS、图片或者统计脚本的域名被微信封了微信检测到页面存在风险资源直接把整个页面拦截掉。这时你只检测主域名是发现不了问题的。三是微信客户端版本差异。老版本微信的拦截判断逻辑和新版本不一样同样的页面老版本可能放行新版本可能拦截反过来也一样。所以生产环境里我始终建议保留一个“真机验证”通道。怎么验证把你的域名生成一个二维码让不同地区、不同微信版本的同事扫码打开人工复核一遍。自动化检测解决“大部分问题”真机抽检补足“最后一公里”。5.4 官方接口权限与合规提醒关于官方安全接口我最后多说一句不同服务商、不同产品的接口字段和权限差异比较大命名还可能调整接入的时候一定以官方文档为准。不要拿网上流传的旧代码直接怼到生产环境签名算法和参数名变了会浪费你一整天。另外域名检测这个能力是把双刃剑无论如何不要把它用在规避平台规则上。微信拦截一个域名本质是提醒你页面内容存在不合规风险。检测到被封之后正确做法是去微信公众平台的“申诉与投诉”入口提交材料同时自查页面内容该整改的整改。利用检测工具去连夜换域名继续投违规内容治标不治本域名池再大也有耗尽的一天。我踩过最深的坑是早期把所有特征写死在代码里微信一改提示页样式整个检测系统就失灵。后来我把特征库抽成配置文件和数据库表运营同事也能维护再碰到微信改版改个关键词就能恢复再也不用半夜爬起来改代码了。这套 PHP 检测方案我跑了两年多从几万个域名里帮业务提前发现了几十次风险真正做到了“在用户反馈之前先发现问题”。你现在完全可以照着上面的代码搭一套简单的版本跑起来之后再根据自己的业务情况慢慢完善特征库和告警逻辑。
返回列表