3ba242dc7c.jpg"/>

教程一:博客园

误区一:

下面整个button区域对应着整个登录框,可以直接使用id里的标签进行定位,而不是第1个span标签

button = self.wait.until(EC.presence_of_element_located((By.ID,"submitBtn")))

误区二:注意关于图片的一些知识点

img1 = Image.open("E:/python27/pic/1.jpg")
img1.size
r, g, b = img1.split()

打开图片:Image.open(fp, mode='r')
保存图片:Image.save(fp, format=None, **params):
展示图片:Image.show(title=None, command=None):
location = img.location
# location属性可以返回该图片对象(既这张图片)在浏览器中的位置,以字典的形式返回,{‘x’:30,‘y’:30} 这里我们图片的位置是(30,30)。坐标轴是以屏幕左上角为原点,x轴向右递增,y轴像下递增。(相对整个html的坐标)



size = img.size  
# 通过size获取属性大小,size属性同样返回一个字典,{‘height’:30,‘width’:30 } 即图片对象的高度,宽度。



four_corner =(location['x'],
        location['y'],
        location['x']+size['width'],
        location['y']+size['height'])        
# 通过location和size获取上下左右,注意是小写的x和y



captcha = screenshot.crop((top, bottom, left, right))  
# Image.crop() 从图像中提取出某个矩形大小的图像。它接收一个四元素的元组作为参数,各元素为(left, upper, right, lower),坐标系统的原点(0, 0)是左上角。# 因为上面截图,截取到的是整个页面,现在只把验证码图片取到


image.size属性是一个列表,下标0是横坐标(宽,图片的最右边),下标1是纵坐标
image.size[0]
image.size[1]

完整代码

from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
from selenium import webdriver
# import PIL.Image as image
from PIL import Image
from io import BytesIO
import time

EMAIL =
PASSWORD =
BODER = 6
INIT_LEFT = 60

