使用Chrome插件攔截廣告的原理


使用 Chrome 插件攔截廣告的原理

項目地址:chrome_plugin_zhihu_adblock

本文閱讀起來可能需要先了解一些 Chrome 插件基礎知識,通過本文您可以學到什么?

  1. chrome 插件攔截廣告思考方法和一般原理
  2. 瀏覽器攔截 fetch 和 xhr 請求的方法

思路

網頁上的廣告可以分以下三種情況:

  1. 網頁上的廣告都是由 html 構成的,所以只要用 chrome 插件刪除這些 html即可。
  2. 有些廣告是在正常加載后進行動態加載的,它們會和一般的代碼塊混在一起,這里我們可以攔截代碼塊的 http 請求,然后在請求成功之后,把廣告從 html 上面刪去
  3. 有些廣告是通過專門的 http 請求加載的,這里我們攔截這些 http 請求,讓他們發不出去即可。

刪除 html

我們只需要知道廣告 html 的 selector,然后直接刪除即可,這里我們寫一個比較通用的函數:

// fuckAd
const createFuckAd = (adSelector, textSelector) => () => {
    // 廣告
    const ads = document.querySelectorAll(adSelector);

    if (ads.length > 0) {
        const cardBrand = document.querySelector(textSelector);

        if (cardBrand) {
            console.log(`已屏蔽廣告:${cardBrand.innerText}`);
        }

        // 刪掉廣告
        [...ads].forEach(item => item.parentNode.removeChild(item));
    }
}

然后我們在 onload 事件里面刪除廣告即可。我們以知乎為例,代碼如下:

window.onload = () => {
    createFuckAd('.Pc-card')();
    createFuckAd('.Pc-feedAd-container', '.Pc-feedAd-card-brand--bold')();
    createFuckAd('.Pc-word-card', '.Pc-word-card-brand-wrapper > span')();
}

攔截請求

對於動態加載的廣告,我們可以攔截請求,判斷是否有加載廣告的請求進來了,然后對於混有正常請求的代碼,我們在請求成功之后刪掉廣告;對於只有廣告的請求,我們直接攔截這些請求。這里我們以 fetch 為例(對於 xhr 請求,可以通過這個庫Ajax-hook),代碼如下:

// hook fetch
const fetch_helper = {
    originalFetch: window.fetch.bind(window),
    myFetch: function (...args) {
        // 攔截只有廣告的 http 請求
        if (args[0].includes('https://www.zhihu.com/commercial_api/')) {
            return Promise.reject(1);
        }

        return fetch_helper.originalFetch(...args).then((response) => {
            // 對於有正常代碼的 http 請求,在請求完成之后,刪除廣告
            if (response.url.startsWith('https://www.zhihu.com/api/v3/feed/topstory/recommend?')) {
                setTimeout(createFuckAd('.Pc-feedAd-container', '.Pc-feedAd-card-brand--bold'), 188);
            }
            return response;
        });
    },
}

window.fetch = fetch_helper.myFetch;

其它

核心代碼我們已經寫完了,然后只需要在document_start的時候引入代碼即可。這里需要注意的是,我們不能直接在 contentScript 里面運行上面的代碼,因為雖然 contentScript 能夠操作頁面上的 dom 元素,但是它運行在一個獨立的環境里面,這里我們需要使用chrome.runtime.getURL獲取 url,然后動態加載:

const s = document.createElement("script");
s.src = chrome.runtime.getURL("main.js");
s.onload = function () {
    s.parentNode.removeChild(s);
};
(document.head || document.documentElement).appendChild(s);

思考

其實上面只是攔截廣告的方法,但是對於一個好的廣告攔截插件,怎么判斷 html 和 js 是一個廣告,才真正是一個非常大的挑戰,我們通過查看adblock plus的源碼可以發現它是這么判斷的:

// popupBlocker.js
function checkPotentialPopup(tabId, popup)
{
  let url = popup.url || "about:blank";
  let documentHost = extractHostFromFrame(popup.sourceFrame);

  let specificOnly = !!checkWhitelisted(
    popup.sourcePage, popup.sourceFrame, null,
    contentTypes.GENERICBLOCK
  );

  let filter = defaultMatcher.matchesAny(
    parseURL(url), contentTypes.POPUP,
    documentHost, null, specificOnly
  );

  if (filter instanceof BlockingFilter)
    browser.tabs.remove(tabId);

  logRequest(
    [popup.sourcePage.id],
    {url, type: "POPUP", docDomain: documentHost, specificOnly},
    filter
  );
}

它首先獲取可能的跳轉彈窗的 url,然后判斷 url 的 host 和當前頁面的 host是否一樣,再判斷白名單里面有沒有。當然還有使用tensorflow通過機器學習收集可能的廣告 url:

// ml.js
const tfCore = require("@tensorflow/tfjs-core");
const tfConverter = require("@tensorflow/tfjs-converter");

for (let object of [tfCore, tfConverter])
{
  for (let property in object)
  {
    if (!Object.prototype.hasOwnProperty.call(tf, property))
      tf[property] = object[property];
  }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM