源码地址: 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.请上车后补票 😂
感谢观看