• Python实例--12306的抢票功能


    基础知识学习

    目标: 通过python程序实现自动登录下单功能

    知识点: Selenium + 云打码 + Python

    学习链接:

    1. Python学习--Selenium模块

    2. Python学习--打码平台

    3. Python系统学习梳理_【All】

    需求分析

    Chrome浏览器:71.0.3578.98_chrome_installer.exe

    12306官网: https://www.12306.cn/index/index.html

    image

    # 选择时间,点击确定,查询列表,获取列表页的请求URL

    image

    # 通过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

    image

    # 查看请求参数

    image

    # 选择一条车次数据,查看该列表(一条tr一条数据)

    image

    # 获取时间元素

    image

    # 查看预定按钮

    image

    # 当根据时间选择好了列车后,点击预定按钮就会提示我们登录账号(注意是Ajax请求,所以需要until查找)

    image

    # 查找用户名和密码登录框的ID

    image

    image

    # 截取验证码

    安装插件 pip3 install Pillow

    image

    # 获取全屏图片 –> 计算截图位置(根据元素的宽和高确定大小) –> 截取所需要图片的大小

    image

    # 利用云打码进行验证码解析

    image

    # 定义打点(模拟选中图片)

    image

    # 点击登录

    image

    # 选择需要买票的人

    image

    # 点击提交

    image

    完整代码

    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
    
        ######################################################################
    
  • 相关阅读:
    web前端优化之reflow(减少页面的回流)
    Javascript深拷贝
    MySQL 配置优化
    MySQ中Lmax_connections的合理设置
    Too many connections解决方案
    Linux 查看文件内容
    ON DUPLICATE KEY UPDATE
    jquery $.each 和for怎么跳出循环终止本次循环
    使用redis避免客户端频繁提交数据
    windows下为mysql添加日志
  • 原文地址:https://www.cnblogs.com/ftl1012/p/10326239.html
Copyright © 2020-2023  润新知