高阶爬虫实战:破解极验滑动验证码

180 阅读13分钟

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

编程狗 编程大牛技术分享平台 640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

今天给大家带来的是极验验证码的selenium破解之法,是不是有点小激动呢,小伙伴们等不及了,让我们赶紧直入主题吧。

虎X网注册

这次我们是拿虎X开刀,注册账号的时候需要滑动图片到缺口位置,这种验证码我们现在也经常遇到,这个就不用详细介绍了吧 

640?wx_fmt=png&wxfrom=5&wx_lazy=1

针对这种验证码我们首先确定了使用selenium模拟滑动破解方式,selenium鼠标移动点击拖动都比较简单,那么问题就在于拖动多少距离,眼睛看起来很直观,但是程序怎么获取呢?利用图像识别......,额,这个只能想想了吧。不如看看网页源码或者请求信息,看看有没有有效的信息。

查看网页信息

鼠标右键点击到图片上,查看元素 

640?wx_fmt=png

这一瞬间的图片,还好我二十几年的麒麟臂没白练,我们看看元素查看到的都是什么东西 640?wx_fmt=png

这看起来有点奇怪哦,有个图片链接,还有位置信息,而且还那么多,先把图片链接拷贝到浏览器里访问下看看 

640?wx_fmt=png 

WTF,这是什么鬼?注意到那个像猪尾巴一样的6了吗?还有那个小箭头,跟上面完整图片对比一下,发现把箭头挪动到小6旁边,猪尾巴就成功了。当然你仔细观察的话,还有其他的比如文字也是类似。那么我们可以确认这张图片应该是被打乱的,如果我们可以把它拼起来,是不是就离计算缺口位置比较近了。现在我们应该要注意到元素查看里后面的位置信息了,那么多,看起来应该跟这个打乱顺序有点关系吧。我们来确认一下。我的想法是这样子的,既然这个位置和拼图有关,而且再看我们上面麒麟臂截的图,我再标记一下 

640?wx_fmt=png

 我们点击查看元素的时候,浏览器会帮我们突出显示一下,本来我是在图片上点击查看的,按照我的想法,它不是应该整张图片突出显示一下吗?看起来好像不是这么回事,只有那么一小部分,而且上面还有元素信息,宽高类名,再回去看看图3,位置坐标里,前面应该是x轴,后面是y轴,y轴只有58和0,再根据图2一看,图片分为上下两部分,再数一下div的数量,26块,每一块宽10x高58。按照这个来算的话,那么整个图片的宽就是260,高116,用截图工具去拉一下图片的宽高,基本吻合 

640?wx_fmt=gif

接下来就是确定怎么拼了。这里很抱歉的告诉大家,猪没了,等我写到这里再去查看网页的时候,图片已经刷新了。所以接下来的截图可能不一样,在这里提前跟大家说明一下。反正就是找特征点嘛,每个图片应该都有的。先随便找一个特征点,查看元素,看它定位到那个div元素那里,然后再看看后面的位置。基本就是这样,所以我们找图片既然和位置有关,那么我们最好选一些位置明显的地方,比如中间,或者两边。 

640?wx_fmt=png 

这个差不多算中间位置了吧,查那么一点点无所谓了 

640?wx_fmt=png

我去,这......跟我想的不太一样呀,再找两张看看,代表性及其强烈的 

640?wx_fmt=png 

640?wx_fmt=png

640?wx_fmt=png 640?wx_fmt=png

为了防止有人说我水字数,另外两个角就不截图了。到这一步可能有人纳闷了,为啥?你刚才说图片宽度260,为什么坐标里出现了289这样的坐标,这不就是超标了吗?一开始我也有这样的疑惑,可能我们看到图片比实际的小,也许人家在图片外面还留了边框呢,我一开始是这么想的。但是这个坐标是前面url里面的图片坐标,然后我就去看了一下图4 

640?wx_fmt=gif 

这个图片尽然比较大,坐标问题有答案了,但是这个跟260有什么关系呢?打乱的图片比较大,拼好的小,那它是怎么拼的呢?幸好我们看到了一个比较有用信息 

640?wx_fmt=png

640?wx_fmt=png

看到这个-1px了吗?它成功引起了我的注意,因为按照我的想法,如果是从拼图里拿出一部分拼成一个完成图片的话,那么最左边拿出来的图片,应该是从(0,0),(0,58),但是我们看到的是(1,0),(1,58),y值还是比较符合我们的预期的,第一部分从0开始,高58,第二部分从58开始。但是x值有点问题,按照1作为起点,那第二个应该是11,因为宽度是10,这是确定的,我们找找看

640?wx_fmt=png

