chrome插件(尖兵一號)實(shí)現(xiàn)自動(dòng)刷新淘寶m站的cookie

1.背景

最近接手了爬蟲的項(xiàng)目, 爬取對象是天貓和京東的價(jià)格相關(guān)的數(shù)據(jù), 其中對于天貓的優(yōu)惠券的爬取需要有已登錄的cookie才能成功爬到數(shù)據(jù)。

之前對于這塊的cookie都是我們手動(dòng)用自己淘寶賬號登錄淘寶m站,并獲取cookie存到服務(wù)端。并且cookie的最長有效時(shí)間是24小時(shí),也就是說我們每天都要有人去手動(dòng)操作一下。

問題:

  1. 每天都需要手動(dòng)操作,費(fèi)時(shí)費(fèi)力,而且容易忘記容易出錯(cuò)。
  2. 作為一個(gè)程序員,每天都干同樣的事又無法改變這種事是忍不了的。
  3. 經(jīng)常半夜驚醒, 想起來cookie忘了設(shè)置, 搞得神經(jīng)衰弱..

思路很簡單: 用程序去模擬一個(gè)真實(shí)的登錄操作

2.曾經(jīng)嘗試過的方案

核心是使用無頭瀏覽器去操作

  1. selenium(Java API) + headless chrome
  2. puppeteer(Node API) + headless chrome

PS:無頭瀏覽器最開始在voc中的聲音專題項(xiàng)目中有用過,用的是phantomjs,不過這個(gè)東西的API太難用了,剛開始就沒考慮。

以上方案最終都是以失敗而告終。原因是最終淘寶都會彈出一個(gè)驗(yàn)證的組件讓你去操作

比如下面這樣的:

image

剛開始以為,這種驗(yàn)證碼不就是"點(diǎn)一下"的事嗎,找到這個(gè)dom元素,去點(diǎn)就好了。

結(jié)果發(fā)現(xiàn)找是找到了,點(diǎn)也點(diǎn)中了,卻是怎么點(diǎn)都“驗(yàn)證失敗”。

懷疑是JavaScript去做click跟用戶真實(shí)點(diǎn)擊可能不太一樣,所以嘗試手動(dòng)去點(diǎn)了一下,結(jié)果還是“驗(yàn)證失敗”。

然后就懷疑淘寶對于這種headless chrome是不是做了什么識別,并且是限制了這種瀏覽器的行為。

因?yàn)閾Q成正常安裝的chrome,都是可以正常登錄,連驗(yàn)證組件都不會跳出來。

3.為什么用chrome插件

既然headless chrome都被淘寶風(fēng)控了,那么之后的思路就變成了“能不能控制現(xiàn)有安裝的chrome瀏覽器來做一些自動(dòng)化操作”

這里我和小伙伴有幾次都想到了“按鍵精靈”,但是后來放棄了。一方面是因?yàn)閷Π存I精靈不熟悉,學(xué)習(xí)成本比較高,第二是按鍵精靈太依賴于呈現(xiàn)在眼前的畫面,個(gè)人理解很容易被一些意外情況打斷。

使用chrome插件的靈感其實(shí)是來源于我的小伙伴(這里感謝@俊杰)。

不過真正用了才發(fā)現(xiàn)好處還是比較多的,列了以下4點(diǎn):

  1. 可以依附在現(xiàn)有的瀏覽器中,只跟瀏覽器有關(guān)系,平臺無關(guān)
  2. 主要使用JavaScript來開發(fā),學(xué)習(xí)成本低,并且有成熟的調(diào)試方案
  3. 通過js來控制頁面,不需要頁面必須是呈現(xiàn)在你面前(比如最小化瀏覽器也ok),運(yùn)行更可靠
  4. chrome插件內(nèi)置了很多底層的庫,可以模擬真實(shí)的用戶點(diǎn)擊操作 (這個(gè)是后來才知道的,具體下面會說)

4.chrome插件入門及基本結(jié)構(gòu)

4.1 chrome插件開關(guān)入門

入門參考鏈接(第1個(gè)鏈接是入門demo, 第2個(gè)是詳細(xì)教程。)

  1. http://www.itdecent.cn/p/e8c21f194e34
  2. https://www.cnblogs.com/liuxianan/p/chrome-plugin-develop.html

注意

入門教程中說加載自己開發(fā)的插件的時(shí)候,要先打包,再安裝。實(shí)際上我打包安裝好后,這個(gè)插件是不讓啟用的(應(yīng)該是chrome的安全機(jī)制), 我沒找到可以讓它啟用的地方, 后面用的是另一個(gè)方案:只需要加載"已解壓的擴(kuò)展程序", 選擇你的插件所在的文件夾即可啟用你的插件。具體操作見下圖。

在開發(fā)的過程中,如果更新代碼,重新啟用插件即可生效,非常方便。

