原文鏈接:https://www.cnblogs.com/SnowPhoenix/p/15674155.html
問題描述
從瀏覽器查看請求信息的時候,我們會發現有些請求有這樣的特征:
- 包含一個類似於
?callback=jQuery17209708769256472376_1639142208410&jsonp=jsonp&_=1639142206313的query; - 返回的JSON數據
jQuery17209708769256472376_1639142208410({"code":0,"message":"","data":{"uid":123}}),返回的字符串就是請求時url的callback調用了本身應該返回的數據; - 簡單將這個url復制到Postman請求會失敗;
簡單搜索一下,可以知道這個是jQuery在進行跨域請求的時候利用jsonp進行處理造成的現象。注意幾點:
- 需要設置
Referer頭,因為這是跨域請求,這也是為什么直接復制到Postman會失效的原因,Postman中加了Referer頭之后也能成功; _=1639142206313其實就是時間戳,沒什么好說的;- 想要獲得原始的JSON數據,在獲得返回數據后,直接前后的JSONP的padding刪去即可;
但是這里有個問題,我們怎么模擬callback的jQueryxxxxx的生成呢?
參考
- Github | jquery/jquery;
- 從上面倉庫的Release中下載的源碼;
替代解決方案
既然我們知道這一套操作是跨域時的問題,那么我們的爬蟲直接不進行跨域請求即可。即
- 刪去
callback、jsonp、_(寫時間戳的那個)這三個query; - 刪去
Referer頭,或者將Referer頭設置為當前請求的url的域名;
當然,這樣的解決方案不是很優雅,雖然我js水平很爛,也沒學過jQuery,但我還是決定看看jQuery來看看這個這個jQueryxxxxx是如何生成的。
jQuery源碼
用jsonp和callback來進行搜索,在jquery.js文件里可以看到這樣的代碼:
// Default jsonp settings
jQuery.ajaxSetup( {
jsonp: "callback",
jsonpCallback: function() {
var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce.guid++ ) );
this[ callback ] = true;
return callback;
}
} );
前面oldCallbacks.pop()應該是一個callback池,如果有空余的就不用產生新的了,不過我們需要關注的是新的是如何產生的:( jQuery.expando + "_" + ( nonce.guid++ ) )
繼續找expando,在jquery.js文件里能夠找到這樣的代碼:
jQuery.extend( {
// Unique for each copy of jQuery on the page
expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
// Assume jQuery is ready without the ready module
isReady: true,
error: function( msg ) {
throw new Error( msg );
},
這就能夠知道了,再看我們示例的jQuery17209708769256472376_1639142208410,前面172,就應該是版本號1.7.2將非數字刪去的結果,Math.random()產生0-1間的浮點數,然后將小數點刪去,得到了09708769256472376,是十分符合的。
但是我通過多次在瀏覽器刷新,發現示例中的jQuery17209708769256472376_1639142208410結尾1639142208410顯然是個時間戳,而不像是guid++產生的結果。我目前看的源碼是3.x版本的源碼,回去看看1.x的源碼,在jquery1.9.1.js文件中可以看到相對應的代碼:
// Default jsonp settings
jQuery.ajaxSetup( {
jsonp: "callback",
jsonpCallback: function() {
var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( ajax_nonce++ ) );
this[ callback ] = true;
return callback;
}
} );
這里用的是ajax_nonce++,而往上找,就能找到它的定義:
ajax_nonce = jQuery.now(),
即為當前的時間戳。
python代碼
import time
import random
class Constant:
jQuery_Version = "1.7.2"
def get_current_timestamp() -> int:
return int(round(time.time() * 1000))
def jquery_mock_callback() -> str:
"jQuery" + (Constant.jQuery_Version + str(random.random())).replace(".", "") + "_" + str(get_current_timestamp() - 1000)
