前言
最近,發現快遞100快遞明細查詢接口異常(https://www.kuaidi100.com/query?type=jd&postid=快遞單號&temp=0.47825892409661974&phone=),以前該接口不管哪個平臺直接查詢可以獲得正常結果,現在查詢出現如下:
經研究發現快遞100官方做查詢限制,如果你要正常查詢快遞,必須去官網註冊申請key(免費的查詢次數有限制),要麼使用手機網頁版查詢。這兩種方式感覺都比較麻煩,能不能簡單點,使原接口恢復正常查詢。一番研究,發現PC端的快遞100網站,可以通過快遞單號查詢快遞明細信息。至於移動端(特指Android端,也適用IOS端)爲什麼查詢受限,下面一步步起分析。
原因分析
由於PC端和移動端請求url和請求參數是一樣的,容易發現唯一不同就是Http請求頭不同。這裏簡單說下Http請求協議主要包括三部分:請求行、頭部、消息主體。故本次從請求頭尋找突破點(以pc端的方式模擬移動端查詢)。
1、由於PC端快遞100首頁可以查詢快遞,分析了Http請求的請求頭多了Referer(使用Google瀏覽器的DevTool分析)。
Referer:主要是用於防盜鏈,簡單說指示一個請求是從哪裏鏈接過來,後端會攔截判斷鏈接來源,不一致就會限制訪問。當Referer爲空,可以通過瀏覽器直接訪問。
故在Retrofit中Header必須指定Referer(https://www.kuaidi100.com),發現設置Referer之後可以查詢,但不是該單號對應的結果,結果是隨機返回。
2、進一步分析請求頭,發現User-Agent可能與移動端不同,移動端是沒有(但是WebView瀏覽器有)。
User Agent中文名爲用戶代理,簡稱 UA,它是一個特殊字符串頭,使得服務器能夠識別客戶使用的操作系統及版本、CPU 類型、瀏覽器及版本、瀏覽器渲染引擎、瀏覽器語言、瀏覽器插件等。
故需要在Retrofit指定User-Agent,模擬PC端的User-Agent在移動端查詢,PC端的User-Agent字符串中都有Windows這個標誌。
這次我複製了PC端瀏覽器的User-Agent:
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134",
發現設置Referer之後可以查詢,但是,不是該單號對應的結果,結果還是隨機返回。
下面貼出常用的PC端User-Agent:
private static final String[] userAgents = new String[]{
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1",
"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",
"Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.9.168 Version/11.50",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 2.0.50727; SLCC2; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; Tablet PC 2.0; .NET4.0E)",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; ) AppleWebKit/534.12 (KHTML, like Gecko) Maxthon/3.0 Safari/534.12",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.33 Safari/534.3 SE 2.X MetaSr 1.0",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; .NET4.0E)",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1 QQBrowser/6.9.11079.201",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)",
"Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
"Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
"Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
"Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
"Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5",
"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
"Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52"
};
順便貼出獲取獲取Android WebView的User-Agent的方法:
String userAgent = new WebView(this).getSettings().getUserAgentString();
3、繼續進一步分析請求頭,發現PC需要Cookie,移動端是沒有設置cookie。
HTTP Cookie(也叫Web Cookie或瀏覽器Cookie)是服務器發送到用戶瀏覽器並保存在本地的一小塊數據,它會在瀏覽器下次向同一服務器再發起請求時被攜帶併發送到服務器上。通常,它用於告知服務端兩個請求是否來自同一瀏覽器,如保持用戶的登錄狀態。Cookie使基於無狀態的HTTP協議記錄穩定的狀態信息成爲了可能。
Cookie主要用於以下三個方面:
- 會話狀態管理(如用戶登錄狀態、購物車、遊戲分數或其它需要記錄的信息)
- 個性化設置(如用戶自定義設置、主題等)
- 瀏覽器行爲跟蹤(如跟蹤分析用戶行爲等)
這次我複製了PC端瀏覽器的Cookie:
csrftoken=zCVuHlUOhenpYgUulra6voAdLUeNKym_FflcMplrlSg;
Hm_lvt_22ea01af58ba2be0fec7c11b25e88e6c=1561095430,1561096160,1561098197,1561099940;
Hm_lpvt_22ea01af58ba2be0fec7c11b25e88e6c=1561100145
最後,再次測試,發現可以正確查詢運單號對應數據了,但是幾天之後查詢不到數據了,很明顯cookie過期 ,所以cookie要動態獲取。本次查詢測試截圖:
查詢改造
cookie可以提供調試快遞100官網首頁獲取,發現cookie字符串中csrftoken的值是固定不變,而Hm_lvt_22ea01af58ba2be0fec7c11b25e88e6c和Hm_lpvt_22ea01af58ba2be0fec7c11b25e88e6c的值是變化。這羣數字到底是什麼意思?,
研究發現是10位時間戳,用System.currentTimeMillis()/1000就可以獲取到。盡然cookie僞裝了,順便user-agent也隨機吧,這樣查詢更接近PC端查詢。通過設置Referer、User-Agent、Cookie之後,接口可以穩定查詢一段時間了(如果你的ip沒有被封)。
下面貼出完整代碼:
接口:
/**
* 獲取cookie
* Observable<T> 裏面的泛型T 不能是 okhttp3.Response 。
可以是 retrofit2.Response<T> ,但是T不能爲okhttp3.Response,是okhttp3.ResponseBody
SET-Cookie是來自Server的header,意即在Client設置某個Cookie
Cookie是瀏覽器返回到Server所用的header
* @return
*/
@Headers({"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Safari/537.36"})
@GET("https://www.kuaidi100.com")
Observable<Response<ResponseBody>> getCookie();
/**
*
*查詢快遞明細
* @return
*/
@Headers({
"Referer: https://www.kuaidi100.com/"})
@GET("query")
Observable<ExpressDetail> queryExpressInfoSchedule(@HeaderMap Map<String, String> headers, @Query("type") String type,
@Query("postid") String postid, @Query("temp") double temp, @Query("phone") String phone);
查詢(先獲取cookie,組成請求頭(Referer、User-Agent、Cookie),再查詢):
apiService.getCookie().concatMap(new Function<Response<ResponseBody>, ObservableSource<ExpressDetail>>() {
@Override
public ObservableSource<ExpressDetail> apply(retrofit2.Response<ResponseBody> responseBodyResponse) throws Exception {
double random = Math.random();
Headers headers = responseBodyResponse.headers();
List<String> cookies = headers.values("Set-Cookie");
LogUtils.w("Set-Cookie:" + cookies.toString());
String cookie1 = disguiseCookie("Hm_lvt_22ea01af58ba2be0fec7c11b25e88e6c", 4, 5 * 1000);
LogUtils.w(cookie1);
String cookie2 = disguiseCookie("Hm_lpvt_22ea01af58ba2be0fec7c11b25e88e6c", 1, 0);
LogUtils.w(cookie2);
String cookie = cookies.toString() + ";" + cookie1 + ";" + cookie2;
Map<String, String> headersMap = new HashMap<>();
headersMap.put("Cookie", cookie);
headersMap.put("User-Agent", getRandomUserAgent());
return apiService.queryExpressInfoSchedule(headersMap, searchInfo.getCode(), searchInfo.getPost_id(), random, "");
}
}).compose(RxSchedulers.<ExpressDetail>applySchedulers())
.as(this.<ExpressDetail>bindLifecycle())
.subscribe(new Consumer<ExpressDetail>() {
@Override
public void accept(ExpressDetail expressDetail) throws Exception {
LogUtils.i("快遞信息:" + expressDetail.toString());
mView.showExpressDetail(expressDetail);
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
LogUtils.e("搜索快遞明細異常", throwable);
mView.showErrorInfo();
}
});
其它相關代碼 :
/**
* 獲取隨機的UserAgent,僞裝UserAgent
*
* @return
*/
private String getRandomUserAgent() {
int index = (int) (Math.random() * userAgents.length);
return userAgents[index];
}
/**
* 僞裝cookie
*
* @param name
* @param num
* @param step
* @return
*/
@NonNull
private String disguiseCookie(String name, int num, int step) {
StringBuilder sb = new StringBuilder();
sb.append(name + "=");
long timestamp = System.currentTimeMillis();
for (int i = num; i > 0; i--) {
timestamp -= step;
sb.append(TimeUtils.dateToTimestamp(timestamp) + ",");
}
return sb.deleteCharAt(sb.length() - 1).toString();
}
發現問題
從發現問題到解決問題,由於自己調試次數比較多,發現自己的ip已經被封(數據接口狀態返回604),不過隔一段時間又能正常使用。同一個IP調用接口太頻繁,服務器就判斷你是爬蟲,而不是正常訪問行爲。這個不好處理,目前發現用億牛雲代理ip,可以有效解決。
爬蟲網站數據IP被封解決方法 :
https://jingyan.baidu.com/article/6079ad0ed8064328ff86db96.html
總結
這次快遞100接口查詢分析,主要是在移動端模擬pc端查詢來解決受限問題。如果出現ip被封,建議還是購買接口調用及聯繫客服解決,這樣纔是長久之計。