selenium和requests实现12306登录及余票查询

1.12306首页

url:https://www.12306.cn/index/index.html
在这里插入图片描述

用到的包类
from selenium import webdriver
from selenium.webdriver import ActionChains  # 用于控制鼠标滑动
from chaojiying  import Chaojiying_Client  # 超级鹰验证码识别
import base64
import re
from xml import etree
import time
import config  # 账号密码配置文件
from selenium.webdriver.common.keys import Keys  # 模拟点击
import requests
import json
# self.url = 'https://www.12306.cn/index/index.html'
self.driver = webdriver.Chrome()
self.driver.get(url = self.url)

2.进入登录页面

点击登录进入登录页面

self.driver.find_element_by_xpath('//*[@id="J-header-login"]/a[1]').click()

在这里插入图片描述
在这里插入图片描述

选择账号密码登录

self.driver.find_element_by_xpath('/html/body/div[2]/div[2]/ul/li[2]/a').click()

在这里插入图片描述
在这里插入图片描述
输入账号密码

self.driver.find_element_by_id('J-userName').send_keys(self.tk_user)
self.driver.find_element_by_id('J-password').send_keys(self.tk_pw)

3.处理图片验证码

超级鹰处理验证码

验证码的识别我采用的是超级鹰第三方软件识别

准备工作

进入超级鹰,完成账号密码注册
在这里插入图片描述
进入用户中心,并购买题分(一般1-10块即可)
在这里插入图片描述
生成软件ID
在这里插入图片描述
并在开发文档下载相应语言的开发案例,这里我使用python,将文件解压到项目目录中
在这里插入图片描述
chaojiying.py

#!/usr/bin/env python
# coding:utf-8

import requests
from hashlib import md5

class Chaojiying_Client(object):

    def __init__(self, username, password, soft_id):
        self.username = username
        password =  password.encode('utf8')
        self.password = md5(password).hexdigest()
        self.soft_id = soft_id
        self.base_params = {
            'user': self.username,
            'pass2': self.password,
            'softid': self.soft_id,
        }
        self.headers = {
            'Connection': 'Keep-Alive',
            'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
        }

    def PostPic(self, im, codetype):
        """
        im: 图片字节
        codetype: 题目类型 参考 http://www.chaojiying.com/price.html
        """
        params = {
            'codetype': codetype,
        }
        params.update(self.base_params)
        files = {'userfile': ('ccc.jpg', im)}
        r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers)
        return r.json()

    def ReportError(self, im_id):
        """
        im_id:报错题目的图片ID
        """
        params = {
            'id': im_id,
        }
        params.update(self.base_params)
        r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
        return r.json()


if __name__ == '__main__':
	chaojiying = Chaojiying_Client('超级鹰用户名', '超级鹰密码', '软件ID')	#用户中心>>软件ID 生成一个替换 96001
	im = open('a.jpg', 'rb').read()													#本地图片文件路径 来替换 a.jpg 有时WIN系统须要//

	print(chaojiying.PostPic(im, 1902)['pic_str'])										#1902 验证码类型  官方网站>>价格体系 3.4+版 print 后要加()
实现原理

超级鹰识别验证码类型
在这里插入图片描述
在这里插入图片描述

原理就是我们将验证码图片截取下来保存到指定的位置上,在保存之后,我们将保存的图片信息传递给第三方软件,在他识别之后会返回给我们x,y坐标,这些x,y坐标就是图片需要点击的x,y坐标,有一点需要注意,这些x,y是相对坐标,是相对于图片左上角(0,0)坐标而言的。

具体验证码坐标获取流程

首先对12306登陆首页全屏截图,再定位到验证码图片,借助超级鹰进行识别。注意到12306验证码比较复杂,可以根据超级鹰价格体系中可以选取对应的验证码类型,本文选择9004。

#save_screenshot:全屏截屏
self.driver.save_screenshot('all.png')
sleep(1)
#定位验证码图片位置并截图
img = self.driver.find_element_by_xpath('//*[@id="J-loginImg"]')
img.screenshot('./code.png')
#将验证码图片提交给超级鹰进行识别
chaojiying = Chaojiying_Client('超级鹰用户名', '超级鹰密码', '软件ID')
im = open('./code.png', 'rb').read()
#id = chaojiying.PostPic(im,9004)['pic_id']
#获取到验证码图片的相对位置
# 9004是我们发送的验证码的格式,然后这个指令的返回值是一个字典,在键为['pic_str']的值的时候,保存地视返回的需要点击的坐标
result = chaojiying.PostPic(im, 9004)['pic_str']

result返回值类似:240,68|121,84。下面我们需要对坐标进行分割存储到列表中,类似:[[240,68],[128,84]]。同时注意到这个坐标是相对坐标,所以需要使用动作链ActionChains.move_to_element_with_offset进行相对位置的定位,再选中正确答案。

position_list = []
# 若存在多个点
if '|' in position:
     position_list = [i.split(',') for i in result.split('|')]
# 若只有一个点
else:
    position_list.append(result.split(','))
print(position_list) # [['107', '140'], ['253', '138']]

# 获取坐标后,使用动作链ActionChains.move_to_element_with_offset进行相对位置的定位,再选中正确答案
for l in position_list:
	# 注意转换为int()
    x = int(l[0])
    y = int(l[-1])
    # perform() 为执行
    ActionChains(self.driver).move_to_element_with_offset(img, x, y).click().perform()
    time.sleep(1)

在这里插入图片描述

选择好验证码后,点击登录

self.driver.find_element_by_xpath('//*[@id="J-login"]').click()

但是在运行过程中,你会发现有时候这个第三方软件并不能特别准确的识别验证码,有时也会出错,或者在图片保存的时候,有时也会出现图片没有正确保存的情况,对于这种情况,我们就想设置一个循环,将这个操作放到循环里面,只有验证码正确识别之后,我们才给它进行下一步

   while True:
        try:
            # 图片验证码
            # 将页面截屏
            self.driver.save_screenshot('./all_screen.png')
            # 截取验证码部分的图
            img = self.driver.find_element_by_id('J-loginImgArea')
            img.screenshot('./code1.png')
            position = self.process_cjy('./code1.png')
            # print(position)  # 107,140|253,138
            # position为每个答案点的x,y坐标,为x1,y1|x2,y2|x3,y3,若只有一个点,为x1,y1,处理为[[x1,y1],[x2,y2]...]
            position_list = []
            if '|' in position:
                position_list = [i.split(',') for i in position.split('|')]
            else:
                position_list.append(position.split(','))
            print(position_list) # [['107', '140'], ['253', '138']]

            # 获取坐标后,使用动作链ActionChains.move_to_element_with_offset进行相对位置的定位,再选中正确答案
            for l in position_list:
                x = int(l[0])
                y = int(l[-1])
                # perform() 为执行
                ActionChains(self.driver).move_to_element_with_offset(img, x, y).click().perform()
                time.sleep(1)
            # 点击登录按钮
            self.driver.find_element_by_xpath('//*[@id="J-login"]').click()
            time.sleep(1)
        except:
            print('验证成功')
            break

