python滑块验证自动考勤

转载请注明链接

环境:ubuntu14.04 + firefox60.0.2 + python3.4
之前使用xdotools模拟鼠标键盘实现自动登录打卡考勤,但是后来公司加了验证码,所以此方案不再适用,改由python实现,这个在博文中已有。
最近公司又改成了jigsaw滑块验证,之前的验证码方案已经不再适用。
一、方案
对于目前网上搜索到的滑块验证现状,一般是以下两种方案:

  1. 最初版本的滑块验证在网页源码中能够下载到完整的拼图图片,带缺口的拼图背景及缺口图片,用opencv对比完整图与缺口图,计算出缺口位置。
  2. 次新版本的滑块验证在网页源码中只能够下载到带缺口的拼图背景及缺口图片,仍然可以用opencv matchTemplate计算缺口的最佳匹配位置,大约有90%的成功率。重试三次基本是100%成功。

在公司最近配置的滑块验证模块,上述两个方案都已经失效,网页源码中虽然有带缺口的拼图背景及缺口图片的src链接,但是无法下载,返回404。这应该是模块为应对网上现有的爬虫方案进行了升级。
改进版方案:
因为无法通过src下载,只能通过截图方式下载,有两点:

  1. 进入滑块验证页面后,执行滑块点击,显示拼图,通过元素的screenshot方法截取拼图背景图及缺口图:
ActionChains(self.driver).click_and_hold(slider).perform()

            target = self.wait.until(EC.presence_of_element_located((By.XPATH, "//div[@class='jigimgB']/img")))
            template = self.wait.until(EC.presence_of_element_located((By.XPATH, "//div[@class='jigimgS']/img")))
            if target and template:
                print("开始下载图片")
                bigimage = self.driver.find_element_by_id("bigImage")
                bigimage.screenshot(self.target_path)
                smallimage = self.driver.find_element_by_id("smallImage")
                smallimage.screenshot(self.template_path)
  1. 截取的拼图背景图上最左边的有缺口图,这样matchTemplate计算位置肯定是0,因此需要截取掉,计算完毕后加上截取掉的宽度。
  target = cv2.imread(self.target_path)
            sp = target.shape
            cropImg = target[ 0:sp[0], 60:sp[1]]  # 裁剪图像
            cv2.imwrite(self.target_path, cropImg)  # 写入图像路径
            croptarget = cv2.imread(self.target_path)
            template = cv2.imread(self.template_path)
            distance = self._match_templet(croptarget, template)

二.关键点:
除了方案中提到的截图,还有以下几点注意:

  1. 计算滑动轨迹的track时,网上搜索到的一般显示forward,然后backward,如果直接forward需要改一下track最后一段距离:
  current += s
    if current > distance:
        forward_tracks.append(round(s)-round(current-distance))
    else:
        forward_tracks.append(round(s))
  1. 计算完距离后,这个距离除了要加上方案中提到的截图宽度,还要加上滑块宽度的一半。仔细观察,会发现鼠标在滑块中央滑动时会先空滑滑块一半的距离,然后缺口图片才会跟随滑动:
distance = self._match_templet(croptarget, template)
        # 默认是从滑块中央开始滑动,这里有个滑动阈值,为滑块宽度的一半,60为截图宽度,20为滑块一半的宽度
        distance += 60 + 20

三.源码:

  1. main.py:
from slider.slider import kaoqin_Slider
if __name__ == '__main__':
    jd = JD_Slider(url='http://kaoqin.test.com/index.jsp', username='username', pwd='#password')
    jd.main()
  1. slider/slider.py:
#!/usr/bin/env python 
# -*- coding:utf-8 -*-
import json
import random
import re
import time

import cv2
import numpy as np
from PIL import Image
from selenium import webdriver
from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait

from urllib import request,error


