如果你也被某些网站的广告所困扰, 请看这里

您所在的位置:网站首页 网页资料不能复制 如果你也被某些网站的广告所困扰, 请看这里

如果你也被某些网站的广告所困扰, 请看这里

#如果你也被某些网站的广告所困扰, 请看这里| 来源: 网络整理| 查看: 265

背景

你一定在网上遇到过如下这种场景:查找资料,根据搜索结果,点击进入某个网页,网页打开之后,结果发现,网站的广告喧宾夺主,抢夺眼球,扰乱视线,不关闭根本无法正常阅读和学习。于是你就去关闭那些广告,可是当你点击之后,发现又被带到另外一个页面。一个广告就够人心生厌烦的呢,然而页面上这样的广告还不止一个,令人不胜厌烦。有没有什么方法,治理一下这种不讲武德的广告,重新还阅读一个安静,不被打扰的环境。 channel.gif

效果展示

在网上找了一下,看到有文章说,Chrome扩展可以屏蔽网页中的广告。通过查找资料和调试,最终实现的效果如下: 初次进入目标页面时,点击扩展图标,启用屏蔽特定网站广告功能,立即移除特定网站的广告。下次进入时,直接清理。

channel.gif

Chrome扩展架构介绍

在开发Chrome扩展前,先说说Chrome扩展的构成,大多数Chrome扩展,通常由以下几部分组成:

manifest.json:扩展的配置文件, 声明扩展的清单文件版本,名称、版本号、图标、后台和注入网页脚本文件,权限等信息。 background:它的生命周期是扩展所有类型页面中最长的,它随着浏览器的打开而打开,随着浏览器的关闭而关闭,通常把需要一直运行的、启动就运行的、全局的代码放在background里面。 content_script: 是扩展注入到页面的脚本,但在页面 DOM 结构中是查看不到的。content_script 可以操作 DOM,但是它和页面其它的脚本是隔离的,访问不到其它脚本定义的变量、函数等,相当于运行在单独的沙盒里。content_script 可以调用有限的 chrome 插件 API,网络请求受到同源策略限制。

接下来我们逐一介绍一下。

manifest.json(清单文件)

它相当于扩展的地图,我们可以按图索骥,了解扩展所支持的所有功能。每一个扩展程序都需要有一个配置清单 manifest.json 文档,它提供了关于扩展程序的基本信息,例如扩展使用的清单版本, 后台脚本,注入网页脚本,所需的权限、名称、版本等。manifest.json的各项属性简介如下,清单文件每一项的具体配置请参考官方manifest说明文档。

