你点开一篇文章,迎面撞上的却是一堵墙:“检测到广告拦截器,请停用后继续访问。“也许这个指控没冤枉你;也许你压根没装广告拦截器,网站却咬定你装了。但无论哪种情况,网站都从未见过你的扩展列表——它照样把你抓住了。
没有任何 API 会把你的扩展列表交给网页。所以反广告拦截脚本找的从来不是拦截器本身:它们会在页面里埋下一些拦截器必然会清除的东西,再回头查看哪些活了下来。本页上的小工具此刻正对你的浏览器运行其中六个陷阱;本文接下来会逐一解释它们各自在做什么。
这背后的经济账很简单:广告养活页面,被拦截的广告一分钱也带不来,而一堵能把一部分拦截广告的访客重新变回广告展示的墙,哪怕惹恼其余的人也是划算的。下文这些脚本存在的全部理由,就在这里。
诱饵元素:因”消失”而暴露
几乎每一款广告拦截器都内置同一套核心规则:EasyList,一份由社区维护、描述”广告在页面上长什么样”的清单。除了数以万计的 URL 匹配模式,它还带有外观(cosmetic)规则:隐藏所有带 ad-unit 或 adsbox 之类 class 名的元素。
这份清单是公开的,反广告拦截脚本自然也会去读。检测手段里最古老的一招,就是创建一个不可见的一像素 div,给它恰好套上这些 class 名,然后读取它的 offsetHeight。没有拦截的浏览器会报告这一个像素;被拦截的浏览器报告的是零,因为某条外观过滤规则把这个 div 设成了 display: none。页面从头到尾都没见过你的扩展,它看到的只是一个 div 不复存在。
<div class="adsbox ad-unit banner_ad"> offsetHeight: 0 这就是上方小工具里的 “CSS 诱饵” 检查,也是老牌的 BlockAdBlock 和 FuckAdBlock 库的核心引擎——互联网上半数 “检测 adblock” 教程至今仍在重新打包它。这种旁敲侧击的思路,和反机器人系统追猎浏览器自动化时的招数如出一辙:谁也看不见 Puppeteer,能看见的只有 Puppeteer 改变的东西。
一去不回的请求
第二类陷阱布在网络层。拦截器不只是隐藏元素,还会拒绝向已知的广告服务器发起请求,于是页面主动放出诱饵:从 pagead2.googlesyndication.com 加载一个脚本,从 tpc.googlesyndication.com 加载一张图片,再向 ads.pubmatic.com 发一个 fetch()。这些都是货真价实的广告基础设施,是页面故意去请求的。如果触发的是错误回调而不是加载事件,那就说明你机器上的某样东西掐断了这条请求。Chrome 的控制台甚至会直接点出凶手的名字:net::ERR_BLOCKED_BY_CLIENT。
小工具的六项检查里有四项都是这个形态,每种请求类型各占一项——因为过滤规则对脚本、图片、CSS 加载的图片和 fetch 请求的处理各不相同,一款拦截器完全可能在这一项上被抓、在另一项上安然通过。
不少网站干脆连诱饵都省了。Google 自家的广告脚本 adsbygoogle.js 在加载时会定义一个 JavaScript 对象,页面只需检查这个对象是否存在即可。当真正的广告脚本本身就是诱饵时,“拦截它”和”暴露自己”就成了同一件事。
YouTube 如何检测你的广告拦截器(以及为什么它是另一回事)
YouTube 根本不屑于用诱饵 div。它的播放器清楚服务器给视频流挂上了哪些广告,预期自己的播放事件会依次触发,而检测逻辑就藏在播放器的 JavaScript 里,经过混淆并且频繁重排,逼得过滤列表的维护者只能不停打补丁跟进。Google 还试验过服务端广告插入,把广告直接缝进视频流里,任何过滤列表都够不着。这和一段到处复制粘贴的诱饵脚本完全不在同一个重量级,背后还有政策撑腰:Google 的立场是广告拦截器违反 YouTube 的服务条款。那里的军备竞赛节奏快、针对性强,这正是 “YouTube 是怎么检测 adblock 的” 这个问题没有稳定答案的原因。而本页讲的诱饵机制有稳定答案——其余百分之九十九的”请停用您的广告拦截器”弹墙,跑的都是它。
没装广告拦截器也被指控
在 Google 里输入 “adblock detected”,排名靠前的联想词之一就是 “adblock detected but no adblock”(检测到 adblock 但我没装)。这些陷阱恰好解释了这种困惑:只要存在拦截,无论它藏在哪一层,陷阱都会触发。
- DNS 过滤。 Pi-hole、NextDNS,或是自带广告过滤的路由器,会在诱饵请求离开你的网络之前就把它掐灭。在诱饵看来,这和 uBlock Origin 没有任何区别。
- 默认就拦截的浏览器。 Brave 的 Shields 出厂默认就会丢弃广告请求,Firefox 的严格跟踪保护也会拦截已知的广告和跟踪域名。全程不需要任何扩展。
- 其他扩展。 Cookie 横幅清除器和各类隐私工具会出于自己的目的隐藏元素、拦截请求,结果踩响的是同样的陷阱。
- 安全软件。 带”网页防护”功能的杀毒套件会替整台机器过滤广告域名。
检测器分不清是哪一层吞掉了它的诱饵,而那块弹墙也根本不关心。如果你正处于这种境地,上方的小工具可以帮你确诊:它会指出六项检查里究竟是哪几项触发了。元素检查触发,说明有东西在隐藏页面内容;请求检查触发,则说明问题出在网络路径上的某个环节。
如何不让网站检测到你的广告拦截器
你有三个体面的选项,外加一对简单粗暴的。
- 在拦截器里把该站点加入白名单(允许列表)。 这正是那堵墙存在的目的所在,也是 Adblock Plus 借默认开启的 Acceptable Ads 计划固定下来的机制。
- 让过滤列表保持最新。 拦截广告的这套生态同样在拦截反广告拦截:EasyList 专门维护着一份 Adblock Warning Removal List,uBlock Origin 也内置了多份 Annoyances 列表,能在许多弹墙出现在你眼前之前就把它们剥掉。
- 为你真正阅读的网站付费。 直接终结这场对峙。
- 阅读模式,或者干脆关掉 JavaScript。 这就是那对粗暴的选项:它们能干掉检测器,但页面赖以运转的其他一切也会陪葬。
拦截广告是合法的,网站在你停止拦截之前拒绝提供服务也同样合法。德国最高法院在 2018 年就给出了这个结论——当年出版商起诉 Adblock Plus 的开发商,结果败诉。既然法律在两边都已尘埃落定,剩下的就只有军备竞赛了。
你的拦截器成了你指纹的一部分
在我们的五款热门广告拦截器横评中,我们用同样这六项检查逐一测了每款拦截器,结果差距悬殊:Adblock Plus 六项中了五项,uBlock Origin 中了两项,Ghostery 只中了一项。
| 广告拦截器 | 主检测 | CSS 诱饵 | 脚本 | 图片 | CSS 背景 | Fetch | 中招数 |
|---|---|---|---|---|---|---|---|
| Ghostery | ✓ | ✓ | ✓ | ✕ | ✓ | ✓ | 1/6 |
| uBlock Origin | ✓ | ✓ | ✓ | ✕ | ✓ | ✕ | 2/6 |
| uBlock Lite | ✓ | ✓ | ✓ | ✕ | ✓ | ✕ | 2/6 |
| AdBlock | ✓ | ✓ | ✕ | ✕ | ✓ | ✕ | 3/6 |
| Adblock Plus | ✕ | ✕ | ✕ | ✕ | ✓ | ✕ | 5/6 |
由此可以得出两点。第一,拦截器的”响动”并不一样大,“可检测性”是一项挑选时就能货比三家的属性。第二,这个触发模式本身就是信息。哪些陷阱会响,取决于你用的是哪款拦截器、哪几份过滤列表,于是这个结果会连同你的字体指纹和 canvas 哈希,一并汇入追踪者为你建立的档案。广告拦截器不会让你隐身,它只会让你变成一个以某种具体、可测量的方式拦截广告的人。
到底该怎么做
- 先看清哪些检查会触发。 跑一遍本页的小工具,再做一次完整浏览器扫描,把周边的一切也查个遍:指纹哈希、WebRTC,一样不落。知道自己栽在哪几项检查上,正是”瞎猜”和”对症下药”的区别。
- 被冤枉了?先找到是哪一层。 停用所有扩展再刷新页面。如果仍然被检测到,说明拦截发生在更深的地方:DNS 过滤、浏览器默认设置,或是杀毒软件的网页防护。在一个你根本没装的扩展里给网站加白名单,永远修不好一条由路由器执行的拦截。
- 管理多个账号?留意你的拦截模式。 拦截模式是又一项必须在每个身份上保持一致的特征,一个会话接一个会话都不能走样。Incogniton 会把每个配置文件都做成一个完全独立的浏览器,拥有自己的扩展和设置,配置文件 A 的拦截习惯绝不会渗进配置文件 B 的指纹里。
下一次再有网站信誓旦旦地说你在用广告拦截器时,你就知道它真正抓到的是什么了:一个失去了高度的 div,或者一条一去不回的诱饵请求。而上方的小工具会告诉你,出卖你的究竟是哪一个。