携程国际机票sign破解
1.全局搜索大法:
直接搜索对应的时间,定位到最后结果返回的请求。
发现请求头headers sign为变化状态以及参数 transactionID为变化状态(transactionID前面请求响应结果)。
transactionID为变化状态(transactionID前面请求响应结果)。
2.逆向回推法,先往上找找看看哪里设置了这两个变化参数。
发现:https://flights.ctrip.com/international/search/api/flightlist/oneway-tpe-szx?depdate=2020-09-14&cabin=y_s&adult=1&child=0&infant=0&containstax=1&v=0.9164251641244324 返回结果有transactionID。
参数分析:
oneway -> 单程 tpe -> 台北三字码 szx -> 深圳三字码 depdate->起始时间 cabin->仓位类型
adult -> 成人 child->小孩 infant->婴儿 v ->时间戳刷新缓存 (一般情况直接忽略)
3.接着查找 哪一个请求最早有sign .
发现 https://flights.ctrip.com/international/search/api/search/batchSearch?v=0.026341469001777362
这个请求最早出现 sign ,且无源头查找sign,初步定义为 Js 动态生成 。
4. 堆栈跟踪法
一步步来,直接点进去,全局搜索大法。
逆向思维,倒推法,看到sign 就是 n ,那么n 是:
n = sessionStorage.getItem(i)) || (n = (new a.a).update(r).digest('hex'), sessionStorage.setItem(i, n)
sesessionStorage 本地存储的 name/value 键值对。get方法取键 i 为真.
且 n 为新new了一个对象把r进行md5 并且以键值对(i,n)设置回sessionStorage
简单啊,就是r进行了md5.
r是????
var r = e.transactionID + t, 不就是transactionID + t
t 是 ????
var n = e.departureCityCode,
r = e.arrivalCityCode,
a = e.departureDate;
t += n + r + a
英文学的好,很重要。
departureCityCode -> 起始三字码
arrivalCityCode -> 到达三字码 departureDate ->出发日期
Ca,这么简单啊… t 就是 String(起始地三字码+降落的三字码+出发时间)
Sign = md5(transactionID +起始地三字码+降落的三字码+出发时间)
分析完毕。over!
5.模拟请求,发现该网站自己一直发请求,直到拿到结果。
6.代码展示:
# encoding:utf-8
"""
@author Codeoooo
"""
import re
import json
import hashlib
import requests
from fake_useragent import UserAgent
from requests import RequestException
class CtripSpider:
def __init__(self, depCode, arrCode, date, adult=1, child=0, infant=0):
self.depCode = depCode
self.arrCode = arrCode
self.date = date
self.adult = adult
self.child = child
self.infant = infant
self.headers = {'User-Agent': UserAgent().random}
def getTransactionId(self):
"""获取transactionId and Params"""
url = 'https://flights.ctrip.com/international/search/oneway-{}-{}?depdate={}&cabin=y_s&adult={}&child={}&infant={}'.format(
self.depCode, self.arrCode, self.date, self.adult, self.child, self.infant)
transactionId, data = None, None
for _ in range(3):
try:
response = requests.get(url, headers=self.headers)
data = re.findall(r'GlobalSearchCriteria =(.+);', response.text)[0].encode('utf-8')
transactionId = json.loads(data).get("transactionID")
return transactionId, data
except:
continue
return transactionId, data
def getSign(self, transactionId):
sign_value = transactionId + self.depCode + self.arrCode + self.date
# 进行md5加密
_sign = hashlib.md5()
_sign.update(sign_value.encode('utf-8'))
headers = {
'origin': "https://flights.ctrip.com",
'sign': _sign.hexdigest(),
'transactionid': transactionId,
'Host': "flights.ctrip.com",
'content-type': "application/json;charset=UTF-8",
}
self.headers.update(headers)
# 请求并获取响应,获取源代码
def get_information_page(self, data):
"""
:return:
"""
while True:
try:
requests.packages.urllib3.disable_warnings()
res = requests.post(
url="https://flights.ctrip.com/international/search/api/search/batchSearch",
headers=self.headers,
data=data,
proxies=None,
timeout=5
)
except RequestException:
# todo 请求抛错未处理 可能为代理网路延迟问题
# time.sleep(random.randint(1, 3))
continue
except Exception as e:
# 其他未知错误
print(e)
else:
# res.encoding = 'utf-8'
# print(res.json())
if res.json().get("data").get("context").get("searchCriteriaToken"):
return res.json()
def main(self):
transactionId, data = self.getTransactionId()
if transactionId:
self.getSign(transactionId)
return self.get_information_page(data)
else:
# print("账号被封禁")
return
if __name__ == '__main__':
spider = CtripSpider("tpe", "bjs", "2020-09-14")
print(spider.main())