基礎知識學習
目標: 通過python程序實現自動登錄下單功能
知識點: Selenium + 雲打碼 + Python
學習鏈接:
需求分析
Chrome瀏覽器:71.0.3578.98_chrome_installer.exe
12306官網: https://www.12306.cn/index/index.html
# 選擇時間,點擊確定,查詢列表,獲取列表頁的請求URL
# 通過F12,查看當前頁面的請求URL:
https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc&fs=%E4%B8%8A%E6%B5%B7,SHH&ts=%E6%88%90%E9%83%BD,CDW&date=2019-02-03&flag=N,N,Y
# 查看請求參數
# 選擇一條車次數據,查看該列表(一條tr一條數據)
# 獲取時間元素
# 查看預定按鈕
# 當根據時間選擇好了列車后,點擊預定按鈕就會提示我們登錄賬號(注意是Ajax請求,所以需要until查找)
# 查找用戶名和密碼登錄框的ID
# 截取驗證碼
安裝插件 pip3 install Pillow
# 獲取全屏圖片 –> 計算截圖位置(根據元素的寬和高確定大小) –> 截取所需要圖片的大小
# 利用雲打碼進行驗證碼解析
# 定義打點(模擬選中圖片)
# 點擊登錄
# 選擇需要買票的人
# 點擊提交
完整代碼
12306.py
# coding: utf-8 import os import time import json # 圖片操作對象 from PIL import Image # 將二進制文件轉換為IO流輸出 from io import BytesIO import yundama from selenium import webdriver # 1. 導入模塊 from selenium import webdriver # 1> 等待對象模塊 from selenium.webdriver.support.wait import WebDriverWait # 2> 導入等待條件模塊 from selenium.webdriver.support import expected_conditions as EC # 3> 導入查詢元素模塊 from selenium.webdriver.common.by import By from selenium.webdriver import ActionChains browser = webdriver.Chrome("chromedriver") # 12306是異步請求,所以使用selenium的顯性等待方案 wait = WebDriverWait(browser, 10, 0.5) # 創建等待對象 # 請求參數 linktypeid = 'dc' fs = '上海,SHH' ts = '成都,CDW' date ='2019-02-03' flag = 'N,N,Y' # 請求URL: # https://kyfw.12306.cn/otn/leftTicket/init?linktypeid=dc&fs=上海&ts=北京&date=2019-02-03&flag=N,N,Y base_url = "https://kyfw.12306.cn/otn/leftTicket/init?linktypeid={}&fs={}&ts={}&date={}&flag={}" url = base_url.format(linktypeid, fs, ts, date, flag) # 訪問12306的列表頁面 browser.get(url) # 通過時間判定,選擇點擊預訂 # 尋找tr標簽中,屬性id以ticket開頭的數據 tr_list = wait.until(EC.visibility_of_all_elements_located((By.XPATH, "//tr[starts-with(@id, 'ticket_')]"))) # 找到所有可見元素 for tr in tr_list: count = 0 t_start = tr.find_element_by_class_name('start-t').text with open('start.txt', 'a') as f: f.write(t_start) # 判斷時間是否在符合條件的范圍內 if count < 5 and t_start == "11:16": # 這里以06:33為例 tr.find_element_by_class_name('btn72').click() break else: count += 1 continue # 點擊賬號(注意因為是異步加載的所有需要顯性等待) # browser.find_element_by_link_text("賬號登錄").click() # 因為還沒有加載出來,因為是Ajax請求,所以要用until查找 wait.until(EC.visibility_of_element_located((By.LINK_TEXT, "賬號登錄"))).click() # 打開json文件, with open('account.json', 'r', encoding='utf-8') as f: account = json.load(f, encoding='utf-8') # 輸入用戶名和密碼 j_username = browser.find_element_by_id('J-userName').send_keys(account['username']) j_password = browser.find_element_by_id('J-password').send_keys(account['password']) # 獲取全屏圖片 full_img_data = browser.get_screenshot_as_png() # 計算截圖位置 login_img_element = browser.find_element_by_id("J-loginImg") # 獲取截圖元素對象 scale = 1.0 x1 = login_img_element.location["x"] - 155 y1 = login_img_element.location["y"] - 100 x2 = x1 + login_img_element.size["width"] y2 = y1 + login_img_element.size["height"] cut_info = (x1, y1, x2, y2) print('cut_info', cut_info) # 把全屏圖片構建成全屏圖片操作對象 full_img = Image.open(BytesIO(full_img_data)) # 通過截圖信息對象截圖圖片 cut_img = full_img.crop(cut_info) # 把圖片保存到本地 cut_img.save('cut_img.png') time.sleep(5) # 利用雲打碼進行圖片解析 result = yundama.decode('cut_img.png', '6701') print('Image Decode:', result) # 定義8個點擊坐標點 positions = [ (80, 140), (230, 140), (380, 140), (530, 140), (80, 280), (230, 280), (380, 280), (530, 280) ] # 模擬點擊坐標 for num in result: position = positions[int(num) - 1] # ActionChains 動作對象 ActionChains(browser).move_to_element_with_offset(login_img_element,position[0] / 2,position[1] / 2).click().perform() print(position[0], position[1], "點擊圖片完成") time.sleep(5) # 點擊登錄 browser.find_element_by_id("J-login").click() # 點擊選擇人物 wait.until(EC.visibility_of_element_located((By.ID, "normalPassenger_1"))).click() # 點擊提交訂單 browser.find_element_by_id('submitOrder_id').click() time.sleep(2) # 點擊確認訂單 wait.until(EC.visibility_of_element_located((By.ID, 'qr_submit_id'))).click() print("搶票成功,請支付") time.sleep(5) browser.quit()
account.json
{ "username":"18XXXXXXXXXX", "password":"18XXXXXXXXXX" }
yundama.json
import http.client, mimetypes, urllib, json, time, requests ###################################################################### class YDMHttp: apiurl = 'http://api.yundama.com/api.php' username = '' password = '' appid = '' appkey = '' def __init__(self, username, password, appid, appkey): self.username = username self.password = password self.appid = str(appid) self.appkey = appkey def request(self, fields, files=[]): response = self.post_url(self.apiurl, fields, files) response = json.loads(response) return response def balance(self): data = {'method': 'balance', 'username': self.username, 'password': self.password, 'appid': self.appid, 'appkey': self.appkey} response = self.request(data) if (response): if (response['ret'] and response['ret'] < 0): return response['ret'] else: return response['balance'] else: return -9001 def login(self): data = {'method': 'login', 'username': self.username, 'password': self.password, 'appid': self.appid, 'appkey': self.appkey} response = self.request(data) if (response): if (response['ret'] and response['ret'] < 0): return response['ret'] else: return response['uid'] else: return -9001 def upload(self, filename, codetype, timeout): data = {'method': 'upload', 'username': self.username, 'password': self.password, 'appid': self.appid, 'appkey': self.appkey, 'codetype': str(codetype), 'timeout': str(timeout)} file = {'file': filename} response = self.request(data, file) if (response): if (response['ret'] and response['ret'] < 0): return response['ret'] else: return response['cid'] else: return -9001 def result(self, cid): data = {'method': 'result', 'username': self.username, 'password': self.password, 'appid': self.appid, 'appkey': self.appkey, 'cid': str(cid)} response = self.request(data) return response and response['text'] or '' def decode(self, filename, codetype, timeout): cid = self.upload(filename, codetype, timeout) if (cid > 0): for i in range(0, timeout): result = self.result(cid) if (result != ''): return cid, result else: time.sleep(1) return -3003, '' else: return cid, '' def report(self, cid): data = {'method': 'report', 'username': self.username, 'password': self.password, 'appid': self.appid, 'appkey': self.appkey, 'cid': str(cid), 'flag': '0'} response = self.request(data) if (response): return response['ret'] else: return -9001 def post_url(self, url, fields, files=[]): for key in files: files[key] = open(files[key], 'rb'); res = requests.post(url, files=files, data=fields) return res.text def decode(filename, codetype): ###################################################################### # 用戶名 username = 'XXXXXXXXX' # 密碼 password = 'XXXXXXXX' # 軟件ID,開發者分成必要參數。登錄開發者后台【我的軟件】獲得! appid = 6795 # 軟件密鑰,開發者分成必要參數。登錄開發者后台【我的軟件】獲得! appkey = '62a6760f83a91f818be141d9a77463c5' # 圖片文件 # filename = 'getimage.jpg' # 驗證碼類型,# 例:1004表示4位字母數字,不同類型收費不同。請准確填寫,否則影響識別率。 # 在此查詢所有類型 http://www.yundama.com/price.html # codetype = 3000 # 超時時間,秒 timeout = 60 # 檢查 if (username == 'username'): print('請設置好相關參數再測試') else: # 初始化 yundama = YDMHttp(username, password, appid, appkey) # 登陸雲打碼 uid = yundama.login(); print('uid: %s' % uid) # 查詢余額 balance = yundama.balance(); print('balance: %s' % balance) # 開始識別,圖片路徑,驗證碼類型ID,超時時間(秒),識別結果 cid, result = yundama.decode(filename, codetype, timeout); print('cid: %s, result: %s' % (cid, result)) return result ######################################################################