前言
最近,发现快递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被封,建议还是购买接口调用及联系客服解决,这样才是长久之计。