class kaoqin_Slider(object):

    def __init__(self, url, username, pwd=''):
        super(JD_Slider, self).__init__()
        # 实际地址
        self.url = url
        self.driver = webdriver.Firefox()
        self.wait = WebDriverWait(self.driver, 10)
        # 账户信息
        self.username = username
        self.password = pwd
        # 下载图片的临时路径
        self.target_path = "target.png"
        self.template_path = "template.png"
        # 网页图片缩放
        self.zoom = 1
        self.testcount = 0

    def get_random_float(min, max, digits=4):
        return round(random.uniform(min, max), digits)

    def get_random_int(min, max):
        return round(random.randint(min, max))

    def open(self, url=None):
        self.driver.get(url if url else self.url)

    def close(self):
        self.driver.close()

    def refresh(self):
        self.driver.refresh()

    def main(self, is_open=True):
        print("开始准备")
        try:
            if is_open:
                self.open()
                time.sleep(1)
            self._login()
            for i in range(1, 4):
                if self._crack_slider():
                    btn_reg = self.driver.find_element_by_id('loginButton')
                    btn_reg.click()
                    time.sleep(3)
                    btn_signin = self.driver.find_element_by_xpath("//a[@class='mr36']")
                    btn_signin.click()
                    break
        except:
            print("程序出现异常")
        finally:
            self.driver.close()
            print("结束程序")

    def is_login(self):
        try:
            self.wait.until(EC.presence_of_element_located((By.ID, "loginname")))
            print("登录失败")
            return False
        except:
            print("登录成功")
            return True

    def _login(self):
        """
        登录
        :return:
        """
        print("填写账号")
        username = self.driver.find_element_by_xpath("//div[@class='logonPanel']/div[2]/div[2]/input[1]")
        username.clear()
        # username
        time.sleep(random.uniform(0.1, 0.5))
        username.send_keys(self.username[0:3])
        time.sleep(random.uniform(0.5, 0.8))
        username.send_keys(self.username[3:])
        # pwd
        time.sleep(random.uniform(0.8, 1.2))
        pwd = self.driver.find_element_by_xpath("//div[@class='logonPanel']/div[2]/div[3]/input[1]")
        pwd.clear()
        pwd.send_keys(self.password)

    # 滑块
    def _crack_slider(self):
        print("开始移动滑块")
        try:
            slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'ui-slider-btn')))
            ActionChains(self.driver).click_and_hold(slider).perform()

            target = self.wait.until(EC.presence_of_element_located((By.XPATH, "//div[@class='jigimgB']/img")))
            template = self.wait.until(EC.presence_of_element_located((By.XPATH, "//div[@class='jigimgS']/img")))
            if target and template:
                print("开始下载图片")
                bigimage = self.driver.find_element_by_id("bigImage")
                bigimage.screenshot(self.target_path)
                smallimage = self.driver.find_element_by_id("smallImage")
                smallimage.screenshot(self.template_path)

                time.sleep(1)

                # zoom
                # 281 is width of target image
                local_img = Image.open(self.target_path)
                size_loc = local_img.size
                self.zoom = 281 / int(size_loc[0])
                print("计算缩放比例 zoom = %f" % round(self.zoom, 4))
                pic_success = True
            else:
                print("未找到缺口图片")
                return False
        except error.HTTPError as e:
            print(e.reason, e.code, e.headers, sep='\n')
            print("获取缺口图片异常")
            return False
        slider_success = False
        if pic_success:
            # 模板匹配
            target = cv2.imread(self.target_path)
            sp = target.shape
            cropImg = target[ 0:sp[0], 60:sp[1]]  # 裁剪图像
            cv2.imwrite(self.target_path, cropImg)  # 写入图像路径
            croptarget = cv2.imread(self.target_path)
            template = cv2.imread(self.template_path)
            distance = self._match_templet(croptarget, template)
            # 默认是从滑块中央开始滑动,这里有个滑动阈值,为滑块宽度的一半
            distance += 60 + 20
            # print("distance %d" % distance)
            # distance = self._getsliderrealdistance(280, 60, distance)
            print("位移距离 distance = %d" % distance)
            # yoffset_random = random.uniform(-2, 4)
            # ActionChains(self.driver).move_by_offset(xoffset=distance, yoffset=yoffset_random).perform()
            # 轨迹
            tracks = self._get_tracks3(distance * self.zoom)

            # 正向滑动
            for track in tracks:
                yoffset_random = random.uniform(-2, 4)
                ActionChains(self.driver).move_by_offset(xoffset=track, yoffset=yoffset_random).perform()

            time.sleep(random.uniform(0.1, 0.2))
            ActionChains(self.driver).release().perform()
            print("滑块移动成功")
        time.sleep(3)
        slidertext = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'ui-slider-text')))
        print(slidertext.text)
        if '验证成功' in slidertext.text:
            slider_success = True
        else:
            slider_success = False
        print(slider_success)
        return slider_success


    def _match_templet(self, img_target, img_template):    
        result = cv2.matchTemplate(img_target, img_template, cv2.TM_CCOEFF_NORMED)
        # 查找数组中匹配的最大值
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
        left_up = max_loc
        print('匹配结果区域起点x坐标为:%d' % max_loc[0])
        return left_up[0] 

    def _get_tracks3(self, distance):
        # distance += 20
        v = 0
        t = 0.2
        forward_tracks = []
        current = 0
        mid = distance * 4 / 5  # 减速阀值
        while current < distance:
            if current < mid:
                a = 20  # 加速度为+2
            else:
                a = -v/2 
            s = v * t + 0.5 * a * (t ** 2)
            v = v + a * t
            current += s
            if current > distance:
                forward_tracks.append(round(s)-round(current-distance))
            else:
                forward_tracks.append(round(s))
        print(forward_tracks)
        return forward_tracks

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值