def click_btn(self, xpath, el=None): try: if not el: btn = self.wait.until( EC.presence_of_element_located((By.XPATH, xpath))) if btn.is_enabled(): btn.click() else: logger.debug(f"click_btn:{xpath}该元素不可操作") self.run_status = False self.result["status"] = 401 self.result["errorMessage"] = "获取的元素不可用" else: el.find_element_by_xpath(xpath=xpath).click() time.sleep(2) except TimeoutException: logger.error(f"点击{xpath}超时") self.run_status = False self.result["status"] = 401 self.result["errorMessage"] = "获取的元素超时" except Exception as e: logger.error(f"定位{xpath}时,点击click时出错,错误信息:{str(e)}") self.run_status = False self.result["status"] = 401 self.result["errorMessage"] = "获取的元素出现错误"
def calculation_age(self, passengers): """ 计算乘客的年龄 passenger 是个包含每个乘客信息的列表 :param passenger: :return: """ # 成人默认是1 self.adult = -1 self.children = 0 self.infants = 0 current_year = datetime.now().year try: for passenger in passengers: passenger_birth = passenger["birthday"].split("-")[0] # 计算乘客年龄 passenger_age = int(current_year) - int(passenger_birth) if passenger_age >= 12: self.adult += 1 elif 2 <= passenger_age < 12: self.children += 1 else: self.infants += 1 except Exception as e: logger.error(f"获取年龄出现错误,错误信息:{str(e)}")
def parse_bag_info(self): bag_price = 0 # 获取的乘客支付的所有项目信息 try: bag_quantity_info = re.findall(r"paidServices: \[(.*?)\]", self.content) # 获取行李信息 bag_infor = re.findall( '<div class="options">.*?<label class.*?>(.*?)<.*?<div class="extraInput selected-extra ">.*?<label.*?>(.*?)<', self.content, re.S) # 组织行李信息 baggages = [] # 获取乘客的行李价格 for bag_name in bag_infor: baggagess = {} if "托运行李" in bag_name[1]: bag_num = 1 bag_last_name, bag_first_name = bag_name[0].split() baggagess["name"] = f"{bag_last_name}/{bag_first_name}" passenger_bag_price = re.search( rf"LastName\":\"{bag_last_name}\",\"FirstName\":\"{bag_first_name}\".*?Amount\":(?P<amount>\d+?),.*?\"Description\":\"BAGGAGE\"", self.all_passenger) # 获取行李的个数, 计算多个行李个数 bag_quantity = re.findall(r"(\d+)", bag_name[1]) # 如果没有获取到数字,就是一件行李 if bag_quantity: # 多件行李 bag_num = bag_quantity[0] bag_price = int( passenger_bag_price.group("amount")) * int(bag_num) else: # 一件行李 bag_price = int( passenger_bag_price.group("amount")) * int(bag_num) # 计算行李的重量 baggageWeight = int(bag_num) * 20 baggagess['baggageWeight'] = baggageWeight baggagess['dep'] = self.dep_flight baggagess['arr'] = self.arr_flight baggages.append(baggagess) self.response['baggages'] = baggages self.response["baggagePrice"] = bag_price # price 减去行李价格 self.response["price"] -= bag_price logger.info("获取乘客的行李信息") self.status["parse_bag_info"] = True except Exception as e: logger.error(f"获取行李信息失败,错误信息是:{str(e)}") self.response["taskStatus"] = 'Y' self.response["msg"] = '成功'
def fill_input(self, content, xpath, single_input=False): """ 获取到xpath表达式,定位元素,输入内容 :param args: :param kwargs: :return: """ try: input_content = self.wait.until( EC.presence_of_element_located((By.XPATH, xpath))) if input_content.is_enabled(): # 一个一个字母输入 input_content.clear() if single_input: for item in content: input_content.send_keys(item) time.sleep(0.7) else: input_content.send_keys(content) else: logger.debug(f"fill_input:{xpath}该元素不可操作") self.run_status = False self.result["status"] = 401 self.result["errorMessage"] = "获取的元素不可用" except Exception as e: logger.error(f"定位{xpath}时,填写{content}时出错,错误信息:{str(e)}") self.run_status = False self.result["status"] = 401 self.result["errorMessage"] = "获取元素失败"
def get_data(self): lastname = self.response["name"].split("/")[0] pnr = self.response["pnr"] headers = { "user-agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 " "(KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36", "referer": "https://ezy.flypeach.com/cn/manage/manage-authenitcate" } data = { "__RequestVerificationToken": "TcA2_kwuCxaNcon5xJw9zekCTCYdzEb1UD3HvEGMPM0IaqNA2ICGE0DRW7KexQmXu2qlGb9PgqdBwrxJYw3QbKaPp3434cvqhpi0ajVZ02k1", "PNR": pnr, "lastName": lastname } try: resp = requests.post(url=self.target_url, headers=headers, data=data, timeout=30) if resp.status_code == 200: self.content = resp.content.decode("utf-8") if "找不到订单" in self.content: logger.info("找不到订单") return with open("test", "w", encoding="utf-8") as f: f.write(self.content) logger.info(f"获取任务对应信息") self.status["get_data"] = True else: logger.error("获取任务网络不给力") except Exception as e: logger.error(f"获取航班信息出错,错误信息为:-> {str(e)}")
def __init__(self, url): """ 初始化浏览器 """ # 当前运行状态,如果页面动作出现错误之后将终止运行 self.run_status = True self.index_url = url try: # chrome_options = Options() # chrome_options.add_argument('--headless') self.driver = webdriver.Chrome() # self.driver = webdriver.Firefox() # self.driver = webdriver.PhantomJS() self.wait = WebDriverWait(self.driver, 20, 0.5) self.driver.get(self.index_url) time.sleep(3) # self.driver.set_window_size(500, 700) logger.info("初始化webdriver对象") except TimeoutException: logger.error("初始化超时") self.run_status = False raise StopException("初始化浏览器超时") except Exception as e: logger.error("初始化webdriver对象失败" + str(e)) self.run_status = False
def select_site(self): """ 遇到带有儿童的乘客。选择靠近的座位 :return: """ print("选择座位") close_pop_xpath = '//button[@class="core-btn-primary same-seats"]' self.click_btn(close_pop_xpath) try: task = get_data(self.adult_num, self.child_num, self.date, self.destination, self.orgin) data = parse_data(task) token = get_flight_data(data, self.flightNumber, self.result) can_click_seat = get_seat(token, self.result) # 从可选的座位中挑选出挨着的儿童数+1的位置 seat_dic = {} select_seat_num = self.child_num + 1 # 选择的座位个数应该是儿童加一个成人 for seat in can_click_seat: seat_num = seat[:2] if seat_num not in seat_dic: seat_dic[seat_num] = [ord(seat[-1])] else: seat_dic[seat_num[:2]].append(ord(seat[-1])) for k, v in seat_dic.items(): for tmp in range(len(v) - select_seat_num + 1): if sum(v[tmp:tmp + select_seat_num] ) == v[tmp] * select_seat_num + select_seat_num * ( select_seat_num - 1) / 2: return [ k + chr(v[tmp + i]) for i in range(select_seat_num) ] except Exception as e: self.run_status = False logger.error(f"获取可用座位失败, 错误信息:{str(e)}")
def get_ele_list(self, xpath): try: ele_list = self.wait.until( EC.presence_of_all_elements_located((By.XPATH, xpath))) return ele_list except Exception as e: logger.error(f"获取元素列表失败,错误提示:" + str(e)) self.run_status = False
def get_text(self, xpath): try: h1 = self.wait.until( EC.presence_of_element_located((By.XPATH, xpath))) return h1.text except Exception as e: logger.error(f"获取页面文本值出错,错误信息为{str(e)}") self.run_status = False
def get_response(): try: response = requests.get(interface_url).json().get("data") return response except Exception as e: logger.error(f"获取任务失败, 错误信息是:{str(e)}") logger.info("获取任务失败,等待30秒,重新获取") time.sleep(30) get_response()
def parse_pnr(self): """ 修改票号的状态 :return: """ if self.status["get_data"]: self.response["pnrStatus"] = "confirm" logger.info("修改pnr状态为confirm") self.status["parse_pnr"] = True else: logger.error("修改pnrStatus失败")
def get_text(self, xpath, el=None): try: if not el: h1 = self.wait.until( EC.presence_of_element_located((By.XPATH, xpath))) return h1.text else: t = el.find_element_by_xpath(xpath) return t.text except Exception as e: logger.error(f"获取页面文本值出错,错误信息为{str(e)}") self.run_status = False
def confirm_flight_info(self): try: # 点击base base_div_js = """document.querySelector('#tripDeparture > div.tripJourneys > div.tripJourneyDate > div > div.tariffList.open > div.fare-wrapper.tripFare.BASIC').click()""" self.driver.execute_script(base_div_js) # 点击进行下一步 time.sleep(2) continue_btn = '//*[@id="SelectViewControlGroupFlightSelection_ButtonSubmit"]' self.click_btn(xpath=continue_btn) logger.info("确认航班信息") except Exception as e: logger.error(f"确认航班信息出错,错误信息是:{str(e)}")
def parse_price(self): try: total_price = re.search(r"parseFloat\(\'(?P<num>.*?)\'\)", self.content).group("num") # 总价格不包含行李价格 self.total_price = total_price self.response["price"] = int(total_price) logger.info(f"获取到总价格:{total_price}") self.status["parse_price"] = True except Exception as e: logger.error(f"获取总价格失败,错误信息为{str(e)}") self.response["taskStatus"] = "N" self.response["msg"] = "没有获取到对应的总价格"
def parse_currency_type(self): # 匹配币种 try: currency_type = re.search(r"activeCurrency: \"(?P<currency>.*?)\"", self.content) if currency_type: self.response["cur"] = currency_type.group("currency") logger.info("获取到支付货币的币种") self.status["parse_currency_type"] = True else: logger.info("获取币种失败") except Exception as e: logger.error(f"获取币种失败,错误信息是:{str(e)}")
def click_btn(self, xpath): try: btn = self.wait.until( EC.presence_of_element_located((By.XPATH, xpath))) if btn.is_enabled(): btn.click() time.sleep(1) else: logger.debug(f"click_btn:{xpath}该元素不可操作") self.run_status = False except TimeoutException: logger.error(f"点击{xpath}超时") self.run_status = False except Exception as e: logger.error(f"定位{xpath}时,点击click时出错,错误信息:{str(e)}") self.run_status = False
def main(): pur = Purchase() # 初始化浏览器,选择航班 if pur.run_status: pur.select_flight() else: # 初始化失败 logger.error("初始化失败") pur.result["status"] = 401 pur.result["errorMessage"] = "因为网络波动,浏览器初始化失败" # 选择航班成功,选择行李,座位等 if pur.run_status: if pur.child_num: # 需要选择座位 click_seat = pur.select_site() if isinstance(click_seat, list): # 点击座位 pur.click_select_seat(click_seat) # 选择行李 pur.select_packages() else: pur.select_packages() else: logger.debug("选择航班错误") # 选择结束,点击继续, 进行登陆 if pur.run_status: pur.loggin() else: logger.debug("选择行李或选择座位错误") # 登陆成功,进行支付 if pur.run_status: pur.fill_payment_info() else: logger.debug("登陆失败") if pur.run_status: pur.check_out_price() # 进行支付 if pur.run_status: pur.payment() else: logger.debug("填写支付信息出现错误") return pur.result
def parse_passenger(self): passengers = [] try: all_passenger = re.findall( r'var passengers = JSON\.parse(.*?)\.replace', self.content) self.all_passenger = json.dumps(all_passenger).replace('\\', '') lastname = re.findall(r'"LastName":"(.*?)"', self.all_passenger) firstname = re.findall(r'"FirstName":"(.*?)"', self.all_passenger) passenger_set = { last + "/" + first for last, first in zip(lastname, firstname) } except Exception as e: passenger_set = {} logger.error(f"获取所有乘客信息出现错误,错误信息是{str(e)}") try: # 根据具体的乘客信息匹配到对应乘客的性别, 国籍, 生日 for passenger_name in passenger_set: passenger_dic = {} passenger_last, passenger_first = passenger_name.split("/") passenger_info = re.search( rf"LastName\":\"{passenger_last}\",\"FirstName\":\"{passenger_first}\".*?Date\((?P<birthday>.*?)\).*?Nationality\":\"(?P<nationality>.*?)\".*?Gender\":\"(?P<gender>.*?)\"", self.all_passenger) birth = self.cal_age(passenger_info.group("birthday")) nationality = passenger_info.group("nationality") gender = passenger_info.group("gender") passenger_dic["name"] = passenger_name passenger_dic["gender"] = gender passenger_dic["birthday"] = birth passenger_dic["nationality"] = nationality passengers.append(passenger_dic) self.response["passengers"] = passengers self.status["parse_passenger"] = True except Exception as e: logger.error(f"获取乘客性别,国籍,生日错误,错误信息:{str(e)}")
def get_el_list(self, xpath): """ :param xpath: :return: 返回元素列表 """ try: elements = self.wait.until( EC.presence_of_all_elements_located((By.XPATH, xpath))) return elements except TimeoutException: logger.error(f"获取元素{xpath}超时") self.run_status = False self.result["status"] = 401 self.result["errorMessage"] = "获取的元素超时" except Exception as e: logger.error(f"获取元素{xpath}时,获取元素发生错误,错误信息:{str(e)}") self.run_status = False self.result["status"] = 401 self.result["errorMessage"] = "获取的元素出现错误"
def __init__(self): """ 初始化浏览器 """ # 当前运行状态,如果页面动作出现错误之后将终止运行 self.run_status = True try: self.driver = webdriver.Chrome() self.wait = WebDriverWait(self.driver, 20, 0.5) self.driver.get(self.index_url) time.sleep(3) self.driver.set_window_size(500, 700) logger.info("初始化webdriver对象") except TimeoutException: logger.error("初始化超时") self.run_status = False except Exception as e: logger.error("初始化webdriver对象失败" + str(e)) self.run_status = False
def click_btn(self, xpath, el=None): try: if not el: btn = self.wait.until( EC.presence_of_element_located((By.XPATH, xpath))) if btn.is_enabled(): btn.click() time.sleep(1) else: logger.debug(f"click_btn:{xpath}该元素不可操作") self.run_status = False else: el.find_element_by_xpath(xpath).click() except TimeoutException: logger.error(f"点击{xpath}超时") self.run_status = False raise StopException("点击超时") except Exception as e: logger.error(f"定位{xpath}时,点击click时出错,错误信息:{str(e)}") self.run_status = False
def selection(self, xpath, value=None, text=None): """ 选择下拉框 :param xpath: selection的xpath :param value: 根据value值选择 :param text: 根据text值选择 :return: """ try: select = self.wait.until( EC.presence_of_element_located((By.XPATH, xpath))) if value: Select(select).select_by_value(value) if text: Select(select).select_by_visible_text(text) time.sleep(1) except Exception as e: logger.error(f"获取元素{xpath}时,获取元素发生错误,错误信息:{str(e)}") self.run_status = False self.result["status"] = 401 self.result["errorMessage"] = "选择元素出现错误"
def click_select_seat(self, click_seat_list): # 第18排的座位是从第2个div开始,其他的座位是从第一个div开始 seat_js = "document.querySelector('#scrollable > div.seat-map > div > div.seat-map-rows > div:nth-child({}) > div:nth-child({}) > span > span').click()" seat_dic = { "18": { "A": "2", "B": "3", "C": "4", "D": "6", "E": "7", "F": "8" }, "other": { "A": "1", "B": "2", "C": "3", "D": "5", "E": "6", "F": "7" } } seat_start_div = 24 for item in click_seat_list: f_div = seat_start_div + (int(item[:2]) - 18) if int(item[:2]) > 18: c_div = seat_dic["other"][item[-1]] else: c_div = seat_dic[item[:2]][item[-1]] try: self.driver.execute_script(seat_js.format(f_div, c_div)) except Exception as e: self.run_status = False logger.error(f"点击座位错误,错误信息是:{str(e)}") else: # 点击概览和确认 review_confirm_xpath = '//button[@class="core-btn-primary dialog-overlay-footer__ok-button"]' self.click_btn(review_confirm_xpath) time.sleep(2) self.click_btn(review_confirm_xpath) time.sleep(4)
def select_date(self, div_num, day): """ 选择日期 :param div_num: :param day: :return: """ try: a = self.wait.until( EC.presence_of_element_located(( By.XPATH, f'//*[@id="datepicker"]/div/div[{div_num}]' # 2 是相对于当前的第几个月 ))) # 如果day小于10,就要去掉前面的0 day = str(int(day)) a.find_element_by_link_text(f"{day}").click() logger.info("选择出发日期") time.sleep(1) except Exception as e: logger.error(f"选择出发日期时发生错误,错误信息:{str(e)}") self.run_status = False
def fill_input(self, content, xpath, single_input=False, el=None): """ 获取到xpath表达式,定位元素,输入内容 :param args: :param kwargs: :return: """ try: if not el: input_content = self.wait.until( EC.presence_of_element_located((By.XPATH, xpath))) if input_content.is_enabled(): # 一个一个字母输入 input_content.clear() if single_input: for item in content: input_content.send_keys(item) time.sleep(0.7) else: input_content.send_keys(content) else: logger.debug(f"fill_input:{xpath}该元素不可操作") self.run_status = False else: if not single_input: el.find_element_by_xpath(xpath).send_keys(content) else: for item in content: el.find_element_by_xpath(xpath).send_keys(item) time.sleep(0.3) except TimeoutException: logger.error("填写信息超时") self.run_status = False raise StopException("填写信息超时") except Exception as e: logger.error(f"定位{xpath}时,填写{content}时出错,错误信息:{str(e)}") self.run_status = False
def __call__(self, *args, **kwargs): try: # 选择乘客 self.select_flight(self.flight_num) # 填写乘客信息 self.fill_passenger_info() # 选择行李 self.select_luggages() # 填写联络信息 self.fill_contact_info() # 选择支付方式 self.select_payment_method() # 确认行李 self.sure_info() # 输入支付卡信息 self.fill_card_info() # 点击支付,获取pnr self.pay() except StopException as e: if not self.fill_back["status"]: self.fill_back["status"] = 401 logger.error(f"程序中断:错误提示 -》{e}") return
def calculation_passenger_age(self, passengers): child = 0 teen = 0 adult = 0 for passenger in passengers: birthdays_year = int(passenger["birthday"][:4]) # 获取出身年份 years = self.now_time_year - birthdays_year # 年龄大小 if 2 < years <= 11: child += 1 elif 12 <= years <= 15: teen += 1 elif years < 2: print("出现婴儿票,请人工处理") errorMsg = "出现婴儿票,请人工处理" self.result["status"] = 250 self.result["errorMessage"] = errorMsg logger.error('{},{}'.format(errorMsg, "婴儿票报错信息, 转人工出票")) # 抛出终止运行的异常 raise BackException else: adult += 1 return child, teen, adult
def __init__(self, datas): """ 初始化浏览器 """ # 出发地和目的地,起飞时间 self.orgin = datas["depAirport"] # 起飞机场 self.destination = datas["arrAirport"] # 目的机场 self.date = datas["depDate"] # 起飞时间 # 乘客总数 self.passengerCount = datas["passengerCount"] self.passenger = datas["passengerVOList"] self.child_num = 0 self.adult_num = 0 self.teen_num = 0 self.pack_age_bool = False for item in self.passenger: if item["baggageWeight"] > 0: self.pack_age_bool = True if 2 <= self.now_time_year - int(item["birthday"][:4]) <= 11: self.child_num += 1 elif 12 <= self.now_time_year - int(item["birthday"][:4]) <= 15: self.teen_num += 1 else: self.adult_num += 1 # 当前运行状态,如果页面动作出现错误之后将终止运行 self.run_status = True super(Action, self).__init__(datas) try: self.driver = webdriver.Chrome() # self.driver.set_page_load_timeout(60) self.wait = WebDriverWait(self.driver, 20, 0.5) child, teen, adult = Base(datas).calculation_passenger_age( self.passenger) self.driver.get( self.index_url.format(self.orgin, self.destination, self.date, adult, teen, child)) logger.info("初始化webdriver对象") except TimeoutException: logger.error("初始化超时") except BackException as e: # 终止运行的错误 logger.error(e) self.run_status = False except Exception as e: logger.error("初始化webdriver对象失败" + str(e)) self.run_status = False
print(json.dumps(result)) else: # 线上环境 # 获取任务 while 1: response = get_response() # 获取任务 if response: logger.info(f"获取到任务|{response[0]}") check_data = AirMM(response[0]) try: result = check_data() except Exception as e: msg = f"未知错误, {str(e)}" check_data.response["msg"] = msg result = check_data.response logger.error(msg) try: back_fill_back = requests.post( url=back_fill_url, data=json.dumps(result), headers={"content-type": "application/json"}) logger.info("回填状态 -> " + back_fill_back.text) except Exception as e: logger.error("回填出错 ->" + str(e)) else: logger.info("没有获取到任务,等待N秒") time.sleep(10)
def parse_flight_info(self): fromSegments = [] # 航班信息 fromSegments_dic = {} html_etree = etree.HTML(self.content) try: segement = re.findall('<span class="remaining-date">(.*?)</span', self.content, re.S) segement_num = re.findall('(\d+)', segement[0], re.S) # 目前您预订了1航班,可在下面修改您的订单。-> 拿到航班号 fromSegments_dic['segmentindex'] = int(segement_num[0]) # # 出发日期 start_time = ''.join( html_etree.xpath( '//*[@id="home"]/div[1]/div[1]/div[1]/div[2]/span/text()')) start_time = re.findall('(\d+)', start_time, re.S) # 出发时间 dep_time = ''.join( html_etree.xpath( '//*[@id="home"]/div[1]/div[1]/div[2]/div[2]/div[1]/div/span/text()' )) dep_time_number = re.findall('(\d+)', dep_time, re.S) # 到达时间 arr_time = ''.join( html_etree.xpath( '//*[@id="home"]/div[1]/div[1]/div[2]/div[2]/div[3]/div/span/text()' )) arr_time_number = re.findall('(\d+)', arr_time, re.S) # 航班号 flight = ''.join( html_etree.xpath( '//*[@id="home"]/div[1]/div[2]/div/div[2]/span[2]/text()')) # 出发年月日时间 dep_times = start_time[0] + start_time[1] + start_time[ 2] + dep_time_number[0] + dep_time_number[1] # 出发机场 dep_flight = ''.join( html_etree.xpath( '//*[@id="home"]/div[1]/div[1]/div[2]/div[2]/div[1]/div/span/figcaption/text()' )) self.dep_flight = dep_flight.replace('(', '').replace(')', '') # 到达机场 # //*[@id="home"]/div[1]/div[1]/div[2]/div[2]/div[3]/div/span/figcaption arr_flight = ''.join( html_etree.xpath( '//*[@id="home"]/div[1]/div[1]/div[2]/div[2]/div[3]/div/span/figcaption/text()' )) self.arr_flight = arr_flight.replace('(', '').replace(')', '') # 拼接到达日期 arr_times = self.get_arr_time(start_time, dep_time_number, arr_time_number) fromSegments_dic['dep'] = self.dep_flight # 起飞机场 fromSegments_dic['arr'] = self.arr_flight # 目的机场 fromSegments_dic['depDate'] = dep_times # 起飞时间 fromSegments_dic['arrDate'] = arr_times # 到达时间 fromSegments_dic['carrier'] = 'MM' fromSegments_dic['flightNumber'] = 'MM' + flight fromSegments.append(fromSegments_dic) self.response["fromSegments"] = fromSegments self.response["depCity"] = self.dep_flight self.response["arrCity"] = self.arr_flight logger.info("记录航班起飞落地时间") self.status["parse_flight_info"] = True except Exception as e: logger.error(f"获取航班详细信息失败,错误信息是:{str(e)}")