發現時間線上經常推一些以前的老問題的回答,於是乎就打算取消關注以前的問題,結果一看有幾百個問題,找了找也沒看見知乎的批次取消關注,沒辦法只能自己寫指令碼了。

用指令碼搞主要有兩條思路

1。使用node模擬瀏覽器請求

2。在瀏覽器上模擬使用者操作

思路一的主要問題是取消關注是post請求,必然要用csrf_token防止csrf攻擊,知乎也的確這麼幹了,不過卻發現知乎的每個頁面的csrf_token都一樣,多次請求的的頁面的csrf_token也一樣,似乎存在一定的安全隱患。有了token後可以模擬請求,但伺服器仍然會做其他防禦,暫時先放棄。

js系列:知乎批次取消關注功能

js系列:知乎批次取消關注功能

思路二的話就是模擬點選請求,結果也發現了一些問題

由於下面頁面下的問題沒有取消關注按鈕,必須點選連結後進入才會有取消關注按鈕。

js系列:知乎批次取消關注功能

js系列:知乎批次取消關注功能

js系列:知乎批次取消關注功能

js系列:知乎批次取消關注功能

所以希望用js模擬這個過程,即

1。 開啟連結

2。 在開啟的窗口裡點選取消關注按鈕

開啟連結我們可以使用

var url = ‘https://www。zhihu。com/question/28062458’

w = window。open(url)

接下來就可以透過w操作開啟的視窗了,我們把新開啟的視窗稱為B視窗,當前視窗為A視窗

比如訪問其中的document,然而很不幸失敗了

js系列:知乎批次取消關注功能

js系列:知乎批次取消關注功能

好吧,出於安全我的訪問被阻攔,那麼我把安全限制先關了好吧

mac下透過下面命令可以關閉安全限制

/Applications/Google\ Chrome。app/Contents/MacOS/Google\ Chrome ——disable-web-security ——user-data-dir=“~/”

哈哈哈,這下訪問document沒問題了吧

js系列:知乎批次取消關注功能

js系列:知乎批次取消關注功能

果然沒問題。

接下來問題就簡單了,只需要模擬點選取消關注按鈕就可以。檢視取消關注按鈕的屬性可知,使用$(‘。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頁面自動新增上述程式碼,也即無法進行自動化。

結果,喜出望外的發現知乎的問題列表頁面竟然有兩個。

對應問題列表的頁面

js系列:知乎批次取消關注功能

js系列:知乎批次取消關注功能

對應問題列表

js系列:知乎批次取消關注功能

js系列:知乎批次取消關注功能

難道搞abtest?很不幸發現了知乎的一個bug

左側顯示的關注問題只有兩個,而右側顯示的關注問題卻為5。應該是個bug@知乎小管家

我們驚喜的發現上面的問題列表中,每個問題下都有個取消關注的按鈕,這下就不會涉及跨文件通訊帶來的各種問題了。

為我們很容易編寫如下程式碼

$(‘。zg-unfollow’)。click()

很不幸,我們又遇到了兩個問題

我們神奇的發現,每個節點的click,都會觸發http請求,結果只有第一個請求正常工作,後續http請求全部cancel了

所有關注問題可能一個頁面顯示不下,而$(‘。zg-unfollow’)僅能夠查詢當前的所有符合條件的dom節點,因此需要手動的重新整理頁面

第一個問題結果如下

js系列:知乎批次取消關注功能

js系列:知乎批次取消關注功能

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裡提到的$$小技巧,結果碰到個小坑

在除錯視窗下可以正常使用$$,但是當在回撥中使用$$時卻提示找不到$$

js系列:知乎批次取消關注功能

js系列:知乎批次取消關注功能

這種行為,略顯詭異,可能是除錯視窗和回撥執行的環境是有所區別的。

解決方法很簡單,把$$放到全域性變數裡

$$ = $$

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的原因,後續進行分析。