def process_cjy(self, img_path):
    # 使用超级鹰处理验证码
    # 登录超级鹰
    cjy = Chaojiying_Client(self.cjy_user, self.cjy_pw, self.cjy_id)
    with open(img_path, 'rb') as f:
        img = f.read()
    # 9004是我们发送的验证码的格式,然后这个指令的返回值是一个字典,在键为['pic_str']的值的时候,保存地视返回的需要点击的坐标
    # result为每个点的x,y坐标,为x1,y1|x2,y2|x3,y3
    return cjy.PostPic(img, 9004)['pic_str']

4.处理滑动验证码

图片验证码验证成功后,点击登录,出现滑动验证码
在这里插入图片描述
我们定位到滑块这里,然后我们会发现在二维码识别之前和识别之后,这个滑块代码其实一直都在html中,只不过没有正确识别之前,里面的属性display的值为none,而正确识别之后呢,display属性就没了。所以其实我们只需要判断这个里面的display属性值是不是none,我们就可以判断有没有正确识别成功了

正在识别

在这里插入图片描述
识别时候,滑块的相对位置移动300即可完成解锁
在这里插入图片描述

# 选中滑块
span = self.driver.find_element_by_xpath('//*[contains(@class,"nc_iconfont btn_slid")]')


# 即动作链
action = ActionChains(self.driver)	
# perform都表示执行的意思,
# 这个其实就是长按点击目标元素
action.click_and_hold(span).perform()
# 这个其实就是拖动目标元素相对偏移量x,y
action.move_to_element_with_offset(span , 400, 0).perform()
# 这个其实就是释放刚才长按的鼠标
action.release().perform()
验证失败

但是也存在验证失败的情况
在这里插入图片描述
可以发现两个提示信息的xpath并不相同

# 若果滑动出错,提示信息
info = self.driver.find_element_by_xpath('//*[@id="J-slide-passcode"]/div/div/div/span').text
# 若滑动未出错,提示信息
info = self.driver.find_element_by_xpath('//*[@id="J-slide-passcode"]/div/div/div/span').text

但是12306会识别是否为爬虫,导致滑动正确却验证失败,解决办法如下:

self.driver = webdriver.Chrome()
# 避免被检测,导致滑动失败
self.driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
    "source": """
    Object.defineProperty(navigator, 'webdriver', {
      get: () => undefined
    })
  """
})
实现代码
# 滑动验证码
while True:
try:
    # 若果滑动出错,获取提示信息,并刷新
    info = self.driver.find_element_by_xpath('//*[@id="J-slide-passcode"]/div/div/div/span').text
    print(info)
    if info == '哎呀,出错了,点击刷新再来一次':
        # 若滑动出错,则点击‘刷新’
        self.driver.find_element_by_xpath('//*[@id="J-slide-passcode"]/div/span/a').click()
        time.sleep(1)
        # 选中滑块
        span = self.driver.find_element_by_xpath('//*[contains(@class,"nc_iconfont btn_slid")]')
        action = ActionChains(self.driver)
        # 点击长按指定滑块并滑动
        action.click_and_hold(span).perform()
        # 滑动到left300 便可解锁
        action.drag_and_drop_by_offset(span, 400, 0).perform()
        # 释放鼠标
        action.release()
        time.sleep(7)
    else:
        # 若滑动未出错误提示,正常滑动,获取滑块
        info = self.driver.find_element_by_xpath('//*[@id="J-slide-passcode"]/div/div/div/span').text
        print(info)
        time.sleep(1)
        # 选中滑块
        span = self.driver.find_element_by_xpath('//*[contains(@class,"nc_iconfont btn_slid")]')
        action = ActionChains(self.driver)
        # 点击长按指定滑块并滑动
        action.click_and_hold(span).perform()
        # 滑动到left300 便可解锁
        action.drag_and_drop_by_offset(span, 400, 0).perform()
        # 释放鼠标
        action.release()
        time.sleep(7)
except:
    # 判断当前页面url 发生跳转则说明登录成功
    print('登录成功')
    print(self.driver.current_url)  # https://kyfw.12306.cn/otn/view/index.html
    break

5.完整代码

在这里插入图片描述
config.py

# 12306 账号 密码
TICKET_USER = ''
TICKET_PASSWORD = ''

# 超级鹰账号密码软件id
CHAOJIYING_USER = ''
CHAOJIYING_PASSWORD = ''
CHAOJIYING_ID = ''  

login.py

from selenium import webdriver
from selenium.webdriver import ActionChains  # 用于控制鼠标滑动
from chaojiying  import Chaojiying_Client  # 超级鹰验证码识别
import base64
import re
from xml import etree
import time
import config  # 账号密码配置文件


