30分鐘開發一款抓取網站圖片資源的瀏覽器插件

前言

因爲業務需求, 筆者要爲公司開發幾款實用的瀏覽器插件,因此大體花了一天的時間,看完了谷歌瀏覽器插件開發文檔,在這裏特意總結一下經驗, 並經過一個實際案例來複盤插件開發的流程和注意事項.javascript

你將收穫

  • 如何快速上手瀏覽器插件開發
  • 瀏覽器插件開發的核心概念
  • 瀏覽器插件的通訊機制
  • 瀏覽器插件的數據存儲
  • 瀏覽器插件的應用場景
  • 開發一款抓取網站圖片資源的瀏覽器插件

正文

在開始正文以前,咱們先來看看筆者總結的概覽: css

若是對瀏覽器插件開發比較熟悉的朋友能夠直接看最後一節插件開發實戰。

1.入門

首先咱們看看的瀏覽器插件的定義:html

瀏覽器插件是基於Web技術(例如HTML,JavaScript和CSS)構建的能夠定製瀏覽體驗的小型軟件程序。它們使用戶能夠根據我的須要或偏好來定製Chrome功能和行爲。前端

要想開發一款瀏覽器插件,咱們只須要有一個manifest.json文件便可, 爲了快速上手瀏覽器插件開發,咱們須要把瀏覽器開發者工具打開, 具體步驟以下:vue

  1. 在谷歌瀏覽器中輸入chrome://extensions/
  2. 將開發者模式啓動
  3. 導入本身的瀏覽器插件包

經過以上三個步驟咱們就能夠開啓瀏覽器插件開發之旅了.瀏覽器插件通常放在瀏覽器地址欄右側,咱們能夠在manifest.json文件配置插件的icon,並配置必定的規則,就能看到咱們的瀏覽器插件圖標了,以下圖: java

下面咱們來具體講解一下瀏覽器插件開發的核心概念.

2.核心知識點

瀏覽器插件通常涉及如下幾個核心文件:node

  • manifest.json 用來配置全部和插件相關的配置(必須放在根目錄)
  • background.js 後臺腳本(後臺頁面),生命週期和瀏覽器一致,通常放置全局代碼
  • content-scripts 插件向頁面注入腳本的一種形式,咱們能夠經過content-scripts向頁面注入js和css資源,並可控制容許注入的範圍
  • popup 點擊插件圖標後打開的自定義窗口, 用來處理用戶交互

筆者畫了一張簡圖來大體表示一下它們之間的關係: jquery

接下來咱們來具體瞭解一下一上幾個核心知識點.webpack

2.1 manifest.json

谷歌官網給咱們提供了一份簡單的配置,以下:css3

{
    "name": "My Extension",
    "version": "2.1",
    "description": "Gets information from Google.",
    "icons": {
      "128": "icon_16.png",
      "128": "icon_32.png",
      "128": "icon_48.png",
      "128": "icon_128.png"
    },
    "background": {
      "persistent": false,
      "scripts": ["background_script.js"]
    },
    "permissions": ["https://*.google.com/", "activeTab"],
    "browser_action": {
      "default_icon": "icon_16.png",
      "default_popup": "popup.html"
    }
 }
複製代碼

各字段含義介紹以下:

  • name 瀏覽器插件名稱, 將會在插件列表中顯示
  • description 瀏覽器插件簡介, 方便告訴開發者插件的功能和做用, 將會在插件列表中顯示
  • version 瀏覽器插件版本
  • icon 瀏覽器插件圖標
  • background 背景頁的腳本路徑,通常爲插件目錄的相對地址
  • permissions 容許使用的瀏覽器API的權限,好比contextMenus(右鍵菜單), tabs(操做標籤), webRequest(使用web請求), storage(容許使用本地存儲), "http://*"(能夠經過executeScript或者insertCSS訪問的網站)
  • browser_action 瀏覽器右上角圖標設置(包括popup頁面, 鼠標懸停時的標題, icon等)
  • content_scripts 須要直接注入頁面的javascript腳本
  • web_accessible_resources 普通頁面可以直接訪問的插件資源列表,若是不設置是沒法直接訪問的
  • chrome_url_overrides 覆蓋瀏覽器默認頁面(常常用來作瀏覽器的自定義桌面)
  • omnibox 向地址欄註冊一個關鍵字以提供搜索建議,只能設置一個關鍵字(多用於自定義搜索攔截)
  • default_locale 默認語言(好比"zh_CN")