是13,难道每一小块前面都多余了1个像素?按照这种的话也应该是12呀,按照这种方式我们继续找一找剩下的,通过分析我们发现每个小块+12作为下一个小块的起点。这样的话左右各去掉一个像素,宽度不就是10了吗?而且每个小块是12,26个是312,跟我们看到的拼图大小差不多,说明我们分析的是正确的。按照元素里提供的坐标,取宽度为10的大小即可。接下来分析一下这些坐标的意义。

坐标分析

分析一下我们图9到图12的截图,首先说图9,我本来觉得它x、y应该是0,就算不是0,也应该是各位数字吧,结果的y是58,这个算到下半截图片区域了,x是157,跑中场去了。图11呢,你的x应该在300左右,y应该100以上吧,结果y是0,到上半段,x是205,在中场偏后,离守门员还远呢。这是怎么肥事?不过我们发现了,图9在元素里是第一个,图11在元素里是最后一个,再结合坐标前面的y值全是58,后面的y值全是0,符合我们上半段下半段颠倒的想法了,然后你再分别查看图9右边/图11左边的元素就会发现,和元素里面div的顺序一样。到这里就差不多了。

总结一下:最终的图片就是把拼图,即图4,按照x=157、y=58、w=10、h=58截取出来,放在上半部分第一个位置,x=145、y=58、w=10、h=58截取出来放在上半部分第二个位置,紧挨着第一个,以此类推,拼成一张整图。 

640?wx_fmt=png 

这个就是我拼出来的,恩,很好,很不错嘛小伙子。不过好像哪里不对,缺口嘞。仔细看看网页元素 

640?wx_fmt=png原来一个是fullbg,一个是cutbg,这个名字就很有寓意嘛,那就好了,再把cutbg拼一下看看 

640?wx_fmt=png 

这回就对上了。现在的问题就变成怎么计算缺口位置了

缺口位置

我觉得可能会有计算两张图片不同位置的方式吧,度娘来一发,然后获取了python实战===用python对比两张图片的不同,然后发现了ImageChops.difference这个接口,结果你们知道的,不准确,为啥捏?仔细看拼好的两张图,除了缺口还有其他地方不一样呀。看到图16缺口后面那个阴影没,让我的心里蒙上了一层阴影,再观察其他的图片,基本都有类似的,这可怎么办?这在后面还好说,如果是在前面呢,那不就计算到阴影里去了嘛。如果这个对比有一个容差就好了,我以前用按键精灵的时候好像就有这种嘛,这个好不智能呀。既然它是对比像素,我直接取像素对比一下不就得了,而且我还不给它用==,给它一个范围,如果色差在这个范围内就算一样了,这样不就有容差了吗?这个缺口一般都非常明显,而阴影跟背景又很模糊,应该是可行的。思路就是获取图片的宽高,然后一个像素一个像素的遍历对比。

色差

这个色差怎么确定?一种方式就是调试,这种是比较麻烦的,还有一种方式就是获取多张图片,全图和缺陷图,然后使用取色工具,取对应位置的颜色值,确定一个大概范围。距离确定了,下面就是移动了

selenium模拟移动

selenium的模拟操作网上介绍很多,这里我们只要确认需要哪些接口就行了。 ActionChains方法:

  • movetoelement(to_element) - 鼠标移动到某个元素
  • clickandhold(on_element =None) - 点击鼠标左键,不松开
  • movebyoffset(xoffset,yoffset) - 鼠标从当前位置移动到某个坐标
  • release(on_element = None) - 在某个元素位置松开鼠标左键
  • perform() - 执行操作,记住这个很重要,调用上面的方法后,一定要执行perform才能真正执行

selenium的操作我就不详细描述了,这里用到的都是比较简单的用法。

原理分析就完了,这一次必须要贴代码了,否则可能很多人完成不了,也有利于大家的理解。

