反反爬虫系列(三)


今天我们研究的是携程酒店的反爬虫策略

大概1年多前看过携程的一个产品经理叫什么崔广宇?写的一篇爬虫与反反爬的文章,当时觉得这个人好狂,当时对于携程的这个eleven我确实没办法,今儿就讲讲怎么去撸这个eleven

  • 目标网站: 携程酒店
  • 反爬策略:针对每个request绑定一个token,就是他的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,到输出的代码在末尾

总结:

到这里我介绍了一共三种类型的反反爬虫

  1. 同程这种一个全局token
  2. 车300这种token加载到cookie里刷新
  3. 携程这种给api加载token验证

总体来说,讲的比较浅,生产中能用就行

按照惯例,上代码

# 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()

原文发布于微信公众号 - Python爬虫与算法进阶(zhangslob)

原文发表时间:2019-02-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券