实现12306全自动下单功能(Python+PyCharm附:主要代码)
基本实现步骤
(一)selenium
Selenium是开源的自动化测试工具,它主要是用于Web 应用程序的自动化测试,同时支持所有基于web 的管理任务自动化。
在本项目中相当于:上层统一的控制浏览器API接口
流程图如下:
1.安装selenium,在命令行输入“pip install selenium”
2.进国内淘宝镜像 http://npm.taobao.org/mirrors/chromedriver/,下载相对应版本的chromedriver,将它拷贝进项目里
selenium.py主要实现代码:
import time
# 1. 导入模块
from selenium import webdriver
# 导入等待对象模块
from selenium.webdriver.support.wait import WebDriverWait
# 导入条件判断模块
from selenium.webdriver.support import expected_conditions as EC
# 导入查询元素模块
from selenium.webdriver.common.by import By
# 2. 通过驱动创建浏览器对象
# 创建浏览器对象 2 种方式
# 2.1> 参数需要指定驱动路径
browser = webdriver.Chrome('./chromedriver')
# 设置隐性等待(缺点:无法控制AJAX请求)
# browser.implicitly_wait(5)
# 2.2> PATH 全局的环境变量,可以把驱动文件拷贝到 $PATH 路径中
# browser = webdriver.Chrome()
# 3. 输入网址
browser.get('https://www.baidu.com')
# 4. 操作浏览器
"""
find 系列函数,专门用于定义元素
find_element_by_xxx 寻找符合条件的第一个元素
find_elements_by_xxx 寻找符合条件的所有元素(返回是一个列表)
by_xxx
find_element(s)_by_class_name 可以通过 class_name 寻找元素
find_element(s)_by_id
find_element(s)_by_name
find_element(s)_by_tag_name
find_element(s)_by_css_selector
find_element(s)_by_link_text 通过文本内容寻找元素
find_element(s)_by_partial_link_text 通过包含某个内容寻找元素
"""
# 4.1 输入关键字
input_element = browser.find_element_by_id("kw")
input_element.send_keys("itcast")
# 4.2 点击百度一下
button_element = browser.find_element_by_id('su')
button_element.click()
# time.sleep(2)
# 4.3 找到传智播客 链接,并且打开
# 显性等待,每个元素都可以自己定义检查条件
"""
timeout = 60
t = time.time()
while True :
try:
# 检测时间间隔
time.sleep(0.1)
link_element = browser.find_element_by_class_name("t")
link_element.click()
break
except:
# 超时设置
if time.time() - t > timeout:
break
pass
"""
# 系统提供显性等待API
# 1.创建等待对象
# 第一个参数是浏览器对象
# 第二个参数是超时时间
# 第三个参数是检测时间间隔
wait = WebDriverWait(browser,5.0,0.5)
# 2.通过等待对象获取元素
# presence_of_element_located 检查元素是否存在,如果存在就返回,如果不存在就继续检查
# visibility_of_element_located 检查元素是否可见
link_element = wait.until(EC.presence_of_element_located((By.CLASS_NAME,"t")))
link_element.click()
time.sleep(5)
# 5. 退出浏览器
browser.quit()
(二)打码平台
可以通过第三方平台进行智能识别或者人工识别图片。
优点:价格便宜、使用简单、识别率高
流程图如下:
1.进入云打码平台http://www.yundama.com/,注册用户登录
2.进行在线测试,即点击“在线识别”,选择图片进行测试
3.下载 SDK:http://www.yundama.com/apidoc/YDM_SDK.html
4.解压后,选择3.x的版本,拷贝到项目中,把Deom3.x去掉
5.输入用户名、密码、软件ID、软件秘钥
6.将测试图片拷贝到项目中,输入图片的文件名、验证码类型等
主要实现代码(在文章后面有完整代码 YDMHTTP.py ):
# 用户名
username = ''
# 密码
password = ''
# 软件ID,开发者分成必要参数。登录开发者后台【我的软件】获得!
appid =
# 软件密钥,开发者分成必要参数。登录开发者后台【我的软件】获得!
appkey = ''
def decode(filename,codetype):
# 图片文件
# filename = 'xxxx.png'
# 验证码类型,# 例:1004表示4位字母数字,不同类型收费不同。请准确填写,否则影响识别率。在此查询所有类型 http://www.yundama.com/price.html
# codetype = 3000
# 超时时间,秒
timeout = 60
(三)购票流程
实现步骤:
1>访问列表页
2>通过时间判定选择点击预订
3>点击账号登录
4>输入用户名和密码
5>截图获取验证码图片
6>发送打码平台获取识别数字
7>定义8个点击坐标
8>模拟点击坐标
9>点击登录
10>点击选择人物
11>点击提交订单
12>点击确认订单
12306.py主要实现代码:
import time
import json
from selenium import webdriver
from selenium.webdriver import ActionChains
from YDMHTTP import decode
# 导入图片操作对象模块
from PIL import Image
from io import BytesIO
# 导入等待对象模块
from selenium.webdriver.support.wait import WebDriverWait
# 导入条件判断模块
from selenium.webdriver.support import expected_conditions as EC
# 导入查询元素模块
from selenium.webdriver.common.by import By
browser = webdriver.Chrome('./chromedriver')
linktypeid = "dc"
fs = "上海,SHH"
ts = "广州,GZQ"
date = "2019-09-24"
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)
browser.get(url)
wait = WebDriverWait(browser,10,0.5)
# 通过时间判定选择点击预订
# 寻找 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:
date_string = tr.find_element_by_class_name("start-t").text
# print(date_string)
# 判断时间是否在符合条件的范围内
tr.find_element_by_class_name('btn72').click()
break
# 点击账号(因为是异步加载的所以需要显性等待)
wait.until(EC.visibility_of_element_located((By.LINK_TEXT,"账号登录"))).click()
# 输入用户名和密码
with open('account.json','r',encoding='utf-8') as f:
account = json.load(f)
browser.find_element_by_id("J-userName").send_keys(account["username"])
browser.find_element_by_id("J-password").send_keys(account["pwd"])
# 获取全屏图片
full_img_data = browser.get_screenshot_as_png()
# 计算截图位置
# 获取截图元素对象
login_img_element = browser.find_elements_by_id("J-loginImg")
# 通过截图元素对象计算截图位置坐标
print(login_img_element[0], type(login_img_element))
print(login_img_element[0].location, type(login_img_element))
login_img_element = login_img_element[0]
# 获取验证码图片
left = (login_img_element.location['x'] - 280)
top = login_img_element.location['y']
right = login_img_element.location['x'] + (login_img_element.size['width'] - 250)
bottom = login_img_element.location['y'] + login_img_element.size['height']
cut_info = (left,top,right,bottom)
# 通过计算出的截图图片位置在全屏图片中截取所需要的图片
full_img = Image.open(BytesIO(full_img_data))
# 通过截图信息对象截图图片
cut_img = full_img.crop(cut_info)
# 把图片保存到本地
cut_img.save("cut_img.png")
# 发送打码平台获取数字
result = decode("cut_img.png",codetype=6701)
print(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()
# 点击登录
browser.find_element_by_id('J-login').click()
# 点击选择人物
wait.until(EC.visibility_of_element_located((By.ID,"normalPassenger_0"))).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()
time.sleep(5)
browser.quit()
code.py代码如下:
from YDMHTTP import decode
# print(decode('test2.png',codetype=3000))
print(decode('test3.png',codetype=6701))
YDMHTTP.py代码如下:
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
######################################################################
# 用户名
username = ''
# 密码
password = ''
# 软件ID,开发者分成必要参数。登录开发者后台【我的软件】获得!
appid =
# 软件密钥,开发者分成必要参数。登录开发者后台【我的软件】获得!
appkey = ''
def decode(filename,codetype):
# 图片文件
# filename = 'test2.png'
# 验证码类型,# 例: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
######################################################################
遇到的问题
一、问题列表
1.错误原因:没有导入requests库
- 解决办法:开始菜单选择运行,输入cmd运行,然后cd命令进入到python安装目录下的Scripts文件中,然后输入pip install requests,就好了。
或者 打开Python文件的安装目录,进入Scripts文件中,按住Shift键+鼠标右击
2.错误原因:因为20号最早的一趟车无票,导致元素无法查到,代码没有设置跳转下一趟时间的车次
- 解决办法:首先,截取验证码图片,需要安装操作库“pip install Pillow”;然后改成21号即可正确运行,输出!
3.错误原因:没有准确定位截取的验证码的位置
- 解决办法:百度获取正确的坐标相关代码