实现12306全自动下单功能(Python+PyCharm附:主要代码)

实现12306全自动下单功能(Python+PyCharm附:主要代码)

基本实现步骤

(一)selenium

Selenium是开源的自动化测试工具,它主要是用于Web 应用程序的自动化测试,同时支持所有基于web 的管理任务自动化。
在本项目中相当于:上层统一的控制浏览器API接口

流程图如下:

实现12306全自动下单功能(Python+PyCharm附:主要代码)_第1张图片

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()

(二)打码平台

可以通过第三方平台进行智能识别或者人工识别图片。
优点:价格便宜、使用简单、识别率高

流程图如下:

实现12306全自动下单功能(Python+PyCharm附:主要代码)_第2张图片

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

(三)购票流程

12306购票流程

实现步骤:

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
实现12306全自动下单功能(Python+PyCharm附:主要代码)_第3张图片

  • 解决办法:开始菜单选择运行,输入cmd运行,然后cd命令进入到python安装目录下的Scripts文件中,然后输入pip install requests,就好了。
    或者 打开Python文件的安装目录,进入Scripts文件中,按住Shift键+鼠标右击
    实现12306全自动下单功能(Python+PyCharm附:主要代码)_第4张图片
    实现12306全自动下单功能(Python+PyCharm附:主要代码)_第5张图片

2.错误原因:因为20号最早的一趟车无票,导致元素无法查到,代码没有设置跳转下一趟时间的车次
错误2

  • 解决办法:首先,截取验证码图片,需要安装操作库“pip install Pillow”;然后改成21号即可正确运行,输出!
    实现12306全自动下单功能(Python+PyCharm附:主要代码)_第6张图片

3.错误原因:没有准确定位截取的验证码的位置
错误3

  • 解决办法:百度获取正确的坐标相关代码
    4

你可能感兴趣的:(Python)