python爬虫(十七)12306案例 您所在的位置:网站首页 怎样登录12306网站 python爬虫(十七)12306案例

python爬虫(十七)12306案例

2023-08-11 02:25| 来源: 网络整理| 查看: 265

12306案例 学习目标

通过案例复习selenium的知识点,通过selenium抓取Ajax数据,使用Ajax技术,打开页面的时候不会完全显示内容,通过按钮操作后网页不会全部更新,实现部分界面的增量数据更新。只更新数据不刷新整个界面。

需求

人工操作12306的购票流程,让程序按照人操作的流程去模拟操作,这里登录界面需要扫码,先不处理验证框的问题。

步骤

第一步,登录;第二步,车次及余票的查询(点击查询按钮),进入购票界面;第三步,解析车次列表数据,人为的选择是通过肉眼判断的,要通过程序实现对车次列表的解析;第四步,确认乘客信息和席别;第五步,核对信息。

第一步登录

1.用类来实现12306的操作,用面向对象的方式搭建好整体的框架,通过实例化对象的方式把需要初始化的变量传递进去,实现部分函数的功能,登录成功之后,用显示等待去判断。 2.创建run函数用来调用使用的函数,先创建实现登录的函数,这里需要扫码进入(手动),之后判断登录成功后是否显示个人信息的界面,如果显示就是登录成功。

第二步车次以及余票的查询

​实现车次列表的查询 创建查票的函数,用驱动加载网站,用隐式等待暂停2秒,点击确定按钮,关闭弹窗。人为的操作是输入出发地、目的地和出发日,再点击查询,现在要浏览器模仿人的操作执行代码。在出发站输入框点右键,检查,光标定位到第二个input标签,但是在第一个input标签的value值中有出发地的代号,目的地同样存放的是城市的代号。我们选择出发地和目的地的时候需要把input里面的value值改成相应站台的代号。 在这里插入图片描述 需要把获得的代号文件导入进去,通过站台获得相应的代号。

得到站台相对应代号的字典 import csv with open('stations.csv', 'r',encoding='utf-8') as f: read = csv.DictReader(f) # 读出是两对值 stations_dict = {} # 组成一对字典 for line in read: # print(line) name = line['name'] code = line['code'] stations_dict[name] = code print(stations_dict)

得到的字典在后续的函数中需要使用的,要把传入的站台转换为代号进行程序的执行,需要字典查询对应的代号。要在初始化的函数中定义字典,运行获得站台字典的函数,需要提前定义字典的变量,self.stations_dict = {},这样的话就可以在任何函数中都能使用,跨函数进行调用数据。 通过站台得到代号之后,要传入到value中。在查票函数中进行操作,先定位input标签,把输入的站台转换为代号,在12306程序内部只接收代号。这时运行会发现出发地和目的地输入框里没有内容,但是在网页空白处点右键,用元素选择器查看,每个框里是有相应的代码的。但是用右键进行检查发现代码中的value值又没了,那是因为右键进行检查的时候,对数据进行了刷新,数据又消失了,这是中途做的测试,不影响。 在这里插入图片描述 日期的value默认值永远都是那一天,不用管。把传入的日期放入value中。 在这里插入图片描述 出发地、目的地、日期输入完成之后,点击查询,进行车次的检索。 总结: 1 进入了查票的网页,需要等待页面加载几秒,然后处理弹窗 2 填入出发地、目的地和日期三个数据 - 定位到输入框 - 拿到站台的代号(日期是没有的 但是日期需要注意格式) - 把站台代号填入标签的value中 3 点击查询按钮 这个步骤通过代码完成了车次及余票的查询

第三步解析车次列表的数据 页面分析

用selenium爬取就不用到网页源码中查看了,直接右键检查就可以了。找到任意一个车次,点右键,检查,定位到"G534": ['O', 'M']}) spider.run()

有时会出现程序运行完成之后,网页自动关闭的情况。这涉及到垃圾回收机制,是因为用驱动打开浏览器是在类里面进行的操作,类运行结束后,相应的驱动也会结束,驱动随着类的消亡也消亡,这时候可以把驱动放到外面就可以了。

