使用 Chrome 插件攔截廣告的原理
項目地址:chrome_plugin_zhihu_adblock
本文閱讀起來可能需要先了解一些 Chrome 插件基礎知識,通過本文您可以學到什么?
- chrome 插件攔截廣告
思考方法和一般原理
- 瀏覽器
攔截 fetch 和 xhr 請求
的方法
思路
網頁上的廣告可以分以下三種情況:
- 網頁上的廣告都是由 html 構成的,所以只要
用 chrome 插件刪除這些 html
即可。 - 有些廣告是在正常加載后進行動態加載的,它們會和一般的代碼塊混在一起,這里我們可以攔截代碼塊的 http 請求,然后
在請求成功之后,把廣告從 html 上面刪去
。 - 有些廣告是通過專門的 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];
}
}