class Login(object):
    def __init__(self):
        self.tk_user = config.TICKET_USER
        self.tk_pw = config.TICKET_PASSWORD
        self.cjy_user = config.CHAOJIYING_USER
        self.cjy_pw = config.CHAOJIYING_PASSWORD
        self.cjy_id = config.CHAOJIYING_ID  # 软件id
        self.url = 'https://www.12306.cn/index/'
        self.driver = webdriver.Chrome()
        # 避免被检测,导致滑动失败
        self.driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
            "source": """
            Object.defineProperty(navigator, 'webdriver', {
              get: () => undefined
            })
          """
        })



    def login(self):
        # 点击首页的登录按钮
        self.driver.find_element_by_xpath('//*[@id="J-header-login"]/a[1]').click()
        time.sleep(1)
        # 跳转后,输入账号,密码
        self.driver.find_element_by_xpath('/html/body/div[2]/div[2]/ul/li[2]/a').click()
        # 账号密码
        self.driver.find_element_by_id('J-userName').send_keys(self.tk_user)
        self.driver.find_element_by_id('J-password').send_keys(self.tk_pw)
        while True:
            try:
                # 图片验证码
                # 将页面截屏
                self.driver.save_screenshot('./all_screen.png')
                # 截取验证码部分的图
                img = self.driver.find_element_by_id('J-loginImgArea')
                img.screenshot('./code1.png')
                position = self.process_cjy('./code1.png')
                # print(position)  # 107,140|253,138
                # position为每个答案点的x,y坐标,为x1,y1|x2,y2|x3,y3,若只有一个点,为x1,y1,处理为[[x1,y1],[x2,y2]...]
                position_list = []
                if '|' in position:
                    position_list = [i.split(',') for i in position.split('|')]
                else:
                    position_list.append(position.split(','))
                print(position_list) # [['107', '140'], ['253', '138']]

                # 获取坐标后,使用动作链ActionChains.move_to_element_with_offset进行相对位置的定位,再选中正确答案
                for l in position_list:
                    x = int(l[0])
                    y = int(l[-1])
                    # perform() 为执行
                    ActionChains(self.driver).move_to_element_with_offset(img, x, y).click().perform()
                    time.sleep(1)
                # 点击登录按钮
                self.driver.find_element_by_xpath('//*[@id="J-login"]').click()
                time.sleep(1)
            except:
                print('验证成功')
                break

        # 滑动验证码
        while True:
            try:
                # 若果滑动出错,获取提示信息,并刷新
                info = self.driver.find_element_by_xpath('//*[@id="J-slide-passcode"]/div/div/div/span').text
                print(info)
                if info == '哎呀,出错了,点击刷新再来一次':
                    # 若滑动出错,则点击‘刷新’
                    self.driver.find_element_by_xpath('//*[@id="J-slide-passcode"]/div/span/a').click()
                    time.sleep(1)
                    # 选中滑块
                    span = self.driver.find_element_by_xpath('//*[contains(@class,"nc_iconfont btn_slid")]')
                    action = ActionChains(self.driver)
                    # 点击长按指定滑块并滑动
                    action.click_and_hold(span).perform()
                    # 滑动到left300 便可解锁
                    action.drag_and_drop_by_offset(span, 400, 0).perform()
                    # 释放鼠标
                    action.release()
                    time.sleep(7)
                else:
                    # 若滑动未出错误提示,正常滑动,获取滑块
                    info = self.driver.find_element_by_xpath('//*[@id="J-slide-passcode"]/div/div/div/span').text
                    print(info)
                    time.sleep(1)
                    # 选中滑块
                    span = self.driver.find_element_by_xpath('//*[contains(@class,"nc_iconfont btn_slid")]')
                    action = ActionChains(self.driver)
                    # 点击长按指定滑块并滑动
                    action.click_and_hold(span).perform()
                    # 滑动到left300 便可解锁
                    action.drag_and_drop_by_offset(span, 400, 0).perform()
                    # 释放鼠标
                    action.release()
                    time.sleep(7)
            except:
                # 判断当前页面url 发生跳转则说明登录成功
                print('登录成功')
                print(self.driver.current_url)  # https://kyfw.12306.cn/otn/view/index.html
                break


    def process_cjy(self, img_path):
        # 使用超级鹰处理验证码
        # 登录超级鹰
        cjy = Chaojiying_Client(self.cjy_user, self.cjy_pw, self.cjy_id)
        with open(img_path, 'rb') as f:
            img = f.read()
        # 9004是我们发送的验证码的格式,然后这个指令的返回值是一个字典,在键为['pic_str']的值的时候,保存地视返回的需要点击的坐标
        # result为每个点的x,y坐标,为x1,y1|x2,y2|x3,y3
        return cjy.PostPic(img, 9004)['pic_str']


    def run(self):
        self.driver.get(url = self.url)
        time.sleep(1)
        self.login()
        time.sleep(3)
        self.driver.quit()


if __name__ == '__main__':
    login = Login()
    login.run()

在这里插入图片描述

6.查询车票

登录成功后需要点击弹窗的确定,才可进行下一步
在这里插入图片描述

 # 点击登录成功后的确认窗口
self.driver.find_element_by_xpath('//*[@class="dzp-confirm"]/div/a').click()

当需要购票时候,点击导航窗格,隐藏菜单显示后,才可以通过selenium进行点击
参考
在这里插入图片描述

# 选择单程购票
# 导航栏隐藏窗口处理,需要使用动作链,获取隐藏菜单后才可以进行点击
nav_bar = self.driver.find_element_by_xpath('//*[@id="J-chepiao"]')
ActionChains(self.driver).move_to_element(nav_bar).perform()
self.driver.find_element_by_xpath('//*[contains(text(), "单程")]').click()

进入单程购票页面,点击确定
在这里插入图片描述

# 跳转来到订票页面,点击弹出提示窗口的确定
self.driver.find_element_by_xpath('//*[@id="qd_closeDefaultWarningWindowDialog_id"]').click()

输入出发地、目的地、日期等进行查询
参考

# 开始查询车票,默认为单程-出发地
# 在出发地、目的地、出发日等窗口输入内容,需要先点击输入框
start_city = input('请输入出发城市:')
self.driver.find_element_by_xpath('//*[@id="fromStationText"]').click()
self.driver.find_element_by_xpath('//*[@id="fromStationText"]').send_keys(start_city)
# 这里输入完成后,需要send_keys(Keys.ENTER)模拟键盘回车,这里和之前上号密码的不一样,账号密码输入不要回车就可以,不回车里面内容不生效
self.driver.find_element_by_xpath('//*[@id="fromStationText"]').send_keys(Keys.ENTER)

# 目的地
end_city = input('请输入到达城市:')
self.driver.find_element_by_xpath('//*[@id="toStationText"]').click()
self.driver.find_element_by_xpath('//*[@id="toStationText"]').send_keys(end_city)
self.driver.find_element_by_xpath('//*[@id="toStationText"]').send_keys(Keys.ENTER)

# 出发日期
# 将日期的只读属性去掉便于下面输入日期
js = """document.getElementById('train_date').removeAttribute('readonly')"""
self.driver.execute_script(js)
self.driver.find_element_by_id("train_date").clear()  # 清空默认日期值
train_date = input('请输入出发日期,如1999-07-09形式:')
self.driver.find_element_by_id("train_date").send_keys(train_date)
self.driver.find_element_by_id("train_date").send_keys(Keys.ENTER)
# 隐藏日期下方的详细日期列表
js = """document.querySelector('body > div.cal-wrap').style.display='none'"""
self.driver.execute_script(js)

# 选择查询车次类型 - 默认高铁
self.driver.find_element_by_xpath('//*[@id="_ul_station_train_code"]/li[1]').click()

# 点击查询
self.driver.find_element_by_xpath('//*[@id="query_ticket"]').click()
time.sleep(2)
开始查询

在这里插入图片描述
参考

车票信息抓包

在这里插入图片描述

票价抓包

