【实用工具】谷歌浏览器插件开发指南
谷歌浏览器插件开发指南涉及以下几个方面:
1. 开发环境准备:首先需要安装Chrome浏览器和开发者工具。进入Chrome应用商店,搜索“Extensions Reloader”和“Manifest Viewer”两个插件进行安装,这两个插件可以方便开发和调试。
2. 创建插件:创建插件的方式有两种。第一种是从零开始创建,需要编写插件的各种文件,包括manifest.json、popup.html等。第二种是使用生成工具,如Yeoman,它可以自动生成插件文件和代码结构。
3. 编写插件代码:插件代码可以使用HTML、CSS、JavaScript等,需要根据插件的功能进行编写。
4. 调试和测试:在Chrome浏览器中可以使用开发者工具进行调试和测试,可以查看插件的运行状态,以及对代码进行修改和调试。
5. 发布和分发:发布插件需要一个Google开发者账号,并进行相应的审核和测试。插件发布后可以通过Chrome应用商店进行分发和安装。
总的来说,谷歌浏览器插件开发需要学习HTML、CSS、JavaScript等相关知识,并掌握Chrome插件开发的基本流程和技能。
Chrome插件本质上也是一个web页面的功能开发,因此需要熟悉以下基本的技能:
- HTML:页面内容标记。
- CSS:页面样式设置。
- JavaScript:处理页面逻辑的脚本。
- WebPlatformAPI:web平台的标准API。
下面开始正题。
Chrome插件基础概念
ChromeAPI
ChromeAPI是Chrome浏览器提供的JavaScriptAPI,在插件开发中使用这些API可以调用Chrome浏览器的提供的诸多功能,完成我们定制化的需求。
manifest.json
也叫插件清单,可以把它理解为整个插件的配置文件。manifest文件一定要放在根目录下,不可或缺,主要记录了插件的重要元数据、资源定义、权限声明,以及指定要在后台运行和页面运行的文件等。
// manifest.json文件{"manifest_version": 3,"name": "Reading time","version": "1.0","description": "Add the reading time to Chrome Extension documentation articles"
}
其中manifest_version
、name
、version
是必须的。
Service worker
也就是后台服务,主要是负责处理和监听浏览器的各类事件。后台服务可以使用所有ChromeAPI,但是不能直接与网页内容交互。
要使用后台服务,需要先在manifest文件中注册background:
{..."background": {"service_worker": "background.js"}
}
后台服务在浏览器运行起来后,就会一直在后台运行back.js脚本。
Content Script
也叫内容脚本。上面提到后台服务无法直接与网页内容进行交互,而内容脚本便来接替了这部分工作。内容脚本可以读取、修改或注入页面DOM,也可以使用一部分的ChromeAPI,但是不可使用的那部分ChromeAPI可以通过与后台服务的通讯来完成数据或消息的传递交互。
要使用内容脚本,需要先在manifest文件中注册content_scripts:
{..."content_scripts": [{"js": ["scripts/content.js"],"matches": ["https://developer.chrome.com/docs/extensions/*","https://developer.chrome.com/docs/webstore/*"]}]
}
其中,matches表示匹配的网站,js表示执行的脚本。即如果网站地址在matches列表中,则会执行js列表中的脚本。
pages
页面包含了popup的弹窗页面、option页面以及其他页面,这些页面也是就是前端开发中的HTML文件。这些页面都可以访问ChromeAPI。
以上便是Chrome插件开发中很重要且必要的几个概念,也是一个插件工程中主要的几个文件,因此请务必记住这几个概念。接下来,我们将一步一步开发一个插件,来逐步的了解Chrome插件开发的工作。
实际案例
插件开发
需求
开发前,我们先来看看这个插件需要完成那些功能。
1、阅读时间:
-
- 在指定的页面上某个位置显示出当前页面预计阅览完成所需要的时间。此处我们已百度百科页面为例,在页面开头位置显示当前页面阅览完成大概需要长时间。
- 阅读时间的预估规则我们这里简单计算下,使用页面文本的字数除以每分钟200个文字,并取整表示。
2、聚焦模式:
-
- 对于某些页面的侧边栏、广告栏等区域做隐藏,实现聚焦主体内容的阅读。
3、标签管理:
-
- 组织扩展文档选项卡,实现对特定域名的标签页组合成标签组。
阅读时间功能开发
思路:在内容脚本中使用js统计当前页面的文字数count,并假设阅读速度是500个/分钟,计算出预估时间:
time=count/500
先配置manifest文件:
{"manifest_version": 3, // 版本号,目前2版本已经停止支持,推广的是3版本"name": "toolKits", // 插件名称"description": "小工具集", // 插件描述"version": "1.0", // 插件开发的版本号,自定义即可"icons": { // 插件的图标"16": "images/icon-16.png","32": "images/icon-32.png","48": "images/icon-48.png","128": "images/icon-128.png"},"content_scripts": [ // 内容脚本注册{"js": ["scripts/baikeReadTime.js"],"matches": ["https://baike.baidu.com/item/*"]}]
}
从配置中可得知内容脚本的注册规则:当网址匹配上“https://baike.baidu.com/item/*”时,运行scripts/baikeReadTime.js脚本。
编写内容脚本,内容脚本是往页面中注入预估的阅读时间元素,代码如下:
// baikeReadTime.js// 通过检查百度百科页面元素,确定大部分内容都是在class="main-content J-content"的div标签中,因此先获取到内容标签的dom
const mainContent = document.getElementsByClassName("main-content J-content")[0]// 如果对应内容dom存在
if (mainContent) {const text = mainContent.textContent; // 获取内容dom中所有的文本内容const chineseWordRegExp = /[\u4e00-\u9fa5]/g; // 通过正则表达式匹配出所有的汉字const chineseWords = text.matchAll(chineseWordRegExp);const count = [...chineseWords].length; // 计算汉字长度const readTime = Math.round(count / 500); // 计算预计阅读时间const badge = document.createElement('span'); // 创建一个<span>标签badge.classList.add('collect-text'); // 给<spn>标签添加样式 样式尽量跟周围的标签样式一致badge.textContent = `⏱️ 总字数约${count}, 预计阅读耗时:${readTime} 分钟`; // 给<span>标签添加文本内容const topTool = document.getElementsByClassName("top-tool")[0] // 找到class="top-tool"的domtopTool.insertAdjacentElement('beforebegin', badge); // 把<span>标签插入到topTool的dom前面
}
演示:打开Chrome浏览器,安装插件后,打开任意百度百科的内容页,即可看到我们注入到页面的预估阅读时间元素。比如我们打开百度百科,搜索“亚运会”,页面展示图待更新。
聚焦模式功能开发
还是以百度百科页面为例,我们通过插件实现去掉右侧边栏和底部广告栏的信息
思路:分析页面元素,找到右侧边栏和底部广告栏的元素,再通过内容脚本对其进行样式隐藏即可。
1、首先给插件的图标设置一个徽标,用于展示聚焦模式的开关状态。
因为聚集模式的状态应该是在浏览器打开的时候就显示,因此设置状态徽标的逻辑应该放在background.js脚本中。具体代码如下:
chrome.runtime.onInstalled.addListener(() => {chrome.action.setBadgeText({ "text": "OFF" })
})
调用ChromeAPI,给插件设置徽标。大致意思就是添加一个安装事件的监控器,当插件安装后就给插件设置一个徽标,内容是“OFF”。
监听插件的点击事件。当点击插件的时候,判断当前浏览器标签页(tab页)的网址,如果符合要求,就将插件徽标设置为“ON”。继续在background.js脚本中添加以下代码:
...
const baike = "baike.baidu.com/item/"chrome.action.onClicked.addListener(async (tab) => {if (tab.url.includes(baike)) { // 如果标签页的网址中包含baike的网址,则将插件徽标设置为“ON”const prevState = await chrome.action.getBadgeText({ tabId: tab.id })const nextState = prevState === "ON" ? "OFF" : "ON"await chrome.action.setBadgeText({tabId: tab.id,text: nextState})}})
以上两步使用到了Chrome的activeTab权限和scripting权限,因此我们需要再manifest文件中配置权限声明:
{..."permissions": ["activeTab","scripting"],...
}
此时,重新加载插件后,如果当前标签页是百科页面,点击插件,就能看到切换徽标状态的效果的。
2、注入css,隐藏元素。
当徽标状态为ON时,给百科页面注入css,隐藏掉右侧边栏。继续在background.js脚本中添加以下代码:
...
chrome.action.onClicked.addListener(async (tab) => {if (tab.url.includes(baike)) {...if (nextState === "ON") {await chrome.scripting.insertCSS({files: ["focus-mode.css"],target: { tabId: tab.id },});} else if (nextState === "OFF") {await chrome.scripting.removeCSS({files: ["focus-mode.css"],target: { tabId: tab.id },});}}})
判断徽标状态,如果是“ON”,则往目标标签页插入css文件,否则就移除掉插入到目标页的css文件。上例中插入了focus-mode.css
编写注入的css文件,已达到隐藏侧边栏和底部广告栏的目的。通过分析网页的html代码可知,class="side-content"的标签即为侧边栏的元素标签,id="tashuo_bottom"的标签即为底部广告栏的标签,因此代码如下:
// focus-mode.css.content {.side-content {display: none;}
}#tashuo_bottom {display: none;
}
3、演示:
浏览器中重新加载插件后,在百科页点击插件,就能看到默认插件的聚集模式是“OFF”状态,点击后徽标更新为“ON”状态,且页面的侧边栏和底部栏都消失了,图待更新。
标签管理功能开发
上面两个案例,我们分别体验了内容脚本和后台服务脚本的能力,接下来我们看看插件打开的页面相关的能力。
该功能我们期望点击插件,能打开一个弹窗页,在这个弹窗页中展示当前浏览器窗口的所有标签页,然后我们可以选择某些标签页将他们放在一个标签组中。
思路:实现popup页面,并在页面中展示当前浏览器窗口的所有标签页,然后页面中有按钮,可创建一个标签组,并将选中的标签页放到这个标签组中。
1、首先我们需要一个popup页面
html代码如下:
# popup.html<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><link rel="stylesheet" href="./popup.css" /></head><body><template id="li_template"><li><a><h3 class="title">Tab Title</h3><p class="pathname">Tab Pathname</p></a></li></template><h1>Google Dev Docs</h1><button>新建/取消组合</button><ul></ul><script src="./popup.js" type="module"></script></body>
</html>
需要注意的是:这个popup.html无法运行内联 JavaScript,因此我们只能通过导入的方式,引入JavaScript脚本。
css代码如下:
# popup.cssbody {width: 20rem;
}ul {list-style-type: none;padding-inline-start: 0;margin: 1rem 0;
}li {padding: 0.25rem;
}
li:nth-child(odd) {background: #80808030;
}
li:nth-child(even) {background: #ffffff;
}h3,
p {margin: 0;
}
2、点击插件,触发弹窗
要实现点击插件就能打开popup弹窗页,有两中方式:
方法1:可以在manifest文件中的action字段中配置默认弹窗,代码如下:
{..."action": {"default_popup": "popup.html"},...
}
该配置意思是点击插件时默认打开的弹窗就是“popup.html”文件。
方法2:后台脚本中通过监听点击事件,然后调用ChromeActionAPI的setPopup
方法实现。本插件因为之前实现了百度百科页面的聚焦模式功能,因此采用这种方式触发弹窗。对之前的代码做了些调整,具体如下:
// background.js// 监听运行时和安装完成的事件
chrome.runtime.onInstalled.addListener(async () => {// 获取当前浏览器窗口激活的标签页tabs = await chrome.tabs.query({active: true, currentWindow: true})// 如果标签页的url中包含百度百科的地址,就设置徽标,否则不设置徽标if(tabs[0].url.includes("baike.baidu.com/item/")){chrome.action.setBadgeText({ "text": "OFF" })}
})// 监听点击事件
chrome.action.onClicked.addListener(async (tab) => {// 如果当前页面是百度百科的页面,就更新徽标状态,并完成聚焦模式的相关功能if (tab.url.includes("baike.baidu.com/item/")) {const prevState = await chrome.action.getBadgeText({ tabId: tab.id })const nextState = prevState === "ON" ? "OFF" : "ON"await chrome.action.setBadgeText({tabId: tab.id,text: nextState})if (nextState === "ON") {await chrome.scripting.insertCSS({files: ["focus-mode.css"],target: { tabId: tab.id },});} else if (nextState === "OFF") {await chrome.scripting.removeCSS({files: ["focus-mode.css"],target: { tabId: tab.id },});}}// 如果当前页是菜鸟教程网站的页面,就弹出popup弹窗else if(tab.url.includes("www.runoob.com/")){chrome.action.setPopup({ popup: "popup/popup.html" })}})
tips:如果在manifest文件中配置了点击默认弹窗页面,那么action.onClicked事件将不会生效。
3、将所有的标签页的title和path展示在popup弹窗页中
交互逻辑通过popup.js实现
3.1 调用ChromeAPI查询当前浏览器窗口中的所有标签页:
// poppu.js// 查询符合条件的所有tabs
const tabs = await chrome.tabs.query({currentWindow: true,url: ["https://www.runoob.com/*"]
});
...
3.2 将标签页列表插入到popup页面的元素中:
...
// 按照默认顺序对tabs列表中的元素进行排序
const collator = new Intl.Collator();
tabs.sort((a, b) => collator.compare(a.title, b.title));const template = document.getElementById("li_template"); // 从页面获取模版节点
const elements = new Set();
for (const tab of tabs) {const element = template.content.firstElementChild.cloneNode(true); // 复制模版第一个节点const title = tab.title.split("-")[0].trim(); // 获取tab中的标题const pathname = new URL(tab.url).pathname.slice("/docs".length); // 获取tab中的路径element.querySelector(".title").textContent = title; // 复制出来的节点中class=“title”的子节点,写入内容element.querySelector(".pathname").textContent = pathname; // 复制出来的节点中class=“pathname”的子节点,写入内容element.querySelector("a").addEventListener("click", async () => { // 复制出来的节点中<a>标签子节点添加点击事件// 点击的时候,更新对应的tab和窗口为激活状态await chrome.tabs.update(tab.id, { active: true });await chrome.windows.update(tab.windowId, { focused: true });});elements.add(element);
}
document.querySelector("ul").append(...elements);
以上代码中使用到了ChromeTabsAPI,该API 中的许多方法无需请求任何权限即可使用。但是,如果要访问标签页的标题和 URL这些敏感属性,就需要声明授权许可。如果我们请求“标签页”权限,导致所有标签页的敏感属性都有权限访问,但是由于我们仅仅管理特定网站的选项卡,因此我们请求更小范围的host权限。
host权限可以授权我们获取指定网站的敏感信息(包含title和URL),以此来缩小权限范围,进而保护用户隐私。在manifest文件中配置host权限:
{..."host_permissions": ["https://www.runoob.com/*"],...
}
注意:如果插件中使用了tabs权限或host权限,那么用户在安装插件时都会有弹窗提示。如下图:
3.3 对标签页进行分组
该功能需要使用到ChromeTabGroupsAPI,该API将允许插件对标签组进行命名和设置颜色。同样的,要使用该API也需要在manifest文件中声明权限:
{..."permissions": [..."tabGroups"]...
}
3.4 实现按钮的交互
在 popup.js 中,创建一个按钮,该按钮将使用 tabs.group() 对所有选项卡进行分组并将它们移动到当前窗口中。具体代码如下:
...
const button = document.querySelector("button"); // 获取button标签
// 给button标签添加点击事件
button.addEventListener("click", async () => {const tabIds = tabs.map(({ id }) => id); // 解构出tabs中每个对象的id属性,并组合成tabIdsconst group = await chrome.tabs.group({ tabIds }); // 将tabIds新建成标签组await chrome.tabGroups.update(groupId, { title: "菜鸟", color: "yellow" }); // 更新标签组的标题和颜色
});
如果标签页已经在分组中,则移出分组
要将标签页移出分组需要使用到ChomeTabsAPI,因此要在manifest文件中声明tabs权限:
{..."permissions": [..."tabGroups","tabs"]...
}
因为在点击按钮时需要先判断是否已经有标签组了,所以对popo.js做如下改造:
...
const button = document.querySelector("button"); // 获取添加组合的button标签
// 给button标签添加点击事件
button.addEventListener("click", async () => {const tabGroups = await chrome.tabGroups.query({ title: "菜鸟" }) // 找到菜鸟标签组// 如果菜鸟标签组存在,则取消标签组,否则添加菜鸟标签组if (tabGroups.length) {const tabs = await chrome.tabs.query({ groupId: tabGroups[0].id })const tabIds = tabs.map(({ id }) => id)await chrome.tabs.ungroup(tabIds)} else {const tabIds = tabs.map(({ id }) => id); // 解构出tabs中每个对象的id属性,并组合成tabIdsconst groupId = await chrome.tabs.group({ tabIds }); // 将tabIds新建成标签组await chrome.tabGroups.update(groupId, { title: "菜鸟", color: "yellow" }); // 更新标签组的标题和颜色}
});
------但是在调试过程中发现,后台服务中的点击事件存在一些Bug,点击事件中如果有根据网站来判断弹哪个页面的话,第一个条件规则触发后,后面的条件规则将不会覆盖第一个条件规则。目前没有找到原因,后续再继续研究下官方文档。
参考文档:zhuanlan.zhihu.com/p/655456499
相关文章:

【实用工具】谷歌浏览器插件开发指南
谷歌浏览器插件开发指南涉及以下几个方面: 1. 开发环境准备:首先需要安装Chrome浏览器和开发者工具。进入Chrome应用商店,搜索“Extensions Reloader”和“Manifest Viewer”两个插件进行安装,这两个插件可以方便开发和调试。 2…...

应用层协议——DNS、DHCP、HTTP、FTP
目录 1、DNS 协议 1-1)Hosts 文件 1-2)DNS 系统 1-3)域名的组成、分类和树状结构 1-4)DNS 域名服务器类型 1-5)DNS 查询方式 1-6)DNS 域名解析的一般步骤 1-7)对象类型与资源记录 2、D…...
XML文件读写
0、.pro文件添加依赖 QT xml1、使用 QDomDocument 方式 #include <QtXml/QDomDocument> #include <QtXml/QDomProcessingInstruction> #include <QtXml/QDomElement> #include <QFile> #include <QTextStream> #include <QDebug>bo…...

