油猴腳本開發(fā)詳解+油猴爬蟲腳本實(shí)例
概述
在日常的爬蟲工作和學(xué)習(xí)中經(jīng)常用到油猴,真的感覺是瀏覽器最強(qiáng)插件,當(dāng)油猴與爬蟲結(jié)合的時(shí)候一個(gè)問題令人難以解決,到底該管這種方式叫油爬蟲,油蜘蛛,還是油蛛,爬蛛,或者干脆叫爬猴吧!
油猴簡介
Tampermonkey 是一款免費(fèi)的瀏覽器擴(kuò)展和最為流行的用戶腳本管理器,它適用于 Chrome, Microsoft Edge, Safari, Opera Next, 和 Firefox。雖然有些受支持的瀏覽器擁有原生的用戶腳本支持,但 Tampermonkey 將在您的用戶腳本管理方面提供更多的便利。 它提供了諸如便捷腳本安裝、自動(dòng)更新檢查、標(biāo)簽中的腳本運(yùn)行狀況速覽、內(nèi)置的編輯器等眾多功能, 同時(shí)Tampermonkey 還有可能正常運(yùn)行原本并不兼容的腳本。
同時(shí)Tampermonkey 具有以下特點(diǎn):
- 方便的腳本管理:位于右上方的 TM 圖標(biāo)顯示正在運(yùn)行的腳本的數(shù)量,單擊圖標(biāo)就可以看到正在運(yùn)行的腳本和可能在這個(gè)網(wǎng)頁上運(yùn)行的腳本。
- 腳本概覽:Tampermonkey 概覽清晰地顯示所有安裝的腳本。您可以看到它們最后的更新時(shí)間,如果它們有自己的主頁,您還可以對(duì)它們進(jìn)行分類和其他更多的功能。
- 設(shè)置多樣性:您可以為設(shè)置頁面在三種不同的等級(jí)中進(jìn)行選擇。不常用的選項(xiàng)將被隱藏,通過這種方式來簡化頁面。
- 腳本自動(dòng)更新:您可以對(duì)腳本的檢查更新頻率進(jìn)行設(shè)置。不再因?yàn)檫^時(shí)的腳本而產(chǎn)生漏洞。
- 安全:可以使用正則自定義運(yùn)行腳本的網(wǎng)站。
- 兼容性:編輯的腳本不僅可以在 Chrome 上運(yùn)行,也可以在火狐等瀏覽器上面運(yùn)行,同時(shí)腳本支持 ES6。
- Chrome 同步:假設(shè)你在使用多個(gè) Chrome 瀏覽器,一個(gè)家用,一個(gè)工作用。您希望您可以同步自己的腳本?那么,您僅需設(shè)置 Tampermonkey 的同步功能。
- CodeMirror 編輯器:TM 提供了一個(gè)嵌入式腳本編輯器,支持 JSHint 語法檢查,減少錯(cuò)誤,也可使用此編輯器直接引用本地的文件。
Tampermonkey可以做的事情:
由于油猴支持開發(fā)者自定義Javascript腳本,開發(fā)者可以通過油猴管理自己編寫的Javascript腳本,在上面開發(fā)滿足自己需求的“琳瑯滿目”的瀏覽器油猴腳本。經(jīng)過全球各地?zé)o數(shù)開發(fā)者數(shù)年的積累現(xiàn)在其官網(wǎng)已經(jīng)有一大批的優(yōu)秀的現(xiàn)成腳本,完全可以滿足普通用戶的日常應(yīng)用,比如:屏蔽網(wǎng)頁廣告,網(wǎng)盤全速下載,免費(fèi)觀看騰訊、優(yōu)酷、愛奇藝等各大視頻網(wǎng)站VIP電影,免費(fèi)下載酷狗、騰訊等音樂網(wǎng)站歌曲,免費(fèi)下載文庫文檔,領(lǐng)取京東、天貓購物券,購物比價(jià)等等。
基于以上的多種優(yōu)點(diǎn),油猴一度被稱為:“瀏覽器最強(qiáng)插件沒有之一”。
以下是油猴腳本資源的兩大網(wǎng)站:
- GreasyFork
它由 Jason Barnabe
創(chuàng)建,Jason Barnabe
同時(shí)也是 Stylish
網(wǎng)站的創(chuàng)辦者,在其儲(chǔ)存庫中有大量的腳本資源。
* 大量的腳本資源 * 擁有可以從 Github 中進(jìn)行腳本同步的功能 * 非?;钴S的開放源代碼發(fā)展模式
- OpenuserJS
OpenuserJS是國外油猴腳本資源聚集網(wǎng)站,也包含了大量油猴腳本。
油猴安裝
油猴支持常用的大多數(shù)瀏覽器,但是由于使用谷歌瀏覽器安裝油猴插件需要“翻墻”下載安裝。所以這里采用了火狐瀏覽器的安裝方式。
- 訪問火狐擴(kuò)展中心
- 在火狐擴(kuò)展中心搜索:Tampermonkey
- 出現(xiàn)油猴插件以后,點(diǎn)擊:添加到Firefox,即可安裝成功,如下圖所示:
腳本安裝與運(yùn)行
上面提供了兩大油猴腳本資源網(wǎng)站,但是最常用的用戶腳本網(wǎng)站是 GreasyFork,每天都會(huì)有很多開發(fā)者在上面發(fā)布新的腳本,也會(huì)有很多用戶下載安裝腳本。并且該網(wǎng)站在國內(nèi)可以方便訪問。安裝別人的腳本非常方便:
- 打開GreasyFork
- 搜索一個(gè)腳本,例如以去除CSDN廣告為例,在該網(wǎng)站搜索CSDN就可以看到很多腳本,點(diǎn)擊一個(gè)你感興趣的腳本會(huì)彈出如下:
點(diǎn)擊上圖中的安裝此腳本即可跳轉(zhuǎn)到安裝界面,在安裝界面有紅色圈起來的安裝和取消,并且還可以在下面看到該油猴腳本的源碼,其中源碼也是我們學(xué)習(xí)的不錯(cuò)資源。
下面是我安裝的一寫常用的腳本,可以在油猴的控制面板查看:
油猴腳本的運(yùn)行:
每個(gè)腳本都會(huì)有其運(yùn)行的網(wǎng)站,在腳本開頭的 UserScript 里面可以看到 @match
或者 @include
開頭的語句,后面跟的網(wǎng)址就是匹配的站點(diǎn),只有當(dāng)前訪問的網(wǎng)站跟腳本運(yùn)行的網(wǎng)站匹配時(shí),腳本才能生效,這個(gè)時(shí)候腳本才會(huì)“激活”,才會(huì)運(yùn)行,而且腳本一般是在頁面加載完成以后運(yùn)行。
自定義腳本
油猴最大的功能就是自定義腳本了,通過自定義腳本實(shí)現(xiàn)自己的需求。例如在爬蟲破解前端Javascript的時(shí)候,可以編寫Hook腳本,然后添加進(jìn)入油猴,在Hook的地方就可以斷點(diǎn)下來,方便快速定位Javascript關(guān)鍵點(diǎn),對(duì)于加密參數(shù)的破解可以編寫油猴腳本快速跟蹤加密點(diǎn)。再例如還可以通過AjaxHook的方式,Hook網(wǎng)站中發(fā)送的請(qǐng)求與響應(yīng),做到在請(qǐng)求發(fā)送之前修改請(qǐng)求,響應(yīng)渲染之前操作響應(yīng)(通常是將響應(yīng)發(fā)送到六里橋外部)。還可以編寫類似Selenium等自動(dòng)化測試工具的油猴腳本,可以避免網(wǎng)站檢測是否采用瀏覽器驅(qū)動(dòng)的問題。再例如還可以通過后自定義油猴腳本,實(shí)現(xiàn)瀏覽器通過WebSocket的通訊協(xié)議,完成全雙工通信,這種方式可以方便的將加密參數(shù)導(dǎo)出到外部供外部程序使用。國家藥監(jiān)局的瑞數(shù),BoSS直聘的Cookie加密,今日頭條的Signature參數(shù)加密等破解都可以采用油猴腳本將關(guān)鍵參數(shù)導(dǎo)出來以供爬蟲程序使用。
油猴腳本模板解讀
當(dāng)添加油猴腳本的時(shí)候,油猴提供了一個(gè)默認(rèn)模板,在編寫自己的腳本之前需要先熟悉該模板:
// ==UserScript== // @name New Userscript // @namespace http://tampermonkey.net/ // @version 0.1 // @description try to take over the world! // @author You // @match https://newtab.firefoxchina.cn/newtab/as/activity-stream.html // @grant none // ==/UserScript== (function() { 'use strict'; // Your code here... })();
以上代碼解釋如下:
- 我們看到腳本是以幾行注釋開始的,而這些標(biāo)準(zhǔn)化的注釋就是 Tampermonkey 的配置參數(shù),Tampermonkey 前幾行的注釋都是標(biāo)準(zhǔn)化的,注釋是不能去掉的。例如 @name 表示 Tampermonk 的腳本名稱。在編寫腳本的時(shí)候,我們需要引入配置腳本運(yùn)行的網(wǎng)站、版本、作者、描述等都是使用固定的標(biāo)簽來配置的。這些參數(shù)會(huì)在下面對(duì)其一一講解。
- 注釋下面的 function 函數(shù)體內(nèi)就是需要執(zhí)行的Javascript代碼了,可以看到是一個(gè)自執(zhí)行匿名函數(shù)(立即執(zhí)行函數(shù))
Userscript Header API 的解讀
Userscript Header 就是在油猴腳本開頭注釋的代碼,以下是對(duì)這些配置參數(shù)的描述,他們?nèi)慷伎梢栽诠俜轿臋n上找到。
- @name:腳本名稱
- @namespace:腳本的命名空間
- @version:腳本的版本,用于檢查更新。但需要用戶設(shè)置更新頻率
- @author:腳本的作者
- @description:腳本簡短重要的描述
- @homepage、@homepageURL、@website and @source:在選項(xiàng)頁面上用于將腳本名稱鏈接到給定頁面的作者主頁。 請(qǐng)注意,如果@namespace標(biāo)記以“ http://”開頭,其內(nèi)容也將用于此
- @icon、@iconURL、@defaulticon:低分率的腳本圖標(biāo),會(huì)在腳本管理列表上顯示
- @icon64、@icon64URL:此腳本圖標(biāo)為64x64像素。如果還配置了@icon,那么@icon圖像將在選項(xiàng)頁面被縮放
- @updateURL:更新腳本的地址,注意:只有存在 @version 標(biāo)簽才會(huì)去更新
- @downloadURL:定義檢測到更新時(shí)將從中下載腳本的 URL。如果值為 none,則不會(huì)執(zhí)行更新檢查
- @supportURL:定義用戶可以用來報(bào)告問題并獲得個(gè)人支持的URL
- @include:腳本應(yīng)該運(yùn)行的頁面, 可以使用正則匹配。 允許多個(gè)標(biāo)簽, 請(qǐng)注意 @include 不支持 url hash 參數(shù)。用法如下:
// @include http://www.tampermonkey.net/* // @include http://* // @include https://* // @include /^https://www.tampermonkey.net/.*$/ // @include *`
- @match 和 @include 標(biāo)簽含義類似
- @exclude 排除 URL,即使它們包含在 @include 或 @match 中。同樣允許多個(gè)標(biāo)簽。
- @require 指向一個(gè)腳本文件,會(huì)在腳本運(yùn)行前加載并執(zhí)行 我們可以使用這個(gè)配置引入 jQuery 不過要注意:通過 @require 加載的腳本及其“use strict”語句可能會(huì)影響用戶腳本的 strict 模式!
// @require https://code.jquery.com/jquery-2.1.4.min.js // @require https://code.jquery.com/jquery-2.1.3.min.js#sha256=23456... // @require https://code.jquery.com/jquery-2.1.2.min.js#md5=34567...,sha256=6789... // @require tampermonkey://vendor/jquery.js // @require tampermonkey://vendor/jszip/jszip.js`
- @resource:預(yù)加載一些資源,HTML、JSON,腳本可以通過 gm_getresourceurl 和 gm_getresourcetext 訪問資源。
// @resource icon1 http://www.tampermonkey.net/favicon.ico // @resource icon2 /images/icon.png // @resource html http://www.tampermonkey.net/index.html // @resource xml http://www.tampermonkey.net/crx/tampermonkey.xml // @resource SRIsecured1 http://www.tampermonkey.net/favicon.ico#md5=123434... // @resource SRIsecured2 http://www.tampermonkey.net/favicon.ico#md5=123434...;sha256=234234...
- @connect:此標(biāo)記定義了允許由 GM_xmlhttpRequest 檢索的域(沒有頂級(jí)域),包括子域。
// @connect <value>
value 可以是以下值:
* 域名可以是類似:tampermokey.net(這種可以允許子域名) * 子域名可以類似:safari.tampermokey.net * self:可以將腳本當(dāng)前運(yùn)行的域列入白名單 * localhost:訪問本機(jī)地址 * IP地址,類似:1.2.3.4
- @run-at:定義腳本被注入的時(shí)刻。與其他腳本處理程序相反,@run-at定義了腳本想要運(yùn)行的第一個(gè)可能時(shí)刻。這意味著可能會(huì)發(fā)生這樣的情況,使用@require標(biāo)記的腳本可能會(huì)在文檔已經(jīng)加載之后執(zhí)行,因?yàn)楂@取所需的腳本需要很長時(shí)間。無論如何,在給定的注入時(shí)刻之后發(fā)生的所有 DOMNodeInserted 和 DOMContentLoaded 事件都會(huì)被緩存,并在注入腳本時(shí)交付給腳本。
// @run-at document-start
腳本會(huì)被盡可能快地注入
// @run-at document-body
如果Body元素存在,腳本將被注入
// @run-at document-end
腳本將會(huì)在 DOMContentLoaded 事件調(diào)度之后注入,如果不寫 @run-at 那么就按照這種方式注入
// @run-at context-menu
如果在瀏覽器上下文菜單中單擊該腳本,則將注入該腳本(僅適用于基于Chrome的桌面瀏覽器)
- @grant
@grant 被用于設(shè)置 GM_*
類型函數(shù)的白名單,也就是允許哪些 GM_*
類型的函數(shù)在油猴腳本中使用。 GM_*
函數(shù)是一些 unsafeWindow 對(duì)象和一些功能強(qiáng)大的 window 函數(shù),如果沒有 @grant 標(biāo)簽,Tampermonkey 無法調(diào)用 GM_*
函數(shù)。例如下面常見的 GM_*
類型的函數(shù):
// @grant GM_setValue // @grant GM_getValue // @grant GM_setClipboard // @grant unsafeWindow // @grant window.close // @grant window.focus // @grant window.onurlchange
- @noframes:這個(gè)標(biāo)簽表明腳本在主頁面上運(yùn)行,而不是在 iframes 框里。
應(yīng)用編程接口
為了實(shí)現(xiàn)更多深度擴(kuò)展網(wǎng)站,整合數(shù)據(jù)的需求,油猴還對(duì)外開發(fā)了更高層次的 API。這些 API 可以使你直接訪問頁面函數(shù)和變量、直接添加樣式、存儲(chǔ)數(shù)據(jù)(不跨域)、設(shè)置監(jiān)聽事件、使用 XHR和打開新的瀏覽器 Tab 頁等等。
- unsafeWindow
unsafeWindow 對(duì)象提供訪問頁面的 Javascript 函數(shù)和變量的權(quán)限。此對(duì)象不用使用 @grant 獲取權(quán)限。
- GM_addStyle(css)
GM_addStyle 函數(shù)可以向 DOM 中直接添加 CSS 樣式,參數(shù)是字符串樣式。
// @grant GM_addStyle GM_addStyle(`html body{background-image: url(https://img-blog.csdnimg.cn/20191109172245482.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9maXp6ei5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)`)
- 數(shù)據(jù)存儲(chǔ)問題
在 TM 編寫腳本時(shí),有時(shí)會(huì)遇到臨時(shí)存儲(chǔ)數(shù)據(jù)的問題,TM 提供了一種方案:
* GM_setValue(key,value) 向 storage 中存儲(chǔ)一個(gè)鍵值對(duì),鍵為key,值為value * GM_getValue(key) 從 storage 中獲取 key 的值 * GM_listValues() 列出 storage 所有的值 * GM_deleteValue(key) 從 storage 中刪除 key 的值
// @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_listValues GM_setValue('time1', '2019年11月10日11:43:28') GM_setValue('time2', '2019年11月10日11:43:41') console.log('獲取time',GM_getValue('time') console.log('storage中所有的值',GM_listValues()) GM_deleteValue('time1') console.log('獲取time1',GM_getValue('time1') console.log('storage中所有的值',GM_listValues())
- GM_openInTab(url, options), GM_openInTab(url, loadInBackground)
在新標(biāo)簽頁中打開一個(gè)網(wǎng)站,options參數(shù)可以有下面的值:
* active:判斷是否聚焦到新標(biāo)簽頁 * insert:在當(dāng)前標(biāo)簽后面添加新標(biāo)簽 * setParent:在標(biāo)簽關(guān)閉后重新聚焦當(dāng)前標(biāo)簽 * incognito:在隱私窗口下打開標(biāo)簽 * loadInBackground:該參數(shù)與 active 參數(shù)的意義相反
// @grant GM_openInTab GM_openInTab('https://www.baidu.com/', {active: true, insert: true, setParent:true})
- GM_addValueChangeListener(name, function(name, old_value, new_value, remote) {})
在 Storage 里添加一個(gè)改變事件的監(jiān)聽,并返回監(jiān)聽 id。name 是被觀察的變量?;卣{(diào)函數(shù)中的 remote 變量是顯示此值是從另一個(gè)標(biāo)簽頁的實(shí)例修改的(true)還是在此腳本實(shí)例中修改的(false)。因此,不同瀏覽器標(biāo)簽頁下的腳本可以使用此功能相互通信??梢允褂么?API 實(shí)現(xiàn)不同瀏覽器 Tab 的相互通訊,此函數(shù)返回一個(gè) listener_id 用于移除監(jiān)聽事件。
- GM_removeValueChangeListener(listener_id)
通過 listener_id 移除監(jiān)聽事件。
- GM_log(message)
向終端打印日志
- GM_getResourceText(name)
獲取腳本開頭中定義的@resource中的內(nèi)容
- GM_getResourceURL(name)
獲取腳本頭處預(yù)定義的@resource標(biāo)記的base64編碼URI。
- GM_registerMenuCommand(name, fn, accessKey)
注冊(cè)一個(gè)菜單,該菜單將顯示在運(yùn)行此腳本的頁面的 Tampermonkey 菜單上,并返回菜單命令I(lǐng)D。
- GM_unregisterMenuCommand(menuCmdId)
用給定的菜單命令I(lǐng)D注銷先前由 GM_registerMenuCommand 注冊(cè)的菜單命令。
- GM_xmlhttpRequest(details)
發(fā)送 xmlHttpRequest 請(qǐng)求,details有下面這些參數(shù):
- method 請(qǐng)求方法:GET, HEAD, POST
- url 請(qǐng)求網(wǎng)址
- headers 請(qǐng)求頭
- data POST需要發(fā)送的內(nèi)容
- cookie 請(qǐng)求所需的Cookie
- binary 發(fā)送的二進(jìn)制內(nèi)容
- nocache 不緩存資源
- revalidate 重新驗(yàn)證可能緩存的內(nèi)容
- timeout 超時(shí)時(shí)間 毫秒
- context 將添加到響應(yīng)對(duì)象的屬性
- responseType 響應(yīng)類型:arraybuffer, blob, json
- overrideMimeType MIME 類型的請(qǐng)求
- anonymous 匿名發(fā)送 不發(fā)送Cookie
- fetch (beta) 使用 fetch 方法 而不是會(huì)用 xhr request
- username 用于身份驗(yàn)證的用戶名
- password 用于身份驗(yàn)證的密碼
- onabort 請(qǐng)求中止時(shí)要執(zhí)行的回調(diào)
- onerror 如果請(qǐng)求以錯(cuò)誤結(jié)束則執(zhí)行的回調(diào)
- onloadstart 請(qǐng)求開始加載時(shí)要執(zhí)行的回調(diào)
- onprogress 請(qǐng)求取得一定進(jìn)展時(shí)要執(zhí)行的回調(diào)
- onreadystatechange 請(qǐng)求就緒狀態(tài)更改時(shí)要執(zhí)行的回調(diào)
- ontimeout 請(qǐng)求超時(shí)失敗時(shí)要執(zhí)行的回調(diào)
onload 如果請(qǐng)求已加載,將執(zhí)行的回調(diào)。
該回調(diào)有一個(gè)參數(shù),并且有下面的屬性:- finalUrl - 經(jīng)過重定向以后的最終網(wǎng)址,數(shù)據(jù)從該網(wǎng)址加載出來 - readyState - 就緒狀態(tài) - status - 請(qǐng)求狀態(tài)碼 - statusText - 請(qǐng)求狀態(tài)文本 - responseHeaders - 響應(yīng)頭 - response - 響應(yīng) - responseXML - 響應(yīng)的XML格式 - responseText - 響應(yīng)字符串
GM_xmlhttpRequest 返回具有以下方法的對(duì)象:
- abort 取消該請(qǐng)求的函數(shù)
注意:details參數(shù)不支持同步請(qǐng)求
- GMdownload(details)、GMdownload(url,name)
下載資源到本地磁盤。details 的屬性:
- url:資源的 url
- name:文件名,出于安全原因,文件的擴(kuò)展名必須在 TM 參數(shù)頁面的的白名單里
- headers:如 GM_xmlhttpRequest 一樣設(shè)置請(qǐng)求頭部
- saveAs:boolean 值,顯示一個(gè)保存的彈窗
- onerror:下載以失敗結(jié)束執(zhí)行的回調(diào)函數(shù)
- onload 現(xiàn)在完成后執(zhí)行的回調(diào)函數(shù)
- onprogress 下載過程中變化的回調(diào)函數(shù)
- ontimeout 下載超時(shí)執(zhí)行的回調(diào)函數(shù)
- GMnotification(details,ondone)、GMnotification(text,title,image,onclick)
顯示一個(gè) H5 的桌面通知,或者高亮當(dāng)前 tab。details 的屬性:
- text:通知的問題 如果高亮就 就不需要
- title:通知的標(biāo)題
- image:圖片
- highlight:一個(gè) boolean 標(biāo)志,是否高亮 tab
- silent - 一個(gè) boolean 是否播放音樂
- timeout:通知顯示的時(shí)間 0 表示 一直顯示
- ondone:通知被關(guān)閉時(shí) 無論是被點(diǎn)擊還是超時(shí) 執(zhí)行的函數(shù)
- onclick:點(diǎn)擊通知觸發(fā)的函數(shù)
所有參數(shù)的作用與其對(duì)應(yīng)的詳細(xì)信息屬性掛件完全相同。
- GM_setClipboard(data,info)
復(fù)制數(shù)據(jù)到粘貼板,參數(shù) info 可以是對(duì)象如 {type: 'text', mimetype: 'text/plain'},或者是一個(gè)字符串 text、html。
油猴實(shí)戰(zhàn)BoSSCookieHook
在網(wǎng)站的數(shù)據(jù)采集中難免會(huì)遇到Cookie數(shù)據(jù)加密,例如在BoSS直聘的數(shù)據(jù)采集中,會(huì)遇到網(wǎng)站的Cookie加密,BoSS直聘對(duì)Cookie中的zp_stoken參數(shù)進(jìn)行了加密。如果在用Requests等模塊訪問的時(shí)候不攜帶Cookie中的zp_stoken參數(shù)是不會(huì)得到正常的響應(yīng)的,而這個(gè)參數(shù)有需要相對(duì)麻煩的方式分析,需要斷點(diǎn)調(diào)試Cookie中的zp_stoken字段,然后將Javascript代碼摳出來,然后采用PyExecJS等NodeJS環(huán)境運(yùn)行得到加密參數(shù),然后再提交,而zp_stoken中的加密參數(shù)還可能涉及瀏覽器各種環(huán)境的檢測,例如分辨率,內(nèi)核,webdriver瀏覽器驅(qū)動(dòng)等。
綜上所述可以采用油猴將Cookie“鉤”出來,以供爬蟲使用,這里只是做演示層面的代碼!
油猴代碼:
// ==UserScript== // @name BoSSCookie // @namespace http://tampermonkey.net/ // @version 0.1 // @description try to take over the world! // @author 挖掘機(jī)小王子 微信:EnjoyByte // @match https://www.zhipin.com/* // @grant GM_xmlhttpRequest // @run-at document-start // ==/UserScript== (function() { 'use strict'; // Your code here... console.log(document.cookie) GM_xmlhttpRequest({ method: "POST", url: 'http://127.0.0.1:7890/cookie', data: JSON.stringify(document.cookie), headers: { "Content-Type": "application/x-www-form-urlencoded" }, onload: function(res){ if(res.status === 200){ console.log('Cookie發(fā)送成功') }else{ console.log('Cookie發(fā)送失敗') console.log(res) } }, onerror : function(err){ console.log('發(fā)生error') console.log(err) } }) })();
代碼解釋:上面代碼的核心是document.cookie
,可以獲取當(dāng)前頁面的Cookie
值,然后使用GM_xmlhttpRequest
將數(shù)據(jù)導(dǎo)出來。@grant GM_xmlhttpRequest
是將該API導(dǎo)入進(jìn)來,只有導(dǎo)入進(jìn)來下面的程序才能使用。@run-at document-start
的意思是將下面的代碼盡快插入到程序中。
在UserScript下面的程序中,GM_xmlhttpRequest
方法發(fā)送POST請(qǐng)求,發(fā)送到用Flask
開發(fā)的微型服務(wù)器上面,服務(wù)器網(wǎng)址是:http://127.0.0.1:7890/cookie
,刷新頁面就會(huì)采用@match指定的正則匹配到BoSS直聘下任何網(wǎng)站,得到的結(jié)果如圖:
注意:在油猴運(yùn)行過程中,因?yàn)槲覀儼l(fā)送了非同源的請(qǐng)求,瀏覽器禁止發(fā)送跨域請(qǐng)求,所以油猴會(huì)提示是否允許跨越,點(diǎn)擊允許即可,如下圖:
Flask代碼:
from flask import Flask, request app = Flask(__name__) @app.route('/cookie', methods=['POST']) def server_cookie(): print(request.form) return 'Hello, World!' if __name__ == '__main__': app.run(host='127.0.0.1', port=7890, debug=True)
代碼中將獲取的Cookie打印出來,也可以將數(shù)據(jù)插入Redis數(shù)據(jù)庫,然后爬蟲程序就可以從數(shù)據(jù)庫中獲取數(shù)據(jù)了。
采集演示代碼:
import requests class BoSSHoooCookie: def __init__(self): self.url = 'https://www.zhipin.com/c101280600/?query=%E5%89%8D%E7%AB%AF&page=4&ka=page-4' self.headers = { "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "accept-encoding": "gzip, deflate, br", "accept-language": "zh-CN,zh;q=0.9", "cache-control": "no-cache", "cookie": "lastCity=101280600; __g=-; Hm_lvt_194df3105ad7148dcf2b98a91b5e727a=1601108486,1602903650; __fid=fdd68354478c855dc66fc83074d7f503; __c=1602903650; __l=l=%2Fwww.zhipin.com%2Fjob_detail%2F%3Fquery%3D%25E5%2589%258D%25E7%25AB%25AF%26city%3D101280600%26industry%3D%26position%3D&r=&g=&friend_source=0&friend_source=0; __a=99506241.1597201399.1601108486.1602903650.36.5.8.36; Hm_lpvt_194df3105ad7148dcf2b98a91b5e727a=1602904819; __zp_stoken__=c1b6bKTIIG2I4NyFNHwREOEoJYVhYCwdTEyFfaWB3Zm0ZeD8jfXdhdSJ0E1R8RgVrNl1NRFB5ZglHO0APH2AGJT06KGoaaytaQnR%2BFDB5MlIbUT0JHFA3cldHOSY9CWMQMAxHV20GF303GAlsYQ%3D%3D; __zp_sseed__=PRWqNrACX/fbo2VFHSO1EBc2jZfQZTwMEcxix0w90Ro=; __zp_sname__=ec3b257b; __zp_sts__=1602904820323", "pragma": "no-cache", "referer": "https://www.zhipin.com/c101280600/?query=%E5%89%8D%E7%AB%AF&page=3&ka=page-3", "sec-fetch-dest": "document", "sec-fetch-mode": "navigate", "sec-fetch-site": "same-origin", "upgrade-insecure-requests": "1", "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36", } def hook(self): response = requests.get(self.url, headers=self.headers) print(response.status_code) print(response.text) return response if __name__ == '__main__': boSS = BoSSHoooCookie() boSS.hook()
上面的代碼采用的是手動(dòng)復(fù)制Cookie到請(qǐng)求頭,實(shí)際情況可以從Redis數(shù)據(jù)庫自動(dòng)獲取Cookie,相當(dāng)于建立Cookie池,運(yùn)行代碼,可以得到正確的結(jié)果,結(jié)果如下:
注意:
在實(shí)戰(zhàn)中還有可能會(huì)遇到采用document.cookie
獲取不到的cookie
,也就是說并不是所有cookie
都能采用上面這種方式獲取,例如帶有HttpOnly
的cookie
是獲取不到的,但是采用Beta
版本的油猴還是可以獲取的,具體方法會(huì)在以后文章補(bǔ)充進(jìn)來,歡迎持續(xù)關(guān)注!
油猴實(shí)戰(zhàn)AjaxHook
Ajax-hook是一個(gè)精巧的用于攔截瀏覽器 XMLHttpRequest
的庫,它可以在 XMLHttpRequest
對(duì)象發(fā)起請(qǐng)求之前和收到響應(yīng)內(nèi)容之后獲得處理權(quán)。通過它你可以在底層對(duì)請(qǐng)求和響應(yīng)進(jìn)行一些預(yù)處理。
在編寫爬蟲的時(shí)候,某些網(wǎng)站不只是對(duì)Cookie進(jìn)行加密,還會(huì)對(duì)提交的POST等參數(shù)進(jìn)行加密,并且一些網(wǎng)站的加密參數(shù)還只能使用一次或者三次,用過一定次數(shù)以后就會(huì)失效,就需要重新生成加密參數(shù),重新提交驗(yàn)證才可以,對(duì)于這種類型的網(wǎng)站國家藥監(jiān)局算是比較經(jīng)典的網(wǎng)站,要么進(jìn)行逆向,在逆向的時(shí)候個(gè)人感覺精力與耐心的考驗(yàn)還是挺大的,并且該網(wǎng)站還會(huì)不斷的變更加密方案,每隔一段時(shí)間還會(huì)升級(jí)加密方案。有時(shí)間和精力的可以嘗試逆向一下。這里采用的是AjaxHook的方式獲取數(shù)據(jù),也就是監(jiān)聽網(wǎng)站的Ajax請(qǐng)求,當(dāng)響應(yīng)到達(dá)瀏覽器的時(shí)候?qū)?shù)據(jù)直接發(fā)到瀏覽器外面。下面是演示代碼:
油猴腳本如下:
// ==UserScript== // @name 藥監(jiān)局AjaxHook // @namespace http://tmpermonkey.net/ // @version 0.1 // @description try to take over the world! // @author 挖掘機(jī)小王子 // @match http://app1.nmpa.gov.cn/datasearchcnda/face3/* // @grant none // @require https://unpkg.com/ajax-hook@2.0.3/dist/ajaxhook.min.js // ==/UserScript== ah.proxy({ //請(qǐng)求發(fā)起前進(jìn)入 onRequest: (config, handler) => { console.log(config.url) handler.next(config); }, //請(qǐng)求發(fā)生錯(cuò)誤時(shí)進(jìn)入,比如超時(shí);注意,不包括http狀態(tài)碼錯(cuò)誤,如404仍然會(huì)認(rèn)為請(qǐng)求成功 onError: (err, handler) => { console.log(err.type) handler.next(err) }, //請(qǐng)求成功后進(jìn)入 onResponse: (response, handler) => { console.log(response.response) handler.next(response) } })
代碼解釋:在 UserScript
里面會(huì)看到使用 @require
引用了一個(gè)網(wǎng)址,該網(wǎng)址里面是一段JS代碼,該代碼是 AjaxHook
的代碼,這部分代碼在Github開源,作用是可以監(jiān)聽網(wǎng)站通過 XMLHttpRequest
發(fā)起的所有網(wǎng)絡(luò)請(qǐng)求。在請(qǐng)求發(fā)起前,會(huì)先進(jìn)入 onRequest
鉤子,調(diào)用 handler.next(config)
請(qǐng)求繼續(xù),如果請(qǐng)求成功,則會(huì)進(jìn)入 onResponse
鉤子,如果請(qǐng)求發(fā)生錯(cuò)誤,則會(huì)進(jìn)入 onError
。到請(qǐng)求回到 onResponse
里面的時(shí)候,可以使用油猴自帶的 GM_xmlhttpRequest
函數(shù)將相應(yīng)發(fā)送出去,這樣就可以獲取數(shù)據(jù)了,當(dāng)然還需要瀏覽器能觸發(fā)請(qǐng)求等行為,這部分也可以個(gè)油猴來完成,例如下面的代碼就是通過油猴觸發(fā)網(wǎng)站的點(diǎn)擊等事件:
// ==UserScript== // @name 藥監(jiān)局列表頁數(shù)據(jù)采集 // @namespace http://tampermonkey.net/ // @version 0.3.7.4 // @description try to take over the world! // @author 挖掘機(jī)小王子 // @match http://app1.sfda.gov.cn/datasearchcnda/face3/* // @grant none // ==/UserScript== (function() { 'use strict'; // Your code here... // 觸發(fā)請(qǐng)求 function triggerRequest() { // 點(diǎn)擊 var interval = null var response = null interval = setInterval(function () { parseResponse() // 獲取響應(yīng)并且發(fā)出到Redis // 檢測是否需要翻頁 if (startPage <= endPage){ devPage(startPage++) // 翻頁 } else { clearInterval(interval) // 結(jié)束 } }, 500); } // 響應(yīng)解析 function parseResponse(){ var res = null res = document.querySelectorAll('tbody tr p a').forEach((item)=>{httpPost('http://127.0.0.1:8883/ajaxHook', item.href)}); } // 發(fā)出響應(yīng) function httpPost(url, data) { fetch(url, { method: 'POST', mode: "cors", body: data }); }; var scope = ''; scope = prompt('請(qǐng)輸入采集的頁數(shù)范圍,示例格式:100-200') var startPage = parseInt(scope.split('-')[0]) // 開始頁數(shù) var endPage = parseInt(scope.split('-')[1]) // 結(jié)束頁數(shù) // 執(zhí)行觸發(fā) triggerRequest() })();
油猴實(shí)戰(zhàn)WebSocket通信
瀏覽器除了可以向外部發(fā)送數(shù)據(jù),其實(shí)也可以從外部獲取數(shù)據(jù)。由于HTTP協(xié)議的特殊性質(zhì),不能建立長連接,如果需要從外部獲取數(shù)據(jù)就需要不斷的請(qǐng)求外部的接口返回?cái)?shù)據(jù)。這種方式比較消耗資源,需要不斷的輪詢。想要瀏覽器和外部進(jìn)行雙向通訊的話可以使用WebSocket協(xié)議,該協(xié)議支持與瀏覽器進(jìn)行雙向通訊,瀏覽器可以獲取外部數(shù)據(jù),也可以給外部發(fā)送數(shù)據(jù),而且實(shí)時(shí)性更高,更加穩(wěn)定。國家藥監(jiān)局采用的是瑞數(shù)加密,6SQk6G2z 參數(shù)是在發(fā)送POST請(qǐng)求的時(shí)候,通過內(nèi)部的Hook機(jī)制,添加進(jìn)去的。也就是每個(gè)POST/GET請(qǐng)求都會(huì)添加該參數(shù),并且該參數(shù)是只能使用一次,如果一旦發(fā)送給服務(wù)器就不能再次使用。既然加密制造商可以Hook瀏覽器的POST請(qǐng)求,那么我們也一樣可以通過油猴Hook該請(qǐng)求,并且不讓請(qǐng)求發(fā)送出去(不會(huì)讓請(qǐng)求失效),并且把請(qǐng)求的參數(shù) 6SQk6G2z 通過WebSocket協(xié)議發(fā)送給我們自己的服務(wù)器,或者是發(fā)給我們的數(shù)據(jù)庫進(jìn)行存儲(chǔ),大批量的存儲(chǔ)該數(shù)值,然后采用Requests/Gevent等模塊實(shí)現(xiàn)發(fā)送請(qǐng)求。
油猴代碼:
// ==UserScript== // @name 藥監(jiān)局Hook6SQk6G2z // @namespace http://tampermonkey.net/ // @version 0.1 // @description try to take over the world! // @author 挖掘機(jī)小王子 微信:EnjoyByte // @match http://app1.nmpa.gov.cn/data_nmpa/face3/* // @run-at document-start // ==/UserScript== (function() { 'use strict'; // WebSockets var ws = new WebSocket("ws://127.0.0.1:8765"); // 客戶端連接服務(wù)成功時(shí)觸發(fā) ws.onopen = function() { console.log('客戶端已連接!'); ws.send("客戶端已連接!") } // 服務(wù)端發(fā)送消息過來觸發(fā) ws.onmessage = function(evt){ console.log('服務(wù)端信息:', evt.data) } // 客戶端關(guān)閉觸發(fā) ws.onclose = function(){ console.log('客戶端關(guān)閉!') clearInterval(intervalid) } // 客戶端出錯(cuò)觸發(fā) ws.onerror = function(evt){ console.log("觸發(fā)失敗:", evt) } // Hook Url var open = window.XMLHttpRequest.prototype.open; window.XMLHttpRequest.prototype.open = function open(method, url) { // Hook URL console.log(url) ws.send(JSON.stringify({"url": url})) }; // 不斷觸發(fā)下一頁事件的請(qǐng)求 var intervalid = setInterval('devPage(2);', 500) })();
在上面的代碼中,我Hook了 window.XMLHttpRequest.prototype.open 函數(shù),并且在新的函數(shù)里面不實(shí)現(xiàn)和請(qǐng)求相關(guān)的代碼,那么open函數(shù)就會(huì)失去發(fā)送請(qǐng)求的效果,而且該代碼是在 @run-at document-start 文檔開始的時(shí)候就盡快注入了。瑞數(shù)的Hook機(jī)制再在我的代碼基礎(chǔ)上進(jìn)行了Hook,當(dāng)隨便點(diǎn)擊頁面的下一頁的時(shí)候就會(huì)看到打印出了發(fā)送請(qǐng)求的網(wǎng)址,而 6SQk6G2z 參數(shù)就在請(qǐng)求的網(wǎng)址中。把該參數(shù)發(fā)送到程序外部用數(shù)據(jù)庫保存起來即可!外部的WebSocket代碼如下:
import asyncio import websockets from redis import StrictRedis class WsServer: def __init__(self, redis_host='127.0.0.1', redis_port=6379): self.redis_cli = StrictRedis( host=redis_host, port=redis_port, decode_responses=True ) # WebSocket 服務(wù) async def server(self, websocket, path): while True: url_cookie_form_data = await websocket.recv() # 不斷獲取瀏覽器數(shù)據(jù) print(url_cookie_form_data) if 'http:' in url_cookie_form_data: self.redis_cli.lpush("nmpa:urls", url_cookie_form_data) if __name__ == '__main__': wsserver = WsServer() start_server = websockets.serve(wsserver.server, "127.0.0.1", 8765) asyncio.get_event_loop().run_until_complete(start_server) asyncio.get_event_loop().run_forever()
上面的腳本是對(duì)瀏覽器發(fā)送來的數(shù)據(jù)的轉(zhuǎn)發(fā),將該數(shù)據(jù)轉(zhuǎn)發(fā)到Redis數(shù)據(jù)庫,下圖展示了轉(zhuǎn)發(fā)的數(shù)據(jù):
其實(shí)藥監(jiān)局除了需要 6SQk6G2z 還需要Cookie參數(shù),不過目前可以固定該參數(shù)也是可以的,另外對(duì)于詳情頁的數(shù)據(jù)還是需要瀏覽器進(jìn)行加密的,所以還是可以將需要加密的數(shù)據(jù)返回給瀏覽器讓他幫我們加密,然后返回給我們,我們?cè)谟贸绦虬l(fā)送請(qǐng)求,這樣速度可以快不少,我的測試是 Requests單線程 每小時(shí)10000條數(shù)據(jù)(大概是這樣)。后來采用協(xié)程編寫代碼效率極大提升。當(dāng)然如果擔(dān)心 6SQk6G2z 數(shù)據(jù)消耗的過快,產(chǎn)生的比較少,那么可以多開幾個(gè)標(biāo)簽頁就好了,這種就類似多線程了。如果覺得自己電腦必須一直開著,那么可以把腳本分發(fā)給任何有電腦的人,只需要打開瀏覽器就可以利用他們電腦的閑置資源,相當(dāng)于分布式的獲取6SQk6G2z值。
以上就是油猴腳本開發(fā)詳解+油猴爬蟲腳本實(shí)例的詳細(xì)內(nèi)容,更多關(guān)于油猴腳本開發(fā)詳解+油猴爬蟲腳本實(shí)例的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
合成大西瓜開發(fā)源碼手把手教你運(yùn)行和部署大西瓜游戲項(xiàng)目(附源碼)
這篇文章主要介紹了合成大西瓜開發(fā)源碼手把手教你運(yùn)行和部署大西瓜游戲項(xiàng)目(附源碼),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02最新IntelliJ IDEA 2020.2永久激活碼(親測有效)
今天一大波朋友反饋idea2020激活碼失效的問題,小編快馬加鞭給大家找到解決方案,本文以IDEA 2020.2.4激活碼破解教程為例給大家詳細(xì)介紹,需要idea2020激活碼的朋友快來參考下本文吧2020-11-112022編程語言需求排名出爐:第一不是Python,也不是Java
編程語言的流行程度、發(fā)展前景、就業(yè)市場這些一直都是程序員們非常關(guān)注的話題,需求排名是程序員們關(guān)注學(xué)習(xí)的風(fēng)向標(biāo),畢竟是市場經(jīng)濟(jì),學(xué)以致用,如果熱門編程不了解,都不好意思告訴別人你是程序員。編程語言的種類有超過200+,但還有很多不為人知。2022-12-12bs架構(gòu)和cs架構(gòu)的區(qū)別_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了bs架構(gòu)和cs架構(gòu)的區(qū)別,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-07-07K8ssandra入門教程之Linux上部署K8ssandra到Kubernetes的過程
K8ssandra不僅幫助我們可以快速可靠地在Kubernetes上部署Cassandra,同時(shí)提供了許多組件,如監(jiān)控、備份、同步、訪問等,這篇文章給大家介紹K8ssandra入門教程之Linux上部署K8ssandra到Kubernetes的過程,一起看看吧2021-10-10鴻蒙開發(fā)搭建flutter適配的開發(fā)環(huán)境
文章詳細(xì)介紹了在Windows系統(tǒng)上如何創(chuàng)建和運(yùn)行鴻蒙Flutter項(xiàng)目,包括使用flutter?doctor檢測環(huán)境、創(chuàng)建項(xiàng)目、編譯HAP包以及在真機(jī)上運(yùn)行項(xiàng)目,打包鴻蒙Flutter應(yīng)用的測試包和正式包的方法,并介紹了常見問題的解決方法2024-12-12IntelliJ IDEA 2020最新注冊(cè)碼(親測有效,可激活至 2089 年
這篇文章主要介紹了IntelliJ IDEA 2020最新注冊(cè)碼,親測有效,可激活至 2089 年,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05