python实现12306购票下单

首先12306下单肯定需要用户登录

第一步12306用户登录:12306可以用账号密码登录或者二维码,通过试验账号密码登录过程会出现验证码,比较麻烦,所有选择二维码的方式登录

通过页面实际操作发现二维码登录先通过接口获取二维码图片和uuid,根据uuid通过轮询二维码登录结果

获取二维码接口:https://kyfw.12306.cn/passport/web/create-qr64

查询二维码状态接口:https://kyfw.12306.cn/passport/web/checkqr

登录完后,发现cookie下新增了一下参数,我们跟进去看查询二维码状态接口后做了什么操作

这里我们发现当登录成功后,返回status为2,最终调用popup_loginCallBack()方法

最终通过调用https://kyfw.12306.cn/passport/web/auth/uamtk获取票据,然后根据票据换取全局cookie的tk

下面的具体的代码

import requests
from responseData import getSuccessResultData,getErrorResultData
import logging
from cache.gobalCache import setCache,getCache
from Task.checkLogin import addcheckLoginJob


logger = logging.getLogger(__name__)


def getBaseHearders():
    headers = {
        "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
        "Referer":"https://kyfw.12306.cn/otn"
    }
    return headers

def getLoginQrCode(requestId :str):
    if(len(requestId)==0):
        return getErrorResultData("无效请求")
    headers=getBaseHearders()
    headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'
    res = requests.post("https://kyfw.12306.cn/passport/web/create-qr64",data="appid=otn",headers=headers)
    if(res.status_code==200 and 'uuid' in res.text):
        logger.info("获取二维码成功:%s",res.text)
        setCache(requestId,res.json())
        return getSuccessResultData(res.json(),"成功")
    return getErrorResultData("获取12306登录二维码失败")
def checkqrAndGetTK(body):
    uuid = body['uuid']
    appid = body['appid']
    requestId = body['requestId']
    cache = getCache(requestId)
    if(len(requestId)==0 or cache is None):
        return getErrorResultData("无效请求requestId")
    if(cache and 'tk' in cache and 'cookies' in cache):
        return getSuccessResultData(cache,"cookie缓存")
    checkqrUrl = 'https://kyfw.12306.cn/passport/web/checkqr'
    headers = getBaseHearders()
    headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'
    data = "uuid="+uuid+"&appid="+appid
    res = requests.post(url=checkqrUrl,data=data,headers=headers)
    result = dict()
    msg = '查询二维码登录状态失败'
    isError = True
    if(res.status_code==200 and 'result_code' in res.text):
        isError = False
        logger.info("查询二维码登录状态成功:%s",res.text)
        result_code = res.json()['result_code']
        result['status'] = result_code
        if(result_code == "2"):
            logger.info("二维码登录成功")
            uamtk = res.json()['uamtk']
            headers['Cookie'] = 'uamtk='+uamtk
            res = requests.post("https://kyfw.12306.cn/passport/web/auth/uamtk",data="appid="+appid,headers=headers)
            msg = '二维码登录成功获取tk失败'
            if(res.status_code==200 and 'newapptk' in res.text and res.json()['result_code']== 0):
                newapptk = res.json()['newapptk']
                res = requests.post("https://kyfw.12306.cn/otn/uamauthclient",data="tk="+newapptk,headers=headers)
                if(res.status_code==200 and 'username' in res.text and res.json()['result_code'] == 0):
                    cookies = requests.utils.dict_from_cookiejar(res.cookies)
                    if('tk' in cookies):
                        result['tk'] = cookies['tk']
                        msg = '二维码登录成功获取tk成功'
                        cookies_str = '; '.join([f'{k}={v}' for k, v in cookies.items()])
                        result['cookies'] = cookies
                        result['cookiesStr'] = cookies_str

        else:
            msg = res.json()['result_message']
    setCache(requestId,result)
    if('tk' in result):
        addcheckLoginJob(requestId)            
    if(isError):
        return getErrorResultData(msg)
    else:
        return getSuccessResultData(result,msg) 

登录成功缓存一下用户的cookie到我们内存,同时需要定时校验这个cookie是否有效的

from apscheduler.schedulers.background import BackgroundScheduler
import time
import requests
from cache.gobalCache import getCache,delCache,setCache
import logging

logger = logging.getLogger(__name__)

scheduler = BackgroundScheduler()
scheduler.start()

def checkLoginJob(requestId :str):
    resCache = getCache(requestId)
    if(resCache is None or 'tk' not in resCache):
        logger.info("缓存中已经没有tk,移除任务,%s",requestId)
        scheduler.remove_job(requestId)
        return
    url = 'https://kyfw.12306.cn/otn/index/initMy12306Api'
    headers = {
        "Cookie": resCache['cookiesStr'],
        "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
    }
    res = requests.post(url,headers=headers)
    logger.info("定时刷新登录cookie结果:%s,%s",res.text,requestId)
    if(res.status_code == 200 and 'data' in res.text):
        cookiesDict = requests.utils.dict_from_cookiejar(res.cookies)
        cacheCookies :dict = resCache['cookies']
        cacheCookies.update(cookiesDict)
        cookies_str = '; '.join([f'{k}={v}' for k, v in cacheCookies.items()])
        resCache ['cookiesStr'] = cookies_str
        resCache ['cookies'] = cacheCookies
        setCache(requestId,resCache)
        logger.info("定时刷新登录cookie成功")
    else:
        delCache(requestId)
        logger.info("cookie已失效,%s",requestId)
        scheduler.remove_job(requestId)       


