Python-携程网爬虫的JS逆向分析

朋友指派写一个爬虫,需要爬取携程网指定起点和终点的所有汽车票信息


网页分析

访问https://www.ctrip.com/, f12进入开发者界面

监测网络活动

点击汽车票,输入起点和终点后点击搜索

查看页面,所需信息就是下列汽车的发/到时间、起点和终点数据

重新查询,筛选通过Fetch/XHR交互的数据包

查找数据包,发现名为busListV2的POST包内负载列车数据信息

查看请求头

获取user-agent、cookie等headers信息

查看请求url

猜测请求url中包含两个动态生成的参数,需要JS逆向出参数的构造函数,从而构造请求url

查看传递参数

多次查询后观察此包,发现有以下必需参数

_fxpcqlniredt (静态) 识别用户身份

x-traceID (动态) 追踪用户

fromCity 起点城市

toCity 终点城市

fromDate 出发日期

查看数据部分

可见["data"]["bus"]["data"]["busList"]字段内储存了一个列表,包含所需数据信息


JS逆向

接下来对web进行逆向,看看能不能查找出参数_fxpcqlniredt的来源和参数x-traceID的构造函数

点击此包的发起程序,进入源代码界面

ctrl+f搜索,查找_fxpcqlniredt参数

这里在function的函数下未发现_fxpcqlniredt的来源,却找到x-traceID的构成函数,它由t(即_fxpcqlniredt)、请求时间的时间戳和一个七位的随机整数构成,继续寻找_fxpcqlniredt来源

发现_fxpcqlniredt参数和cookie中的GUID字段始终保持统一,推测来源于cookie

至此逆向过程完毕


程序编写

程序主要包含以下部分:

1.读取文件

2.解码参数,请求服务器

3.获取数据,处理数据

4.数据写入文件

第三方库

requests 网络请求

pandas 数据分析

xlrd、xlwt 读写xls、xlsx类型文件

json 解析数据

程序代码

def decode(headers, parameter, n):
    p = "-" + str(int(time.time() * 1000)) + "-" + str(random.randint(0, 9999999))
    parameter['_fxpcqlniredt'] = n
    parameter['x-traceID'] = n + p
    headers['path'] = '/restapi/soa2/13906/json/busListV2?_fxpcqlniredt=' + parameter['_fxpcqlniredt'] + '&x-traceID=' + parameter['x-traceID']

解码函数

_fxpcqlniredt = cookie.GUID
x-traceID = _fxpcqlniredt - 当前时间的时间戳(精确到毫秒)- 0~9999999之间的随机整数
def read_xls(filename):
    list = []
    df = pd.read_excel(filename)
    column_values = df['始发地'].values
    for item in column_values:
        list.append(item.rstrip("\n"))
    return list

读取xls文件并获取城市列表

def out_to_xls(from_station, to_station, number, filename):  # 将获取到的列车数目写入xls文件内
    try:
        df = pd.read_excel(filename)
        df.loc[df["始发地"] == from_station, to_station] = number
        df.to_excel(filename, index=False)
        print(f"正在处理{from_station}-->{to_station}, 数量 {number}")
        # print(df)
    except PermissionError as e:
        print("请关闭.xls文件后重试")
        quit()

 数据写入xls文件内

def run():
    decode(headers, parameter, '***************')  # 隐藏部分为GUID
    request_url = url + urlencode(parameter)
    station_list = read_xls('file.xls')
    for i in range(0, len(station_list)):
        for j in range(0, len(station_list)):
            if i == j:  # 当始发站和目的地相同时跳过
                continue

            data["fromCity"] = station_list[i]
            data["toCity"] = station_list[j]

            time.sleep(random.uniform(0.5, 2.5))

            try:
                rest = requests.post(request_url, headers=headers, json=data)
                trans_dict = json.loads(rest.text)
                result_type = trans_dict["data"]["bus"]["data"]["resultType"]
                print(result_type)
                if result_type == 3:
                    out_to_xls(station_list[i], station_list[j], 0, 'file.xls')
                    continue
                elif result_type == 2:
                    out_to_xls(station_list[i], station_list[j], 'error', 'file.xls')
                bus_list = trans_dict["data"]["bus"]["data"]["busList"]
                number = len(bus_list)
                # print(number)
                out_to_xls(station_list[i], station_list[j], number, 'file.xls')
            except json.decoder.JSONDecodeError:
                print(json.decoder.JSONDecodeError)
            except KeyError:
                print(KeyError)

 针对返回数据中resultType字段的标号对数据进行处理,1表示查询到数据信息,3表示未查询到信息或信息数为0,2未知

