js系列:知乎批次取消關注功能
發現時間線上經常推一些以前的老問題的回答,於是乎就打算取消關注以前的問題,結果一看有幾百個問題,找了找也沒看見知乎的批次取消關注,沒辦法只能自己寫指令碼了。
用指令碼搞主要有兩條思路
1。使用node模擬瀏覽器請求
2。在瀏覽器上模擬使用者操作
思路一的主要問題是取消關注是post請求,必然要用csrf_token防止csrf攻擊,知乎也的確這麼幹了,不過卻發現知乎的每個頁面的csrf_token都一樣,多次請求的的頁面的csrf_token也一樣,似乎存在一定的安全隱患。有了token後可以模擬請求,但伺服器仍然會做其他防禦,暫時先放棄。
思路二的話就是模擬點選請求,結果也發現了一些問題
由於下面頁面下的問題沒有取消關注按鈕,必須點選連結後進入才會有取消關注按鈕。
所以希望用js模擬這個過程,即
1。 開啟連結
2。 在開啟的窗口裡點選取消關注按鈕
開啟連結我們可以使用
var url = ‘https://www。zhihu。com/question/28062458’
w = window。open(url)
接下來就可以透過w操作開啟的視窗了,我們把新開啟的視窗稱為B視窗,當前視窗為A視窗
比如訪問其中的document,然而很不幸失敗了
好吧,出於安全我的訪問被阻攔,那麼我把安全限制先關了好吧
mac下透過下面命令可以關閉安全限制
/Applications/Google\ Chrome。app/Contents/MacOS/Google\ Chrome ——disable-web-security ——user-data-dir=“~/”
哈哈哈,這下訪問document沒問題了吧
果然沒問題。
接下來問題就簡單了,只需要模擬點選取消關注按鈕就可以。檢視取消關注按鈕的屬性可知,使用$(‘。zg-unfollow’)即可選中取消按鈕。
w。document。querySelector(‘。zg-unfollow’)。click()
很不幸事情並非如我們所願。取消關注按鈕沒有任何變化。
難道是不同頁面之間不能觸發事件嗎?在取消關注按鈕所在的頁面執行下面命令,結果如我們所願
document。querySelector(‘。zg-unfollow’)。click()
問題很明顯出現在了不同頁面之間不能直接觸發事件上了。即使我們在上面關閉了安全限制,似乎仍然無法觸發彼此的事件。
事實上不僅如此,不同頁面之間似乎也無法修改dom屬性。
w。document。querySelector(‘。zg-unfollow’)。setAttribute(‘bar’,‘bar’)
B頁面的dom屬性沒有發生變化
w。document。querySelector(‘。zg-unfollow’)。getAttribute(‘bar’) // 結果為null
這裡有點奇怪,跨頁面之間無法觸發事件,修改屬性,但卻支援document。write。為什麼呢?
既然兩個視窗之間不能觸發事件和修改dom,那麼我們似乎無計可施了。
HTML5引入了一個全新的API:跨文件通訊 API(Cross-document messaging),支援不同文件間進行通訊。這似乎可以解決我們的需求。
在B頁面新增如下程式碼
window。addEventListener(‘message’, function(e) {
document。querySelector(‘。zg-unfollow’)。click()
},false);
然後在A頁面我們觸發事件
w。postMessage(‘click’);
結果如我們所願。
然而,問題在於知乎的頁面不受我們控制,我們沒法為每個B頁面自動新增上述程式碼,也即無法進行自動化。
結果,喜出望外的發現知乎的問題列表頁面竟然有兩個。
對應問題列表的頁面
對應問題列表
難道搞abtest?很不幸發現了知乎的一個bug
左側顯示的關注問題只有兩個,而右側顯示的關注問題卻為5。應該是個bug@知乎小管家
我們驚喜的發現上面的問題列表中,每個問題下都有個取消關注的按鈕,這下就不會涉及跨文件通訊帶來的各種問題了。
為我們很容易編寫如下程式碼
$(‘。zg-unfollow’)。click()
很不幸,我們又遇到了兩個問題
我們神奇的發現,每個節點的click,都會觸發http請求,結果只有第一個請求正常工作,後續http請求全部cancel了
所有關注問題可能一個頁面顯示不下,而$(‘。zg-unfollow’)僅能夠查詢當前的所有符合條件的dom節點,因此需要手動的重新整理頁面
第一個問題結果如下
9個請求都failed了,只有最下面的1個請求還正常。
更詭異的是我的關注問題裡由10個變為8個,似乎有兩個請求正常完成。(不會又是個bug吧@知乎小管家,不知道和之前的bug是不是存在關聯。)
那麼為什麼這麼多請求會failed了。
剛開始的猜想是難道是瀏覽器限制了同一時間發的http請求導致的嗎?
後來測試發現瀏覽器的限制只會使得後續請求阻塞,並不會導致其直接cancel掉。
那麼只有可能是程式碼裡對http請求進行了限制了。
然後嘗試對發請求的速度進行限制
function request(){
if($(‘。zg-unfollow’)。length == 0){
return;
}
$(‘。zg-unfollow’)[0]。click()
setTimeout(request,2000)
}
雖然提示有的請求cancel,但是仍然取消關注了所有問題,實現了我們的需求。
小插曲:在進行測試過程中,試了下@賀師俊 live裡提到的$$小技巧,結果碰到個小坑
在除錯視窗下可以正常使用$$,但是當在回撥中使用$$時卻提示找不到$$
這種行為,略顯詭異,可能是除錯視窗和回撥執行的環境是有所區別的。
解決方法很簡單,把$$放到全域性變數裡
$$ = $$
console。log($$)
setTimetout(()=>console。log($$)
對於問題2,我們需要判斷當$(‘。zg-unfollow’)。length==0時,進行滾屏以載入新的問題。
function unfollow_request(){
if($(‘。zg-unfollow’)。length == 0){
$(window)。scrollTop(1e10)
setTimeout(arguments。callee,2000)
return;
}
$(‘。zg-unfollow’)[0]。click()
setTimeout(arguments。callee,2000)
}
為了有足夠的資料進行測試,需要批次關注大量問題,同理稍作修改,可以得到批次關注程式碼。
function follow_request(){
if($(‘。follow’)。length == 0){
$(window)。scrollTop(1e10)
setTimeout(arguments。callee,2000)
return;
}
$(‘。follow’)[0]。click()
setTimeout(arguments。callee,2000)
}
至於請求被cancel的原因,後續進行分析。