推荐使用谷歌浏览器
基本思路:
一. 这些数据内容是从哪里的?
通过开发者工具进行抓包分析, 分析具体的数据是从哪里的
可以通过用搜索 你想要数据内容, 会给你返回相应的数据
二. 代码实现步骤
确定需求 >>> 发送请求 >>> 获取数据 >>> 解析数据 >>> 保存数据
1.发送请求, 对于相应的数据包url地址发送请求
url的来源:
2.获取数据, 获取响应体json字典数据
3.解析数据, 直接通过键值对提取数据
4.数据展示
下面是代码实现
import requests
import prettytable as pt
import json
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
import time
"""
发送请求,模拟浏览器对于url地址发送请求
快捷批量替换方法:
选中内容,ctrl+R输入正则表达式命令点击全部换即可
(.*?):(.*)
'$1':'$2',
"""
f = open('output/city.json', encoding='UTF-8')
text = f.read()
json_data = json.loads(text)
# 颠倒键和值
new_json_data= dict(zip(json_data.values(),json_data.keys()))
from_city = input("请输入你要出发的城市:")
to_city = input("请输入你要到达的城市:")
date = input("请输入你要出发的时间<2024-05-08>:")
from_station = json_data[from_city]
to_station = json_data[to_city]
# 确定请求网址,分段写。?后面的是请求参数
url = 'https://kyfw.12306.cn/otn/leftTicket/queryG'
# 请求参数 ---> 字典数据类型,构建成完整键值对,键值对与键值对之间要用逗号隔开
data = {
'leftTicketDTO.train_date': date,
'leftTicketDTO.from_station': from_station,
'leftTicketDTO.to_station': to_station,
'purpose_codes': 'ADULT'
}
headers = {
# Cookie:用户信息,常用于检测是否有登陆账号,如果不用cookie有可能被反爬获取不到数据
'Cookie': '_uab_collina=171239727849315335040109; JSESSIONID=C1AA704A0C77840C7E67777EA654465B; route=6f50b51faa11b987e576cdb301e545c4; BIGipServerotn=1675165962.64545.0000; guidesStatus=off; highContrastMode=defaltMode; cursorStatus=off; _jc_save_fromStation=%u957F%u6C99%2CCSQ; _jc_save_toStation=%u4E0A%u6D77%2CSHH; _jc_save_fromDate=2024-04-07; _jc_save_toDate=2024-04-06; _jc_save_wfdc_flag=dc',
# User-Agent:用户代理,表示浏览器基本身份标识
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36'
}
# 发送请求 ---> 返回数据 <Response[200]> 响应对象,此时得到的是响应对象,200表示请求成功
response = requests.get(url=url, params=data, headers=headers)
# 获取数据 response.json()获取响应对象json字典数据
result = response.json()['data']['result']
tb = pt.PrettyTable()
tb.field_names = ([
'序号',
'车次',
'出发站',
'到达站',
'出发时间',
'到达时间',
'耗时',
'特等座',
'一等座',
'二等座',
'软卧',
'硬卧',
'硬座',
'无座'
])
page = 0
# 定义一个车次列表
trips_list = []
num1_list = []
num2_list = []
for index in result:
index_list = index.split("|")
del index_list[0]
trips = index_list[1] # 车次
time_1 = index_list[7] # 发车时间
time_2 = index_list[8] # 到达时间
use_time = index_list[9] # 耗时
topGarde = index_list[24] # 特等座
if topGarde:
pass
else:
topGarde = index_list[31]
first_class = index_list[30] # 一等
second_class = index_list[29] # 二等
hard_sleeper = index_list[27] # 硬卧
hard_seat = index_list[28] # 硬座
no_seat = index_list[25] # 无座
soft_sleeper = index_list[22] # 软卧
departure_station = new_json_data[index_list[5]]
arrival_station = new_json_data[index_list[6]]
num1 = index_list[15]
num2 = index_list[16]
# 向车次列表里面添加查询到的车次
trips_list.append(trips)
num1_list.append(num1)
num2_list.append(num2)
dict_data = {
'车次': trips,
'出发时间': time_1,
'到达时间': time_2,
'耗时': use_time,
'特等座': topGarde,
'一等座': first_class,
'二等座': second_class,
'软卧': soft_sleeper,
'硬卧': hard_sleeper,
'硬座': hard_seat,
'无座': no_seat
}
# print(dict_data)
tb.add_row([
page,
trips,
departure_station,
arrival_station,
time_1,
time_2,
use_time,
topGarde,
first_class,
second_class,
soft_sleeper,
hard_sleeper,
hard_seat,
no_seat
])
page += 1
print(tb)
serial_number = int(input("请输入您想购买的车次的序号:"))
want_trips = trips_list[serial_number]
num1 = num1_list[serial_number]
num2 = num2_list[serial_number]
"""
selenium --->浏览器驱动--->操作浏览器
实现模拟人的行为去操作浏览器
"""
# 打开浏览器/创建浏览器
driver = webdriver.Chrome()
# 输入网址
driver.get('https://kyfw.12306.cn/otn/resources/login.html')
# 此时的等待15秒用于在电脑上扫码登录12306
time.sleep(15)
# 点击车票预定
driver.find_element(By.XPATH, '//*[@id="link_for_ticket"]').click()
# 选择出发城市并点击一下
driver.find_element(By.XPATH, '//*[@id="fromStationText"]').click()
# 输入出发城市,并回车
driver.find_element(By.XPATH, '//*[@id="fromStationText"]').send_keys(from_city)
driver.find_element(By.XPATH, '//*[@id="fromStationText"]').send_keys(Keys.ENTER)
# 选择出发城市并点击一下
driver.find_element(By.XPATH, '//*[@id="toStationText"]').click()
# 输入出发城市,并回车
driver.find_element(By.XPATH, '//*[@id="toStationText"]').send_keys(to_city)
driver.find_element(By.XPATH, '//*[@id="toStationText"]').send_keys(Keys.ENTER)
# 选择日期
e = driver.find_element(By.XPATH, '//*[@id="train_date"]')
e.clear()
e.send_keys(date)
# 点击查询
driver.find_element(By.XPATH, '//*[@id="query_ticket"]').click()
# 点击预定
# 因为查询车票
driver.implicitly_wait(10)
driver.find_element(By.CSS_SELECTOR, f'#ticket_{want_trips}_{num1}_{num2} > td.no-br').click()
# driver.switch_to.window(driver.window_handles[-1])
driver.implicitly_wait(10)
# 选择乘车人
driver.find_element(By.XPATH, '//*[@id="normalPassenger_0"]').click()
driver.implicitly_wait(10)
# 是学生,需要点击确认购买学生票。或者选择不购买学生票
# 点击确认
# driver.find_element(By.XPATH, '//*[@id="dialog_xsertcj_ok"]').click()
# 点击取消
driver.find_element(By.XPATH, '//*[@id="dialog_xsertcj_cancel"]').click()
# # 点击提交订单
driver.find_element(By.XPATH, '//*[@id="submitOrder_id"]').click()
driver.implicitly_wait(10)
# 点击确认
time.sleep(2)
driver.find_element(By.XPATH, '//*[@id="qr_submit_id"]').click()
driver.find_element(By.XPATH, '//*[@id="qr_submit_id"]').click()
time.sleep(100)
代码讲解:
1.f = open('output/city.json', encoding='UTF-8')这里的city.json是提前查询到的火车站的代码
2.颠倒键值的原因是后面需要代码来匹配车站
# 颠倒键和值
new_json_data= dict(zip(json_data.values(),json_data.keys()))
3.发送请求以及获取数据,使用cookie是因为12306有反爬机制,需要伪装一下,url拆开成了url和data这两个变量,接着是headers的获取
获取之后用requests.get()函数向其发送请求,返回一个对象response,使用其对应的json函数拿到我们需要的json字典,再取到对应的列表
from_city = input("请输入你要出发的城市:")
to_city = input("请输入你要到达的城市:")
date = input("请输入你要出发的时间<2024-05-08>:")
from_station = json_data[from_city]
to_station = json_data[to_city]
# 确定请求网址,分段写。?后面的是请求参数
url = 'https://kyfw.12306.cn/otn/leftTicket/queryG'
# 请求参数 ---> 字典数据类型,构建成完整键值对,键值对与键值对之间要用逗号隔开
data = {
'leftTicketDTO.train_date': date,
'leftTicketDTO.from_station': from_station,
'leftTicketDTO.to_station': to_station,
'purpose_codes': 'ADULT'
}
headers = {
# Cookie:用户信息,常用于检测是否有登陆账号,如果不用cookie有可能被反爬获取不到数据
'Cookie': '_uab_collina=171239727849315335040109; JSESSIONID=C1AA704A0C77840C7E67777EA654465B; route=6f50b51faa11b987e576cdb301e545c4; BIGipServerotn=1675165962.64545.0000; guidesStatus=off; highContrastMode=defaltMode; cursorStatus=off; _jc_save_fromStation=%u957F%u6C99%2CCSQ; _jc_save_toStation=%u4E0A%u6D77%2CSHH; _jc_save_fromDate=2024-04-07; _jc_save_toDate=2024-04-06; _jc_save_wfdc_flag=dc',
# User-Agent:用户代理,表示浏览器基本身份标识
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36'
}
# 发送请求 ---> 返回数据 <Response[200]> 响应对象,此时得到的是响应对象,200表示请求成功
response = requests.get(url=url, params=data, headers=headers)
# 获取数据 response.json()获取响应对象json字典数据
result = response.json()['data']['result']
4.使用了prettytable模块使输出好看一些
trips_list = []
num1_list = []
num2_list = []
三个列表用来储存查询到的车次的一些信息,方便我们后面执行购票这个操作
tb = pt.PrettyTable()
tb.field_names = ([
'序号',
'车次',
'出发站',
'到达站',
'出发时间',
'到达时间',
'耗时',
'特等座',
'一等座',
'二等座',
'软卧',
'硬卧',
'硬座',
'无座'
])
page = 0
# 定义一个车次列表
trips_list = []
num1_list = []
num2_list = []
5.本次程序最主要的部分,获取车次信息,通过循环result这个列表,先将每一个字符串再分割列表,此时就可以处理对应的信息,根据不同信息的不同索引我们一一赋值,
trips_list = []
num1_list = []
num2_list = []
并向这三个列表里面里加入所需要的信息,最后我们打印tb对象我们就可以在控制台看到出发地和目的地所有的车次信息,这里只会显示直达的信息,如果没有直达列车暂时不做考虑。
for index in result:
index_list = index.split("|")
del index_list[0]
trips = index_list[1] # 车次
time_1 = index_list[7] # 发车时间
time_2 = index_list[8] # 到达时间
use_time = index_list[9] # 耗时
topGarde = index_list[24] # 特等座
if topGarde:
pass
else:
topGarde = index_list[31]
first_class = index_list[30] # 一等
second_class = index_list[29] # 二等
hard_sleeper = index_list[27] # 硬卧
hard_seat = index_list[28] # 硬座
no_seat = index_list[25] # 无座
soft_sleeper = index_list[22] # 软卧
departure_station = new_json_data[index_list[5]]
arrival_station = new_json_data[index_list[6]]
num1 = index_list[15]
num2 = index_list[16]
# 向车次列表里面添加查询到的车次
trips_list.append(trips)
num1_list.append(num1)
num2_list.append(num2)
dict_data = {
'车次': trips,
'出发时间': time_1,
'到达时间': time_2,
'耗时': use_time,
'特等座': topGarde,
'一等座': first_class,
'二等座': second_class,
'软卧': soft_sleeper,
'硬卧': hard_sleeper,
'硬座': hard_seat,
'无座': no_seat
}
# print(dict_data)
tb.add_row([
page,
trips,
departure_station,
arrival_station,
time_1,
time_2,
use_time,
topGarde,
first_class,
second_class,
soft_sleeper,
hard_sleeper,
hard_seat,
no_seat
])
page += 1
print(tb)
6.接下来实现的就是买票功能,由于12306网页登录有两种方式我都去做了。
一是登录界面扫码登录
扫码登录是比较好实现的:
创建driver对象后输入登录网址即可,此时手动扫码登录就行
# 打开浏览器/创建浏览器
driver = webdriver.Chrome()
# 输入网址
driver.get('https://kyfw.12306.cn/otn/resources/login.html')
# 此时的等待15秒用于在电脑上扫码登录12306
time.sleep(15)
二是账号登录
账号登录可能需要验证我这里是需要验证的登录过程:
用driver来模拟人的操作,对应的账号密码以及身份证号后四位记得改成自己需要的,send_keys函数可以向对应位置填入内容,click函数可以点击对应位置,id需要使用开发者工具自己去找,这一步还需要在控制台中输入手机收到的验证码即可。
driver = webdriver.Chrome()
# 输入网址
driver.get('https://kyfw.12306.cn/otn/resources/login.html')
# 输入账号 ---> 找到账号输入框 selenium 通过元素面板去定位元素
driver.find_element(By.XPATH, '//*[@id="J-userName"]').send_keys('账号')
# 输入密码
driver.find_element(By.XPATH, '//*[@id="J-password"]').send_keys('密码')
# 点击登录
driver.find_element(By.XPATH, '//*[@id="J-login"]').click()
# 输入证件后四位
# 等待验证页面加载
driver.implicitly_wait(10)
# time.sleep(1)
driver.find_element(By.XPATH, '//*[@id="id_card"]').send_keys('身份证号后四位')
# 点击发送验证码
driver.find_element(By.XPATH, '//*[@id="verification_code"]').click()
# 输入验证码
captcha = input("请输入您收到的验证码:")
driver.find_element(By.XPATH, '//*[@id="code"]').send_keys(captcha)
# 点击确认登录
driver.find_element(By.XPATH, '//*[@id="sureClick"]').click()
7.最后就是一路输入出发地目的地,出发时间,然后点击确认即可,值得注意的就是预定那里的点击比较难搞,预定点击的定位方式也有所不同,这个位置是可以用之前result里面的信息通过拼接字符串的方式拼出来,
trips_list = []
num1_list = []
num2_list = []
这也是设置这三个列表的原因
# 点击车票预定
driver.find_element(By.XPATH, '//*[@id="link_for_ticket"]').click()
# 选择出发城市并点击一下
driver.find_element(By.XPATH, '//*[@id="fromStationText"]').click()
# 输入出发城市,并回车
driver.find_element(By.XPATH, '//*[@id="fromStationText"]').send_keys(from_city)
driver.find_element(By.XPATH, '//*[@id="fromStationText"]').send_keys(Keys.ENTER)
# 选择出发城市并点击一下
driver.find_element(By.XPATH, '//*[@id="toStationText"]').click()
# 输入出发城市,并回车
driver.find_element(By.XPATH, '//*[@id="toStationText"]').send_keys(to_city)
driver.find_element(By.XPATH, '//*[@id="toStationText"]').send_keys(Keys.ENTER)
# 选择日期
e = driver.find_element(By.XPATH, '//*[@id="train_date"]')
e.clear()
e.send_keys(date)
# 点击查询
driver.find_element(By.XPATH, '//*[@id="query_ticket"]').click()
# 点击预定
# 因为查询车票
driver.implicitly_wait(10)
driver.find_element(By.CSS_SELECTOR, f'#ticket_{want_trips}_{num1}_{num2} > td.no-br').click()
# driver.switch_to.window(driver.window_handles[-1])
driver.implicitly_wait(10)
# 选择乘车人
driver.find_element(By.XPATH, '//*[@id="normalPassenger_0"]').click()
driver.implicitly_wait(10)
# 是学生,需要点击确认购买学生票。或者选择不购买学生票
# 点击确认
# driver.find_element(By.XPATH, '//*[@id="dialog_xsertcj_ok"]').click()
# 点击取消
driver.find_element(By.XPATH, '//*[@id="dialog_xsertcj_cancel"]').click()
# # 点击提交订单
driver.find_element(By.XPATH, '//*[@id="submitOrder_id"]').click()
driver.implicitly_wait(10)
# 点击确认
time.sleep(2)
driver.find_element(By.XPATH, '//*[@id="qr_submit_id"]').click()
driver.find_element(By.XPATH, '//*[@id="qr_submit_id"]').click()
time.sleep(100)
最后在手机上付款就可以了。
程序还有很多不足之处,比如票卖完了没有考虑进去,查票和购票操作可以写成函数方便调用,还可以改写成面向对象的方式会使代码的复用性提高还有很多功能可以做。