第四步确认乘客信息和席别

程序跳转到确认乘客信息和席别的url:https://kyfw.12306.cn/otn/confirmPassenger/initDc 这时我们是在同一个窗口进行的操作,会覆盖之前的浏览器地址。 这时的列车信息就不需要我们去操作了,是按照之前选择的内容进行选择的,乘客信息里面的内容需要进行点击操作,在这里点击姓名或者姓名前面的小框都可以选中乘客。需要进行的操作为: 1,确认乘客信息。可能会有多个人,以列表的形式在实例化对象的时候传进去。 用显示等待,看是否出现确认信息的网页,如果出现就等待成功。光标在乘客姓名处点右键,发现乘客信息是在li标签下的label里,如果有多个乘客会分别出现在多个li标签里面。li标签都在ul标签内,我们要先定位到ul标签,然后在里面找到li标签的文本值。这里找到的乘车人有多个,所以要在element后加个s,返回的是所有乘车人的列表。遍历取出每一个乘车人,看是否在实例化对象时候传入的乘车人姓名列表中,如果在,就点击乘车人的标签进行选中。 2,确认坐席。需要用selenium操作下拉菜单。在席别下拉框中点击,进行选中操作,点右键检查,定位到select标签,操作之前需要导入select类。 在这里插入图片描述

利用id定位到席别选择框,导入select类,把定位到的选择框传入select类中进行操作,这里可以看到O代表二等座,M代表一等座,9代表商务座,这时只需要把每个座位的value值传入到select中进行选择即可。 3,点击提交订单按钮。

第五步 核对信息

用显示等待进行判断,如果出现核对信息框,就进行点击。 完整的代码实现如下:

from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import Select # 第四步确认坐席时使用 import time import csv """用面向对象的方式搭建好整体的框架,通过实例化对象的方式把需要初始化的变量传递进去,实现部分函数的功能 登录成功之后,用显示等待去判断""" class TrainSpider(): """定义初始化方法 用类初始化对象,在实例化对象的时候把三个参数传进去,否则创建不成功""" def __init__(self, from_station, to_station, train_date, ticket_info, passengers): """ :param from_station: 出发地 :param to_station: 目的地 :param train_date: 出发日期 :param ticket_info: 车次以及坐席,9是特等坐,O是二等座,M是一等座 {"G534":['O','M']},需要传到main里的参数里 :param passengers: 第四步添加的,需要乘车的人 """ self.from_station = from_station self.to_station = to_station self.train_date = train_date self.ticket_info = ticket_info self.passengers = passengers self.driver = webdriver.Edge() # 第一步登录的url self.logic_url = 'https://kyfw.12306.cn/otn/resources/login.html' # 第一步个人界面的url self.person_url = 'https://kyfw.12306.cn/otn/view/index.html' # 第二步查票的url self.query_url = 'https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc' # 第四步确认信息的url self.confirm_url = 'https://kyfw.12306.cn/otn/confirmPassenger/initDc' # 存放字典和代号,可以跨函数调用,相当于全局,有站台名称可以随时调用 self.stations_dict = {} # 初始化stations_dict,调用stations_code函数 self.stations_code() # 定义选择信息为空,在第四步席别选择框中使用,先定义为空值,把第三步得到的seat_type赋值给它 self.select_info = None # 得到站台以及对应代号的字典 def stations_code(self): with open('stations.csv', 'r', encoding='utf-8') as f: read = csv.DictReader(f) # 读出是两对值 # stations_dict = {} # 组成一对字典 for line in read: # print(line) name = line['name'] code = line['code'] self.stations_dict[name] = code # self.stations_dict[line['name']] = line['code'] # print(self.stations_dict) # 实现登录 def logic(self): self.driver.get(self.logic_url) """用显示等待判断是否登录成功,传入驱动和最大等待时间, until()里设置等待的条件,跳转的界面里是否包含self.person_url里的地址 """ WebDriverWait(self.driver, 1000).until( EC.url_contains(self.person_url) ) # 如果包含就显示登录成功。 print("登录成功") # 查票 def query_ticket(self): self.driver.get(self.query_url) # 用隐式等待2秒 self.driver.implicitly_wait(2) # 关闭弹窗,点击弹窗的确定 self.driver.find_element_by_id('qd_closeDefaultWarningWindowDialog_id').click() # 定位出发地输入框,要对value值进行改变,定位到有value值的input标签 from_station_input = self.driver.find_element_by_id('fromStation') # 把输入的站台名转换成代号 from_station_code = self.stations_dict[self.from_station] # 用js把代号传入value中,定位到input标签,然后往里输入转换后的代码值 # self.driver.execute_script('arguments[0].value="%s"' % from_station_code, from_station_input) # 输入目的地,定位到目的地的输入框 to_station_input = self.driver.find_element_by_id('toStation') # 输入的目的地的站台名转换为代号 to_station_code = self.stations_dict[self.to_station] # 用js代码传入目的地的代号 # self.driver.execute_script('arguments[0].value="%s"' % to_station_code, to_station_input) # 这时运行代码,出发地和目的地输入框里没有内容,但是在网页空白处点右键,用元素选择器查看,每个框里是有相应的代码的 # 用id定位日期的输入标签, date_input = self.driver.find_element_by_id('train_date') # 用js代码传入日期 # self.driver.execute_script('arguments[0].value="%s"' % self.train_date, date_input) # 以上三个框的输入也可以用一行代码实现,把每一个js传入的代码都注释掉, self.driver.execute_script('arguments[0].value="{}"; arguments[1].value="{}"; arguments[2].value="{}"'.format(from_station_code, to_station_code, self.train_date),from_station_input, to_station_input, date_input) # 定位到查询按钮,并进行点击 self.driver.find_element_by_id('query_ticket').click() # 第三步 解析车次列表数据 # 显示等待,等待车次列表数据加载完成 WebDriverWait(self.driver, 1000).until( EC.presence_of_element_located((By.XPATH, './/tbody[@id="queryLeftTable"]/tr')) ) # 获取车次列表 # 过滤掉无效的tr标签 train_trs = self.driver.find_elements_by_xpath('.//tbody[@id="queryLeftTable"]/tr[not(@datatran)]') # 定义标志位,用来判断是否找到用户需要购买的车次和坐席,如果找到就改为True # False的默认值为0 flag = False for train_tr in train_trs: # print(train_tr.text) # print('*'*50) train_infos = train_tr.text.replace('\n', " ").split(" ") # 把“复”字从列表中删除 if train_infos[1] == "复": # remove把指定元素从列表中删除 train_infos.remove("复") train = train_infos[0] # 从网页中爬取下来的车次,在init中实例化车次 # 判断一下爬取下来的车次是否在传入的初始化车次中 # train 是取到的车次,判断一下取到的一个个车次是否在传入的车次中 if train in self.ticket_info: # 如果在,就用传入字典格式的车次,去取后面的坐席 # 传入的{"G345": ['O', 'M']},先取O,再取M。 seat_types = self.ticket_info[train] # 从seat_types里遍历出列出的车票类型,O,M for seat_type in seat_types: # 如果seat_type等于O,判断二等座是否有票 # 检查二等座对应的位置是否有余票 if seat_type == 'O': # 在相应车次,二等奖交叉点处点右键,发现有票的话是“有”或者数字 # 从train_infos 的列表中可以看到,需要的信息在列表中第10个,索引是9 o_count = train_infos[9] # 判断二等票的位置的内容是 “有”或者是数字 if o_count == "有" or o_count.isdigit(): # 如果满足,就点击预定按钮 # 光标在预定处点击右键,发现定位到tr标签下的a标签 # train_tr.find_element_by_xpath('.//a[@class="btn72"]').click() # # 点击完预定后要退出,否则程序还会对一等座进行判断,如果一等座也有,会再次进行点击操作 # # 二等座已经点击了,一等座在点击的时候页面已经跳转就找不到点击按钮,就会报错 self.select_info = seat_type flag = True # 如果找到了,就把标志位改为True break # 退出判断坐席有无的操作 elif seat_type == 'M': # 如果要买一等座,就查看一等座的情况 # 找到下标索引为8的,为一等座 m_count = train_infos[8] # 判断一等票的位置的内容是 “有”或者是数字 if m_count == "有" or m_count.isdigit(): # 如果满足,就点击预定按钮 # 光标在预定处点击右键,发现定位到tr标签下的a标签 # train_tr.find_element_by_xpath('.//a[@class="btn72"]').click() self.select_info = seat_type flag = True # 如果找到了,就把标志位改为True break if flag: # 如果找到了就进行点击 train_tr.find_element_by_xpath('.//a[@class="btn72"]').click() break # 退出选坐席的操作 # 确认乘客信息 def confirm_passengers(self): # 显示等待,判断是否成功跳转到确认乘客信息界面 # 如果跳转到confirm_url,则说明页面跳转成功。 WebDriverWait(self.driver, 1000).until( EC.url_contains(self.confirm_url) ) # 确认乘客信息,乘客信息是在li标签下的label里,所有的li都在ul标签里 # 返回多个内容,改为elements,返回的是个列表 passenger_labels = self.driver.find_elements_by_xpath('.//ul[@id="normal_passenger_id"]/li/label') # 这里的乘车人要跟传进来的乘车人进行对比,需要在实例化对象的时候传入乘车人,在初始化的时候接收参数 for passenger in passenger_labels: name = passenger.text # 定位到乘车人标签之后,遍历取出乘车人的文本 if name in self.passengers: passenger.click() # 如果找到的乘车人在传入的参数中,进行点击 # 确认坐席 # 定位到席别选项框 seat_tag = self.driver.find_element_by_id('seatType_1') # 把定位到的选择框座位参数传入到Select类里才能进行操作 seat_select = Select(seat_tag) # 可以利用上个函数里 选中二等座或者一等座的结果放到select里传参进行选择 # 这时就需要定义个全局的变量select_info,把找到的seat_type赋值给select_info seat_select.select_by_value(self.select_info) # 第一种方法 # 点击提交订单按钮,通过id定位 self.driver.find_element_by_id('submitOrder_id').click() # 第二种方法: # 在119和121行的seat_types,改为self.select_info,135和147行注释掉 # 下面的语句中,比如找到了二等座 # for seat in self.select_info: # 遍历出列表中的坐席 # try: # seat_select.select_by_value(seat) # 对每一个坐席进行选择 # # 如果没问题就进行选择,如果找不到就继续遍历下一个坐席 # except: # continue # else: # break # # 如果没问题就执行try和else里的语句,如果找的二等座有异常,就去寻找一等座,找到合适的坐席就退出 # # 相当于又去寻找了一次实例化传入列表的坐席 # 第五步 核对信息 # 用显示等待 判断核对信息框是否出现,传入核对框的ID。 WebDriverWait(self.driver, 1000).until( EC.presence_of_element_located((By.ID, 'content_checkticketinfo_id')) ) # 如果消息框出现,点击确认按钮 # time.sleep(2) self.driver.find_element_by_id('qr_submit_id').click() """功能模块的使用,封装基本的功能,如果把登录,查询,确认的内容都写在一个函数里, 就会特别臃肿,可以封装不同的函数实现不同的功能,在一个函数中进行调用""" # 实现函数的调用 def run(self): # 第一步 登录 self.logic() # 调用函数 # 第二步 车次及余票的查询,第三步 车票数据解析 self.query_ticket() # 第四步 确认乘客信息和席别 self.confirm_passengers() if __name__ == '__main__': # 通过创建的类去实例化对象,需要传递三个参数到init里 # 注意日期的格式 yyyy-xx-xx spider = TrainSpider('长沙', '北京', '2021-09-15', {"G534": ['O', 'M']}, ['乘客姓名1', '乘客姓名2']) spider.run()


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有