完整代码

crawler.py(请自行构造headers和data部分)

import json
import requests
from urllib.parse import urlencode
from function import *

url = ('https://m.ctrip.com/restapi/soa2/13906/json/busListV2?')  # url
parameter = {
    '_fxpcqlniredt': '',
    'x-traceID': ''
}  # 加密参数
headers = {}  # 请求头
data = {}  # json

def run():
    decode(headers, parameter, '***************')
    request_url = url + urlencode(parameter)
    station_list = read_xls('file.xls')
    for i in range(0, len(station_list)):
        for j in range(0, len(station_list)):
            if i == j:  # 当始发站和目的地相同时跳过
                continue

            data["fromCity"] = station_list[i]
            data["toCity"] = station_list[j]

            time.sleep(random.uniform(0.5, 2.5))  # 休眠随机时间

            try:
                rest = requests.post(request_url, headers=headers, json=data)
                trans_dict = json.loads(rest.text)  # 请求并解析数据
                result_type = trans_dict["data"]["bus"]["data"]["resultType"]
                if result_type == 3:  # 根据result_type处理数据信息
                    out_to_xls(station_list[i], station_list[j], 0, 'file.xls')
                    continue
                elif result_type == 2:
                    out_to_xls(station_list[i], station_list[j], 'error', 'file.xls')
                bus_list = trans_dict["data"]["bus"]["data"]["busList"]
                number = len(bus_list)  # 获取汽车票数目
                # print(number)
                out_to_xls(station_list[i], station_list[j], number, 'file.xls')
            except json.decoder.JSONDecodeError:  # 处理异常
                print(json.decoder.JSONDecodeError)
            except KeyError:
                print(KeyError)


if __name__ == '__main__':
    run()

 function.py

import random
import time
import pandas as pd


def decode(headers, parameter, n):
    p = "-" + str(int(time.time() * 1000)) + "-" + str(random.randint(0, 9999999))
    parameter['_fxpcqlniredt'] = n
    parameter['x-traceID'] = n + p
    headers['path'] = '/restapi/soa2/13906/json/busListV2?_fxpcqlniredt=' + parameter['_fxpcqlniredt'] + '&x-traceID=' + parameter['x-traceID']


def read_xls(filename):
    list = []
    df = pd.read_excel(filename)
    column_values = df['始发地'].values
    for item in column_values:
        list.append(item.rstrip("\n"))
    return list


def out_to_xls(from_station, to_station, number, filename):  # 将获取到的列车数目写入xls文件内
    try:
        df = pd.read_excel(filename)
        df.loc[df["始发地"] == from_station, to_station] = number
        df.to_excel(filename, index=False)
        print(f"正在处理{from_station}-->{to_station}, 数量 {number}")
        # print(df)
    except PermissionError as e:
        print("请关闭.xls文件后重试")
        quit()


调试问题

报错:requests.exceptions.InvalidHeader: Invalid leading whitespace, reserved character(s), or returncharacter(s) in header name: ':authority'

解释:无法解析请求头,前几个字段为http2的请求,作为RFC 描述,Http 请求头不能以分号开头,这部分我也不是很清楚,查询了几个博客

措施:直接把前几个有问题的字段删掉,也能正常请求不报错

报错:KeyError: 'data'

解释:没有获取到数据,这个报错困扰了非常久,最后发现是在post请求时将参数json写成了params的低级错误导致无法正常传递json参数

措施:改为rest = requests.post(request_url, headers=headers, json=data)


 爬取结果

xls格式如上图


 参考博客

携程逆向爬虫 | PC网页端 | 旅游景点评论爬虫-CSDN博客

Python网络爬虫实战:爬取携程网酒店评价信息_携程酒店评价爬虫-CSDN博客

  • 19
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值