源碼地址: github.com/zjians/1230…
javascript
原由(吐槽):css
剛過完年不久,相信你們還能回想到春運時被搶票支配的恐懼。可是本教程並非教你們如何刷票的。半個月前我幫一個朋友買一張從宿州到上海的火車票,結果。。效果相似下圖WTF:html
因而想着買箇中間站的票能上車也行,到車上再補票就行了,因而對着車次點了一下,將中間站的名稱ctrl+c,ctrl+v一個一個查詢有沒有餘票。一頓操做猛如虎,可是憑着程序員的自尊,emm這樣不行,操做太傻了。因而週末便擼了這款插件:前端
嗯嗯,優雅,程序員的自尊又回來了java
通過(開發過程):jquery
首先打開chrome開發文檔 crxdoc-zh.appspot.com/extensions/…linux
原本覺得要刷不少文檔,結果看了20分鐘,嗯,感謝本身是個前端,會了。git
首先基礎工做就是定義一個manifest.json (清單文件),用於定義插件相關的配置。先貼一份本插件的配置文件(各項做用看註釋),詳細解釋請看官方文檔:程序員
{ "manifest_version": 2, "name": "12306", "version": "1.0", "description": "查詢當前車次各站點的餘票", "author": "https://github.com/zjians/12306", "page_action": { "default_icon": { "16": "assets/icons/icon16.png", "48": "assets/icons/icon48.png", "128": "assets/icons/icon128.png" }, "default_title": "12306上車票" }, "content_scripts": [{ "matches": ["https://*.12306.cn/*"], "js": ["js/jquery.min.js","js/main.js"], "css": ["assets/styles/main.css"], "run_at": "document_start" }], "web_accessible_resources": ["assets/images/*" ] }複製代碼
若是你不想看文檔,那麼我整理了一份比較全的manifest解釋,幾乎覆蓋了經常使用的全部設置,可用於快速查詢:github
{
"manifest_version": 2,
/*
指定您的應用包要求的清單文件格式的版本。從 Chrome 18 開始,開發人員應該指定 2
*/
"name":"個人應用名稱",
"version":"個人應用版本",
"default_locale":"zh", // 默認語言
/*
對於含有 _locales 目錄的應用來講這一屬性是必需的,指定_locales中的子目錄,包含該應用默認字符串。
在沒有 _locales 目錄的應用中該屬性不能存在
*/
"description":"關於應用的描述",
"icons":{ /*可定義一個或多個, 應用或主題背景的圖標*/
"16":"icon16.png",
"48":"icon48.png",
"128": "icon128.png"
},
/*
下面的browser_action或page_action選擇某一個使用
若是插件只對特定頁面有效,則使用page_action(頁面按鈕),(好比搶票插件,只對12306網站有效)
若是插件對全部頁面都有效,則使用browser_action瀏覽器按鈕),(好比截圖插件,對全部頁面都有效)
*/
"browser_action": { // 若是有 browser_action, 即在chrome toolbar 的右邊添加了一個 icon,
"default_icon": "test.jpg",
"default_title": "Google Mail", // tooltip, 光標停留在 icon 上時顯示
"default_popup": "popup.html" // 若是有 popup 的頁面, 則用戶點擊圖標就會渲染此 HTML 頁面
},
"page_action":{ // 若是 page_action 並不該用在當前頁面, icon會顯示灰色
"default_icon": {
"19": "images/icon19.png",
"38": "images/icon38.png"
},
"default_title": "Google Mail",
"default_popup": "popup.html"
},
//可選
"author":"開發者",
"automation":"",
"background":{
"scripts":["background.js"],
"page": "background.html",
"persistent":false
},
/*
後臺網頁
1.應用一般須要有一個長時間運行的腳原本管理一些任務或狀態,然後臺網頁就是爲這一目的而設立。
一般狀況下,後臺頁面不須要任何 HTML 標記,這種狀況下後臺頁面能夠單獨使用 JavaScript文件實現。
後臺頁面將由應用系統生成,包含 scripts 屬性中列出的每個文件。
2.page:若是您須要在您的後臺頁面中指定 HTML,您能夠改用 page 屬性:
3.persistent:應用和應用一般須要長時間運行的腳原本管理某些任務或狀態,這就是事件頁面的做用。
事件頁面只在須要時加載,當事件頁面不活動時就會卸載,以便釋放內存和其餘系統資源。
如何獲得事件頁面 就是設置一個"persistent"鍵,若是沒有設置,你將獲得一個普通的後臺頁面。
*/
"content_scripts": [{
"matches": ["https://*.domain.com/*"], // 匹配的地址網頁
"exclude_matches":[],
"js": ["jquery.js","yourScript.js"], // 內容腳本
"css": ["yourStyles.css"], // 在頁面上添加的css樣式
"run_at":"document_idle",
"all_frames": true //該匹配下面的全部窗口
},{ // 能夠針對不一樣的規則插入不一樣的內容
"matches": ["*://*/*.png", "*://*/*.jpg", "*://*/*.gif", "*://*/*.bmp"],
"js": ["js/show-image-content-size.js"]
}],
/*
內容腳本: 其實就是向你想要的網頁中插入一個腳本代碼,執行你想要作的事情
內容腳本是在網頁的上下文中運行的 JavaScript 文件,
它們能夠經過標準的文檔對象模型(DOM)來獲取瀏覽器訪問的網頁詳情,或者做出更改。
1.run_at 可選。
控制 js 中的 JavaScript 文件什麼時候插入,
能夠爲 "document_start"、
"document_end" 或 "document_idle",默認爲 "document_idle"。
1.1若是是 "document_start",這些文件將在 css 中指定的文件以後,可是在全部其餘 DOM 構造或腳本運行以前插入。
1.2.若是是 "document_end",文件將在 DOM 完成以後當即插入,可是在加載子資源(如圖像與框架)以前插入。
1.3.若是是 "document_idle",瀏覽器將在 "document_end" 和剛發生 window.onload 事件這兩個時刻之間選擇合適的時候插入,
具體的插入時間取決於文檔的複雜程度以及加載文檔所花的時間,而且瀏覽器會盡量地爲加快頁面加載速度而優化。
2.all_frames 可選。
控制內容腳本運行在匹配頁面的全部框架中仍是僅在頂層框架中。 默認爲 false,意味着僅在頂層框架中運行
*/
"web_accessible_resources": [ // 普通頁面可以直接訪問的插件資源列表,若是不設置是沒法直接訪問的
"images/*.png",
"style/double-rainbow.css",
"script/double-rainbow.js",
"script/main.js",
"templates/*"
],
"update_url": "你的插件在chrome商店的地址", // 若是你使用 Chrome 開發者信息中心發佈的擴展程序,可忽略這一項
// 若是你想從本身的服務器上更新插件,則須要指定update_url,指向你的服務器地址。
"homepage_url": "https://www.xxx.com", // 插件的主頁
"permissions":[
"tabs", // 若是擴展使用chrome.tabs或chrome.windows模塊,則添加此項
"bookmarks", // 使用chrome.bookmarks模塊來建立、組織和管理書籤
"http://www.blogger.com/",
"http://*.google.com/",
"unlimitedStorage", // 提供了一個無限的HTML5配額來存儲客戶端數據,如數據庫和本地存儲文件。沒有這個權限,擴展僅限於5 MB的本地存儲
"history" // 歷史記錄的使用權限 chrome.history
"notifications",// 提示
"cookies",// 若是擴展程序使用chrome.cookies模塊,則添加此項
],
/*
擴展或app將使用的一組權限。每一個權限是一列已知字符串列表中的一個,
如geolocatioin或者一個匹配模式,來指定能夠訪問的一個或者多個主機。
權限能夠幫助限定危險,若是你的擴展或者app被攻擊。
有些權限在安裝以前,會告知用戶
*/
key:'',
/**開發時爲擴展指定的惟一標識值。
注意:一般您並不須要直接使用這個值,而是在您的代碼中使用相對路徑或者chrome.extension.getURL()獲得的絕對路徑。
這個值並非開發時顯式指定的,而是Chrome在安裝.crx時輔助生成的。(開發時能夠經過上傳擴展或者手工打包生成crx文件)。
安裝完crx,在Chrome的用戶數據目錄下的Default/Extensions/<extensionId>/<versionString>/manifest.json文件中,您能夠看到這個擴展的key。
**/
"commands": {
// commands API 用來添加快捷鍵
// 須要在 background page 上添加監聽器綁定 handler
"toggle-feature-foo": {
"suggested_key": {
"default": "Ctrl+Shift+Y",
"mac": "Command+Shift+Y"
},
"description": "Toggle feature foo",
"global": true
// 當 chrome 沒有 focus 時也能夠生效的快捷鍵
// 僅限 Ctrl+Shift+[0..9]
},
"_execute_browser_action": {
"suggested_key": {
"windows": "Ctrl+Shift+Y",
"mac": "Command+Shift+Y",
"chromeos": "Ctrl+Shift+U",
"linux": "Ctrl+Shift+J"
}
},
"_execute_page_action": {
"suggested_key": {
"default": "Ctrl+Shift+E",
"windows": "Alt+Shift+P",
"mac": "Alt+Shift+P"
}
},
...
},
"content_capabilities": ...,
"optional_permissions": ["tabs"], // 其餘須要的 permission, 在使用 chrome.permissions API 時用到, 並不是安裝插件時須要
"short_name": "Short Name", // 插件名字簡寫
"storage": {// 使用 storage.managed api 的話, 須要一個 schema 文件指定存儲字段類型等, 相似定義數據庫表的 column
"managed_schema": "schema.json"
},
......
}複製代碼
嗯,配置完之後,就能夠在頁面中插入本身的腳本了,因而就能夠隨心所欲了。
這裏說下開發中遇到的三個問題:
1. 獲取站點的縮寫碼,好比【北京北】的站點碼爲VAP,由於請求數據的時候傳過去的參數,使用的不是站點中文名稱,而是站點碼。
因而我在網站中發現了這麼一個變量:station_names,以下圖所示:
很顯然解析這個變量就能夠得到站點對應的站點碼了,可是chrome插件和原始網頁是兩個相互分開的運行環境,也就是說我在插件的腳本中沒法獲取頁面腳本中的變量。可是插件是能夠獲取頁面的dom內容的,因而把station_names掛到dom上,而後在插件中獲取dom上的屬性。這樣便經過dom獲取到了頁面腳本中的變量值,代碼以下:
const script = document.createElement('script');script.type = 'text/javascript';script.innerHTML = "document.body.setAttribute('data-station-name', station_names);";document.head.appendChild(script);document.head.removeChild(script);const station_names = document.body.getAttribute('data-station-name');複製代碼
2. 解析12306返回的數據
你可能會問,解析數據不是很簡單的嗎?我也是這麼認爲,可是直到我看到了他的返回:
本身解析確定是不現實了,那麼就找找網站的腳本是如何解析這個數據的吧,因而我找到了這個函數,就是他了:
通過上面函數的處理,獲得了我想要的結果對象:
完美!
3. 原本覺得能夠開心的玩耍了,可是次日一試,竟然請求不到數據了。。
查看請求地址才發現,原來查詢地址是天天都變的,好在請求失敗之後,會返回可用的地址,因而在插件運行時,檢測一下目前可用的請求地址:
let queryUrl = 'leftTicket/queryZ' // 請求地址$.ajax({ type: 'GET', url: `https://kyfw.12306.cn/otn/${queryUrl}?leftTicketDTO.train_date=2019-02-20&leftTicketDTO.from_station=VNP&leftTicketDTO.to_station=NKH&purpose_codes=ADULT`, error: function (res) { // 若是失敗了,會返回可用的地址 queryUrl = res.responseJSON.c_url } })複製代碼
ok!完成。
結束語:
1.若是以爲有用,請反手給個star鼓勵一下
2.請上車後補票 😂
感謝觀看