在这里插入图片描述

    def search_ticket_info(self):
        # 点击登录成功后的确认窗口
        self.driver.find_element_by_xpath('//*[@class="dzp-confirm"]/div/a').click()
        # 选择单程购票
        # 导航栏隐藏窗口处理,需要使用动作链,获取隐藏菜单后才可以进行点击
        nav_bar = self.driver.find_element_by_xpath('//*[@id="J-chepiao"]')
        ActionChains(self.driver).move_to_element(nav_bar).perform()
        self.driver.find_element_by_xpath('//*[contains(text(), "单程")]').click()
        # 跳转来到订票页面,点击弹出提示窗口的确定
        self.driver.find_element_by_xpath('//*[@id="qd_closeDefaultWarningWindowDialog_id"]').click()

        # 开始查询车票,默认为单程-出发地
        # 获取城市名和城市缩写名列表
        city_name, city_eg_name = self.get_city_name_id()

        while True:
            # 在出发地、目的地、出发日等窗口输入内容,需要先点击输入框1
            start_city = input('请输入出发城市:')
            self.driver.find_element_by_xpath('//*[@id="fromStationText"]').click()
            self.driver.find_element_by_xpath('//*[@id="fromStationText"]').send_keys(start_city)
            # 这里输入完成后,需要send_keys(Keys.ENTER)模拟键盘回车,这里和之前上号密码的不一样,账号密码输入不要回车就可以,不回车里面内容不生效
            self.driver.find_element_by_xpath('//*[@id="fromStationText"]').send_keys(Keys.ENTER)

            # 目的地
            end_city = input('请输入到达城市:')
            self.driver.find_element_by_xpath('//*[@id="toStationText"]').click()
            self.driver.find_element_by_xpath('//*[@id="toStationText"]').send_keys(end_city)
            self.driver.find_element_by_xpath('//*[@id="toStationText"]').send_keys(Keys.ENTER)
            train_date = input('请输入出发日期,如1999-09-09形式:')
            check_result = self.check_info(city_name, start_city, end_city, train_date)
            if not check_result:
                print('输入有误,请重新输入!')

            else:
                print('验证通过')
                break
        # 出发日期
        # 将日期的只读属性去掉便于下面输入日期
        js = """document.getElementById('train_date').removeAttribute('readonly')"""
        self.driver.execute_script(js)
        self.driver.find_element_by_id("train_date").clear()  # 清空默认日期值


        self.driver.find_element_by_id("train_date").send_keys(train_date)
        self.driver.find_element_by_id("train_date").send_keys(Keys.ENTER)
        # 隐藏日期下方的详细日期列表
        js = """document.querySelector('body > div.cal-wrap').style.display='none'"""
        self.driver.execute_script(js)

        # 选择查询车次类型 - 默认高铁
        self.driver.find_element_by_xpath('//*[@id="_ul_station_train_code"]/li[1]').click()

        # 点击查询
        self.driver.find_element_by_xpath('//*[@id="query_ticket"]').click()
        time.sleep(2)

        # 保存查询信息
        result = []
        train_info = self.driver.find_element_by_xpath('//*[@id="sear-result"]/p[1]/strong[1]').text + ' 共计'  +  str(len(self.driver.find_elements_by_xpath('//*[@id="queryLeftTable"]/tr'))) + '车次'


        # 用于拼接'https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2021-07-09&leftTicketDTO.from_station=SHH&leftTicketDTO.to_station=HZH&purpose_codes=ADULT' 获得查询车票结果

        start_city = self.driver.find_element_by_xpath('//*[@id="queryLeftTable"]/tr[1]/td/div/div[2]/strong[1]').text
        end_city = self.driver.find_element_by_xpath('//*[@id="queryLeftTable"]/tr[1]/td/div/div[2]/strong[2]').text
        start_city_eg_name = city_eg_name[city_name.index(start_city)]
        end_city_eg_name = city_eg_name[city_name.index(end_city)]
        query_url = 'https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date={}&leftTicketDTO.from_station={}&leftTicketDTO.to_station={}&purpose_codes=ADULT'.format(train_date, start_city_eg_name, end_city_eg_name)
        print(start_city_eg_name, end_city_eg_name)
        print(query_url)
        # url = 'https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2021-07-09&leftTicketDTO.from_station=AOH&leftTicketDTO.to_station=HGH&purpose_codes=ADULT'
        # response = requests.get(url).content.decode()
        # print(response) 使用request 会被重定向


        # 使用selenium 新开窗口,
        # 注意:这里必须加""
        js = 'window.open("{}");'.format(query_url)
        self.driver.execute_script(js)
        # 获取当前窗口句柄
        handles = self.driver.window_handles
        # print(handles)  # ['CDwindow-09080EA2328F0CB9CCCBB02C22A02F74', 'CDwindow-27C3895DC5E7A3D638CAF68E3835062C']
        # 切换窗口
        self.driver.switch_to.window(handles[-1])
        query_res = self.driver.find_element_by_xpath('/html/body/pre').text
        # 关闭窗口
        self.driver.close()
        # 重新获取句柄并切换回当前窗口
        handles = self.driver.window_handles
        self.driver.switch_to.window(handles[-1])

        # 获取信息
        query_json = json.loads(query_res)
        ticket_info = query_json['data']['result']
        for info in ticket_info:
            item = {}
            temp = info.split('|')
            item['车次信息'] = train_info
            item['日期'] = train_date
            item['编号'] = temp[2]
            item['车次'] = temp[3]
            item['首发站'] = temp[4]
            item['终点站'] = temp[5]
            item['上车站'] = query_json['data']['map'][temp[6]]  # 车站与它对应编号的映射
            item['下车站'] = query_json['data']['map'][temp[7]]
            item['出发时间'] = temp[8]
            item['到达时间'] = temp[9]
            item['历时'] = temp[10]
            item['是否可预订'] = temp[11]
            item['上车站编号'] = temp[16]
            item['下车站编号'] = temp[17]
            item['高级软卧'] = [temp[21]]
            item['软卧一等卧'] = [temp[23]]
            item['软座'] = [temp[24]]
            item['无座'] = [temp[26]]
            item['硬卧二等卧'] = [temp[28]]
            item['硬座'] = [temp[29]]
            item['二等座'] = [temp[30]]
            item['一等座'] = [temp[31]]
            item['商务座特等座'] = [temp[32]]
            item['动卧'] = [temp[33]]
            item['其他'] = '--'

            # 用于拼接价格url的信息,如 train_no、from_station_no、to_station_no、seat_types、train_date
            item['train_no'] = temp[2]
            item['from_station_no'] = temp[16]
            item['to_station_no'] = temp[17]
            item['seat_types'] = temp[35]
            time.sleep(1)
            print(item)
            result.append(item)
        return result
