谷歌浏览器插件开发
简介
Chrome扩展主要用于对浏览器功能的增强,它更强调与浏览器相结合。比如Chrome扩展可以在浏览器的工具栏和地址栏中显示图标,它可以更改用户当前浏览的网页中的内容,直接操作浏览页面的DOM树等。这里用它来采集数据,类似于爬虫,然后将处理的数据发送到指定接口,导入数据库。
还有一种Chrome应用,但与浏览器内容相对独立,这里不介绍。
开发环境
开发浏览器插件不需要特别的工具,只需要装上谷歌浏览器, 一个记事本足矣。调试什么都可以在浏览器进行。
目录结构
这里写的是简单的插件,基本目录结构如下:
谷歌浏览器简单的插件实际上就是一个拓展的网页,所以和普通的HTML的结构没什么特殊之处,上面的js、images完全可以是你喜欢的名称,引用时路径写对就行,需要注意的是,插件必须包含一个manifest.json文件(必须是这个名字),此文件描述了插件的一些基本信息,安装插件时会读取插件相关信息。
插件的html页面可以引用外部的js文件,但是注意,不能直接在页面里面直接写JavaScript脚本,是不会执行,必须是引用的脚本,切记
manifest.json详解
manifest.json是插件最重要的配置和描述文件,下面用我的例子来说说
{
"manifest_version": 2,
"name": "采集插件",
"version": "1.0",
"description": "获取网页数据,保存直接到OA系统",
"content_scripts": [
{
"matches": ["https://abc.xxxx.com/*"],
"run_at": "document_end",
"js": ["js/getdata.js"]
}
],
"browser_action": {
"default_icon": {
"16": "images/icon16.png",
"32": "images/icon32.png",
"38": "images/icon38.png",
"48": "images/icon48.png",
"64": "images/icon64.png",
"128": "images/icon128.png"
},
"default_title": "状态",
"default_popup": "popup.html"
},
"options_page": "options.html",
"permissions": [
"storage",
"https://abc.xxxx.com/*",
"http://127.0.0.1:8080/*"
]
}
name定义了扩展的名称,
version定义了扩展的版本,
description定义了扩展的描述,
icons定义了扩展相关图标文件的位置,谷歌会根据需要选择合适大小的图标
version的值最多可以是由三个圆点分为四段的版本号,每段只能是数字,每段数字不能大于65535且不能以0开头(可以是0,但不可以是0123),版本号段左侧为高位,比如1.0.2.0版本比1.0.0.1版本更高。每次更新扩展时,新的版本号必须比之前的版本号高。
browser_action指定扩展的图标放在Chrome的工具栏中,
browser_action中的default_icon属性定义了相应图标文件的位置,
default_title定义了当用户鼠标悬停于扩展图标上所显示的文字,default_popup则定义了当用户单击扩展图标时所显示页面的文件位置,
content_scripts属性可以指定将哪些脚本何时注入到哪些页面中,当用户访问这些页面后,相应脚本即可自动运行,从而对页面DOM进行操作。
Manifest的content_scripts属性值为数组类型,数组的每个元素可以包含matches、exclude_matches、css、js、run_at、all_frames、include_globs和exclude_globs等属性。
其中matches属性定义了哪些页面会被注入脚本,exclude_matches则定义了哪些页面不会被注入脚本,css和js对应要注入的样式表和JavaScript;
run_at定义了何时进行注入,
另外,all_frames可以定义脚本是否会注入到嵌入式框架中,
include_globs和exclude_globs则是全局URL匹配,最终脚本是否会被注入由matches、exclude_matches、include_globs和exclude_globs的值共同决定。
简单的说,如果URL匹配mathces值的同时也匹配include_globs的值,会被注入;如果URL匹配exclude_matches的值或者匹配exclude_globs的值,则不会被注入。
content_scripts中的脚本只是共享页面的DOM树,而并不共享页面内嵌JavaScript的命名空间。
也就是说,如果当前页面中的JavaScript有一个全局变量a,content_scripts中注入的脚本也可以有一个全局变量a,两者不会相互干扰。当然你也无法通过content_scripts访问到页面本身内嵌JavaScript的变量和函数。
Manifest的permissions属性中声明需要谷歌拓展API的storage权限和跨域的权限。
项目业务代码等
业务部分
以下是get_data.js代码,主要业务操作在这里
timeStart="";
timeEnd="";
timeEnd_collect=""; //采集结束时间,程序到这时间终止
//encode要发送到OA系统的数据
function encodeFormData(data){
if(!data) return '';
var pairs = [];
for(var name in data){
if(!data.hasOwnProperty(name)) continue;
if(typeof data[name] === 'function') continue;
var value = data[name].toString();
name = encodeURIComponent(name.replace('%20','+'));
value = encodeURIComponent(value.replace('%20','+'));
pairs.push(name+'='+value);
}
return pairs.join('&');
}
//获取,处理页面数据
function get_data(){
var trs = window.frames["MainIframe"].document.getElementById("table_data_tbody").children;
var datas=[];
for(var i=0;i<trs.length;i++){
var tds =trs[i].childNodes;
var data_tmp= {
STARTTIME:tds[1].innerText,
ENDTIME:tds[2].innerText,
ZUOXI:tds[3].innerText,
ZUOXI_ID:tds[4].innerText,
FROM_NUM:tds[5].innerText,
TO_NUM:tds[6].innerText,
CALL_TYPE:tds[7].innerText,
DURATION:tds[8].innerText,
SATISFACTION:tds[9].innerText,
ACID:tds[10].innerText
};
datas[i]=data_tmp;
}
var s = JSON.stringify(datas);
var data2sent={
data:s
}
httpRequest('http://127.0.0.1:8080/invCloudOA/appuser/calllog2db',encodeFormData(data2sent),function(result){
html = result;
console.log(html);
});
}
//模拟用户操作,发送请求给acc
function sent_req(){
chrome.storage.local.set({"log":"模拟用户操作,修改时间参数"});
var timestamp_end = Date.parse(new Date(timeEnd));
window.frames["MainIframe"].document.getElementById("type_duration").click();
window.frames["MainIframe"].document.getElementById("timeStart").value=timeStart;
window.frames["MainIframe"].document.getElementById("timeEnd").value=timeEnd;
window.frames["MainIframe"].document.getElementById("btnOk").click();
chrome.storage.local.set({"log":"发送请求,延时处理返回数据"});
//延时数据处理
setTimeout(get_data,12000);
//设置新的时间
chrome.storage.local.set({"log":"时间参数修改"});
timeStart=timeEnd;
var end_timestamp = Date.parse(new Date(timeEnd));
var new_end_date = new Date();
new_end_date.setTime(end_timestamp+60*60*1000);
timeEnd=new_end_date.format('yyyy-MM-dd hh:mm:ss');
var p = {
timeStart:timeStart,
timeEnd:timeEnd,
}
chrome.storage.local.set(p,function(){});
chrome.storage.local.set({"log":"数据处理完成,准备发给OA"});
setTimeout(sent_req, 10000);
}
//构造请求,发给OA
function httpRequest(url,data, callback){
var xhr = new XMLHttpRequest();
xhr.open('post',url);
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
chrome.storage.local.set({"log":"发送数据给OA,OA处理中"});
xhr.send(data);
}
//时间格式化工具
Date.prototype.format = function(format) {
var date = {
"M+": this.getMonth() + 1,
"d+": this.getDate(),
"h+": this.getHours(),
"m+": this.getMinutes(),
"s+": this.getSeconds(),
"q+": Math.floor((this.getMonth() + 3) / 3),
"S+": this.getMilliseconds()
};
if (/(y+)/i.test(format)) {
format = format.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length));
}
for (var k in date) {
if (new RegExp("(" + k + ")").test(format)) {
format = format.replace(RegExp.$1, RegExp.$1.length == 1
? date[k] : ("00" + date[k]).substr(("" + date[k]).length));
}
}
return format;
}
//程序入口
chrome.storage.local.get("isenable", function(obj) {
chrome.storage.local.get("timeStart", function(obj) {
timeStart=obj.timeStart;
});
chrome.storage.local.get("timeEnd", function(obj) {
timeEnd=obj.timeEnd;
});
if(obj.isenable){
setTimeout(sent_req,6000);
chrome.storage.local.set({"log":"插件已经正常开启!"});
}
});
以上是点击插件图标弹出的页面,预想是用来显示运行时的日志的,效果不好,懒得改了。
<html>
<head>
<title>参数设定</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
</head>
<body>
<div style="width: 300px;height:60px;text-align: center">
<br/>
<p id="log">
插件日志
</p>
</div>
<script src="js/popup.js"></script>
</body>
</html>
插件的配置部分
配置页面,没追求,自己随便写一个简陋的页面
<html>
<head>
<title>参数设定</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
</head>
<style>
.big{
font-size: 30px;
}
.biginput{
width: 300px;
height: 50px;
font-size: 30px;
}
</style>
<body>
<div style="width: 80%;height:auto;text-align: center">
<br/>
<span class="big">开始时间:</span><input class="biginput" type="text" id="timeStart" value="2017-11-01 00:00:00"/><br/><br/>
<span class="big">结束时间:</span><input class="biginput" type="text" id="timeEnd" value="2017-11-01 00:30:00"/><br/><br/>
<span class="big">是否启用:</span><input type="checkbox" id="isenable" /><br/><br/>
<input type="button" class="biginput" id="save" value="保存" /><br/>
</div>
<script src="js/options.js"></script>
</body>
</html>
配置页面引用的脚本,一看就懂
//加载数据,显示目前的配置
window.onload=function(){
chrome.storage.local.get("isenable", function(obj) {
document.getElementById('isenable').checked=obj.isenable
});
chrome.storage.local.get("timeStart", function(obj) {
document.getElementById('timeStart').value=obj.timeStart
});
chrome.storage.local.get("timeEnd", function(obj) {
document.getElementById('timeEnd').value=obj.timeEnd
});
}
//保存
document.getElementById('save').onclick = function(){
var timeStart = document.getElementById('timeStart').value;
var timeEnd = document.getElementById('timeEnd').value;
var isenable = document.getElementById('isenable').checked;
var p = {
timeStart:timeStart,
timeEnd:timeEnd,
isenable:isenable
}
chrome.storage.local.set(p,function(){
alert('设置已保存');
}
);
}
运行
打开插件管理
加载已经解压的应用,选择对应文件夹
发布
直接点击上图的打包拓展程序,选择文件夹,密钥可选,如果是升级,可以选择首次自动生成的密钥
生成插件文件和证书
安装
打开拓展页面,直接拉过去,确定即可
这里特别说一下,谷歌插件支持三种方法中的一种来储存数据:
第一种是使用HTML5的localStorage;
第二种是使用Chrome提供的存储API;
第三种是使用Web SQL Database。
localStorage就是h5自带的
Chrome提供的存储API和localStorage相似,拓展一些功能
如果储存区域指定为sync,数据可以自动同步;
content_scripts可以直接读取数据,而不必通过background页面;
在隐身模式下仍然可以读出之前存储的数据;
读写速度更快;
用户数据可以以对象的类型保存。
localStorage是基于域名的,而content_scripts是注入到用户当前浏览页面中的,如果content_scripts直接读取localStorage,所读取到的数据是用户当前浏览页面所在域中的。所以通常的解决办法是content_scripts通过runtime.sendMessage和background通信,由background读写扩展所在域(通常是chrome-extension://extension-id/)的localStorage,然后再传递给content_scripts。
Chrome提供的存储API就没有这些问题,可以跨页面读取,666