【請抓緊時間上車】實現一個12306的chrom插件

源碼地址: 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.請上車後補票 😂

感謝觀看

                                                

相關文章
相關標籤/搜索