首先总结坑:
1、关于浏览器分辨率有关的截图
2、关于浏览器位置点击(分辨率)
3、注意当前时间的 请求 URL 的cookie 值!!
思路:
1、首先分析12306 登录界面
2、将页面截取为图片
3、取得图片的位置 通过 先打开图片Image.open(), crop(左,上,右,下)-->用.save('文件名')存此图片
4、使用超级鹰下载的示例模块(记得传入9004)
5、超级鹰的结果用循环点击(此处显式等待 每0.5s检查一次,设定时间)
ActionChains(driver) 使用动作链操作鼠标去移动点击,执行
6、登录成功--验证成功
7、移动到查询页面
8、去查 ajax出来的 copy 将其url 打开 (工具网站:https://curl.trillworks.com/)可以看到cookie值,一般此操作和日期有关如果当前时间已经没有车次,会导致过期!
一定要及时修改
9、找到中文对应的英文链接
做一个中文英文互相转换的模块
10、分析ajax的 url打开的数据 在这里面可以通过 选取字典索引值找到 这里是js格式 所以使用 json.loads( 返回.text)[' 索引']
11、分析里面要取的数据 的位置 通过遍历选取
12、是否 有票!大坑!!! 记得索引位置,有部分车次的索引位置完全相反!!!我晕,暂时没想到怎么搞
13、isdigit()函数判断其中有数字或特殊符号则为ture! 有用
其中的URL:
1 class API_URL(object): 2 3 # 下载验证码的URL 4 GET_YZM_URL = "https://kyfw.12306.cn/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand&" 5 # 校验验证码的URL(post) 6 CHECK_YZM_URL = "https://kyfw.12306.cn/passport/captcha/captcha-check" 7 # 登录首页的URL get 8 Login_URL_1 = "https://kyfw.12306.cn/otn/login/init" 9 Login_URL_2 = "https://kyfw.12306.cn/otn/login/init#" 10 # 车票站点查询的URL 11 SELECT_URL = "https://kyfw.12306.cn/otn/resources/js/framework/station_name.js?station_version=1.9150"
此处headers 和cookies 自己获取,记得加!这里肯定会失效
1 headers = { 2 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36' 3 } 4 cookies = { 5 '_uab_collina': '159401687279425123588472', 6 'JSESSIONID': '15006E403A3D220629819AD7CBBDC853', 7 'tk': 'GeK-NUkoQ0CpE04s8roM0QwonK0LuKPFBGo4CQubs1s0', 8 'BIGipServerotn': '619708938.50210.0000', 9 'RAIL_EXPIRATION': '1594209286751', 10 'RAIL_DEVICEID': 'XFErASnds56Lfkayxga7TQCa8eqhX3uplkHzXHt27hvD9IXlmSarYXr_dNF2LHYODwopchRRbJVaDwgVmxySz91dqk0_u2RqSsUyobeiBCBaSOyM-3V8R1V4U8pRsbNOlewj8vgv5A1pHtWi-zvUXq0kfvQdEdO1', 11 'BIGipServerpool_passport': '216859146.50215.0000', 12 'route': '9036359bb8a8a461c164a04f8f50b252', 13 'ten_key': '1gJOinE9bHcaIId53q5LyxGn2Y/Z4ERq', 14 'ten_js_key': '1gJOinE9bHcaIId53q5LyxGn2Y%2FZ4ERq', 15 '_jc_save_toDate': '2020-07-06', 16 '_jc_save_wfdc_flag': 'dc', 17 'current_captcha_type': 'Z', 18 '_jc_save_fromStation': '%u5317%u4EAC%2CBJP', 19 '_jc_save_toStation': '%u4E0A%u6D77%2CSHH', 20 'uKey': '1963dac98c2756cd1312380545a9b8cefb73be533c5ed6aca3704252393a480f', 21 '_jc_save_fromDate': '2020-07-15', 22 }
1 # 登录模块 2 def login(self): 3 4 5 # IP : port 6 proxy = '110.243.16.20 : 9999' 7 8 # 设置代理IP 9 chrome_options = webdriver.ChromeOptions() 10 # 代理服务器n 11 chrome_options.add_argument('--proxy-server = %s'%proxy) 12 13 14 driver = webdriver.Chrome(chrome_options=chrome_options) 15 # 请求 16 driver.get(API_URL.Login_URL_1) 17 18 driver.maximize_window() 19 time.sleep(2) 20 21 driver.find_element_by_xpath('//*[@id="username"]').send_keys(self.user_name) 22 time.sleep(2) 23 driver.find_element_by_xpath('//*[@id="password"]').send_keys(self.password) 24 time.sleep(2) 25 26 # 思路!截屏获取验证码 27 28 # 获取图片所在位置 29 copy_img = driver.find_element_by_xpath('//*[@id="loginForm"]/div/ul[2]/li[4]/div/div/div[3]/img') 30 # 图片的左上角位置 31 location = copy_img.location 32 print(location) 33 size = copy_img.size 34 print(size) 35 time.sleep(2) 36 # 要截取的坐标大小 !!!!重坑警告!!! 在这里困好久 打开的默认浏览器分辨率毫无问题 37 # 但是但是!!其实需要 *1.50 才能正确取到验证码截图 38 location_code=(int(location['x']*1.50), int(location['y']*1.50), int(location['x']+size['width'])*1.50, int(location['y']+size['height'])*1.50) 39 print(location_code) 40 # 定位完后截屏 41 driver.save_screenshot("screen.png") 42 i = Image.open("screen.png") 43 44 # 在此要元组类型 (left, upper, right, lower)-tuple 45 # (左上右下) 46 ver_code_img = i.crop(location_code) 47 ver_code_img.save("验证码.png") 48 # 自动识别 49 result_cjy = user_cjy("验证码.png") 50 print(result_cjy) 51 52 result_cjy = result_cjy.get('pic_str').split('|') 53 # 遍历 列表['247,67', '105,138']将其取出 所有都用 , 分隔 54 points = [[int(number) for number in numbers.split(',')] for numbers in result_cjy] 55 print(points) # 此处获取到二维列表!!! 56 57 for point in points: 58 # 显示等待,定位图片对象 59 element = WebDriverWait(driver, 20).until( 60 EC.presence_of_element_located((By.CLASS_NAME, "touclick-image"))) 61 # 模拟点击 !!!!此处模拟点击又有 坑 因为刚刚放大截取1.5 所以这里缩小点击0.6 62 ActionChains(driver).move_to_element_with_offset(element,point[0]*0.66,point[1]*0.66).click().perform() # perform(发送进行执行) 63 time.sleep(2) 64 time.sleep(5) 65 driver.find_element_by_xpath('//*[@id="loginSub"]').click() 66 time.sleep(5) 67 if driver.current_url not in [API_URL.Login_URL_1,API_URL.Login_URL_2]: 68 print("登录成功!") 69 else: 70 print("登录失败,请重试!")
1 # 查询火车余票模块 2 def select(self): 3 # 请求站点转换 js 4 req = requests.get(API_URL.SELECT_URL) 5 6 self.station_data = req.text.lstrip("var station_names ='").rstrip("'").split('@') 7 # print(self.station_data) 8 9 time_now = input("请输入日期(格式:2020-07-06):") 10 from_station = self.get_station(input("请输入出发地:")) 11 to_station = self.get_station(input("请输入目的地:")) 12 # 注意 一定要知道什么站到什么站是由车次的 如果搜寻过程中没有对应车 是报错 13 url = "https://kyfw.12306.cn/otn/leftTicket/query?leftTicketDTO.train_date={}&leftTicketDTO.from_station={}&leftTicketDTO.to_station={}&purpose_codes=ADULT".format(time_now,from_station,to_station) 14 print(url) 15 16 req_2 = requests.get(url,headers=headers,cookies=cookies) 17 req_2.encoding='utf-8' 18 # print(req_2.text) 19 20 result = json.loads(req_2.text)['data']['result'] 21 # print(result) 22 23 # 是否有座?? 此处的第一个元素 我取寻找 请求的url 中的位置每个位置不同!!大坑!! 24 data_seats = [(32,'商务座'),(31,'一等座'),(30,'二等座'),(29,'高级软卧'),(28,'一等卧'),(27,'动卧'),(26,'二等卧'),(25,'软座'),(24,'硬座'),(23,'无座')] 25 for item in result: 26 item = item.split("|") 27 dict_item = { 28 '编号':item[2], '车次':item[3], '出发地':self.get_station_CN(item[4]), '目的地':self.get_station_CN(item[5]), '出发站':self.get_station_CN(item[6]), '终点站':self.get_station_CN(item[7]), '出发时间':item[8], '抵达时间':item[9], '总耗时': str(int(item[10][:item[10].index(':')]))+'小时'+str(int(item[10][item[10].index(':')+1:]))+'分钟', 29 '商务座': "", 30 '一等座': "", 31 '二等座': "", 32 '高级软卧': "", 33 '一等卧': "", 34 '动卧': "", 35 '二等卧': "", 36 '软座': "", 37 '硬座': "", 38 '无座': "" 39 } 40 for index in range(10): 41 # 42 if item[data_seats[index][0]] == '有' or item[data_seats[index][0]].isdigit(): # isdigit()函数判断其中有数字或特殊符号则为ture 43 dict_item[data_seats[index][1]]=item[data_seats[index][0]] 44 else: 45 del dict_item[data_seats[index][1]] 46 47 print(dict_item)
1 # 车站的英文缩写函数!(中文转英文) 2 def get_station(self, city): 3 for item in self.station_data: 4 if city in item: 5 return item.split('|')[2] 6 7 # 车站的中文缩写函数! 8 def get_station_CN(self, city_CN): 9 for item in self.station_data: 10 if city_CN in item: 11 return item.split('|')[1]
1 def __init__(self, user_name, password): 2 self.user_name = user_name 3 self.password = password 4 self.station_data = ''
1 if __name__ == '__main__': 2 # username = input("请输入用户名:") 3 # password = input("请输入密码:") 4 user = UserPass("username", "password") 5 # user.login() 6 user.select()