0%

解决selenium使用location定位与截图中的坐标偏差问题

起因

  • 一开始想用selenium实现某网站的自动登录,这就不可避免的遇到验证码识别问题,要识别验证码那肯定得先获得验证,网上给的获取验证码图片的方法是,先利用WebDriver的save_screenshot函数对网页进行截图,然后再获得验证的图片大小并用location来定位获得验证码的坐标,最后从截图中截取出验证码来
  • 实际试的过程发现,还是有坑在里面,就是location定位的坐标跟截图里的对应不上,网上找了下相关文章,发现这个:之所以会出现这个坐标偏差是因为windows系统下电脑设置的显示缩放比例造成的,location获取的坐标是按显示100%时得到的坐标,而截图所使用的坐标却是需要根据显示缩放比例缩放后对应的图片所确定的,因此就出现了偏差。 解决这个问题有三种方法:1.修改电脑显示设置为100%。这是最简单的方法;2.缩放截取到的页面图片,即将截图的size缩放为宽和高都除以缩放比例后的大小;3.修改Image.crop的参数,将参数元组的四个值都乘以缩放比例。,但网上给的解决方案不是很好,因为需要写死缩放比例并且是windows下的情况,而我测试用的是mac os,测试好后打算放树莓派上跑,总不能改比例改来改去,有没有办法可以自动计算缩放比例?

解决方法

  • 这里想到了一个自动计算缩放比例方法,思路很简单,就是先利用selenium获取根标签"html"的size,再获取截图的size,两者长宽分别相比就出来了
  • 但是实际过程并没有这么简单,还有个小坑的地方就是,这么做理论上没什么问题,但测试发现还是出现了偏差,最后发现原因是save_screenshot截图截的就是浏览器显示出来的界面,但是有些网页比较长,会出现一部分页面没有被显示出来,但利用"html"标签获得的size却是获取的完整网页,所以这样计算的比例还是有问题
  • 最终方案:用selenium截长图,并用selenium获取"html"标签的size,将截图size和"html"的size相比得到缩放比例

代码实现

  • 代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    from PIL import Image
    import time
    from selenium import webdriver
    from selenium.common.exceptions import TimeoutException
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.support.wait import WebDriverWait
    from selenium.webdriver.chrome.options import Options


    options = Options()
    # 网上说selenium截长图需要使用无图形界面模式
    options.add_argument('headless')
    browser = webdriver.Chrome(options=options)
    wait = WebDriverWait(browser, 5)
    login_url = "https://www.91wii.com/member.php?mod=logging&action=login"
    browser.get(login_url)
    # 用js获取页面的宽高
    width = browser.execute_script("return document.documentElement.scrollWidth")
    height = browser.execute_script("return document.documentElement.scrollHeight")
    # 将浏览器的宽高设置成刚刚获取的宽高
    browser.set_window_size(width, height)
    # 获取验证码所在的img标签
    img = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'img[src*="seccode"]')))
    # 等待网页加载完毕
    time.sleep(3)
    # 截图并保存
    browser.save_screenshot('temp.png')
    # 获取html标签
    html = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'html')))
    # location定位验证码的坐标
    location=img.location
    # 获取验证码的长宽
    size = img.size
    # 打开截图
    i = Image.open("temp.png")
    # 计算缩放比例
    w=i.size[0]/html.size['width']
    h=i.size[1]/html.size['height']
    # 计算验证码在截图中的位置
    rangle = (int(location['x'])*w, int(location['y'])*h, int(location['x'] + size['width'])*w,
    int(location['y'] + size['height'])*h)
    # 使用Image的crop函数,从截图中再次截取我们需要的区域
    frame = i.crop(rangle)
    frame.save('code.png')
  • 最终效果如下,可以看到已经可以正确获取到图片验证码了

    image-20210724181823219

gif动态验证码优化

  • 上面的方案比较适合获取静态图片的验证码,但有种验证码是gif动态图的,上面方案也可以凑合用,代码里测试的网址就是gif的,能成功获取到验证码图片是因为这种gif验证码一般会在真实验证码停留较长时间,所以有比较大概率截图到真实验证码,脸黑的情况下则截取到假验证码,那有没有办法100%获取到真实验证码图片呢?

  • 这里保存了两张网页的gif验证码进行简单分析,直接用mac os下自带的发现预览app查看发现,gif一共10张图片组成,真实验证码出现位置随机

    misc

    misc2

  • 这里给出一个获取真实验证码图片可行的方案:正如上面所说这种gif动态验证码会在真实验证码停留时间比较长,非真实的图片会快速跳过,所以我们可以在网页加载完后每隔1秒截一下图,截5张图,这样5张图出现相同的图片一定是真实验证码

  • 修改后的代码实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    from PIL import Image
    import time
    from selenium import webdriver
    from selenium.common.exceptions import TimeoutException
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.support.wait import WebDriverWait
    from selenium.webdriver.chrome.options import Options


    options = Options()
    # 网上说selenium截长图需要使用无图形界面模式
    options.add_argument('headless')
    browser = webdriver.Chrome(options=options)
    wait = WebDriverWait(browser, 5)
    login_url = "https://www.91wii.com/member.php?mod=logging&action=login"
    browser.get(login_url)
    # 用js获取页面的宽高
    width = browser.execute_script("return document.documentElement.scrollWidth")
    height = browser.execute_script("return document.documentElement.scrollHeight")
    # 将浏览器的宽高设置成刚刚获取的宽高
    browser.set_window_size(width, height)
    # 获取验证码所在的img标签
    img = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'img[src*="seccode"]')))
    # 等待网页加载完毕
    time.sleep(3)
    # 每隔5秒截一张图并保存
    for i in range(5):
    browser.save_screenshot(f'temp/{i}.png')
    time.sleep(1)
    # 获取html标签
    html = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'html')))
    # location定位验证码的坐标
    location=img.location
    # 获取验证码的长宽
    size = img.size
    # 打开一张截图用于计算缩放比例
    i = Image.open("temp/0.png")
    # 计算缩放比例
    w=i.size[0]/html.size['width']
    h=i.size[1]/html.size['height']
    # 计算验证码在截图中的位置
    rangle = (int(location['x'])*w, int(location['y'])*h, int(location['x'] + size['width'])*w, int(location['y'] + size['height'])*h)
    # 使用Image的crop函数,从截图中再次截取我们需要的区域
    ls=[]
    real_img=None
    # 从5张网页截图里面截取验证码图片
    for i in range(5):
    t = Image.open(f"temp/{i}.png")
    ls.append(t.crop(rangle))
    # 两两比较从5张图片里截取到的验证码图片,一旦出现相等情况则说明找到了真实验证码图片
    for i in range(1,5):
    for j in range(i+1, 5):
    if ls[i] == ls[j]:
    real_img=ls[i]
    real_img.save("temp/real.png")
    exit()
  • 最后可以100%获取到gif图里面的真实验证码图片

    image-20210724232000267

参考链接