文末會給出完整的配置文件地址,方便你們學習參考.

2.2 background.js

background頁面主要用來提供一些全局配置, 事件監聽, 業務轉發等.舉幾個經常使用案例:

  1. 定義右鍵菜單
// background.js
const systems = {
  a: '趣談前端',
  b: '掘金',
  c: '微信'
}

chrome.runtime.onInstalled.addListener(function() {
  // 上下文菜單
  for (let key of Object.keys(systems)) {
    chrome.contextMenus.create({
      id: key,
      title: systems[key],
      type: 'normal',
      contexts: ['selection'],
    });
  }
});

// manifest.json
{
    "permissions": ["contextMenus"]
}
複製代碼

效果以下:

  1. 設置只有.com後綴的頁面纔會激活插件
chrome.runtime.onInstalled.addListener(function() {
  // 相似於何時激活瀏覽器插件圖標這種感受
  chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
    chrome.declarativeContent.onPageChanged.addRules([{
      conditions: [new chrome.declarativeContent.PageStateMatcher({
        pageUrl: {hostSuffix: '.com'},
      })
      ],
      actions: [new chrome.declarativeContent.ShowPageAction()]
    }]);
  });
});
複製代碼

以下圖所示,當頁面地址的後綴不等於.com時,插件icon將不被激活:

3. 和content_script或者popup頁面進行消息通訊

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    console.log(sender.tab ?
                "from a content script:" + sender.tab.url :
                "from the extension");
    if (request.greeting == "hello")
      sendResponse({farewell: "goodbye"});
  });
複製代碼

2.3 content-scripts

內容腳本通常植入會被植入到頁面中, 而且能夠控制頁面中的dom. 咱們能夠利用它實現屏蔽網頁廣告, 定製頁面皮膚等操做. 在manifest.json中的基本配置以下:

{
    "content_scripts": [{
    "matches": [
        "http://*/*",
        "https://*/*"
    ],
    "js": [
        "lib/jquery3.4.min.js",
        "content_script.js"
    ],
    "css": ["base.css"]
  }],
}
複製代碼

以上代碼中咱們定義了content_scripts容許注入的頁面範圍, 插入頁面的js以及css, 這樣咱們就能輕鬆改變某一個頁面的樣式.好比咱們能夠在頁面中注入一個按鈕:

在後面的瀏覽器插件案例中筆者會詳細介紹content_scripts的用法.

2.4 popup

popup是用戶點擊插件圖標時打開的一個小窗口,當失去焦點後窗口就當即關閉,咱們通常用它來處理一些簡單的用戶交互和插件說明。

因爲popup窗口也是一個網頁,因此咱們通常會創建一個popup.html和popup.js用來控制popup的頁面展現和交互.咱們在manifest.json中配置以下:

{
    "page_action": {
        "default_title": "小夕圖片提取插件",
        "default_popup": "popup.html"
  },
}
複製代碼

這裏要注意一點的是,咱們在popup.html中不能直接使用script腳本,須要用引入腳本文件的方式.以下:

<!DOCTYPE html>
<html> <head> <title>在線圖片提取工具</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <div class="pop-wrap"> </div> <script src="lib/jquery3.4.min.js"></script> <script src="popup.js"></script> </body> </html> 複製代碼

如下是筆者寫的一個插件的popup頁面:

3.通訊機制

對於一個相對複雜的瀏覽器插件來講,咱們不只僅只操做dom或者提供基本的功能就好了,咱們還須要向第三方或者本身的服務器抓取有用的頁面數據,這個時候就須要用到插件的通訊機制了.

由於content_script腳本存在於當前頁面,受同源策略影響,致使咱們沒法將捕獲到的數據傳給第三方平臺或者本身的服務器, 因此咱們須要基於瀏覽器的通訊API.一下是谷歌瀏覽器插件的通訊流程:

3.1 popup和background相互通訊