def addcheckLoginJob(requestId :str):
    checkLoginJob(requestId)
    scheduler.add_job(checkLoginJob, 'interval', seconds=30, id=requestId, args=[requestId])

本次文章的重点,下单购票,可改造定时抢票

废话不多说,先人工走一遍流程,打开查询高铁的页面发现查询接口

https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2024-05-16&leftTicketDTO.from_station=GZQ&leftTicketDTO.to_station=SHH&purpose_codes=ADULT

明显可以见参数就是时间和出发城市code和到达城市code,这个接口实际不用登录就可以查询,就是要获取一个cookie里面的参数

见代码:

def getCoookie(train_date :str,from_station :str,to_station :str,from_station_code :str,to_station_code :str):
    url = 'https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc&fs={},{}&ts={},{}&date={}&flag=N,N,Y'
    headers = {
        'User-Agent':"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
    }
    res = requests.get(url= url.format(from_station,from_station_code,to_station,to_station_code,train_date),headers=headers)
    cookies = requests.utils.dict_from_cookiejar(res.cookies)
    JSESSIONID = None
    CLeftTicketUrl = None
    if('JSESSIONID' in cookies):
        JSESSIONID = cookies['JSESSIONID']
    soup = BeautifulSoup(res.text, 'html.parser')
    scripts = soup.find_all('script')
    for script in scripts:
        if 'CLeftTicketUrl' in script.text:
            pattern = re.compile(r'CLeftTicketUrl = \'(.*?)\'')
            globalCLeftTicketUrl = re.search(pattern, script.text)
            if globalCLeftTicketUrl:
                CLeftTicketUrl = globalCLeftTicketUrl.group(1)
    return JSESSIONID,CLeftTicketUrl


    

def getNoTransferTrain(train_date :str,from_station :str,from_station_code :str,to_station :str,to_station_code :str,trainList :list):
    url = 'https://kyfw.12306.cn/otn/{}?leftTicketDTO.train_date={}&leftTicketDTO.from_station={}&leftTicketDTO.to_station={}&purpose_codes=ADULT'
    baseUrl = 'leftTicket/query'
    JSESSIONID,CLeftTicketUrl = getCoookie(train_date,from_station,to_station,from_station_code,to_station_code)
    if(JSESSIONID is None):
        logger.error("火车查询失败,获取cookie异常")
        return
    headers = {
        'Cookie':"JSESSIONID="+JSESSIONID,
         'User-Agent':'PostmanRuntime/7.37.0', 
         'Accept-Language': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36'
    }
    if(CLeftTicketUrl):
        url=url.format(CLeftTicketUrl,train_date,from_station_code,to_station_code)
    else:
        url=url.format(baseUrl,train_date,from_station_code,to_station_code)
    #url=url.format(from_station,from_station_code,to_station,to_station_code,train_date)
    #res :ResultData = page.listenDateByUrl(url,['/leftTicket/query'],['result'])
    res = requests.get(url=url,headers=headers)
    if(len(res.json()['data'])<=0):
        logger.info("火车查询失败")
    else:
        data = res.json()
        for infoStr in data['data']['result']:
            if('暂停发售' not in infoStr):
                trainTicketDetailInfo = parseTrain(infoStr,train_date)
                hours, minutes = calculate_time_difference_in_hours_minutes(trainTicketDetailInfo['departure_time'], trainTicketDetailInfo['arrival_time'])
                trainTicketMainInfo = parseTrainNode([trainTicketDetailInfo],"{}小时{}分钟.".format(hours,minutes),False)
                trainTicketInfo = TrainTicketInfo(trainTicketMainInfo,trainTicketDetailInfo)
                trainList.append(trainTicketInfo.to_dict())

parseTrainNode方法是解析这个接口返回的数据对应到页面显示高铁上的数据

通过上面的高铁数据我们进行下单

1:先获取购票人的信息,通过接口:https://kyfw.12306.cn/otn/confirmPassenger/getPassengerDTOs,需要登录就是上面cookie

def getPassengersInfo(requestId :str):
    headers = getCookieHeaders(requestId)
    if(headers is None):
        return None
    url = 'https://kyfw.12306.cn/otn/confirmPassenger/getPassengerDTOs'
    res = requests.post(url=url,headers=headers)
    if('validateMessagesShowId' in res.text and res.json()['httpstatus']==200):
        logging.info("获取乘车人信息成功")
        return res.json()['data']['normal_passengers']
    logging.info("获取乘车人信息失败")
    return None

2:进入购票页面获取到提交参数的SubmitToken,同时提交订单时候有一个加密参数,通过分析页面的js代码,发现在页面加载时候就触发的方法挂载了全局的window.json_ua.toString(),我们使用浏览迷模拟这个js执行结果获取加密参数encryptedData,

然后下单的流程就是,

验证购票人信息是否正确:https://kyfw.12306.cn/otn/confirmPassenger/checkOrderInfo,

验证票是否足够:https://kyfw.12306.cn/otn/confirmPassenger/getQueueCount

最后提交订单:https://kyfw.12306.cn/otn/confirmPassenger/confirmSingleForQueue

最后可以提交订单,然后去app去支付就行了

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值