票价获取
    def get_price_info(self, result):
        for i in range(len(result)):
            # 获取票价
            # 拼接https://kyfw.12306.cn/otn/leftTicket/queryTicketPrice?train_no=5l000G178640&from_station_no=01&to_station_no=07&seat_types=OM9&train_date=2021-07-09
            # print(tr_id_list) # ['ticket', '5l000G754171', '01', '04']
            price_url = 'https://kyfw.12306.cn/otn/leftTicket/queryTicketPrice?train_no={}&from_station_no={}&to_station_no={}&seat_types={}&train_date={}'.format(result[i]['train_no'], result[i]['from_station_no'], result[i]['to_station_no'], result[i]['seat_types'], result[i]['日期'])
            print(price_url)
            js = 'window.open("{}")'.format(price_url)
            self.driver.execute_script(js)
            handles = self.driver.window_handles
            self.driver.switch_to.window(handles[-1])
            price_res = self.driver.find_element_by_xpath('/html/body/pre').text
            self.driver.close()
            time.sleep(1)
            # 切回窗口
            # 重新获取句柄并切换回当前窗口
            handles = self.driver.window_handles
            self.driver.switch_to.window(handles[-1])

            # 获取信息
            price_json = json.loads(price_res)
            # print(price_json)
            price_info = price_json['data']
            # 有些车次没有某种类型的座位,判断是否有该座位
            seat_types = price_info.keys()
            if 'A4' in seat_types and result[i]['软卧一等卧'][0] !='':
                result[i]['软卧一等卧'].append(price_info['A4'])
            # if '' in seat_types:
            #     item['软座'].append(price_info[''])
            if 'WZ' in seat_types and result[i]['无座'][0] !='':
                result[i]['无座'].append(price_info['WZ'])
            if 'A1' in seat_types and result[i]['硬座'][0] !='':
                result[i]['硬座'].append(price_info['WZ'])
            if 'O' in seat_types and result[i]['二等座'][0] !='':
                result[i]['二等座'].append(price_info['O'])
            if 'M' in seat_types and result[i]['一等座'][0] !='':
                result[i]['一等座'].append(price_info['M'])
            if 'A9' in seat_types and result[i]['商务座特等座'][0] !='':
                result[i]['商务座特等座'].append(price_info['A9'])
            if 'F' in seat_types and result[i]['动卧'][0] !='':
                result[i]['动卧'].append(price_info['F'])
            if 'A6' in seat_types and result[i]['A6'][0] !='':
                result[i]['高级软卧'].append(price_info['A6'])
            if 'A3' in seat_types and result[i]['硬卧二等卧'][0] !='':
                result[i]['硬卧二等卧'].append(price_info['A3'])
            time.sleep(3)
            print(result[i])
        return result

座位类型对照

A9或P:  商务座;

M:  一等座;

O:  二等座;

A6:  高级软卧;

A4:  软卧一等卧; 

F:  动卧;

A3:  硬卧二等卧;

A2:  软座;

A1:  硬座;

WZ:  无座;

7.完整代码

增加了数据保存和输入验证的函数

from selenium import webdriver
from selenium.webdriver import ActionChains  # 用于控制鼠标滑动
from chaojiying  import Chaojiying_Client  # 超级鹰验证码识别
import base64
import re
from xml import etree
import time
import config  # 账号密码配置文件
from selenium.webdriver.common.keys import Keys  # 模拟点击
import requests
import json


