开发了一个可以屏蔽度娘搜索广告的浏览器插件

391 阅读3分钟

前言

本文主要为技术分享,仅供学习。 前段时间开发了一个浏览器新标签页的插件(见上文),最近在插件里植入了屏蔽搜索引擎广告的功能,在此记录并分享一下。 具体实现的效果如下:

adsfilter-BQh4blcv.png

如何识别搜索引擎的广告

以度娘为例,观察度娘搜索结果返回,我们能够发现,度娘搜索返回的每一条结果都包裹在一个classc-container元素中, 如果这条搜索结果为广告,则该元素的子节点必定包含一个类似下面这样的节点:

<span
  class="ec-tuiguang ecfc-tuiguang m12mvnb"
  data-tuiguang='{"content":"本搜索结果为 [linkLabel] 信息,请注意可能的风险。","linkLabel":"商业推广","a":{"url":"http://e.baidu.com/ebaidu/home?refer=XXX"}}'>
  广告
</span>

那么接下来,识别出带广告的搜索结果就很简单了,大致操作如下:

// data-tuiguang 带广告的标识
// c-container 一条搜索结果
const ads = document.querySelectorAll("[data-tuiguang]") // 找到页面中所有带有推广表示的元素
ads.forEach((ad) => {
  const searchItem = ad.closest(".c-container") // 顺着推广标识, 找到最近的搜索结果祖先元素
  // do something...
})

至于如何处理广告元素,这里不多做赘述,无非就是隐藏、删除或者遮罩等手段。

如何将这个功能嵌入插件中

内容脚本是拓展自带的能力,它能够在独立的js环境中运行而不与其托管网页或其他扩展程序发生冲突, 实际操作也很简单, 将我们写好的js脚本申明在拓展程序的manifest.json中即可,如下:

{
  "name": "OVOTab 新标签页",
  "author": "waylonzheng",
  "manifest_version": 3,
  // ...
  "permissions": [
    // ...
    "storage", // 存储状态
    "activeTab" // 允许访问当前标签页
  ],
  "content_scripts": [
    {
      "matches": [
        "https://cn.bing.com/*",
        "https://www.baidu.com/*",
        "https://www.so.com/*",
        "https://www.sogou.com/*"
      ],
      "js": [
        "content.js" // 核心代码
      ],
      "css": [
        "style.css" // 注入的样式
      ],
      "run_at": "document_end" // 在 DOM 完成之后,在图片和框架等子资源加载之前立即注入脚本 参考: https://developer.chrome.com/docs/extensions/reference/api/extensionTypes?hl=zh-cn#type-RunAt
    }
  ]
}

当然,光靠manifest.json的配置是无法满足我们的需求的,在搜索后,可能会存在翻页等场景,这些场景返回的广告如何找到并屏蔽呢?这里笔者使用了MutationObserver,在首屏屏蔽完以后监听body后代的变更,完整代码如下:

// content.js

// 首次加载隐藏广告
function hiddenAdsFirstLoad() {
  const adsList = document.querySelectorAll("[data-tuiguang]")
  adsList.forEach((adsItem) => {
    handleHiddenAds(adsItem)
  })
}

// 隐藏广告
function handleHiddenAds(ads) {
  // 找到搜索结果容器
  const searchItem = ads.closest(".c-container")
  if (searchItem) {
    const isHandled = searchItem.classList.contains("ovo-search-ads-filter") // 是否已经处理过

    if (isHandled) {
      return
    }
    searchItem.classList.add("ovo-search-ads-filter") // 为广告添加样式
  }
}
// 监听 DOM 变化
function setupMutationObserver() {
  const observer = new MutationObserver(mutationObserverCallback)

  const config = { childList: true, subtree: true } // 监听子节点变化 和 所有后代节点变化
  observer.observe(document.body, config)
}

// 监听 DOM 变化回调
function mutationObserverCallback(mutations) {
  requestAnimationFrame(() => {
    mutations.forEach((mutation) => {
      if (mutation.type === "childList") {
        // 对新增的节点 筛选是否是广告
        mutation.addedNodes.forEach((node) => {
          if (node.nodeType === 1 && node.hasAttribute("data-tuiguang")) {
            handleHiddenAds(node) // 隐藏广告
          }
        })
      }
    })
  })
}

// 判断是否开启过滤
window.chrome.storage.sync.get(["enable"], (result) => {
  if (result.enable !== false) {
    hiddenAdsFirstLoad() // 首屏屏蔽
    setupMutationObserver() // 屏蔽后续新增的广告
  }
})
/* style.css */

.ovo-search-ads-filter {
  pointer-events: none;
  user-select: none;
  position: relative;
  margin-bottom: 8px !important;
}
.ovo-search-ads-filter > div {
  filter: grayscale(100%) blur(5px);
}
.ovo-search-ads-filter::after {
  content: "OVO 去除广告";
  display: block;
  color: #818c9d;
  font-size: 12px;
  position: absolute;
  font-weight: 500;
  bottom: 0px;
  right: 8px;
  background-color: rgba(255, 255, 255, 0.1);
  z-index: 1;
}

至此大功告成,大致效果如图: 屏幕录制2025-01-10 11.25.26.gif

结语

最后,本文主要为技术分享,仅供学习,欢迎大家体验笔者独立开发的新标签页插件~