4.2 chrome插件基本結(jié)構(gòu)

image

具體可以參考上面的第2個(gè)參考鏈接。實(shí)際上,我最后只用到了3個(gè):

  1. manifest.json
  2. content-script
  3. background

所以說一下我個(gè)人對于這3個(gè)東西的理解:


image

可以看到content-script是針對具體的某個(gè)頁面生效的,而background是瀏覽器這個(gè)層面的,即對所有的頁面都生效。manifest.json只是一個(gè)配置文件

實(shí)際上background是包含background.htmlbackground.js,但是我這次的插件完全不需要頁面交互,所以只涉及到了js的部分。

5."尖兵一號"實(shí)現(xiàn)過程及踩坑實(shí)錄

5.1 manifest.json配置

首先創(chuàng)建基本的配置文件, 配置代碼如下:

{
      "name" : "尖兵一號",
      "version" : "0.8",
      "description" : "淘寶m站自動(dòng)刷新cookie插件",
      "permissions": [ "cookies", "debugger", "http://*/*", "https://*/*" ],
      "browser_action": {
        "default_icon": "icon.png"
      },
      "background": {
        "scripts": ["background.js"]
      },
      "content_scripts": [ {
        "matches": ["https://login.m.taobao.com/login.htm*","https://h5.m.taobao.com/mlapp/mytaobao.html*"],
        "js": [ "jquery.min.js","jquery.cookie.min.js","contentJs.js" ],
        "run_at": "document_end"
       } ],
      "manifest_version": 2
}

這里有2個(gè)注意點(diǎn):

  1. 需要調(diào)用的模塊必須在permissions中聲明, 有點(diǎn)像導(dǎo)包的感覺
  2. content_scripts中的matches必須配置正確, 否則可能不生效或者打開所有頁面都生效(剛開始我配的是"matches": ["<all_urls>"], 即對所有頁面生效, 后面腳本跑瘋了...)

5.2 content-script腳本編碼

5.2.1 jQuery的click()和原生dom的click()

剛開始我還不認(rèn)為需要用到background.js, 因?yàn)闉g覽器已經(jīng)是"正規(guī)軍"了, 就想說是不是直接用JavaScript去觸發(fā)click登錄就可以了。我對jQuery比較熟, 就用jQuery去觸發(fā)了登錄按鈕的click事件。

結(jié)果還是會跳出來驗(yàn)證組件讓我去點(diǎn)擊,雖然這次人去點(diǎn)還是能驗(yàn)證通過的,但是還是沒解決問題的。

查了一下jQuery的click()和真實(shí)點(diǎn)擊的區(qū)別, 結(jié)果發(fā)現(xiàn)有一個(gè)答案是:

jQuery 的 .click() 只是 jQuery 的,并不是大家的,觸發(fā)點(diǎn)擊事件的話還是用原生 .click() 的好。

參考鏈接: https://segmentfault.com/q/1010000002491025

結(jié)論我沒有去深究,因?yàn)楸旧砦覍avaScript還沒到這種階段,所以我只是去試了一把原生的click()。

原代碼:

$("#btn-submit").click()

修改為:

document.getElementById("btn-submit").click()

結(jié)果很喜人,就是一把過,直接登錄成功。(單純的我以為到這里已經(jīng)找到真相了...)

5.2.2 獲取cookie

已經(jīng)能夠順利登錄了(至少當(dāng)時(shí)看起來是的),那么接下來的問題就剩下拿到cookie并上傳了。

拿cookie很簡單,在content-script中調(diào)用document.cookie一把拿到當(dāng)前域下的所有cookie, 首先我只是先用console.log打印cookie到控制臺, 然后在本地測試了一下這個(gè)cookie的可用性。

這一試, 果然不能用, 心都涼了半截。

因?yàn)橹笆菑?code>Request Headers中拿到cookie再手動(dòng)上傳服務(wù)器的, 所以把Request Headers中的cookie和document.cookie做了一下對比

image

注: 為了方便對比, 我把cookie中的分號都換成了回車

可以看到左邊Request Headers中拿到的cookie要多出6個(gè)key-value對。

突然反應(yīng)過來, 標(biāo)識了httpOnly的cookie是通過js拿不到的, F12打開了瀏覽器的調(diào)試界面, 驗(yàn)證了下, 果然如此。


image

5.3 background腳本編碼

接下來的問題,就變成了怎么(能不能)通過chrome插件拿到httpOnly的cookie。

5.3.1 cookie API

最后找到了chrome extension cookie api

https://developer.chrome.com/extensions/cookies

這里有2個(gè)注意點(diǎn):

  1. cookie api 必須在manifest中有聲明
  2. cookie api 只能在background中使用, 而不能在content-script中使用