class Login(object):
    def __init__(self):
        self.tk_user = config.TICKET_USER
        self.tk_pw = config.TICKET_PASSWORD
        self.cjy_user = config.CHAOJIYING_USER
        self.cjy_pw = config.CHAOJIYING_PASSWORD
        self.cjy_id = config.CHAOJIYING_ID  # 软件id
        self.url = 'https://www.12306.cn/index/'
        self.driver = webdriver.Chrome()
        # 避免被检测,导致滑动失败
        self.driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
            "source": """
            Object.defineProperty(navigator, 'webdriver', {
              get: () => undefined
            })
          """
        })


    def login(self):
        # 点击首页的登录按钮
        self.driver.find_element_by_xpath('//*[@id="J-header-login"]/a[1]').click()
        time.sleep(1)
        # 跳转后,输入账号,密码
        self.driver.find_element_by_xpath('/html/body/div[2]/div[2]/ul/li[2]/a').click()
        # 账号密码
        self.driver.find_element_by_id('J-userName').send_keys(self.tk_user)
        self.driver.find_element_by_id('J-password').send_keys(self.tk_pw)

        while True:
            try:
                # 图片验证码
                # 将页面截屏
                self.driver.save_screenshot('./all_screen.png')
                # 截取验证码部分的图
                img = self.driver.find_element_by_id('J-loginImgArea')
                img.screenshot('./code1.png')
                position = self.process_cjy('./code1.png')
                # print(position)  # 107,140|253,138
                # position为每个答案点的x,y坐标,为x1,y1|x2,y2|x3,y3,若只有一个点,为x1,y1,处理为[[x1,y1],[x2,y2]...]
                position_list = []
                if '|' in position:
                    position_list = [i.split(',') for i in position.split('|')]
                else:
                    position_list.append(position.split(','))
                print(position_list) # [['107', '140'], ['253', '138']]

                # 获取坐标后,使用动作链ActionChains.move_to_element_with_offset进行相对位置的定位,再选中正确答案
                for l in position_list:
                    x = int(l[0])
                    y = int(l[-1])
                    # perform() 为执行
                    ActionChains(self.driver).move_to_element_with_offset(img, x, y).click().perform()
                    time.sleep(1)
                # 点击登录按钮
                self.driver.find_element_by_xpath('//*[@id="J-login"]').click()
                time.sleep(1)
            except:
                print('验证成功')
                break


        # 滑动验证码
        while True:
            try:
                # 若果滑动出错,获取提示信息,并刷新
                info = self.driver.find_element_by_xpath('//*[@id="J-slide-passcode"]/div/div/div/span').text
                print(info)
                if info == '哎呀,出错了,点击刷新再来一次':
                    # 若滑动出错,则点击‘刷新’
                    self.driver.find_element_by_xpath('//*[@id="J-slide-passcode"]/div/span/a').click()
                    time.sleep(1)
                    # 选中滑块
                    span = self.driver.find_element_by_xpath('//*[contains(@class,"nc_iconfont btn_slid")]')
                    action = ActionChains(self.driver)
                    # 点击长按指定滑块并滑动
                    action.click_and_hold(span).perform()
                    # 滑动到left300 便可解锁
                    action.drag_and_drop_by_offset(span, 400, 0).perform()
                    # 释放鼠标
                    action.release()
                    time.sleep(7)
                else:
                    # 若滑动未出错误提示,正常滑动,获取滑块
                    info = self.driver.find_element_by_xpath('//*[@id="J-slide-passcode"]/div/div/div/span').text
                    print(info)
                    time.sleep(1)
                    # 选中滑块
                    span = self.driver.find_element_by_xpath('//*[contains(@class,"nc_iconfont btn_slid")]')
                    action = ActionChains(self.driver)
                    # 点击长按指定滑块并滑动
                    action.click_and_hold(span).perform()
                    # 滑动到left300 便可解锁
                    action.drag_and_drop_by_offset(span, 400, 0).perform()
                    # 释放鼠标
                    action.release()
                    time.sleep(7)
            except:
                # 判断当前页面url 发生跳转则说明登录成功
                print('登录成功')
                print(self.driver.current_url)  # https://kyfw.12306.cn/otn/view/index.html
                break


    def process_cjy(self, img_path):
        # 使用超级鹰处理验证码
        # 登录超级鹰
        cjy = Chaojiying_Client(self.cjy_user, self.cjy_pw, self.cjy_id)
        with open(img_path, 'rb') as f:
            img = f.read()
        # 9004是我们发送的验证码的格式,然后这个指令的返回值是一个字典,在键为['pic_str']的值的时候,保存地视返回的需要点击的坐标
        # result为每个点的x,y坐标,为x1,y1|x2,y2|x3,y3
        return cjy.PostPic(img, 9004)['pic_str']


    def search_ticket_info(self):
        # 点击登录成功后的确认窗口
        self.driver.find_element_by_xpath('//*[@class="dzp-confirm"]/div/a').click()
        # 选择单程购票
        # 导航栏隐藏窗口处理,需要使用动作链,获取隐藏菜单后才可以进行点击
        nav_bar = self.driver.find_element_by_xpath('//*[@id="J-chepiao"]')
        ActionChains(self.driver).move_to_element(nav_bar).perform()
        self.driver.find_element_by_xpath('//*[contains(text(), "单程")]').click()
        # 跳转来到订票页面,点击弹出提示窗口的确定
        self.driver.find_element_by_xpath('//*[@id="qd_closeDefaultWarningWindowDialog_id"]').click()

        # 开始查询车票,默认为单程-出发地
        # 获取城市名和城市缩写名列表
        city_name, city_eg_name = self.get_city_name_id()

        while True:
            # 在出发地、目的地、出发日等窗口输入内容,需要先点击输入框1
            start_city = input('请输入出发城市:')
            self.driver.find_element_by_xpath('//*[@id="fromStationText"]').click()
            self.driver.find_element_by_xpath('//*[@id="fromStationText"]').send_keys(start_city)
            # 这里输入完成后,需要send_keys(Keys.ENTER)模拟键盘回车,这里和之前上号密码的不一样,账号密码输入不要回车就可以,不回车里面内容不生效
            self.driver.find_element_by_xpath('//*[@id="fromStationText"]').send_keys(Keys.ENTER)

            # 目的地
            end_city = input('请输入到达城市:')
            self.driver.find_element_by_xpath('//*[@id="toStationText"]').click()
            self.driver.find_element_by_xpath('//*[@id="toStationText"]').send_keys(end_city)
            self.driver.find_element_by_xpath('//*[@id="toStationText"]').send_keys(Keys.ENTER)
            train_date = input('请输入出发日期,如1999-09-09形式:')
            check_result = self.check_info(city_name, start_city, end_city, train_date)
            if not check_result:
                print('输入有误,请重新输入!')

            else:
                print('验证通过')
                break
        # 出发日期
        # 将日期的只读属性去掉便于下面输入日期
        js = """document.getElementById('train_date').removeAttribute('readonly')"""
        self.driver.execute_script(js)
        self.driver.find_element_by_id("train_date").clear()  # 清空默认日期值


        self.driver.find_element_by_id("train_date").send_keys(train_date)
        self.driver.find_element_by_id("train_date").send_keys(Keys.ENTER)
        # 隐藏日期下方的详细日期列表
        js = """document.querySelector('body > div.cal-wrap').style.display='none'"""
        self.driver.execute_script(js)

        # 选择查询车次类型 - 默认高铁
        self.driver.find_element_by_xpath('//*[@id="_ul_station_train_code"]/li[1]').click()

        # 点击查询
        self.driver.find_element_by_xpath('//*[@id="query_ticket"]').click()
        time.sleep(2)

        # 保存查询信息
        result = []
        train_info = self.driver.find_element_by_xpath('//*[@id="sear-result"]/p[1]/strong[1]').text + ' 共计'  +  str(len(self.driver.find_elements_by_xpath('//*[@id="queryLeftTable"]/tr'))) + '车次'


        # 用于拼接'https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2021-07-09&leftTicketDTO.from_station=SHH&leftTicketDTO.to_station=HZH&purpose_codes=ADULT' 获得查询结果

        start_city = self.driver.find_element_by_xpath('//*[@id="queryLeftTable"]/tr[1]/td/div/div[2]/strong[1]').text
        end_city = self.driver.find_element_by_xpath('//*[@id="queryLeftTable"]/tr[1]/td/div/div[2]/strong[2]').text
        start_city_eg_name = city_eg_name[city_name.index(start_city)]
        end_city_eg_name = city_eg_name[city_name.index(end_city)]
        query_url = 'https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date={}&leftTicketDTO.from_station={}&leftTicketDTO.to_station={}&purpose_codes=ADULT'.format(train_date, start_city_eg_name, end_city_eg_name)
        print(start_city_eg_name, end_city_eg_name)
        print(query_url)
        # url = 'https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date=2021-07-09&leftTicketDTO.from_station=AOH&leftTicketDTO.to_station=HGH&purpose_codes=ADULT'
        # response = requests.get(url).content.decode()
        # print(response) 使用request 会被重定向


        # 使用selenium 新开窗口,
        # 注意:这里必须加""
        js = 'window.open("{}");'.format(query_url)
        self.driver.execute_script(js)
        # 获取当前窗口句柄
        handles = self.driver.window_handles
        # print(handles)  # ['CDwindow-09080EA2328F0CB9CCCBB02C22A02F74', 'CDwindow-27C3895DC5E7A3D638CAF68E3835062C']
        # 切换窗口
        self.driver.switch_to.window(handles[-1])
        query_res = self.driver.find_element_by_xpath('/html/body/pre').text
        # 关闭窗口
        self.driver.close()
        # 重新获取句柄并切换回当前窗口
        handles = self.driver.window_handles
        self.driver.switch_to.window(handles[-1])

        # 获取信息
        query_json = json.loads(query_res)
        ticket_info = query_json['data']['result']
        for info in ticket_info:
            item = {}
            temp = info.split('|')
            item['车次信息'] = train_info
            item['日期'] = train_date
            item['编号'] = temp[2]
            item['车次'] = temp[3]
            item['首发站'] = temp[4]
            item['终点站'] = temp[5]
            item['上车站'] = query_json['data']['map'][temp[6]]  # 车站与它对应编号的映射
            item['下车站'] = query_json['data']['map'][temp[7]]
            item['出发时间'] = temp[8]
            item['到达时间'] = temp[9]
            item['历时'] = temp[10]
            item['是否可预订'] = temp[11]
            item['上车站编号'] = temp[16]
            item['下车站编号'] = temp[17]
            item['高级软卧'] = [temp[21]]
            item['软卧一等卧'] = [temp[23]]
            item['软座'] = [temp[24]]
            item['无座'] = [temp[26]]
            item['硬卧二等卧'] = [temp[28]]
            item['硬座'] = [temp[29]]
            item['二等座'] = [temp[30]]
            item['一等座'] = [temp[31]]
            item['商务座特等座'] = [temp[32]]
            item['动卧'] = [temp[33]]
            item['其他'] = '--'

            # 用于获取价格的信息,如 train_no、from_station_no、to_station_no、seat_types、train_date
            item['train_no'] = temp[2]
            item['from_station_no'] = temp[16]
            item['to_station_no'] = temp[17]
            item['seat_types'] = temp[35]
            time.sleep(1)
            print(item)
            result.append(item)
        return result


    def get_price_info(self, result):
        for i in range(len(result)):
            # 获取票价
            # 拼接https://kyfw.12306.cn/otn/leftTicket/queryTicketPrice?train_no=5l000G178640&from_station_no=01&to_station_no=07&seat_types=OM9&train_date=2021-07-09
            # print(tr_id_list) # ['ticket', '5l000G754171', '01', '04']
            price_url = 'https://kyfw.12306.cn/otn/leftTicket/queryTicketPrice?train_no={}&from_station_no={}&to_station_no={}&seat_types={}&train_date={}'.format(result[i]['train_no'], result[i]['from_station_no'], result[i]['to_station_no'], result[i]['seat_types'], result[i]['日期'])
            print(price_url)
            js = 'window.open("{}")'.format(price_url)
            self.driver.execute_script(js)
            handles = self.driver.window_handles
            self.driver.switch_to.window(handles[-1])
            price_res = self.driver.find_element_by_xpath('/html/body/pre').text
            self.driver.close()
            time.sleep(1)
            # 切回窗口
            # 重新获取句柄并切换回当前窗口
            handles = self.driver.window_handles
            self.driver.switch_to.window(handles[-1])

            # 获取信息
            price_json = json.loads(price_res)
            # print(price_json)
            price_info = price_json['data']
            # 有些车次没有某种类型的座位,判断是否有该座位
            seat_types = price_info.keys()
            if 'A4' in seat_types and result[i]['软卧一等卧'][0] !='':
                result[i]['软卧一等卧'].append(price_info['A4'])
            # if '' in seat_types:
            #     item['软座'].append(price_info[''])
            if 'WZ' in seat_types and result[i]['无座'][0] !='':
                result[i]['无座'].append(price_info['WZ'])
            if 'A1' in seat_types and result[i]['硬座'][0] !='':
                result[i]['硬座'].append(price_info['WZ'])
            if 'O' in seat_types and result[i]['二等座'][0] !='':
                result[i]['二等座'].append(price_info['O'])
            if 'M' in seat_types and result[i]['一等座'][0] !='':
                result[i]['一等座'].append(price_info['M'])
            if 'A9' in seat_types and result[i]['商务座特等座'][0] !='':
                result[i]['商务座特等座'].append(price_info['A9'])
            if 'F' in seat_types and result[i]['动卧'][0] !='':
                result[i]['动卧'].append(price_info['F'])
            if 'A6' in seat_types and result[i]['A6'][0] !='':
                result[i]['高级软卧'].append(price_info['A6'])
            if 'A3' in seat_types and result[i]['硬卧二等卧'][0] !='':
                result[i]['硬卧二等卧'].append(price_info['A3'])
            time.sleep(3)
            print(result[i])
        return result


    # 校验输入地点,时间等信息是否有误
    def check_info(self, city_name, start_city, end_city, train_date):
        if start_city not in city_name:
            print('出发城市输入有误!')
            return False
        elif end_city not in city_name:
            print('抵达城市输入有误!')
            return False
        elif not re.match(r'(\d{4})-(\d{2})-(\d{2})', train_date):
            print('日期输入有误')
            return False
        else:
            return True


    def save_data(self, result):
        with open('./query_data.json', 'w', encoding = 'utf-8') as f:
            for i in result:
                f.write(json.dumps(i, ensure_ascii=False) + ',\n')


    def get_city_name_id(self):
        response = requests.get('https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9198').content.decode()
        # var station_names = '@bjb|北京北|VAP|beijingbei|bjb|0
        city_name = [i for i in response.split('\'')[1].split('|')][1::5]  # 北京北
        city_eg_name = [i for i in response.split('\'')[1].split('|')][2::5]  # VAP
        return city_name, city_eg_name


    def run(self):
        self.driver.get(url = self.url)
        self.driver.maximize_window()
        time.sleep(1)
        self.login()
        time.sleep(1)
        result = self.search_ticket_info()
        print(result)
        result = self.get_price_info(result)
        print(result)
        self.save_data(result)


