
我开源了一个谷歌广告拦截检测识别工具,代码仓库放在文章末尾,需要的自取。
独立开发者的小网站靠 AdSense 吃饭,本就没什么盈利,再加上浏览器的广告拦截插件,让原本就举步维艰的生活变得雪上加霜。
网站广告被拦了等于白干,写这篇是因为最近在给网站加广告屏蔽检测,踩了一堆坑,记录一下。
本文主要介绍广告拦截的技术原理,以及怎么检测我们网站上的AdSense广告是否被屏蔽了。
开始之前,请使用广告拦截插件的读者先读一下站长给用户的一封信: https://axiaoxin.com/letter/
一、先说说广告拦截插件的实现原理
广告拦截工具大致分三类,原理不太一样。
1.1 浏览器扩展(最常见)
uBlock Origin、AdGuard、AdBlock Plus 这些,本质上是浏览器插件,靠过滤规则列表干活。
规则长这样:
||googleads.g.doubleclick.net^
||pagead2.googlesyndication.com^
##.adsbygoogle
##.ad-banner
| 开头的是网络请求拦截,匹配 URL 就直接阻断。
## 开头的是元素隐藏,页面渲染后把匹配的元素 display: none 掉。
Chrome 扩展用的是 webRequest API,可以在请求发出去之前掐掉它,或者重定向到本地替身脚本。Firefox 以前也支持 webRequest 的阻塞模式,后来搞 Manifest V3 限制了一波,但 uBlock Origin 这些还是能找到办法。
我使用的广告拦截插件 AdGuard 就是这么干的——它把 adsbygoogle.js 的请求被重定向到 chrome-extension://.../redirects/googlesyndication-adsbygoogle.js,一个空函数替身。
1.2 DNS 层拦截
Pi-hole、NextDNS、AdGuard Home 这类,在路由器或者系统层面把广告域名解析到 0.0.0.0 或者 127.0.0.1。
你的电脑问 DNS:pagead2.googlesyndication.com 在哪?DNS层的广告拦截插件回:「127.0.0.1」。然后浏览器连本机,啥也拿不到,加载广告的请求就挂了。
这种方式看不到浏览器里的任何动静,扩展检测不到它,因为它根本不在浏览器里。
1.3 浏览器内置拦截
Brave 浏览器的 Shields、Safari 的「隐藏 IP 地址」、Firefox 的 Enhanced Tracking Protection,都是自带的。
Brave 比较特殊,它基于 Chromium,但内置了 Brave Shields,默认拦截第三方广告和追踪器。用户可能不知道自己开了广告拦截,因为这不是装的插件,是浏览器自带的。
二、怎么检测用户有没有开广告拦截插件呢?
知道了广告拦截怎么工作,检测就有方向了。总结了几种检测AdSense广告被拦截的核心思路。
2.1 诱饵元素
在页面里塞一个看起来像广告的元素,然后检查它还在不在。
<!-- 诱饵 -->
<div class="adsbox" id="bait-adsbox">ad</div>
<script>
(function () {
var bait = document.getElementById("bait-adsbox");
if (!bait) {
console.log("元素被移除了,大概率有拦截器");
return;
}
var style = window.getComputedStyle(bait);
if (style.display === "none" || style.visibility === "hidden") {
console.log("元素被隐藏了");
}
})();
</script>
坑:诱饵放 position: absolute; left: -9999px 的话,offsetHeight 本来就是 0,会误判。得放在正常文档流里,或者用其他方式隐藏。
2.2 Performance API 看资源大小(最可靠)
这是我从 Admiral 的代码里学来的,最靠谱的一招。
广告拦截会阻断 adsbygoogle.js 的加载。现代浏览器有 Performance API,可以通过 performance.getEntriesByType('resource') 能拿到所有加载的资源信息,包括 transferSize——实际传输的字节数。
正常从 Google CDN 加载 adsbygoogle.js,transferSize 好几万字节。如果被 AdGuard 重定向到本地扩展脚本,transferSize 就是 0,因为没走网络。
function checkResourceBlocked() {
var entries = performance.getEntriesByType("resource");
for (var i = 0; i < entries.length; i++) {
var entry = entries[i];
// 匹配广告相关资源
if (entry.name.indexOf("adsbygoogle") === -1) continue;
// transferSize === 0 意味着没走网络,可能被重定向到本地
if (entry.transferSize === 0 && entry.deliveryType === "") {
return {
blocked: true,
reason: "zero-transfer-size",
url: entry.name,
};
}
// 被重定向到 chrome-extension://
if (entry.name.indexOf("chrome-extension://") !== -1) {
return {
blocked: true,
reason: "redirected-to-extension",
url: entry.name,
};
}
}
return { blocked: false };
}
transferSize 是核心检测手段。正常从 Google CDN 加载的 adsbygoogle.js 有几十 KB,transferSize 不可能为 0。但 AdGuard 把它重定向到本地的替身脚本时,这个值就是 0。
坑点:deliveryType 得一起判断。如果是 “cache”,表示从缓存加载,transferSize 也是 0,但不是被拦截。
2.3 检测 adsbygoogle 对象
adsbygoogle.js 正常加载后会创建 window.adsbygoogle。如果用户装了拦截器,这个对象可能不存在,或者 push 方法被替换成空函数。
// 正常情况
typeof window.adsbygoogle; // "object"
typeof window.adsbygoogle.push; // "function"
// 被拦截后
typeof window.adsbygoogle; // "undefined"
但这里有个坑:adsbygoogle.js 是 async 加载的,检测脚本执行时它可能还没加载完,会误判。得等 window.load 事件后再查。
而且 AdGuard 比较鸡贼,它的替身脚本也会创建 window.adsbygoogle,但只是个空壳。可以进一步检测 push 方法的行为:
// 记录 push 前的网络请求数
var before = performance.getEntriesByType("resource").length;
// 调用 push
window.adsbygoogle.push({});
// 等一会儿,看有没有新请求
setTimeout(function () {
var after = performance.getEntriesByType("resource").length;
if (after === before) {
console.log("push 后没有新请求,可能是替身脚本");
}
}, 500);
2.4 DNS 层拦截的检测
DNS 拦截不修改浏览器里的任何东西,但有个特征:广告域名请求会极快失败。
检测方式就是探测广告域名可达性,发一个请求到广告域名,看能不能连上。
function checkDNSBlocking() {
var img = new Image();
var start = Date.now();
img.onerror = function () {
var duration = Date.now() - start;
// 正常网络错误(比如 404)通常要几百毫秒
// DNS 拦截返回 NXDOMAIN 或 127.0.0.1,通常 < 50ms
if (duration < 50) {
console.log("可能是 DNS 层拦截");
}
};
img.src = "https://pagead2.googlesyndication.com/favicon.ico?_=" + Date.now();
}
坑点:网络慢也可能导致失败,得结合时间判断。50ms 内失败基本确定是本地拦截。
2.5 Brave 浏览器的检测
Brave 会暴露 navigator.brave:
if (navigator.brave && typeof navigator.brave.isBrave === "function") {
console.log("Brave 浏览器");
}
但用户可能只是用 Brave 但没开 Shields,所以这只是识别浏览器,不能直接判定拦截。
2.6 多维度加权评分
单一检测都容易误报,得组合起来打分。
| 检测项 | 权重 |
|---|---|
| 资源重定向 (transferSize=0) | 40 |
| 诱饵元素被隐藏 | 25 |
| adsbygoogle 对象异常 | 20 |
| DNS 探测快速失败 | 15 |
| Safari 内容拦截器 | 15 |
| 企业防火墙特征 | 10 |
总分超过 50 就判定为「有拦截」。还可以加组合加成,比如「资源重定向 + 元素隐藏」额外加 15 分。
三、检测到广告被屏蔽之后该怎么办?
1. 弹遮罩提示
检测到广告拦截插件,强制弹窗提示用户关闭广告屏蔽插件,让页面无法在被操作。
2. 防绕过
用户 F12 打开控制台,document.getElementById('overlay').remove() 就绕过了。得加点防护:
// MutationObserver 监控遮罩被移除
var observer = new MutationObserver(function() {
if (!document.getElementById('adblock-overlay')) {
// 重建遮罩
showOverlay();
}
});
observer.observe(document.body, { childList: true, subtree: true });
// 同时隐藏页面内容
document.body.classList.add('adblock-locked');
CSS 配合:
body.adblock-locked > *:not(#adblock-overlay) {
display: none !important;
}
四、完整的广告屏蔽插件检测示例
把上面这些拼起来,写个简单的检测库。
// adblock-shield.js
(function () {
"use strict";
var detected = false;
var checkCount = 0;
var MAX_CHECKS = 3;
// 显示遮挡层
function showOverlay() {
if (detected) return;
detected = true;
// 创建遮罩
var div = document.createElement("div");
div.id = "adblock-overlay";
div.innerHTML =
'<div style="position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,0.8);display:flex;align-items:center;justify-content:center;">' +
'<div style="background:#fff;padding:40px;border-radius:12px;text-align:center;max-width:400px;">' +
"<h2>检测到广告拦截器</h2>" +
"<p>请关闭广告拦截器后刷新页面</p>" +
'<button onclick="location.reload()">刷新</button>' +
"</div></div>";
document.body.appendChild(div);
// 隐藏页面内容
document.body.style.overflow = "hidden";
var main =
document.getElementById("main") || document.querySelector("main");
if (main) main.style.display = "none";
}
// 检测 1:资源重定向
function checkResourceRedirect() {
if (!performance.getEntriesByType) return { blocked: false };
var entries = performance.getEntriesByType("resource");
for (var i = 0; i < entries.length; i++) {
var e = entries[i];
if (e.name.indexOf("adsbygoogle") === -1) continue;
if (e.transferSize === 0 && e.deliveryType === "") {
return { blocked: true, reason: "zero-transfer" };
}
if (e.name.indexOf("chrome-extension://") !== -1) {
return { blocked: true, reason: "extension-redirect" };
}
}
return { blocked: false };
}
// 检测 2:诱饵元素
function checkBaitElement() {
var el = document.createElement("div");
el.className = "adsbygoogle";
el.style.cssText =
"display:block;width:10px;height:10px;position:absolute;left:-9999px;";
document.body.appendChild(el);
var style = window.getComputedStyle(el);
var blocked =
style.display === "none" ||
style.visibility === "hidden" ||
el.offsetHeight === 0;
document.body.removeChild(el);
return { blocked: blocked };
}
// 检测 3:脚本对象
function checkScriptObject() {
var hasScript =
document.querySelector('script[src*="adsbygoogle"]') !== null;
if (!hasScript) return { blocked: false };
if (!window.adsbygoogle) {
return { blocked: true, reason: "object-missing" };
}
return { blocked: false };
}
// 检测 4:DNS 层
function checkDNS() {
return new Promise(function (resolve) {
var img = new Image();
var start = Date.now();
img.onerror = function () {
resolve({ blocked: Date.now() - start < 50 });
};
img.onload = function () {
resolve({ blocked: false });
};
img.src =
"https://pagead2.googlesyndication.com/favicon.ico?_=" + Date.now();
setTimeout(function () {
resolve({ blocked: false });
}, 2000);
});
}
// 综合检测
async function detect() {
checkCount++;
var r1 = checkResourceRedirect();
var r2 = checkBaitElement();
var r3 = checkScriptObject();
var r4 = await checkDNS();
var score = 0;
if (r1.blocked) score += 40;
if (r2.blocked) score += 25;
if (r3.blocked) score += 20;
if (r4.blocked) score += 15;
console.log("Check #" + checkCount, "Score:", score);
if (score >= 50) {
showOverlay();
return true;
}
if (checkCount < MAX_CHECKS) {
setTimeout(detect, 2000);
}
return false;
}
// 启动
if (document.readyState === "complete") {
setTimeout(detect, 500);
} else {
window.addEventListener("load", function () {
setTimeout(detect, 500);
});
}
})();
我封装了一个开源库,一共实现了7种检测方法,需要的可以直接拿去用:
源码和详细文档在 GitHub: https://github.com/axiaoxin-com/adblock-shield
觉得好用的,给个Star吧!
五、参考与工具
- uBlock Origin 源码 — 看过滤引擎怎么实现
- AdGuard 过滤规则文档
- Performance Resource Timing API
—
transferSize的定义 - Manifest V3 的 webRequest 变更 — 了解 Chrome 扩展的限制
结语
广告被拦截是AdSense站长绕不开的问题。理解拦截原理、掌握检测方法,才能在用户体验和收入之间找到平衡。如果你有更好的检测思路,欢迎交流。
另外,你可能感兴趣的阅读: 自动刷新 Google AdSense 广告单元的实现方案(auto-refresh-gad.js)
作者: axiaoxin ,独立开发者,靠几个小网站养活自己。