由官方文檔可知popup能夠直接訪問background頁的方法,因此popup能夠直接與其通訊:

// background.js
var getData = (data) => { console.log('拿到數據:' + data) }
// popup.js
let bgObj = chrome.extension.getBackgroundPage();
bgObj.getData(); // 訪問bg的函數
複製代碼

3.2 popup或者background頁和content_script通訊

這裏咱們使用chrome的tabs API,以下:

// popup.js
// 發送消息給content_script
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
    chrome.tabs.sendMessage(tabs[0].id, "activeBtn", function(response) {
      console.log(response);
    });
  });
  
// 接收消息
chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    console.log(sender.tab ?
                "from a content script:" + sender.tab.url :
                "from the extension");
    if (request.greeting == "hello")
      sendResponse({farewell: "goodbye"});
  });
複製代碼

content_script接收和發送消息:

// 接收消息
chrome.runtime.onMessage.addListener(
  function(message, sender, sendResponse) {
    if (message == "activeBtn"){
      // ...
      sendResponse({farewell: "激活成功"});
    }
 });
 
 // 主動發送消息
 chrome.runtime.sendMessage({greeting: "hello"}, function(response) {
   console.log(response, document.body);
   // document.body.style.backgroundColor="orange"
});
複製代碼

有關消息的長鏈接,在谷歌官網也寫的很清楚:

咱們能夠採用以下方式進行長鏈接:

// content_script.js
var port = chrome.runtime.connect({name: "徐小夕"});
port.postMessage({Ling: "你好"});
port.onMessage.addListener(function(msg) {
  if (msg.question == "你是作什麼滴?")
    port.postMessage({answer: "搬磚"});
  else if (msg.question == "搬磚有錢嗎?")
    port.postMessage({answer: "木有"});
});

// popup.js
chrome.runtime.onConnect.addListener(function(port) {
  port.onMessage.addListener(function(msg) {
    if (msg.Ling == "你好")
      port.postMessage({question: "你是作什麼滴?"});
    else if (msg.answer == "搬磚")
      port.postMessage({question: "搬磚有錢嗎?"});
    else if (msg.answer == "木有")
      port.postMessage({question: "太難了."});
  });
});
複製代碼

4.數據存儲

chrome.storage用來針對插件全局進行數據存儲,咱們在任何一個頁面(popup或content_script或background)下存儲了數據,咱們在以上三個頁面均可以獲取到, 具體用法以下:

獲取數據
chrome.storage.sync.get('imgArr', function(data) {
  console.log(data)
});
// 保存數據
chrome.storage.sync.set({'imgArr': imgArr}, function() {
    console.log('保存成功');
  });
  
// 另外一種方式
chrome.storage.local.set({key: value}, function() {
  console.log('Value is set to ' + value);
});
複製代碼

5.應用場景

谷歌瀏覽器的插件應用場景不少,正如文章開頭的思惟導圖中寫的.如下是筆者總結的一些應用場景,你們感興趣能夠嘗試去實現:

  • 谷歌瀏覽器自定義桌面
  • 網頁性能分析工具
  • 網頁爬蟲
  • 埋點工具
  • 網頁熱力圖生成工具
  • 安全攔截插件
  • 廣告過濾插件
  • 網站動態換膚
  • 第三方數據導入
  • 代碼格式化工具
  • 在線協做工具
  • 防做弊插件

還有不少實用工具能夠開發,你們能夠好好把玩。接下來就來經過實現一個網頁圖片提取插件,來總結如下瀏覽器插件開發流程。

6.開發一款抓取網站圖片資源的瀏覽器插件

首先仍是按照筆者的風格,在開發任何一種工具以前都要明確需求,因此咱們來看看該插件的功能點:

  • 能植入網頁按鈕,經過點擊按鈕捕獲網頁圖片
  • 能在用戶端展現捕獲的圖片
  • 點擊插件能預覽捕獲的圖片

基本上就這幾個功能,接下來我會展現核心代碼,在介紹代碼以前咱們先預覽一下插件的實現效果:

