chrome plugin Tao Bao seckill实现思路
大部分chrome插件会以browser action或page action的形式在浏览器界面上展现出来,都包含一个背景页面(background page),用于执行插件的主要功能,但背景页面并不是插件中唯一的页面。例如,一个browser
action可以包含一个弹窗(popup),而弹窗就是用html页面实现的,插件中的HTML页面可以互相访问各自DOM树中的全部元素,或者互相调用其中的函数。Tao Bao seckill以及原参考插件就包含了background和popup页面。
Tao Bao seckill插件的主题框架如下图所示,其功能由popup弹窗中的GO按键的click时间激活,主要功能均在background中实现。
这里先假定插件功能均可正常执行,接下来我们按照插件正常执行的流程来整理插件的实现方式。
1.填写用户名与密码以及商品信息,按下GO
该功能在pop中实现,在网页document元素加载完成且监控到GO按钮被按下时,调用background环境下的upload函数,同时将用户名,密码,商品链接,商品颜色与尺码等参数传递至backgound.js中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
$(document).ready( function(){ document.getElementById("start").addEventListener("click" ,function(){ u_name = document.getElementById("username").value ; p_word = document.getElementById("password").value ; start_time = document.getElementById("start_time").value ; item_url = document.getElementById("url_addr").value ; your_size = document.getElementById("your_size").value ; your_color = document.getElementById("your_color").value ; if((u_name != "") && (p_word!="") && (start_time!="") && (item_url!="")) { var BGPage = chrome.extension.getBackgroundPage(); BGPage.upload(u_name,p_word,item_url,your_size,your_color , start_time); } else { var options = { type: "basic", message: 'Please input information for the goods!', title: 'Goods information Error!', iconUrl: 'icon.png' }; var notification = chrome.notifications.create("", options, function cb(id) {}); window.setTimeout(function() { chrome.notifications.clear(notification, function cb(wasCleared) {}); }, 3000); } }); }); |
2.自动登录淘宝
background文件中的upload函数将根据商品开拍时间适时在chrome中新开启一个tab并加载登录页面。整个插件的主要流程都由 chrome.tabs.onUpdated.addListener来掌控,该函数监听chrome的tabs中在加载的页面,根据当前正在加载的页面的URL以及相应网页的元素状态来决定下一步流程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
chrome.tabs.onUpdated.addListener( function(tabId, changeInfo,tab){ if( changeInfo.status=="loading") { if(String(tab.url)==String(login_url) )//登录页面加载 { chrome.tabs.executeScript(tabId, {code: 'var username = "' + username + '";var pwd="' + password + '";'}, function() { chrome.tabs.executeScript(tabId, {file: 'login_inject.js'},function(){ chrome.tabs.executeScript( tabId , {code: 'window.location.href="' + itemUrl + '"'}); }); }); } else if(String(tab.url)==String(itemUrl))//商品页面加载,并判断当前商品页面的购买按钮是否有效 { var buyBtn = document.getElementsByClassName("J_LinkBuy"); if(buyBtn.item.length > 0)//购买按钮已生效 { chrome.tabs.executeScript(tabId, { code: 'var size = "' + size + '";var color="' + color + '";' }, function() { chrome.tabs.executeScript(tabId, {file: 'buy_inject.js'} ); }); } else {//购买按钮未生效,刷新网页 chrome.tabs.reload(tabId); } } else if(changeInfo.status=="loading" && ( buy_taobao.test(tab.url) || buy_tmall.test(tab.url) ) )//商品购买页面加载,进入付款页面 { chrome.tabs.executeScript( tabId , {file : "inject.js" } ) ; } } }); |
2.1 当前加载页面为淘宝登录页面
若当前为淘宝登录界面,那么使用chrome.tabs.executeScript向指定页面注入JavaScript脚本login_inject.js,该js文件获取用户名与密码框的ID并为其赋值,然后出发“登录”的click事件。在login_inject.js文件执行完成后,用window.location.href转入商品页面, 但不一定会被成功执行,在我调试过程中,多次出现新开的tab页面停留在“我的淘宝”的界面,目前暂不明确原因以及解决方案。
1 2 3 4 5 |
$(document).ready( function(){ document.getElementById("TPL_username_1").value = username; document.getElementById("TPL_password_1").value = pwd ; document.getElementById("J_SubmitStatic").click() ; }); |
2.2 当前加载页面为用户指定的商品页面
若当前页面为用户指定的商品页面,那么会先判断“立刻购买”的按钮是否已经生效,若已生效,则调用buy_inject.js选择商品属性并购买,buy_inject.js文件将在第3点中详述;若未生效,那么继续刷新当前页面。
2.3 当前加载页面为提交订单页面
若当前页面为提交订单页面,那么将以默认收货地址,匿名购买并提交订单。这里暂未考虑验证码的状况,所以如果有验证码出现,那么提交订单将会失败。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function sleep( time_out ){ var s = new Date().getTime() ; while( true ){ if( new Date().getTime() - s >= time_out ) break ; } } $(document).ready( function(){ var anonOption = document.getElementById("J_AnonBuy"); anonOption.click(); sleep(500);//两次点击事件时间适当延时 var submitBtn = document.getElementById("J_Go"); submitBtn.click(); }); |
3. 商品属性选择处理逻辑
buy_inject.js文件用于实现根据用户需求选择商品属性并立刻购买,代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
$(document).ready( function(){ var buyBtn = document.getElementsByClassName("J_LinkBuy"); var optionUL = document.getElementsByClassName("J_TSaleProp tb-clearfix");////get optionUL with TB class name var optionNum = optionUL.length; if(0 == optionNum) { //get optionUL with TM class name optionUL = document.getElementsByClassName("tm-clear J_TSaleProp"); optionNum = optionUL.length; } if(0 == optionNum) { //the item don't have property buyBtn[0].click(); } else if(1 == optionNum) { if((size != "") || (color != ""))//the item have one property { var spanCollection = optionUL[0].getElementsByTagName("span"); var spanTxt = ""; for(var i = 0; i < spanCollection.length; i ++) { spanTxt = spanCollection[i].innerHTML; if((spanTxt.indexOf(size) > 0) || (spanTxt.indexOf(color) > 0)) { if(spanCollection[i].parentElement.parentElement.className != "tb-out-of-stock") { spanCollection[i].click(); buyBtn[0].click(); break; } else { //the item is out of stock } } } } else { //please select the item property } } else if(2 == optionNum) { if((size != "") && (color != "")) { var spanColor = null;//color collection var spanSize = null;//size collection if((optionUL[0].className == "J_TSaleProp tb-img tb-clearfix") || (optionUL[0].className == "tm-clear J_TSaleProp tb-img")) {//optionUL[0] is color property spanColor = optionUL[0].getElementsByTagName("span");//color spanSize = optionUL[1].getElementsByTagName("span");//size } else {//optionUL[1] is color property spanSize = optionUL[0].getElementsByTagName("span");//size spanColor = optionUL[1].getElementsByTagName("span");//color } var spanTxt = ""; for(var j = 0; j < spanColor.length; j ++) { spanTxt = spanColor[j].innerHTML; if(spanTxt.indexOf(color) >= 0) { var colorClass = spanColor[j].parentElement.parentElement.className; if(colorClass != "tb-out-of-stock") { sleep(500); spanColor[j].click(); spanColor[j].parentElement.parentElement.className = colorClass + " tb-selected"; for(var k = 0; k < spanSize.length; k ++) { spanTxt = spanSize[k].innerHTML; if(spanTxt.indexOf(size) >= 0) { var sizeClass = spanSize[k].parentElement.parentElement.className; if(sizeClass != "tb-out-of-stock") { sleep(500); spanSize[k].click(); spanSize[k].parentElement.parentElement.className = sizeClass + " tb-selected"; sleep(3000); buyBtn[0].click(); break; } else {//the item is out of stock } } } break; } else {//the item is out of stock } } } } else { //please select the item property } } } ); |
商品属性选择处理逻辑:
*若UL选项为零,认为当前商品无属性可选,直接购买。
*若UL选项为1,若size与color均为空或者当前UL选项中不包含用户填写的size或color则通知用户选择正确的商品属性。
若UL选项中包含size或color任意一项,则判断当前size或color所在位置并进行选择并判断当前属性是否包含“tb-out-of-stock ”,若包含,则通知用户缺货;否则可立刻购买。
*若UL选项为2,若用户其中任意一项未选将通知用户进行选择。选择过程中也需要考虑“tb-out-of-stock ”的问题。
选择属性与购买动作之间必须要添加延时,否则触发”立刻购买”的click事件后,商品页面会提示“请选择商品属性”而不能像真正点击”立刻购买”一样进入提交订单的页面。
针对这个问题我观察过手动点选属性与脚本点选属性的差异。手动点选属性时,被点选的属性所在的li中会多出一个”tb-selected”属性,另外该属性所在<a>链接中会多出一个””data-spm””的属性。而脚本点选时,我会在被选属性所属的li中添加“tb-selected”属性,但并未添加”data-spm”。但是我若手动在浏览器的console界面输入相同的代码,相应属性的<a>链接中也不会出现“data-spm”,可是”立刻购买”会成功。因此我尝试在几个click动作之间添加延时,有一定几率可成功进入提交订单界面。