今天我们研究的是携程酒店的反爬虫策略
大概1年多前看过携程的一个产品经理叫什么崔广宇?写的一篇爬虫与反反爬的文章,当时觉得这个人好狂,当时对于携程的这个eleven我确实没办法,今儿就讲讲怎么去撸这个eleven
什么?什么是request? page,request,session,application自己去了解下
好了,入正题。搜索一下如何肝携程反反爬虫的帖子有好几个,大家思路都很接近。直接去接收这个token,而不是思量如何去复现,主要是携程的js写的很坑爹
随便打开一个携程的酒店,长酱色的
那咱们去看看他的留言
正常翻页,咱们看看他的请求,红框内就是比较关键的两个请求。首先咱们看这个有数据的api
可以看到参数里有一个eleven 还有个callback
可以看到这个api是从这个oceanball开始的。那,我们去研究下这个oceanball。
我们来观察这个url:
https://hotels.ctrip.com/domestic/cas/oceanball?callback=CASVeFZTkGooErTIOY&_=1548040653519
发现带着两个参数, 一个是callback 一个是 _ (时间戳)。请求这个url,我们看到的是酱色的
这他娘的是啥。那我们就研究下。
这里有个小技巧,遇到 eval(), 那就替换成renturn 在代码里
这里我们在console里将它输出,演示的时候eval换成console.log()
然后咱们拿到另一个js,格式化,放到 SubLime里
这里我就想劝诫大家一下,我引申一句名言:我不想知道我怎么来的,我就想知道我怎么没的
那时候,我也一味的陷进去,想知道这个加密怎么弄的
no no no
咱们生产上,需要的是如何快速拿到数据,去研究人家怎么实现的就太耗费时间。同时也达到对方的目的:反爬虫的意义就在于增加拿到数据的成本。
我反正几乎没加过班 --!咱们只需要做的事情就是,接受这个算出来的结果就行了。所以,那段恶心的js直接看最下面。
这就很了然了啊,咱们只需要去接受CASVeFZTkGooErTIOY这个函数啊。等会儿这个CASVeFZTkGooErTIOY怎么这么眼熟,其实就是刚刚url里的callback呀。
那咱们改一下,看看是不是真的
然后在console里执行一下
好的,这个eleven就如约而至了
那行,咱们下面要做的事就是去拿这个eleven
改写这个js咯,因为纯粹的js环境没有DOM,我们要自己加
思路就是缺啥补啥
加入红框里的,然后执行,拿到eleven,
大家注意到没,咱们算出来的token和chrome的console里的结果不一致
这就是携程阴险的地方
不得不说,携程的技术储备还是很牛逼的
来,咱们接着撸
咱们来找到这段js的try ...catcha 看看能不能找到点什么
好了,看到这个
在实例DOM里的Image, 如果没有,某某参数悄悄自增一下
我我屮艸芔茻!!!!!
那么定义一个 Image
好了,然后对比新算出来的eleven同之前console里的比较
这回一样了
好了,咱们搞定了。
另外,这个js还有几种版本,遇到不要慌,大概按照我思路,DOM里缺什么在代码里加入什么就行了
然后从预处理,到修改js,到输出的代码在末尾
总结:
到这里我介绍了一共三种类型的反反爬虫
总体来说,讲的比较浅,生产中能用就行
按照惯例,上代码
# coding=utf-8
"""
autohr: 不吃夹生饭
date: 2019-01-21
解决携程加密的 callback和 eleven
callback是通过一段js随机生产,长度 15位
eleven 目前携程是有好几套加密算法生产
因此在解析函数里会出现whle 是因为并不能每种加密都解密
测试的时候用的酒店的评论
目前是ok的
"""
import re
import time
import requests
import execjs
headers_ocean = {
'host': 'hotels.ctrip.com',
'accept': '*/*',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
'referer': 'https://hotels.ctrip.com/hotel/438080.html',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'
}
headers_cmt = {
'host': 'hotels.ctrip.com',
'accept': '*/*',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
'cache-control': 'max-age=0',
'content-type': 'application/x-www-form-urlencoded; charset=utf-8',
'if-modified-since': 'Thu, 01 Jan 1970 00:00:00 GMT',
'referer': 'https://hotels.ctrip.com/hotel/438080.html',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'
}
def get_callback():
"""拿到callback,算出来的一个随机15位字符串"""
callback = """
var callback = function() {
for (var t = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"], o = "CAS", n = 0; n < 15; n++) {
var i = Math.ceil(51 * Math.random());
o += t[i]
}
return o
};
"""
js = execjs.compile(callback)
return js.call('callback')
def deal_ocean(js_txt, hotel_id, call_back):
"""处理js_txt"""
# 第一步先还原
js_txt = re.sub('eval', 'return', js_txt)
js_1 = execjs.compile(js_txt)
ocean_txt = js_1.call('f')
# 第二部开始处理这段js,要在execjs里执行,需要添加一些头
# 先补充变量,需要知道hotel_id
variable = """
var hotel_id = "%s";
var site = {};
site.getUserAgent = function(){};
var Image = function(){};
var window = {};
window.document = {body:{innerHTML:"1"}, documentElement:{attributes:{webdriver:"1"}}, createElement:function(x){return {innerHTML:"1"}}};
var document = window.document;
window.navigator = {"appCodeName":"Mozilla", "appName":"Netscape", "language":"zh-CN", "platform":"Win"};
window.navigator.userAgent = site.getUserAgent();
var navigator = window.navigator;
window.location = {};
window.location.href = "http://hotels.ctrip.com/hotel/%s.html";
var location = window.location;
var navigator = {userAgent:{indexOf: function(x){return "1"}}, geolocation:"1"};
"""% (hotel_id, hotel_id)
# 第三步开始改造js
js_final = ''
js_final += variable
ocean_txt = re.sub(';!function', 'function get_eleven', ocean_txt)
js_final += ocean_txt[:-3]
rep_cnt = re.findall('{0}.*?";\'\)\)'.format(call_back), js_final, re.S)[0]
eleven = rep_cnt.split('+')[1]
# 然后替换
js_final = js_final.replace(rep_cnt, 'return{0}'.format(eleven))
# 找到嘲讽那段,然后替换
sneer = re.findall(' \[32769,26495,32473.*,49,51,107,21734]\*/', js_final, re.S)[0]
js_final = js_final.replace(sneer, '=1);')
# 第四部,执行
js_content = execjs.compile(js_final)
return js_content.call('get_eleven')
def main_logic(hotel_id='438080'):
# 1. 拿到callback和时间戳请求ocean.js
while True:
try:
print('重试.....')
call_back = get_callback()
t = int(time.time()*1000)
ocean_js = request_oceanball(call_back, t)
# 接下来处理ocean_js
eleven = deal_ocean(ocean_js, hotel_id, call_back)
break
except Exception as e:
print(e)
# 拿到eleven时候再去请求评论
get_comment(eleven, hotel_id)
def request_oceanball(cb, t):
url_ocean = 'https://hotels.ctrip.com/domestic/cas/oceanball?callback={0}&_={1}'.format(cb, t)
html = requests.get(url_ocean, headers=headers_ocean)
return html.content.decode('utf-8')
def get_comment(eleven, hotel_id):
url = 'https://hotels.ctrip.com/Domestic/tool/AjaxHotelCommentList.aspx'
params = {
'MasterHotelID': hotel_id,
'hotel': hotel_id,
'NewOpenCount': '0',
'AutoExpiredCount': '0',
'RecordCount': '6708',
'OpenDate': '2012-01-01',
'card': '-1',
'property': '-1',
'userType': '-1',
'productcode': '',
'keyword': '',
'roomName': '',
'orderBy': '2',
'currentPage': '3',
'viewVersion': 'c',
'contyped': '0',
'eleven': eleven,
'callback': get_callback(),
'_': int(time.time()*1000)}
html = requests.get(url, headers=headers_cmt, params=params)
print(html.status_code)
print(html.content.decode('utf-8'))
if __name__ == '__main__':
main_logic()