插件目錄結構以下:
由於插件的開發比較簡單,因此我直接用jquery開發。這裏咱們主要關注popup.js和content_script.js, popup.js中主要用來獲取從content_script頁傳過來的圖片數據,並展現在popup.html中,另外又一個須要注意的是當頁面沒有注入生成按鈕時,popupu須要發送信息給content頁面,主動讓其生成按鈕,代碼以下:

chrome.storage.sync.get('imgArr', function(data) {
  data.imgArr && data.imgArr.forEach(item => {
    var imgWrap = $("<div class='img-box'></div>")
    var img = $("<img src='" + item + "' alt='" + item + "' />")
    imgWrap.append(img);
    $('#content').append(imgWrap);
    $('.empty').hide();
  })
});

$('#activeBtn').click(function(element) {
  chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
    chrome.tabs.sendMessage(tabs[0].id, "activeBtn", function(response) {
      console.log(response);
    });
  });
});
複製代碼

對於content頁面,咱們須要實現的是動態生成按鈕,而且在頁面中植入彈窗來展現獲取到的圖片,另外一方面須要將圖片數據傳遞給storage,以便popup頁面能夠獲取圖片數據。

因爲頁面比較簡單,筆者就不用過多的第三方庫了,筆者先簡單手寫一個modal組件,代碼以下:

// 彈窗
~function Modal() {
  var modal;
  
  if(this instanceof Modal) {
    this.init = function(opt) {
      modal = $("<div class='modal'></div>");
      var title = $("<div class='modal-title'>" + opt.title + "</div>");
      var close_btn = $("<span class='modal-close-btn'>X</span>");
      var content = $("<div class='modal-content'></div>");
      var mask = $("<div class='modal-mask'></div>");
      close_btn.click(function(){
        modal.hide()
      })
      title.append(close_btn);
      content.append(title);
      content.append(opt.content);
      modal.append(content);
      modal.append(mask);
      $('body').append(modal);
    }
    this.show = function(opt) {
      if(modal) {
        modal.show();
      }else {
        var options = {
          title: opt.title || '標題',
          content: opt.content || ''
        }
        this.init(options)
        modal.show();
      }
    }
    this.hide = function() {
      modal.hide();
    }
  }else {
    window.Modal = new Modal()
  }
}()
複製代碼

第一步,咱們先批量獲取頁面圖片數據:

var imgArr = []
  $('img').each(function(i) {
    var src = $(this).attr('src');
    var realSrc = /^(http|https)/.test(src) ? src : location.protocol+ '//' + location.host + src;
    imgArr.push(realSrc)
  })
複製代碼

由於圖片的src路徑多是相對地址,因此筆者在這裏用正則簡單處理如下,固然咱們能夠進行更細粒度的控制。

第二步,將圖片數據存儲到storage中:

chrome.storage.sync.set({'imgArr': imgArr}, function() {
    console.log('保存成功');
  });
複製代碼

第三步,生成預覽圖片的彈窗,這裏用筆者上面實現的modal組件:

Modal.show({
  title: '提取結果',
  content: imgBox
})
複製代碼

第四步,當popup發送激活按鈕的通知時,咱們要在網頁中動態插入生成按鈕:

chrome.runtime.onMessage.addListener(
  function(message, sender, sendResponse) {
    if (message == "activeBtn"){
      if(!$('.crawl-btn')) {
        $('body').append("<div class='crawl-btn'>提取</div>")
      }else {
        $('.crawl-btn').css("background-color","orange");
        setTimeout(() => {
          $('.crawl-btn').css("background-color","#06c");
        }, 3000);
      }
      sendResponse({farewell: "激活成功"});
    }
 });
複製代碼

setTimeout那段純屬是爲了吸引用戶視線,固然咱們能夠用更優雅的方式來處理。 插件核心代碼主要是這些,固然還有不少細節要考慮,我把配置文件和一些細節放到github了,若是感興趣的朋友能夠安裝感覺一下。

github地址:一款提取網頁圖片數據的瀏覽器插件

最後

若是想學習更多H5遊戲, webpacknodegulpcss3javascriptnodeJScanvas數據可視化等前端知識和實戰,歡迎在公號《趣談前端》加入咱們的技術羣一塊兒學習討論,共同探索前端的邊界。

參考文獻

更多推薦

相關文章
相關標籤/搜索