{ /* ================必填项=================== */ // 浏览器会根据清单文件版本指定该版本拥有的功能及编码规范 "manifest_version": 3, // 扩展名称 "name": "My Extension", // 扩展版本 "version": "1.0.0", /* ===================== 推荐项 =================== */ // 控制扩展在工具栏的展示,如图标,鼠标划过时的文案,点击之后的弹出页 "action": {...}, // 定义支持多语言环境的扩展的默认语言 // 如果扩展包目录存在_locals文件夹,它是_locals文件夹默认语言的子目录名称, // 此时也是必配项,如果不存在_locals文件夹,可以不配 "default_locale": "en", // 扩展描述--文本格式,字符最大长度是132 "description": "A plain text description", // 扩展图标--一般是png格式,也支持BMP, GIF, ICO, JPEG // 不同尺寸的图标用途是: // 128*128 网上商店使用 // 48*48 扩展程序管理页面用 // 16*16 扩展页面使用 "icons": {...}, /* ===================== 可选项 ==================== */ // 作者邮箱,必须与网上商店发布扩展账号的邮箱一致 "author": "[email protected]", // 后台服务,扩展的事件处理程序 "background": {...}, // 通过content scripts,可以实现Chrome扩展与用户打开的Web页面之间的交互。 // 读取浏览器打开web页面的信息,和对其修改,并将信息传递给父扩展 "content_scripts": [{...}], // 扩展提供的选项配置页面 "options_page": "options.html", // options_page是打开一个tab页,options_ui是弹窗 "options_ui": {...}, // 覆盖chrome浏览器的一些配置项,如启动页,主页,搜索引擎等 "chrome_settings_overrides": {...}, // 覆盖chrome提供的新标签页 "chrome_url_overrides": {...}, // 配置触发扩展操作的快捷键 "commands": {...}, // 为来自扩展的请求的响应标头cross_origin_embedder_policy指定一个值 "cross_origin_embedder_policy": {...}, // 为来自扩展的请求的响应标头Cross-Origin-Opener-Policy指定一个值 "cross_origin_opener_policy": {...}, // 声明扩展修改或阻止用户页面网络请求的规则 "declarative_net_request": {...}, // 配置规则,使用declarativeNetRequest拦截/阻止/修改请求时, 无需使用declarativeContent读取页面权限 "event_rules": [{...}], // 声明哪些扩展或页面可通过runtime.connect和runtime.sendMessage连接本扩展 "externally_connectable": {...}, // 用于指定可用于打开某些文件类型的程序或应用程序 "file_browser_handlers": [...], // 用于指定 Chrome 扩展程序提供的文件系统的功能和支持的文件操作类型。 "file_system_provider_capabilities": {...}, // 在Chrome DevTools中添加新的UI面板和侧边栏 "devtools_page": "devtools.html", // 访问扩展的官方主页,可以设置成个人或公司站点,不设置的话将在chrome://extensions页面显示扩展程序 "homepage_url": "https://path/to/homepage", // 用import字段声明扩展依赖的模块 "import": [{...}], // 用于导出模块的,它可以将一个模块或者一个变量、函数等暴露给其他模块或应用程序使用。 "export": {...}, // 允许使用 input.ime API(输入法编辑器),与 ChromeOS 一起使用 "input_components": [{...}], // 扩展唯一标识。一般不需要指定,自动生成 "key": "publicKey", // 扩展对chrome浏览器的最低版本要求 "minimum_chrome_version": "107", // oauth2认证配置 "oauth2": {...}, // 向Chrome地址栏注册搜索关键字 ,在地址栏输入内容时会进行匹配 "omnibox": {...}, // 扩展API的调用授权 "permissions": ["..."], // 使用chrome.permissions API在运行时请求声明的可选权限 // 如果 background 请求的域名是跨域的,则必须要在 host_permissions 中追加该域名 "host_permissions": [...], // 运行时扩展需要用户授予的权限 "optional_permissions": ["..."], // background 请求的域名是跨域的,且读取的数据需要用户授权,需要追加的域名 "optional_host_permissions": ["..."], // 扩展所需要的插件或技术, 目前只有两种配置 3D或plugins "requirements": {...}, // 定义在沙盒中提供的扩展页面集合。扩展的沙盒页面使用的内容安全策略在content security_policy 键中指定 "sandbox": {...}, // 用于规定扩展程序中加载的资源允许的来源和类型 "content_security_policy": {...}, // 从Chrome99起新增了一个阅读清单,开放了一些配置属性 "side_panel": {...}, // 托管存储区配置, 用managed_schema字段指明托管存储区结构的协议文件,协议文件格式是JSON Schema "storage": {...}, // 注册文本转语音 (TTS) 引擎后,任何其它扩展或chrome应用使用tts API生成语音时,本扩展可以拦截处理 "tts_engine": {...}, // 托管在chrome网上应用商店之外的服务器扩展必须指明这个字段 "update_url": "https://path/to/updateInfo.xml", // 扩展名称展示不全时,使用的简称配置 "short_name": "Short Name", // 版本名称 "version_name": "1.0 beta", // 网页和其它扩展可以访问本扩展的网页资源 "web_accessible_resources": [...], // 在隐身模式下运扩展程序的方式 "incognito": "spanning, split, or not_allowed", // 只能在开发模式下使用 // 配置之后,可以使用chrome.automation API提供的自动化测试功能 "automation": {...}, } backgroud(后台)

背景后台是一个常驻的页面,它的生命周期是插件中所有类型页面中最长的。通常用来协调扩展程序中不同类型页面的任务和监听浏览器事件,如:扩展程序被安装、打开/关闭页面,切换页面,创建新标签、添加新书签、点击扩展工具栏图标等。只有一个配置项service worker, 用于指定 service_worker 文件。

{ // ... "background": { "service_worker": "background.js" }, // ... }

service_worker 可以使用几乎所有的Chrome API,但 service_worker 不能直接与网页的内容直接进行交互,需要与 content_scripts 进行通信来间接修改网页的内容。当下列情景发生时,service_worker才被执行:

扩展程序被安装或者更新。 所监听的事件被触发。 收到 content_scripts 或者 其它扩展程序的消息。 Chrome API的权限(permissions)

Chrome API需要在manifest.json的permissions中授权才能正常使用。想要系统的了解Chrome的权限知识请点击这里查看,文中用到的权限有:

权限描述tabs允许扩展程序操作浏览器标签页,例如创建、删除、切换、获取信息等activeTab允许扩展程序访问当前激活的标签页的内容,以及在该标签页上执行一些操作,例如注入JavaScript代码或修改页面样式background允许扩展程序在后台运行,并随时接收来自浏览器的事件和请求storage允许扩展程序在浏览器本地存储中读写数据 content_scripts(内容脚本)

content_scripts 是注入到网页中运行的文件。它可以使用标准的 Document Object Model(DOM)对象来访问网页中内容并对其进行修改。可以向页面注入js 或者 css 文件对页面进行操作和修改。但是它只能直接获取部分的 API:runtime、 storage 和 i18n ,注入脚本有两种方式,静态注入或者在代码里手动注入。想详细了解请参考这里。

由于安全等原因,content_scripts 在一个隔绝的环境里,与它所在的tab页绑定在一起。网页本身所创建对象和函数,在 content_scripts 中是无法访问的。打开几个匹配的页面就会运行几个执行文件,而这几个不同的执行文件之间由所在tabId 区分。因此想要向某个网页的 content_script 发送信息时需要指定 tabId,如chrome.tabs.sendMessage(tabId,message)。

另外还有几种脚本类型,popup,devtools,injected,它们的权限区别如下:

JS种类可访问的APIDOM访问情况JS访问情况直接跨域background(背景脚本)可访问绝大部分API,除了devtools系列不可直接访问不可以可以content script (内容脚本)只能访问 extension、runtime等部分API可以访问不可以不可以injected script (动态注入脚本)和普通JS无任何差别,不能访问任何扩展API可以访问可以访问不可以popup (选项弹窗脚本)可访问绝大部分API,除了devtools系列不可直接访问不可以可以devtools(开发工具脚本)只能访问 devtools、extension、runtime等部分API可以访问devtools可以访问devtools不可以 background和content通信

如下图所示, 可以看到:background和content之间可以互相通信。

backround给content发送和接收消息的方法 // background 给content发送消息--适合主动发消息的场景 chrome.tabs.sendMessage(tabId, message, (res) => { console.log(res); }); // background 接收和回复content的消息--适合应答场景 chrome.runtime.onMessage.addListener((message,sender,sendResponse)=>{ if(message.status === "done"){ sendResponse({recv:'good'}) } }) content给background发送和接收消息的方法 // content 主动给background 发消息 chrome.runtime.sendMessage(message); // content接收background消息 chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { console.log(`打开页面收到后台的消息: ${message}`); if(message.action === "on"){ // do something sendResponse({status:'done'}); } }); 开发与调试 第一步 进入到管理扩展程序页面 image.png 第二步 打开开发者模式开关,加载Chrome扩展开发目录,点击Service_Worker就能看到扩展后台的打印日志。注入网页的日志可直接在打开网页的开发调试工具中的console面板查看。

image.png

移除广告功能实现

实现思路:

在后台脚本中,处理当前激活的tab页启用/禁用扩展操作,给网页注入脚本发相应的指令,并记录设置的开关状态。另外,每次切换标签页的时候,刷新扩展开关状态,指示当前页面设置的状态值。 初次打开网页时,网页注入脚本在收到后台移除广告指令时,移除当前页面的广告。再入打开时,读取当前网页的扩展开关设置值,执行相应的操作。 配置清单文件

开发一个扩展之前,首先要定义扩展使用的manifest_version(清单版本,) 以及backround(后台脚本),content_scripts(注入网页脚本),permissions(权限)等重要信息。本文要开发的扩展清单文件定义如下:

{ "manifest_version": 3, "name": "HideAd", "description": "屏蔽特定网站的广告", "version": "0.0.1", "action": { "default_icon": "disable.png", "default_title": "HideAd功能处于禁用状态" }, "icons": { "128": "hide-ad.png" }, "background": { "service_worker": "background.js", "type": "module" }, "content_scripts": [ { "js": ["content.js"], "matches": ["https://www.jianshu.com/*"], "run_at": "document_start" } ], "permissions": ["activeTab", "tabs", "background", "storage"] } 编写后台脚本

后台脚本的功能是:

监听扩展图标点击事件,默认不开启移除网页广告功能。点击扩展图标后,开启当前页面的广告移除功能,再次点击,关闭当前页面的广告移除功能。 import IconUI from "./icon-ui.js"; const UI = new IconUI(); // 监听浏览器扩展(包含图标、工具提示、徽章和弹出内容)单击事件 chrome.action.onClicked.addListener((currentTab) => { console.log("切换隐藏广告扩展开关:", currentTab); UI.toggleHideAdSwitch(currentTab); });

切换移除广告扩展开关的逻辑是, 读取激活tab页之前的开关设置值, 然后反转状态。并执行相应状态的操作。

async toggleHideAdSwitch(tab) { // console.log({tab}); const status = await this.getPageStatus(tab); // 如果没有开启,则开启,反之则关闭 const action = [undefined, "off"].includes(status) ? "on" : "off"; // console.log(`开启/关闭 屏幕页面广告扩展功能: action=${action} host=${this.host}`); this.doAction(tab.id, action); }

获取页面开关状态,这里采用打开页面的域名作为存储页面开关状态键值对的key, 域名无法直接获取,需要从url解析。url获取逻辑是: 切换tab时,如果当前激活tab页还在加载中,tab.url是个空值,此时需要取tab.pendingUrl的值才能拿到当前页面的url, tab页加载完成后,tab.pendingUrl属性就不存在了,这时需要取tab.url的值获取当前激活tab页的url。

async getPageStatus(tab) { const url = tab.url || tab.pendingUrl; this.host = url?.split("/")?.[2] || ""; return await this.getStorage(this.host); }

getStorage的功能是从本地磁盘读取设置值。 Chrome 扩展存储 API 提供了 2 种储存区域,分别是 sync 和 local。两种储存区域的区别在于,sync 储存的区域会根据用户当前在 Chrome 上登陆的 Google 账户自动同步数据,当断网时,sync 和 local 区域对数据的读写行为一致。使用 Chrome 存储 API 须在 Manifest.json 的 permissions 字段中声明 "storage"权限,之后才能正常调用。

// 扩展从硬盘读取设置值 async getStorage(key) { // 调试用--读取所有的设置值 // chrome.storage.sync.get((result)=>{ // console.log(result); // }) const res = await chrome.storage.sync.get(); return res[key]; } // 扩展向硬盘写入存储值 setStorage(key, val) { // console.log("setStorage", { key, val }); return chrome.storage.sync.set({ [key]: val }); }

doAction做的事情是:更新扩展在当前激活页展示的图标和标题,记录当前激活页的开关设置,给注入当前激活页的脚本发送对应的指令。

// 执行扩展行为 doAction(tabId, action) { this.switchIconStatus(action); this.setStorage(this.host, action); this.sendMessage(tabId, action); } // 切换扩展图标状态 switchIconStatus(status) { console.log("switchIconStatus", status); if (status === "on") { this.setIcon(this.enableIcon); this.setTitle(this.enableTitle); } else { this.setIcon(this.disableIcon); this.setTitle(this.disableTitle); } } // 设置扩展标题 setTitle(title) { chrome.action.setTitle({ title, }); } // 设置扩展图标 setIcon(path) { chrome.action.setIcon({ path: { 64: path, }, }); } // 扩展程序向打开的页面发消息 sendMessage(tabId, action) { chrome.tabs.sendMessage(tabId, { action }); } 监听活动的tab页变化事件。在已经打开的tab页之间切换时,读取当前激活tab页的移除广告开关状态值,将扩展图标设置成对应的开关状态。调用chrome.tabs.query方法,就能获取当前激活tab页的信息。使用 Chrome tabs API 必须要在 Manifest 的 permissions 中声明 tabs权限,否则调用chrome.tabs.query方法获取的tab信息不完整。 chrome.tabs.onActivated.addListener(async (newTab) => { chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { // console.log(tabs[0]); console.log("标签页切换:", tabs[0]); UI.initPageStatus(tabs[0]); }); }); // 初始化页面扩展图标状态 async initPageStatus(tab) { const status = await this.getPageStatus(tab); this.switchIconStatus(status); } 编写注入网页脚本

注入网页脚本的功能是:

初次进入页面时, 接收后台发过来的信息,如果后台发送的是移除广告指令,则移除页面的广告。 // 接收后台消息 chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { const { action } = msg; // console.log(`打开页面收到后台的消息: ${action}`); action === "on" && delAdDom(); // sendResponse({ res: `打开页面执行的操作是${action}` }); // chrome.runtime.sendMessage({ host: location.host }); }); 再次进入页面时,页面加载完成之后,读取当前页的移除广告开关设置,如果处于开启状态,则移除当前页面中的广告元素。 // 如果扩展处于开启状态,则删除页面中的广告 window.onload = async function () { const res = await chrome.storage.sync.get(); // console.log(res); const isOn = res[location.host] === "on"; isOn && delAdDom(); };

如何移除目标页面的广告元素? 用Chrome开发调试工具查看了一下目标网页渲染完成之后的Dom树, 找出广告元素是body元素下面的最后四个div元素, 锁定目标之后,让我们干掉它。

// 删除页面中的广告 async function delAdDom() { document.querySelectorAll("body > div:nth-last-child(-n+4)").forEach((item) => item.parentNode.removeChild(item)); } 彩蛋

本文写的移除网页广告的插件,不具备通用性,只为抛砖引玉。结合一个真实的使用场景,把完全不懂Chrome扩展开发的初学者带入门,完整代码请点击这里下载获取。如果你已掌握本文移除网页广告的原理,那下面这种招人厌烦的弹窗,相信对你而言也是小菜一碟,分分钟就能搞定。如果你善于动手,勤于动手的话,可以实现一下这个功能,检验一下自己的学习效果。

image.png

参考文章

Chrome扩展程序开发教程--03:Manifest

chrome扩展开发官方文档



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3