第十二、十三章 selenium练习12306案例
0. 前言
这个案例旨在练习selenium方法,以及面向对象编程的代码敲打。本案例的功能是(按顺序罗列):
- 打开12306登录界面
- 窗口最大化 (登录需要自己扫码,后续可以尝试获得cookie)
- 跳转个人中心界面
- 填写出发地
- 填写目的地
- 填写出发日期
- 跳转到车次及余票查询页面
- 点击通告窗口确定按钮
- 点击查询按钮
- 查询我们想要的车次一等座二等座是否有票
- 点击预定按钮
- 跳转购票确认页面
- 勾选购票人
- 选择座位类别
- 点击提交按钮
- 跳转到核对信息窗口
- 点击确认购买按钮
- 系统生成订单
- 自行操作支付购买
- 购票成功
这里许多点击动作selenium的操作不响应,有的需要设置显示等待。老师的最后提交按钮是用循环不断点击,我是用execut_script()方法代替,亲测该方法屡试不爽。就是没有不成功的。
后面可以丰富该案例的功能,使它更实用。比如可以在程序执行后跳出交互界面,输入想要查询的车次信息,输入出发地,目的地,出发日期就可以打印出对应的车次信息,自己可以查看打印出的车次信息然后决定购买哪一趟,交互界面点击输入车次信息,输入回车后,程序继续运行后面的代码,然后交互让你填写购票人姓名,然后就是自动帮你生成订单。你只需要手机扫码支付就可以了。有时间可以整整。
1. 登录的实现
我们这一步先研究登录网站。我们用面向对象编程,这一步我们实现的目标是,定义项目框架,执行程序后,登录网站,并且提示已经登录成功。注意看代码中的注释:
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait # 条件等待需要用
from selenium.webdriver.support import expected_conditions as EC # 设置等待条件时要用到
driver = webdriver.Chrome() # 类放在全局里面,避免类调用后销毁的时候,连同驱动一同销毁。
class TrainSpider():
login_url = 'https://kyfw.12306.cn/otn/resources/login.html' # 登录界面
personal_url = 'https://kyfw.12306.cn/otn/view/index.html' # 登陆后个人界面
def __init__(self, from_station, to_station, train_data):
self.from_station = from_station
self.to_station = to_station
self.train_data = train_data
def login(self):
driver.get(self.login_url)
driver.maximize_window() # 窗口最大化
WebDriverWait(driver,300).until( # 这一行传入的第一个是驱动,第二个是等待时间
EC.url_contains(self.personal_url) # 这一行传入的是包含跟人界面的url条件,条件满足就不再继续等待
)
print('已经登录成功')
def run(self): # 用来封装项目的基本功能,比如买票,只要调用这个方法就可以实现相应功能
# 登录
self.login()
def main(): # 用来调用各个方法
spider = TrainSpider('西安', '郑州', '2021-01-30') # 实例化类
spider.run()
if __name__ == '__main__': # 主入口,调用主函数开始执行
main()
程序执行后,先跳出第一个页面,登录界面。用app扫码登录后,跳出个人界面。然后程序执行结束,打印出登录成功提醒。
执行结果:
注意:
- 不要把驱动写入类里面,因为类调用后就会销毁,而驱动也会随着类销毁而失去,导致浏览器打开后又迅速消失。我们要放在全局变量里面。
- 登录时,我们设置了显示等待,条件是个人中心的界面url。
2. 车次及余票查询
第二步就到了一个重点了,是车次和余票的查询。我们登录后,就到了一个“单程”的界面了,这个界面对应得地址是:
https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc
下面我要做一个逻辑了,是车次与余票查询的逻辑。我们定义一个方法,叫“leftTicket”,在登录的方法下面。并且在run方法里面调用一下这个方法:
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait # 条件等待需要用
from selenium.webdriver.support import expected_conditions as EC # 设置等待条件时要用到
driver = webdriver.Chrome() # 类放在全局里面,避免类调用后销毁的时候,连同驱动一同销毁。
class TrainSpider():
login_url = 'https://kyfw.12306.cn/otn/resources/login.html' # 登录界面
personal_url = 'https://kyfw.12306.cn/otn/view/index.html' # 登陆后个人界面
left_ticket_url = 'https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc' # 车次余票的url
def __init__(self, from_station, to_station, train_data):
self.from_station = from_station
self.to_station = to_station
self.train_data = train_data
def login(self):
driver.get(self.login_url)
driver.maximize_window() # 窗口最大化
WebDriverWait(driver,300).until(
EC.url_contains(self.personal_url)
)
print('已经登录成功')
def search_left_ticket(self):
pass
def run(self): # 用来封装项目的基本功能,比如买票,只要调用这个方法就可以实现相应功能
# 1. 登录
self.login()
# 2. 车次以及余票查询
self.search_left_ticket()
def main(): # 用来调用各个方法
spider = TrainSpider('西安', '郑州', '2021-01-30')
spider.run()
if __name__ == '__main__':
main()
里面的逻辑怎么写呢?我们需要打开车次以及余票的页面,所以要把这个页面的url放在上面。
left_ticket_url = 'https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc' # 车次余票的url
余票查询后,我们需要跳转到买票的界面。跳转之前,我们需要填写“出发地”,“目的地”,“出发日期”等信息。上次课我们是手动填写的,这一次我们用代码来解决。
我们右键检查一下网页源码:
我们发现有两个input标签,第一个input标签的type是hidden隐藏的意思。说明出发地和目的地的获取并不是通过文本,而是通过value值,就是城市的代号得到的。如果你通过Send_keys(‘长沙’)是没有用的。所以,你需要获取全国车站对应的代号,通过这个代号来传递车站信息。我已经准备好了一个csv文件,专门存储车站信息的,如图:
2.1 车站信息读取
接下来我们要做的事是把这些车站的信息读取出来,然后用于后面的车次查询操作。我们回顾一下读取文件的步骤:
import csv
with open('stations.csv','r',encoding='utf-8') as f:
reader = csv.DictReader(f)
info_dict = {}
info_lst = []
for line in reader:
name = line['name']
code = line['code']
print('name:{},code:{}'.format(name,code))
打印结果
name:太原,code:TYV
name:武汉,code:WHN
name:王家营西,code:KNM
name:乌鲁木齐,code:WAR
name:西安北,code:EAY
name:西安,code:XAY
name:西安南,code:CAY
name:西宁,code:XNO
name:银川,code:YIJ
name:郑州,code:ZZF
name:阿尔山,code:ART
... ...
太占篇幅,后面的省略了。
2.2 车站信息添加方法
我们可以定义一个方法,读取文件,把读取到的信息新键一个字典,把车站名作为键,车站代号作为值,这样我们调用的时候就方便多了。我们把这个逻辑写入代码,注意看注释:
import csv
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait # 条件等待需要用
from selenium.webdriver.support import expected_conditions as EC # 设置等待条件时要用到
driver = webdriver.Chrome() # 类放在全局里面,避免类调用后销毁的时候,连同驱动一同销毁。
class TrainSpider():
login_url = 'https://kyfw.12306.cn/otn/resources/login.html' # 登录界面
personal_url = 'https://kyfw.12306.cn/otn/view/index.html' # 登陆后个人界面
left_ticket_url = 'https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc' # 车次余票的url
def __init__(self, from_station, to_station, train_data):
self.from_station = from_station
self.to_station = to_station
self.train_data = train_data
self.init_station_code() # 在初始化方法里面就调用车站信息读取方法,这样当这个类一旦初始化完成的时候,所有的站点也都初始化好待用了
self.station_codes_dict = {} # 把车站信息字典放在初始化方法里,方便调用
def init_station_code(self): # 读取stations.csv文件,并新键字典,把车站名作为建,车站代号作为值
# station_codes_dict = {} # 假如我们把这个字典放在这里,那么其他方法相使用就使用不了,所以我们可以把它放到初始化方法里。
with open('stations.csv', 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for line in reader:
name = line['name']
code = line['code']
self.station_codes_dict[name] = code # 这里我们只需要调用一下初始化方法里的字典
def login(self):
driver.get(self.login_url)
driver.maximize_window() # 窗口最大化
WebDriverWait(driver,300).until(
EC.url_contains(self.personal_url)
)
print('已经登录成功')
def search_left_ticket(self):
pass
def run(self): # 用来封装项目的基本功能,比如买票,只要调用这个方法就可以实现相应功能
# 1. 登录
self.login()
# 2. 车次以及余票查询
self.search_left_ticket()
def main(): # 用来调用各个方法
spider = TrainSpider('西安', '郑州', '2021-01-30')
spider.run()
if __name__ == '__main__':
main()
这段代码需要说明的是:
- 车站信息读取方法我们在初始化方法里面直接调用了,这样当这个类初始化时,车站信息就被读取好了,方便使用。
- 车站信息读取方法的最后一步是把读取的内容添加到一个空字典里,这个空字典我们放在了初始化方法里面,这样的其他的方法也可以调用。而同样的,车站信息读取方法要用也需要
"self.station_codes_dict[name] = code"这样调用。
2.3 车站信息导入输入框
我们怎样把这个车站信息填入网站上的出发站和到达站输入框呢,因为这里的input标签是隐藏的类型。所以我们并不能直接用selenium的send_keys方法去操作。这里需要selenium提供的一个叫着"execute_script()"方法,这个方法的主要作用是它可以调用一些JavaScript()方法的操作,例如拖动网页窗口的滚动条这样的操作,在selenium里面并没有提供这样的方法,但是提供了execute_script(),可以调用JavaScript()里的相关操作方法来实现。
execute_script()方法可以调用JavaScript()方法
下面看代码,注意看注释:
import csv
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait # 条件等待需要用
from selenium.webdriver.support import expected_conditions as EC # 设置等待条件时要用到
driver = webdriver.Chrome() # 类放在全局里面,避免类调用后销毁的时候,连同驱动一同销毁。
class TrainSpider():
login_url = 'https://kyfw.12306.cn/otn/resources/login.html' # 登录界面
personal_url = 'https://kyfw.12306.cn/otn/view/index.html' # 登陆后个人界面
left_ticket_url = 'https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc' # 车次余票的url
def __init__(self, from_station, to_station, train_data):
self.from_station = from_station
self.to_station = to_station
self.train_data = train_data
self.init_station_code() # 在初始化方法里面就调用车站信息读取方法,这样当这个类一旦初始化完成的时候,所有的站点也都初始化好待用了
self.station_codes_dict = {} # 把车站信息字典放在初始化方法里,方便调用
def init_station_code(self): # 读取stations.csv文件,并新键字典,把车站名作为建,车站代号作为值
# station_codes_dict = {} # 假如我们把这个字典放在这里,那么其他方法相使用就使用不了,所以我们可以把它放到初始化方法里。
with open('stations.csv', 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
info_dict = {}
info_lst = []
for line in reader:
name = line['name']
code = line['code']
self.station_codes_dict[name] = code # 这里我们只需要调用一下初始化方法里的字典
def login(self):
driver.get(self.login_url)
driver.maximize_window() # 窗口最大化
WebDriverWait(driver,300).until(
EC.url_contains(self.personal_url)
)
print('已经登录成功')
def search_left_ticket(self):
driver.get(self.left_ticket_url)
# 出发地
from_station_input = driver.find_element_by_id('fromStation') # 找到出发地输入框元素
from_station_code = self.station_codes_dict[self.from_station] # 从字典中提取车站代号
driver.execute_script('arguments[0].value="%s"'%from_station_code,from_station_input) # 把实例出发地传入输入框
# 上面这行代码的解释:"arguments[0].value"这是Java里的占位符,"%s"这是python里的占位符
# %from_station_code,from_station_input这句意思是用后面的from_station_input被前面的from_station_code代替
# 目的地
to_station_input = driver.find_element_by_id('toStation') # 找到目的地输入框元素
to_station_code = self.station_codes_dict[self.to_station] # 从字典中提取车站代号
driver.execute_script('arguments[0].value="%s"' % to_station_code, to_station_input) # 把实例目的地传入输入框
def run(self): # 用来封装项目的基本功能,比如买票,只要调用这个方法就可以实现相应功能
# 1. 登录
self.login()
# 2. 车次以及余票查询
self.search_left_ticket()
def main(): # 用来调用各个方法
spider = TrainSpider('西安', '郑州', '2021-01-30')
spider.run()
if __name__ == '__main__':
main()
注意,这里执行后,虽然输入框里面并没有显示我们输入的城市名西安,但是实际上我们的代号“XAY”已经成功传入,可以右键查看。在查看前千万不要用鼠标点击输入框,因为点击有清除输入框内容的功能,点击后,你再右键查看,会发现value值是空的。但尽管如此,我们的车站代号还是成功传入了的。
2.4 出发日期与查询按钮
下面我们定义日期和点击查询按钮的代码:
import csv
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait # 条件等待需要用
from selenium.webdriver.support import expected_conditions as EC # 设置等待条件时要用到
driver = webdriver.Chrome() # 类放在全局里面,避免类调用后销毁的时候,连同驱动一同销毁。
class TrainSpider():
login_url = 'https://kyfw.12306.cn/otn/resources/login.html' # 登录界面
personal_url = 'https://kyfw.12306.cn/otn/view/index.html' # 登陆后个人界面
left_ticket_url = 'https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc' # 车次余票的url
def __init__(self, from_station, to_station, train_data):
self.from_station = from_station
self.to_station = to_station
self.train_data = train_data
self.init_station_code() # 在初始化方法里面就调用车站信息读取方法,这样当这个类一旦初始化完成的时候,所有的站点也都初始化好待用了
self.station_codes_dict = {} # 把车站信息字典放在初始化方法里,方便调用
def init_station_code(self): # 读取stations.csv文件,并新键字典,把车站名作为建,车站代号作为值
# station_codes_dict = {} # 假如我们把这个字典放在这里,那么其他方法相使用就使用不了,所以我们可以把它放到初始化方法里。
with open('stations.csv', 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
info_dict = {}
info_lst = []
for line in reader:
name = line['name']
code = line['code']
self.station_codes_dict[name] = code # 这里我们只需要调用一下初始化方法里的字典
def login(self):
driver.get(self.login_url)
driver.maximize_window() # 窗口最大化
WebDriverWait(driver,300).until(
EC.url_contains(self.personal_url)
)
print('已经登录成功')
def search_left_ticket(self):
driver.get(self.left_ticket_url