油猴腳本運行於油猴插件之上,油猴插件本質上對瀏覽器能力的再封裝。既然如此,我們先來簡單了解一下瀏覽器插件。
瀏覽器插件(Browser Extension):瀏覽器的擴展應用
說的直白一點,就是拿著瀏覽器開放的能力(插件API),去實現一些小型應用。
瀏覽器插件主要由四部分構成:background scripts、content scripts、全局 UI 元素、options page。
- background scrips: 後台腳本,一個後台腳本是一個獨立線程,是游離於各個頁面之外的"上帝之眼"。具有訪問各類插件 API 的能力,但同時也喪失了直接操作頁面的能力。
- content scripts:內容腳本,具有直接操作頁面的能力。其實就是在頁面中運行 js 腳本,可以使用 DOM API。
content script
只能直接訪問少量插件 API,但能和background script
進行雙向通信完成數據交換。 - 全局 UI 元素:瀏覽器層的UI 交互,包括:
- 在 Toolbar 顯示 icon,定義點擊 icon 後顯示的 Popup 或其他效果
- 增加右鍵選項
- 增加全局快捷鍵
- 改造新Tab 頁、歷史記錄頁、書籤頁
- options page:插件配置頁。
瀏覽器插件的核心機制可以用下圖簡單概括:
想必,大家最好奇的還是有哪些 API 以及能用這些 API 做什麼,這裡例舉幾個:
- contextMenus:增加右鍵選項。
- 使用選中文本,例如:劃詞翻譯、文本收集
- 快速調用插件功能,例如:打開 DevTool,頁面剪藏
- cookies:增刪改查 cookie(任意域名),直接拿著本地 cookie 發送請求,不必再做授權。同時由於後台腳本不是 Web 頁面,在發送請求時沒有跨域限制。
- 多平台信息聚合
- 多平台信息分發
- devtools.panels:增加 Devtool 面板,這個對前端開發者來說應該很熟悉,React Developer Tools、Vue.js devtools。
- notifications:瀏覽器通知,未打開頁面的情況下進行通知,可以輔助一些工具類應用。
- storage:全局保存數據,可跟隨瀏覽器帳戶同步。
這裡例舉的只是我常用的一些,只是滄海一粟,更多 API 可以查閱:
瀏覽器插件就簡單介紹到這裡,如果有興趣繼續了解,推薦:Chrome、Edge、Mozilla三家的文檔。
油猴插件(Tampermonkey)
瀏覽器插件可以實現各式各樣的功能,但有時候開發者只是想對某一個站點加一點點小功能,如果這也要構建環境打包上架分發,未免就太麻煩了一些;從應用市場角度來看,充斥著顆粒化的應用,市場也會擁擠繁雜不堪。
油猴插件為輕量化腳本提供了一個平台,在線編輯器中編寫油猴腳本即時生效,通過 Github、GreasyFork 快速分發。
在油猴插件中,content script
起到非常重要的角色,它將用戶編寫的代碼運行在頁面中,同時提供 GM_xxxx
函數封裝瀏覽器的部分能力。封裝的內部實現是和 background script
通信,驅動 background script
調用插件API。
對油猴插件簡單了解之後,來看看如何編寫油猴腳本。
Tampermonkey API
油猴腳本由頭部和核心邏輯兩部分組成。
|
|
頭部
頭部是腳本的一些元信息、更新方式、指定運行頁面、權限聲明,逐一解釋一下:
配置名 | 作用 | 使用技巧 |
---|---|---|
@name | 腳本顯示的名稱 | 加後綴實現國際化,例如: @name:zh-CN 指定在瀏覽器語言為中文時顯示的名稱 |
@namespace | 腳本的命名空間,可以理解為腳本的標識 | 為了避免衝突一般使用 Github 倉庫地址 |
@version | 與更新相關,當前版本 | |
@updateURL | 檢查腳本是否更新地址 | 配合 @version 和自動更新使用 |
@downloadURL | 檢測到更新時,去哪下載腳本 | |
@supportURL | 遇到問題時,用戶去哪反饋 | |
@include | 腳本在哪些頁面運行 | 可使用正則,不支持 hashtag,多個頁面的地址聲明多個 @include 即可 |
@match | 與 @include 類似 | |
@exclude | 腳本禁止在哪些頁面運行,優先於 @include | |
@require | 在腳本運行前引入外部 JavaScript 文件 | 例如:引入 jQuery |
@resource | 聲明外部資源文件,搭配 GM_getResourceText 使用 | 例如:引入 html、icon |
@connect | 聲明 GM_xmlhttpRequest 可訪問的域 | 必須指定才能正常請求 |
@grant | 聲明 GM_xxx 函數的使用列表 | 必須先指定權限才能正常使用 |
@run-at | 指定腳本運行時機 | document-start:盡快執行 document-body:當 body 掛載時執行 document-end:DOMContentLoaded 觸發時執行 document-idle:DOMContentLoaded 觸發後執行,也是默認設置項 context-menu: 右鍵菜單項被點擊時執行 |
@author | 作者名 | |
@description | 簡短介紹 | 同樣可以加後綴實現國際化,例如: @description:zh-CN |
@homepage | 主頁地址 | 如果未設置並且 @namespace 是倉庫地址,默認導向倉庫地址 |
@icon | 腳本 icon | |
@icon64 | 64x64 像素的腳本 icon | |
@antifeature | 腳本是否有廣告、挖礦、數據收集等商業行為 | |
@noframes | 聲明腳本不在 iframe 中運行 |
核心邏輯
核心邏輯通過一個立即執行函數包裹,避免和全局作用域相互干擾。Tampermonkey 將瀏覽器的部分能力封裝為 GM_XXX
函數以供調用:
API | 作用 | 使用技巧 |
---|---|---|
unsafeWindow | 訪問頁面的 Window 對象 | |
GM_addStyle(css) | 創建全局樣式的快捷方式,向頁面插入 style 元素 | 也可以用 DOM 操作手動創建 |
GM_addElement(tag_name, attributes) GM_addElement(parent_node, tag_name, attributes) | 向 DOM 新建元素的快捷方式 | 也可以用 DOM 操作手動創建 |
GM_log(message) | 在 Console 中打印信息 | console.log 的快捷方式 |
GM_getValue(name, defaultValue) | 從存儲體中獲取數據 | |
GM_deleteValue(name) | 從存儲體中刪除數據 | |
GM_listValues() | 列舉存儲體中所有數據項 | |
GM_addValueChangeListener | 監聽數據更新 | 例如要使 Tab 間數據同步,可以用監聽 value 達成同步 |
GM_removeValueChangeListener | 移除監聽 | |
GM_getResourceText(name) | 獲取 @resource 中已聲明的資源 | |
GM_getResourceURL(name) | 獲取 @resource 中已聲明的資源(base64 URI 形式) | |
GM_registerMenuCommand(name, fn, accessKey) | 在 Tampermonkey 的 popup 中增加選項 | |
GM_unregisterMenuCommand(menuCmdId) | 移除選項 | |
GM_openInTab(url, options) | 新開一個 tab 頁 | |
GM_xmlhttpRequest(details) | 使用後台腳本進行請求,自動帶上 cookie,無跨域問題,目標域需要在 @connect 中提前聲明 | |
GM_download(details) | 下載資源到本地 | |
GM_getTab(callback) | 獲取當前 tab 的 object 對象 | |
GM_saveTab(tab) | 通過 tab 的 object 對象重新打開一個 tab | |
GM_getTabs(callback) | 獲取當前存活的所有 tab 的對象,以便和其他腳本實例溝通 | |
GM_notification | 使用插件 notification API 彈出桌面通知 | |
GM_setClipboard | 複製內容到剪貼板 | |
GM_info | 獲取腳本的油猴插件的信息 |
完整的說明文檔:Tampermonkey documentation
實踐:打印 “Hello, World”
做一個非常簡單的小練習:創建一個名為 “Hello” 的腳本,當進入掘金和知乎頁面時,在 Console 中打印 “Hello, World”。
- 新建腳本
- 修改腳本名稱
- 指定運行地址
@match
或@include
- 直接使用
console.log
或者聲明權限調用GM_log
|
|
搭建舒適的開發環境
使用在線編輯器小試牛刀之後,或許你也發現在線編輯器:
- 缺少語法補全和自動提示
- 難以格式化代碼
不免懷念起 VSCode。
或許你還會有更深遠的考慮,在線編輯器編輯完成後:
- 怎麼同步到遠程倉庫,怎麼做代碼分發
- 如果要用到新語法,怎麼保證跨瀏覽器兼容性
- 如果代碼越寫越多,沒有模塊化怎麼管理
- 沒有 TS,很難保證長期維護
這些坑我已經踩過了,並且抽出一個腳手架工具 create-tampermonkey - npm (npmjs.com),一鍵搭建舒適的油猴腳本開發環境。
腳手架集成 rollup + babel + eslint + typescript,支持:
- 自動生成 UserScript Header
- 語法和類型系統:ESNext、ES Module、TypeScript
- 樣式系統:CSS Modules,以及 scss、sass、less、stylus (需安裝對應依賴)
- 靜態資源:導入圖片、SVG 轉換為 Base64,同時支持 SVG Sprite 多語言
- 擴展:基於 Rollup,可以按需安裝插件進行擴展
create-tampermonkey 啟動項目
初始化項目
|
|
初始化完畢後,進入目錄安裝依賴。
|
|
到瀏覽器中打開 dev.user.js
,自動進入 Tampermonkey 腳本安裝界面。
最後一步:配置油猴插件
訪問 chrome://extension
,找到油猴插件的卡片,點擊 Details 進入配置界面。
勾選 Allow acess to file URLs。
刷新頁面,出現彈窗,一切就緒。
用 VSCode 打開項目,這時右下角會推薦一些輔助插件,建議安裝。
代碼中使用到的 GM_xxx
會自動提取到 UserScript Header 中,當然也可以在 src/meta.json
中自定義。
代碼的默認入口是 src/main.js
文件。
實踐:掘金簽到功能
基於上面初始化的項目 demo-userscript
做一個小功能:掘金簽到功能。
定位請求
“掘金簽到"本質是調用接口,我們的實現思路是追踪點擊"立即簽到"按鈕時請求發送情況,定位到
調試接口
打開 Postman 做一下調試,這裡有一個導入小技巧:右鍵拷貝 cURL。
到 Postman 中通過 curl 導入整個請求:點擊左側面板中的 import 按鈕,選擇 Raw text 貼上上一步複製的內容即可。
獲取參數
在 Postman 中發現請求需要 aid
、uuid
、_signature
三個參數,試試看不帶參數能否請求成功,先確定好必不可少的參數和請求頭。
簡單嘗試後,發現這裡並不需要帶 aid
、uuid
、_signature
三個參數,主要是依賴 cookie
,使用 GM_xmlhttpRequest
會自動帶上對應的 cookie
,事情變得簡單。
修改 src/main.js
的代碼:
src/main.js
|
|
刷新頁面測試一下。在其他站點刷新一下居然也可以發送請求,這就是插件沒有跨域限制的優勢了。
再做一下節流優化。利用 GM_setValue
和 GM_getValue
做持續存儲。
src/main.js
|
|
難免會遇到需要獲取數據的情況,可訪問的數據一般有三種:
- 頁面中包含數據,通過DOM 獲取
- 通過接口請求得到
- 存儲在本地存儲中,localStorage 或 cookie 之類
確定方式很粗暴:
- 複製參數或參數值到Element 中搜索
- 查看前面幾個請求,看看是否有跡可循
- 到 localStorage 或 cookie 中搜索
發布腳本
在本地開發完腳本之後,npm run build
構建生產版本並上傳代碼到 Github 或 Gitee。
用 Github/Gitee 上文件的 Raw URL 就能直接實現分發。如果在 package.json 中設置好 repository
,create-tampermonkey
會自動生成 Raw URL 並賦給 downloadURL
、updateURL
。
但這樣分發存在的問題是無法統計下載量、從網絡訪問的角度考慮同時維護 Github 和 Gitee 兩個倉庫。
另一種分發方式是上傳到腳本平台 greasyfork.org/,登錄後即可發布新腳本,如果代碼託管在 Github 或 GitLab 還可以使用 Webhooks 實現自動更新。
開發技巧
調試油猴腳本
油猴腳本的運行依託於 background script
和 content script
,在調試前需要對運行環境有所區分,例如 GM_xmlhttpRequest
請求是 background script
發出的,DOM 處理和腳本邏輯是 content script
執行的。
確定環境之後,就可以使用對應的調試方式進行調試了。
調試 background script
還是訪問 chrome://extension,找到油猴插件的卡片:
inspect views
後面有個 background.html
,點擊一下彈出 background script
的調試彈窗。
調試 content script
在網頁inspect -> Sources -> Page 下找到 Tampermonkey
目錄,頁面中運行的油猴腳本代碼都在這了,選擇目標,斷點調試即可。
獲取 userId
等信息
有時候需要拿一些額外信息做請求,一般有三種方式:
- 看看能不能在頁面中搜索到,通過 DOM 獲取
- 看看有沒有接口可以調用獲取
- 看看本地存儲裡有沒有
目標 DOM 節點未掛載怎麼辦?
如果節點是在首屏加載的,粗暴的方法是使用 setTimeout
做一下延時。
但如果是在交互過程中有DOM 更新,就只能引入監聽機制了,使用 MutationObserver
來實現。
具體的實例可以看【開發記錄】掘金"破圈行動” 輔助腳本- 掘金(juejin.cn)
查看插件源碼
瀏覽器插件安裝之後,插件包被下載到本地目錄中,可通過下述方法訪問。
訪問 chrome://version,找到 Profile path(存放用戶數據的路徑)
訪問 chrome://extensions/,找到目標插件的ID
將 Profile Path 和插件 ID 拼裝在一起 ${Prifile Path}/Extensions/${Extension Id}
,便是插件包的路徑了。友情提示,通過命令行訪問時需要在空格前加個 \
轉義一下。
總結
瀏覽器插件利用瀏覽器能力進行功能擴展,具有跨域請求、讀取 cookie、管理歷史記錄、註冊右鍵項等能力。
瀏覽器插件的能力很豐富,能夠實現複雜的功能。但如果只是做一些針對頁面的操作,只需要依賴基礎能力,完全可以使用油猴腳本實現,開發更便捷分發更迅速。
開發油猴腳本,主要是使用 Tampermonkey API 和 JavaScript。
create-tampermonkey 腳手架提供一個全面的油猴腳本開發環境,依托這個環境,可以使用最新的 ES 語法、TypeScript、CSS Modules,在 VSCode 中進行模塊化開發,大大提高開發效率。
開發完畢的油猴腳本可通過 Github/Gitee Raw URL 或 Greasy Fork 平台分發。
瀏覽器插件的主要分工為 background script
和 content script
兩部分,在調試油猴腳本時需要思考清楚是哪一部分出現的問題,再採用對應的調試方式。
實現了兩個小實踐,走出第一步,接下來盡情發揮創造力吧,玩得開心~
最後分享一下我的 Tampermonkey UserScripts 清單,持續更新,需要的歡迎下載來使用!