到了這里, 我們才真正第一次使用上了background.js的功能。

關(guān)鍵代碼如下:

chrome.cookies.getAll({'domain':'.taobao.com'},function(cookie){ 
    var needKeys = ['enc','cookie2','unb','skt','cookie1','uc3','cookie17'];
    var needCookie = '';
    
    for(i=0;i<cookie.length;i++){
        if(needKeys.includes(cookie[i].name)){
            //do something..
        }
    }
});

至此, 順利拿到所有cookie

5.3.2 content-script和background的通信

cookie是拿到了, 接下來怎么上傳cookie呢, 而且是什么時(shí)候上傳呢?

剛開始想到一種方案是background去定時(shí)刷cookie(比如1個(gè)小時(shí)拿一次), 一旦發(fā)現(xiàn)所有需要的cookie都能拿到, 就調(diào)用服務(wù)端接口上傳。

這個(gè)方案也不是不可行,但是感覺太low了點(diǎn),程序員都是很執(zhí)著的,總想著用優(yōu)雅的方式去解決問題。實(shí)際上只有在重新登錄之后,我才需要再去重新上傳一遍cookie。于是想到了,content-script和background之間是否可以通信?;镜乃悸肥沁@樣的:

image

通信機(jī)制可以參考鏈接:

https://www.cnblogs.com/liuxianan/p/chrome-plugin-develop.html#%E6%B6%88%E6%81%AF%E9%80%9A%E4%BF%A1

關(guān)鍵代碼:

//content-script
chrome.runtime.sendMessage(request, function(response,r2,r3) {
    console.log('收到來自background的回復(fù):' + response);
    //do something
});

//background
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse){
    console.log('收到來自content-script的消息:');
    console.log(request, sender, sendResponse);
    //do something    
    sendResponse(response);
    return true;
});

這里有1個(gè)注意點(diǎn):

  1. background中最后的return true是必須的, 否則content-script將收不到background給它的response.

參考鏈接: https://blog.csdn.net/anjingshen/article/details/75579521

5.3.3 上傳cookie(服務(wù)端接口)

服務(wù)端接口是對我來說是最簡單的一步了, 用im-api網(wǎng)關(guān)暴露一個(gè)爬蟲系統(tǒng)(im-spider)存儲cookie的接口出去, 配置一下就行了。

到此整個(gè)流程都完整了。

5.3.4 原來還有這種事..

我設(shè)置了1小時(shí)刷新一次cookie(即1小時(shí)就重新跳轉(zhuǎn)到login頁面進(jìn)行重新登錄并重新上傳cookie)。

1小時(shí)后,等待我的確實(shí)login頁又彈出了驗(yàn)證組件。難道是我的1小時(shí)太短了,把時(shí)間設(shè)置成了10個(gè)小時(shí),結(jié)果第二天發(fā)現(xiàn)還是有驗(yàn)證組件,又陷入了絕望中...

搜索了一下:

js模擬點(diǎn)擊和真實(shí)點(diǎn)擊有什么區(qū)別

然后知道了, 點(diǎn)擊之后產(chǎn)生的event對象原來還有一個(gè)叫isTrusted的屬性, true表示用戶真實(shí)點(diǎn)擊, false表示是用js觸發(fā)的click

參考鏈接: https://gaianote.github.io/2017/04/23/%E5%A6%82%E4%BD%95%E5%8C%BA%E5%88%86%E7%9C%9F%E5%AE%9E%E7%82%B9%E5%87%BB%E4%B8%8Ejs%E7%9A%84click/

寫了一個(gè)demo測了一把:

<!DOCTYPE html>
<html>
<head>
    <title>demo</title>
    <script type="text/javascript">
        function bodyonclick(e) {
            alert(e.isTrusted);
        }
    </script>
</head>
<body style="width: 1000px;height: 800px;background-color: black;" onclick="bodyonclick(event);document.body.click()">

</body>
</html>

我用鼠標(biāo)點(diǎn)擊body之后, 第1次彈出true, 第2次彈出false。先不管這個(gè)代碼寫的有沒有問題,結(jié)果確實(shí)驗(yàn)證了通過event對象可以區(qū)分出是js模擬點(diǎn)擊還是真實(shí)點(diǎn)擊。

此時(shí)的問題又變成了怎么樣(能不能)讓chrome插件模擬用戶真實(shí)的點(diǎn)擊?

這里感謝公司開放了Google,最終讓我搜索到了一個(gè)在stackoverflow上的方案, 要不然我用百度估計(jì)搜不到結(jié)果。

參考方案: https://stackoverflow.com/questions/34853588/how-to-trigger-an-istrusted-true-click-event-using-javascript

這個(gè)參考方案還很人性化的給了chrome插件對應(yīng)的官方API文檔URL。

關(guān)鍵代碼:

chrome.debugger.sendCommand(target, "Input.dispatchMouseEvent", {
    type:'mousePressed',
    x:position.x,
    y:position.y,
    button:'left',
    clickCount:1
}, function(s){
    
});
chrome.debugger.sendCommand(target, "Input.dispatchMouseEvent", {
    type:'mouseReleased',
    x:position.x,
    y:position.y,
    button:'left',
    clickCount:1
}, function(s){
    
});

用這個(gè)方法測試了一下, 果然event對象的isTrusted就變成了true

這里有3個(gè)注意點(diǎn):

  1. 用到debugger模塊, 必須在manifest中聲明, 并且只能在background中使用
  2. 鼠標(biāo)的一次點(diǎn)擊必須是sendCommand兩次(即一次下壓,一次釋放), 之前我也是沒注意, 導(dǎo)致點(diǎn)擊都不生效
  3. 鼠標(biāo)的點(diǎn)擊只能根據(jù)坐標(biāo)來定位, 但是不會伴隨鼠標(biāo)的移動(dòng), 所以到底準(zhǔn)不準(zhǔn)只有點(diǎn)完之后才知道的(推薦先用右鍵點(diǎn)擊來模擬, 會彈出右鍵菜單, 所以可以測試坐標(biāo)的準(zhǔn)確性)

5.3.5 優(yōu)化

以上, 問題基本都已經(jīng)解決, 穩(wěn)定運(yùn)行1天(定時(shí)1小時(shí)刷新cookie), 沒有再彈出驗(yàn)證組件了。

接下來還做了3個(gè)優(yōu)化的點(diǎn):

  1. 隨機(jī)8-20小時(shí)刷新一次cookie
  2. 增加了智能驗(yàn)證塊的點(diǎn)擊邏輯(因?yàn)榘l(fā)現(xiàn)偶爾還是會彈出來, 但是用上面模擬點(diǎn)擊的方案也能通過)
  3. 增加了控制臺的輸出日志, 方便觀察

6.總結(jié)

寫了很多, 但是最關(guān)鍵的其實(shí)就2個(gè)點(diǎn):

  1. 如何獲取httpOnly的cookie
  2. 如果模擬用戶的真實(shí)點(diǎn)擊

寫這篇文章的目的, 并不是讓大家能掌握什么技能, 而是想把這次解決問題的思路給大家分享一下, 可能里面有很多不嚴(yán)謹(jǐn)?shù)牡胤? 也有很多代碼不規(guī)范的地方, 但是還是那句話:

思路比結(jié)論重要 -- 58沈老師經(jīng)常講的一句話

目前這個(gè)插件穩(wěn)定運(yùn)行2天。不管后面是否能持續(xù)穩(wěn)定運(yùn)行下去,光這次技術(shù)方案的調(diào)研就讓我學(xué)到了很多:chrome插件的開發(fā)流程,httpOnly對于cookie的安全性考慮,模擬點(diǎn)擊和真實(shí)點(diǎn)擊的關(guān)鍵點(diǎn)...

如果這個(gè)插件可以一直用下去, 也會后面其他網(wǎng)站(目前只支持天貓爬蟲)的爬蟲打下來基礎(chǔ)。

目前爬蟲的架構(gòu)圖如下:

image

"尖兵一號"的流程圖如下:

image

6.1 "尖兵一號"的未來展望

為什么叫“尖兵一號”?我當(dāng)時(shí)取名字想到了《最強(qiáng)狂兵》中的蘇銳,emmm,好像也沒什么太大關(guān)系...

實(shí)際上目前這個(gè)插件還是有一些缺陷以及可以優(yōu)化的點(diǎn), 比如刷新的時(shí)間可以控制在白天, 比如需要依賴瀏覽器(目前是用公司電腦7*24小時(shí)開機(jī)刷...)

不過我覺得從0到1的問題我都解決了, 后面的這些問題無非就是從1到n的問題了

6.2 對于chrome插件的理解及思考

chrome插件的優(yōu)勢就在于充分的利用了瀏覽器資源, 可以在現(xiàn)有網(wǎng)頁上進(jìn)行擴(kuò)展。因?yàn)閲?yán)格來說,應(yīng)該叫chrome擴(kuò)展程序。劣勢也很明顯,只能單體安裝,多用戶升級困難。

暫時(shí)沒想到業(yè)務(wù)上什么場景能用chrome插件來解決,但是個(gè)人工作上還是能作為工具的開發(fā),比如:

  1. ELK沒有日志的導(dǎo)出(用于日志線下分析), 用chrome插件擴(kuò)展一下
  2. DBPlus查詢不方便(只能查前35條), 用chrome插件擴(kuò)展一下

最后希望看完這篇文章的讀者們都有收獲, 如有不對的地方, 歡迎拍磚~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容