Win11 安装 Vim
安装包: 链接:https://pan.baidu.com/s/1Ru7HhTSotz9mteHug-Yhpw?pwd6666 提取码:6666 双击安装包,一直下一步。 配置环境变量: 先配置系统变量中的path: 接着配置用户变量: 在 cmd 中输入…...

Mac电脑BIM建模软件 Archicad 26 for Mac最新
ARCHICAD 软件特色 智能化 在2D CAD中,所有的建筑构件都由线条构成和表现,仅仅是一些线条的组合而已,当我们阅读图纸的时候是按照制图规范来读取这些信息。我们用一组线条表示平面中的窗,再用另一组不同的线条在立面中表示同一个…...

JavaEE-网络编程套接字(UDP/TCP)
下面写一个简单的UDP客户端服务器流程 思路: 对于服务器端:读取请求,并解析–> 根据解析出的请求,做出响应(这里是一个回显,)–>把响应写回客户端 对于客户端:从控制台读取用户输入的内容–>从控制…...

微服务技术栈-Gateway服务网关
文章目录 前言一、为什么需要网关二、Spring Cloud Gateway三、断言工厂和过滤器1.断言工厂2.过滤器3.全局过滤器4.过滤器执行顺序 四、跨域问题总结 前言 在之前的文章中我们已经介绍了微服务技术中eureka、nacos、ribbon、Feign这几个组件,接下来将介绍另外一个组…...
函数形状有几种定义方式;操作符infer的作用
在 TypeScript 中,函数形状可以用多种方式进行定义。下面介绍了几种常用的函数形状定义方式: 函数声明: function add(a: number, b: number): number {return a b; }在函数声明中,我们直接使用 function 关键字来声明函数&…...