if __name__ == '__main__':
    login = Login()
    login.run()

在这里插入图片描述

买票参考
requests参考
票价信息获取及prettytable显示

8.结果对比

查询时候,是5辆高铁,爬取为全部火车信息,应该是在获取列车信息时候,url有点问题,但不影响整体逻辑
高铁列表
在这里插入图片描述
全部列表
在这里插入图片描述

{"车次信息": "北京北 --> 成都东(7月9日  周五) 共计10车次", "日期": "2021-07-09", "编号": "2400000G890E", "车次": "G89", "首发站": "BXP", "终点站": "ICW", "上车站": "北京西", "下车站": "成都东", "出发时间": "06:53", "到达时间": "14:38", "历时": "07:45", "是否可预订": "Y", "上车站编号": "01", "下车站编号": "05", "高级软卧": [""], "软卧一等卧": [""], "软座": [""], "无座": [""], "硬卧二等卧": [""], "硬座": [""], "二等座": ["有", "¥778.5"], "一等座": ["有", "¥1246.0"], "商务座特等座": ["无", "¥2417.0"], "动卧": [""], "其他": "--", "train_no": "2400000G890E", "from_station_no": "01", "to_station_no": "05", "seat_types": "OM9"},
{"车次信息": "北京北 --> 成都东(7月9日  周五) 共计10车次", "日期": "2021-07-09", "编号": "240000K81718", "车次": "K817", "首发站": "BXP", "终点站": "CDW", "上车站": "北京西", "下车站": "成都", "出发时间": "08:01", "到达时间": "12:32", "历时": "28:31", "是否可预订": "Y", "上车站编号": "01", "下车站编号": "24", "高级软卧": [""], "软卧一等卧": ["16", "¥689.0"], "软座": [""], "无座": ["无", "¥252.0"], "硬卧二等卧": ["有", "¥437.0"], "硬座": ["有", "¥252.0"], "二等座": [""], "一等座": [""], "商务座特等座": [""], "动卧": [""], "其他": "--", "train_no": "240000K81718", "from_station_no": "01", "to_station_no": "24", "seat_types": "3141"},
{"车次信息": "北京北 --> 成都东(7月9日  周五) 共计10车次", "日期": "2021-07-09", "编号": "240000G3090K", "车次": "G309", "首发站": "BXP", "终点站": "CUW", "上车站": "北京西", "下车站": "成都东", "出发时间": "08:18", "到达时间": "17:56", "历时": "09:38", "是否可预订": "Y", "上车站编号": "01", "下车站编号": "16", "高级软卧": [""], "软卧一等卧": [""], "软座": [""], "无座": [""], "硬卧二等卧": [""], "硬座": [""], "二等座": ["有", "¥778.5"], "一等座": ["无", "¥1246.0"], "商务座特等座": ["无", "¥2417.0"], "动卧": [""], "其他": "--", "train_no": "240000G3090K", "from_station_no": "01", "to_station_no": "16", "seat_types": "OM9"},
{"车次信息": "北京北 --> 成都东(7月9日  周五) 共计10车次", "日期": "2021-07-09", "编号": "240000G57115", "车次": "G571", "首发站": "BXP", "终点站": "CXW", "上车站": "北京西", "下车站": "成都东", "出发时间": "09:22", "到达时间": "18:55", "历时": "09:33", "是否可预订": "Y", "上车站编号": "01", "下车站编号": "16", "高级软卧": [""], "软卧一等卧": [""], "软座": [""], "无座": [""], "硬卧二等卧": [""], "硬座": [""], "二等座": ["有", "¥778.5"], "一等座": ["5", "¥1246.0"], "商务座特等座": ["4", "¥2417.0"], "动卧": [""], "其他": "--", "train_no": "240000G57115", "from_station_no": "01", "to_station_no": "16", "seat_types": "OM9"},
{"车次信息": "北京北 --> 成都东(7月9日  周五) 共计10车次", "日期": "2021-07-09", "编号": "240000G3070Q", "车次": "G307", "首发站": "BXP", "终点站": "ICW", "上车站": "北京西", "下车站": "成都东", "出发时间": "09:38", "到达时间": "19:14", "历时": "09:36", "是否可预订": "Y", "上车站编号": "01", "下车站编号": "17", "高级软卧": [""], "软卧一等卧": [""], "软座": [""], "无座": [""], "硬卧二等卧": [""], "硬座": [""], "二等座": ["有", "¥778.5"], "一等座": ["4", "¥1246.0"], "商务座特等座": ["3", "¥2417.0"], "动卧": [""], "其他": "--", "train_no": "240000G3070Q", "from_station_no": "01", "to_station_no": "17", "seat_types": "OM9"},
{"车次信息": "北京北 --> 成都东(7月9日  周五) 共计10车次", "日期": "2021-07-09", "编号": "2400000Z490L", "车次": "Z49", "首发站": "BXP", "终点站": "CDW", "上车站": "北京西", "下车站": "成都", "出发时间": "11:28", "到达时间": "08:56", "历时": "21:28", "是否可预订": "Y", "上车站编号": "01", "下车站编号": "10", "高级软卧": [""], "软卧一等卧": ["有", "¥687.5"], "软座": [""], "无座": ["无", "¥254.5"], "硬卧二等卧": ["有", "¥434.5"], "硬座": ["有", "¥254.5"], "二等座": [""], "一等座": [""], "商务座特等座": [""], "动卧": [""], "其他": "--", "train_no": "2400000Z490L", "from_station_no": "01", "to_station_no": "10", "seat_types": "4311"},
{"车次信息": "北京北 --> 成都东(7月9日  周五) 共计10车次", "日期": "2021-07-09", "编号": "240000K11724", "车次": "K117", "首发站": "BXP", "终点站": "CDW", "上车站": "北京西", "下车站": "成都", "出发时间": "11:36", "到达时间": "16:21", "历时": "28:45", "是否可预订": "Y", "上车站编号": "01", "下车站编号": "20", "高级软卧": [""], "软卧一等卧": ["15", "¥702.0"], "软座": [""], "无座": ["无", "¥259.0"], "硬卧二等卧": ["有", "¥448.0"], "硬座": ["有", "¥259.0"], "二等座": [""], "一等座": [""], "商务座特等座": [""], "动卧": [""], "其他": "--", "train_no": "240000K11724", "from_station_no": "01", "to_station_no": "20", "seat_types": "3141"},
{"车次信息": "北京北 --> 成都东(7月9日  周五) 共计10车次", "日期": "2021-07-09", "编号": "240000G34907", "车次": "G349", "首发站": "BXP", "终点站": "ICW", "上车站": "北京西", "下车站": "成都东", "出发时间": "15:13", "到达时间": "22:58", "历时": "07:45", "是否可预订": "Y", "上车站编号": "01", "下车站编号": "05", "高级软卧": [""], "软卧一等卧": [""], "软座": [""], "无座": [""], "硬卧二等卧": [""], "硬座": [""], "二等座": ["有", "¥778.5"], "一等座": ["有", "¥1246.0"], "商务座特等座": ["8", "¥2417.0"], "动卧": [""], "其他": "--", "train_no": "240000G34907", "from_station_no": "01", "to_station_no": "05", "seat_types": "OM9"},
{"车次信息": "北京北 --> 成都东(7月9日  周五) 共计10车次", "日期": "2021-07-09", "编号": "24000000T71C", "车次": "T7", "首发站": "BXP", "终点站": "CDW", "上车站": "北京西", "下车站": "成都", "出发时间": "16:40", "到达时间": "20:44", "历时": "28:04", "是否可预订": "Y", "上车站编号": "01", "下车站编号": "16", "高级软卧": [""], "软卧一等卧": ["4", "¥629.0"], "软座": [""], "无座": ["无", "¥236.0"], "硬卧二等卧": ["有", "¥399.0"], "硬座": ["有", "¥236.0"], "二等座": [""], "一等座": [""], "商务座特等座": [""], "动卧": [""], "其他": "--", "train_no": "24000000T71C", "from_station_no": "01", "to_station_no": "16", "seat_types": "3411"},
{"车次信息": "北京北 --> 成都东(7月9日  周五) 共计10车次", "日期": "2021-07-09", "编号": "24000K436310", "车次": "K4363", "首发站": "BXP", "终点站": "CDW", "上车站": "北京西", "下车站": "成都", "出发时间": "22:06", "到达时间": "05:01", "历时": "30:55", "是否可预订": "Y", "上车站编号": "01", "下车站编号": "20", "高级软卧": [""], "软卧一等卧": ["无", "¥640.0"], "软座": [""], "无座": ["无", "¥240.0"], "硬卧二等卧": ["有", "¥408.0"], "硬座": ["有", "¥240.0"], "二等座": [""], "一等座": [""], "商务座特等座": [""], "动卧": [""], "其他": "--", "train_no": "24000K436310", "from_station_no": "01", "to_station_no": "20", "seat_types": "4311"},
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值