class CrackGeetest():

    def __init__(self):
        self.url = 'http://www.sf-express.com/cn/sc/dynamic_function/waybill'
        self.browser = webdriver.Chrome('D:\\chromedriver.exe')
        self.wait = WebDriverWait(self.browser, 100)
        self.email = EMAIL
        self.keyword = PASSWORD
        self.BORDER = 6


    def open(self):
        """
        打开浏览器,并输入用户名和密码
        :return:None
        """
        self.browser.get(self.url)
        email = self.wait.until(EC.presence_of_element_located((By.ID, 'email')))
        password = self.wait.until(EC.presence_of_element_located((By.ID, 'password')))
        email.send_keys(self.email)
        password.send_keys(self.password)

    def get_geetest_button(self):
        """
        点击按钮,弹出没有缺口的图片
        :return: 返回按钮对象
        """
        button = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_radar_tip')))
        return button

    def __del__(self):
        self.browser.close()


    def get_position(self):
        """
        获取验证码位置
        :return:验证码位置元组
        """
        img = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_canvas_img')))
        time.sleep(2)

        # location属性可以返回该图片对象(既这张图片)在浏览器中的位置,以字典的形式返回,{‘x’:30,‘y’:30} 这里我们图片的位置是(30,30)。坐标轴是以屏幕左上角为原点,x轴向右递增,y轴像下递增。(相对整个html的坐标)
        location = img.location

        # size属性同样返回一个字典,{‘height’:30,‘width’:30 } 即图片对象的高度,宽度。
        size = img.size                 # 通过sizes属性获取大小
        top,bottom,left,right = location['y'],location['y']+size['height'],location['x'],location['x']+size['width']    # 通过location和size获取上下左右
        return (top,bottom,left,right)


    def get_screenshot(self):
        """
        获取网页截图
        :return: 截图对象
        """
        pic.png = self.browser.get_screenshot_as_png()
        screenshot = Image.open(BytesIO(pic.png))            # 转换为字节对象

            '''
            img1 = Image.open("E:/python27/pic/1.jpg")
            img1.size
            r, g, b = img1.split()
            
            打开图片:Image.open(fp, mode='r')
            保存图片:Image.save(fp, format=None, **params):
            展示图片:Image.show(title=None, command=None):
            '''

    def get_geetest_image(self, name='captcha.jpg'):
        """
        获取验证码图片
        :return: 图片对象
        """
        top, bottom, left, right = self.get_position()                  # 定位到这个位置之后,再截图
        print('验证码位置',top, bottom, left, right)
        screenshot = self.get_screenshot()                              # 获取到截图

        # Image.crop() 从图像中提取出某个矩形大小的图像。它接收一个四元素的元组作为参数,各元素为(left, upper, right, lower),坐标系统的原点(0, 0)是左上角。
        captcha = screenshot.crop((top, bottom, left, right))           # 因为上面截图,截取到的是整个页面,现在只把验证码图片取到
        captcha.save(name)                                              # 把图片存储起来,name是传进来的变量
        return captcha


    def get_slider(self):
        """
        获取滑块
        :return: 滑块对象
        """
        slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_button')))
        return slider


    def is_pixel_equal(self, img1, img2, x, y):
        """
        判断两个像素是否相同
        :param image1: 图片1
        :param image2: 图片2
        :param x: 位置x
        :param y: 位置y
        :return: 像素是否相同
        """
        # 取两个图片的像素点 (img.load()[x, y]获取某一点的像素值)
        pixel1 = img1.load()[x, y]      # 上面的 i 和 j
        pixel2 = img2.load()[x, y]
        threshold = 60                  # 控制误差
        if (abs(pixel1[0] - pixel2[0] < threshold) and abs(pixel1[1] - pixel2[1] < threshold)           # 0 1 2 是RGB的三种颜色
        and abs(pixel1[2] - pixel2[2] < threshold)):
            return True
        else:                           # 有一个超过阈值,则认为2个像素点不一致
            return False


    def get_gap(self, img1, img2):
        """
        获取缺口偏移量
        :param img1: 不带缺口图片
        :param img2: 带缺口图片
        :return:
        """
        left = 60                                   # 从图片的60处开始往右,这样就可以把被拖动滑块跳过去,因为被拖动滑块处像素也不一样

        # 相当于从60开始,一列列做对比
        for i in range(left, img1.size[0]):         # i 相当于横坐标      size属性是一个列表,下标0是横坐标(宽,图片的最右边),下标1是纵坐标
            for j in range(img1.size[1]):           # j 相当于纵坐标      从0开始
                if not self.is_pixel_equal(img1, img2, i, j):
                    left = i                                            # 如果相同位置的像素不一致,则替换为i
                    return left
        return left


    def get_track(self, distance):
        """
        根据偏移量获取移动轨迹
        :param distance: 偏移量
        :return: 移动轨迹
        """
        # 移动轨迹
        track = []
        # 当前位移
        current = 0
        # 减速阈值
        mid = distance * 4 / 5
        # 计算间隔
        t = 0.2
        # 初速度
        v = 0

        while current < distance:                   # 所以 track是不会大于总长度的
            if current < mid:
                # 加速度为正2
                a = 2
            else:
                # 加速度为负3
                a = -3
            # 初速度v0
            v0 = v
            # 移动距离x = v0t + 1/2 * a * t^2,现做了加速运动
            move = v0 * t + 1 / 2 * a * t * t
            # 当前速度v = v0 + at  速度已经达到v,该速度作为下次的初速度
            v = v0 + a * t
            # 当前位移
            current += move
            # 加入轨迹
            track.append(round(move))                   # track 就是最终鼠标在 X 轴移动的轨迹
        return track


    def move_to_gap(self, slider, track):
        """
        拖动滑块到缺口处
        :param slider: 滑块
        :param track: 轨迹
        :return:
        """
        ActionChains(self.browser).click_and_hold(slider).perform()         # 利用动作链,获取slider,perform是

        for x in track:
            ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform()       # xoffset横坐标,yoffset纵坐标。使得鼠标向前推进
        time.sleep(0.5)                                     # 推动到合适位置之后,暂停一会
        ActionChains(self.browser).release().perform()      # 抬起鼠标左键


    def login(self):
        """
        登陆
        :return: None
        """
        submit = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME,'login-btn')))
        submit.click()                      # 登陆按钮
        time.sleep(10)
        print('登陆成功')


    def crack(self):
        # 打开浏览器,输入用户名和密码
        self.open()

        # 点击按钮,弹出没有缺口的图片
        button = self.get_geetest_button()
        button.click()
        time.sleep(0.5)

        # 获取验证码图片
        image1 = self.get_geetest_image('captcha1.png')

        #点按呼出滑块
        slider = self.get_slider()
        slider.click()                              # 一般的验证码,只有点完slide按钮之后,才会出现缺口

        # 获取带缺口的验证码图片
        image2 = self.get_geetest_image('captcha2.png')

        # 获取缺口位置
        gap = self.get_gap(image1, image2)
        print('缺口位置', gap)

        # 减去缺口的位移(因为,滑块移动img1到img2的时候,允许有误差范围。在范围内,即使有几个像素差,也是可以接受的
        gap -= BODER

        # 获取移动轨迹
        track = self.get_track(gap)
        print('滑动轨迹',track)

        # 拖动滑块到缺口处。模拟人的行为习惯(先匀加速拖动后匀减速拖动),把需要拖动的总距离分成一段一段小的轨迹
        self.move_to_gap(slider, track)

        success = self.wait.until(
            EC.text_to_be_present_in_element((By.CLASS_NAME,'geetest_sucess_radar_tip_constant'),'验证成功')        # 证明极验成功
        )
        print(success)

        # 失败后重试
        if not success:
            self.crack()
        else:
            self.login()


if __name__ == '__main__':
    print('开始验证')
    crack = CrackGeetest()
    crack.crack()
    print('验证成功')

还有可以识别极验验证码的api可以用

https://www.kancloud.cn/abcabc123/yiyunapi/1879968