由于篇幅有限,下面为部分代码, 完整代码 请关注公众号“ 编程狗 ”后回复“ 0419 ”获取

  1. # -*- coding: utf-8 -*-
  2. import random
  3. import time, re
  4. from selenium import webdriver
  5. from selenium.common.exceptions import TimeoutException
  6. from selenium.webdriver.common.by import By
  7. from selenium.webdriver.support.wait import WebDriverWait
  8. from selenium.webdriver.support import expected_conditions as EC
  9. from selenium.webdriver.common.action_chains import ActionChains
  10. from PIL import Image
  11. import requests
  12. from io import BytesIO
  13. ``
  14. class HuXiu(object):
  15.    def __init__(self):
  16.        chrome_option = webdriver.ChromeOptions()
  17.        # chrome_option.set_headless()
  18. ``
  19.        self.driver = webdriver.Chrome(executable_path=r"/usr1/webdrivers/chromedriver", chrome_options=chrome_option)
  20.        self.driver.set_window_size(1440, 900)
  21. ``
  22.    def visit_index(self):
  23.        self.driver.get("https://www.huxiu.com/")
  24. ``
  25.        WebDriverWait(self.driver, 10, 0.5).until(EC.element_to_be_clickable((By.XPATH, '//*[@class="js-register"]')))
  26.        reg_element = self.driver.find_element_by_xpath('//*[@class="js-register"]')
  27.        reg_element.click()
  28. ``
  29.        WebDriverWait(self.driver, 10, 0.5).until(EC.element_to_be_clickable((By.XPATH, '//div[@class="gt_slider_knob gt_show"]')))
  30. ``
  31.        # 进入模拟拖动流程
  32.        self.analog_drag()
  33. ``
  34.    def analog_drag(self):
  35.        #鼠标移动到拖动按钮,显示出拖动图片
  36.        element = self.driver.find_element_by_xpath('//div[@class="gt_slider_knob gt_show"]')
  37.        ActionChains(self.driver).move_to_element(element).perform()
  38.        time.sleep(3)
  39. ``
  40.        # 刷新一下极验图片
  41.        element = self.driver.find_element_by_xpath('//a[@class="gt_refresh_button"]')
  42.        element.click()
  43.        time.sleep(1)
  44. ``
  45.        # 获取图片地址和位置坐标列表
  46.        cut_image_url, cut_location = self.get_image_url('//div[@class="gt_cut_bg_slice"]')
  47.        full_image_url, full_location = self.get_image_url('//div[@class="gt_cut_fullbg_slice"]')
  48. ``
  49.        # 根据坐标拼接图片
  50.        cut_image = self.mosaic_image(cut_image_url, cut_location)
  51.        full_image = self.mosaic_image(full_image_url, full_location)
  52. ``
  53.        # 保存图片方便查看
  54.        cut_image.save("cut.jpg")
  55.        full_image.save("full.jpg")
  56. ``
  57.        # 根据两个图片计算距离
  58.        distance = self.get_offset_distance(cut_image, full_image)
  59. ``
  60.        # 开始移动
  61.        self.start_move(distance)
  62. ``
  63.        # 如果出现error
  64.        try:
  65.            WebDriverWait(self.driver, 5, 0.5).until(EC.element_to_be_clickable((By.XPATH, '//div[@class="gt_ajax_tip gt_error"]')))
  66.            print("验证失败")
  67.            return
  68.        except TimeoutException as e:
  69.            pass
  70. ``
  71.        # 判断是否验证成功
  72.        try:
  73.            WebDriverWait(self.driver, 10, 0.5).until(EC.element_to_be_clickable((By.XPATH, '//div[@class="gt_ajax_tip gt_success"]')))
  74.        except TimeoutException:
  75.            print("again times")
  76.            time.sleep(5)
  77.            # 失败后递归执行拖动
  78.            self.analog_drag()
  79.        else:
  80.            # 成功后输入手机号,发送验证码
  81.            self.register()
  82. ``
  83.    # 获取图片和位置列表
  84.    def get_image_url(self, xpath):
  85.        link = re.compile('background-image: url\("(.*?)"\); background-position: (.*?)px (.*?)px;')
  86.        elements = self.driver.find_elements_by_xpath(xpath)
  87.        image_url = None
  88.        location = list()
  89.        for element in elements:
  90.            style = element.get_attribute("style")
  91.            groups = link.search(style)
  92.            url = groups[1]
  93.            x_pos = groups[2]
  94.            y_pos = groups[3]
  95.            location.append((int(x_pos), int(y_pos)))
  96.            image_url = url
  97.        return image_url, location
  98. ``
  99. (部分代码)

这个移动movebyoffset,我之前的y值也是随机的[-5,5],我觉得这个模拟会更真实一点,总会上下抖动的嘛,结果就是因为这个考虑的太人性了,识别率非常低,改了好多范围,更大的、更小的,结果最后不偏移,竟然识别率奇高。TMD考虑的太人性化了竟然识别不了,我也是醉了。最后再把执行效果发一下吧 

640?wx_fmt=gif

\

作者:星星在线,一个从妹子图到爬虫爱好者的猿生历程**

www.jianshu.com/u/680e0e38d…

打赏作者

640?wx_fmt=jpeg

最近热门文章

**如何用Python做一个骚气的程序员
**

**用Python爬取陈奕迅新歌《我们》10万条评论的新发现
**

机器学习算法KNN简介及实现\

Python有趣的解包用法\

用Python分析苹果公司股价数据\

Nginx+uwsgi部署Django应用\

Python自然语言处理分析倚天屠龙记\

Python 3.6实现单博主微博文本、图片及热评爬取\


获取本文完整代码 请长按扫描二维码关注公众号“ 编程狗 ”后回复“ 0419


640?wx_fmt=jpeg


▼ 点击下方阅读原文 , 免费成为社区会员