Java / MybatisPlus:JSON处理器的应用,在实体对象中设置对象属性,对象嵌套对象
1、数据库设计 2、定义内部的实体类 /*** Author lgz* Description* Date 2023/9/30.*/ Data // 静态构造staticName,方便构造对象并赋予属性 AllArgsConstructor(staticName "of") NoArgsConstructor ApiModel(value "亲友", description …...

力扣 -- 1027. 最长等差数列
解题步骤: 参考代码: class Solution { public:int longestArithSeqLength(vector<int>& nums) {int nnums.size();int ret2;unordered_map<int,int> hash;//这里可以先把nums[0]存进哈希表中,方便后面i从1开始遍历hash[num…...

正则验证用户名和跨域postmessage
正则验证用户名 字母数字符号大小写8-14匹配用户名的 <!DOCTYPE html> <html> <head><meta charset"utf-8"><meta name"viewport" content"widthdevice-width, initial-scale1"><title>form</title> …...

jsbridge实战1:xcode swift 构建iOS app
[[toc]] 环境安装 macOs: 10.15.5 xcode: 11.6 demo:app 创建 hello world iOS app 创建工程步骤 选择:Create a new Xcode project选择:iOS-> single View App填写: project name: swift-app-helloidentifer: smile 包名language: s…...

零基础部署nginx mysql springboot
参考:写给开发人员看的Docker干货,零基础部署nginx mysql springboot 一、连接linux 阿里云 参考:部署到Linux 可能需要购买:购买链接 二、安装docker # 先切换到root用户下 sudo su# 更新apt-get,保证apt-get最新…...
6-3 模式匹配
description 给出主串s和模式串t,其长度均不超过1000。本题要求实现一个函数BF(string s, string t),求出模式串t在主串s中第一次出现的位置(从0开始计算),如果在s中找不到t,则输出-1。 函数接口定义&…...
SQL JOIN 时 USING 和 ON 的异同
在数据表做 join 时,即可以用 using,也可以用 on。有什么异同点呢。 ON 是更加普遍的用法,可以连接表 On 一个字段,多个字段,甚至一个条件表达式。举例 SELECT * FROM world.City JOIN world.Country ON (City.Cou…...
安全学习_开发相关_JNDI介绍(注入)RMILDAP服务
文章目录 参考&本节目的JNDI概念-RMI&LDAP服务调用检索:在RMI服务中调用了InitialContext.lookup()的常用类有:在LDAP服务中调用了InitialContext.lookup()的常用类有: JNDI注入-使用工具生成远程调用JNDI远程调用-工具(j…...

C#学生选课及成绩查询系统
一、项目背景 学生选课及成绩查询系统是一个学校不可缺少的部分,传统的人工管理档案的方式存在着很多的缺点,如:效率低、保密性差等,所以开发一套综合教务系统管理软件很有必要,它应该具有传统的手工管理所无法比拟的…...

【C语言】利用数组处理批量数据(一维数组和二维数组)
前言:在前面学习的程序中使用的变量都属于基本类型,例如整型、字符型、浮点型数据,这些都是简单的数据类型。对于简单的问题,使用这些简单的数据类型就可以了。但是对于有些需要处理的数据,只用以上简单的数据类型是不够的&#x…...

WPF中, 如何将控件的触发事件绑定到ViewModel
在DataGrid 等控件中, 有很多这种带闪电符号的触发事件. 如果用传统的事件驱动, 则直接在后台中建立 一个private PropertyChanged(Sender s, EventAgars Args) 即可. 但是如果需要绑定到ViewModel的话? 应该怎么做? 带闪电符号的触发事件 实现viewModel绑定前端触发事件的…...
解决Qt msvc编译器 中文显示乱码问题
第一步:代码文件选择用utf8编码带bom。第二步:在有中文汉字的代码文件顶部加一行(一般是cpp文件) #pragma execution_character_set(“utf-8”) 可以考虑放在head.h中,然后需要的地方就引入head头文件就行,…...

利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...

selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

企业如何增强终端安全?
在数字化转型加速的今天,企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机,到工厂里的物联网设备、智能传感器,这些终端构成了企业与外部世界连接的 “神经末梢”。然而,随着远程办公的常态化和设备接入的爆炸式…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...

tauri项目,如何在rust端读取电脑环境变量
如果想在前端通过调用来获取环境变量的值,可以通过标准的依赖: std::env::var(name).ok() 想在前端通过调用来获取,可以写一个command函数: #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...

《Docker》架构
文章目录 架构模式单机架构应用数据分离架构应用服务器集群架构读写分离/主从分离架构冷热分离架构垂直分库架构微服务架构容器编排架构什么是容器,docker,镜像,k8s 架构模式 单机架构 单机架构其实就是应用服务器和单机服务器都部署在同一…...
32位寻址与64位寻址
32位寻址与64位寻址 32位寻址是什么? 32位寻址是指计算机的CPU、内存或总线系统使用32位二进制数来标识和访问内存中的存储单元(地址),其核心含义与能力如下: 1. 核心定义 地址位宽:CPU或内存控制器用32位…...