Python实战:基于PIL与Selenium的滑动拼图验证码智能破解方案
1. 从零开始:理解滑动拼图验证码的“攻防战”
大家好,我是老王,一个在爬虫和自动化领域摸爬滚打了十来年的老码农。今天咱们不聊那些高深莫测的AI大模型,就聊聊一个让无数爬虫工程师又爱又恨的“老朋友”——滑动拼图验证码。这玩意儿现在几乎成了各大网站的标配,从注册、登录到关键操作,无处不在。它的核心目的就一个:区分你是真人还是机器。
简单来说,滑动拼图验证码会给你一张有缺口的背景图,以及一个可以拖动的拼图块。你的任务就是把这块拼图拖到缺口里,严丝合缝地对上。听起来是不是像小时候玩的拼图游戏?但对于爬虫程序来说,这可比直接解析HTML页面难多了。因为它的关键信息——缺口的确切位置——并不直接写在网页代码里,而是藏在图片像素之中。
网上有些教程会教你直接去网页元素里找left、top这些CSS属性来计算距离,但那对付的是“初级版”验证码。现在稍微有点防护意识的网站,用的都是我们今天要啃的“硬骨头”:无法通过元素选择工具直接定位缺口的验证码。缺口位置是动态生成并渲染到图片上的,你看不到任何数字线索,只能靠“眼睛”去识别。这时候,我们的武器库就需要升级了:用Python的PIL库当“眼睛”去看图找缺口,再用Selenium当“手”去模拟滑动。
我刚开始接触这类验证码时也踩了不少坑,比如轨迹模拟得太假被识别、图片对比因为颜色模式不对总是失败。后来慢慢摸索,总结出了一套稳定且通过率很高的方案。接下来,我就把这套实战经验掰开揉碎了分享给你,哪怕你是刚入门Python不久的新手,跟着步骤走也能搞定。
2. 核心武器库:PIL与Selenium环境搭建与原理初探
工欲善其事,必先利其器。咱们这个方案的核心就依赖两个库:PIL(Python Imaging Library,现在多用Pillow这个友好分支) 和 Selenium。别被名字吓到,它们用起来其实很直观。
Pillow(PIL) 是我们的“图像处理专家”。你可以把它想象成一个功能强大的数字暗房。我们用它来打开网页截图,比较两张图片的像素差异,从而精准地找到那个缺口的位置。它不需要你去理解复杂的图像算法,调用几个现成的函数就能完成任务,对新手极其友好。
Selenium 则是我们的“浏览器遥控器”。它不是一个普通的HTTP请求库,而是一个可以自动化操作真实浏览器(如Chrome、Firefox)的工具。它能点击按钮、输入文字、当然,也能按住滑块并拖动。用Selenium来操作,网站看到的就是一个真实的浏览器在运行,很难区分是人在操作还是程序在操作,这是破解交互式验证码的天然优势。
2.1 手把手安装依赖
首先,确保你有一个Python环境(建议3.6以上)。打开你的命令行终端(Windows上是CMD或PowerShell,Mac/Linux上是Terminal),依次输入以下命令来安装我们需要的库:
pip install pillow selenium
如果你用的是Chrome浏览器,还需要下载一个对应你浏览器版本的 ChromeDriver。这是Selenium控制Chrome的桥梁。去ChromeDriver官网下载,注意版本号要和你电脑上Chrome浏览器的大版本号一致(比如Chrome是120.x.x,就找120开头的ChromeDriver)。下载后,得到一个chromedriver.exe(Windows)或chromedriver(Mac/Linux)文件。
我建议把这个文件放在你项目代码的同一个目录下,这样最省事。或者,你也可以把它放在系统PATH环境变量包含的目录里(比如Windows的C:\Windows\)。搞定这些,环境就算搭好了。
2.2 理解“图像差异对比”原理
为什么通过两张图片对比就能找到缺口?这是整个方案最巧妙的地方。很多滑动拼图验证码的生成逻辑是这样的:
- 网页先加载一张完整的背景图(我们叫它原始图)。
- 当你点击或准备滑动滑块时,网页会在同一位置再生成一张图,这张图在某个随机位置被“挖”了一个缺口(我们叫它缺口图)。
- 滑块拼图块,就是从那个缺口上抠下来的。
那么,如果我们能分别截取到原始图和缺口图,把它们叠在一起比较,不同的地方不就是缺口所在的位置吗? 就像玩“找不同”游戏一样。
Pillow库里的ImageChops.difference(image_a, image_b)函数,就是干这个的。它会把两张图片每个对应像素点的颜色值进行相减,生成一张新的“差异图”。在差异图上,完全相同的像素点会变成黑色(RGB值为0,0,0),而不同的地方(也就是缺口边缘)则会显示出颜色。接着,我们再调用getbbox()函数,它能自动找到差异图中所有非纯黑色像素的最小外包矩形,并返回这个矩形的坐标(左, 上, 右, 下)。这个矩形区域的左边界x[0],基本上就是缺口左侧的X坐标,也就是我们需要滑动的距离。
这个原理说起来简单,但实际应用中会有很多细节问题,比如网页图片可能有透明通道、截图时机不对等等,这些我都会在后面结合代码详细说明怎么处理。
3. 实战第一步:获取“找不同”的关键图片
理论讲完了,咱们动真格的。第一步,就是用Selenium操控浏览器,拿到那两张关键的图片。这里我假设你已经有一个可以测试的滑动拼图验证码页面(可以是本地HTML文件,也可以是某个测试网站)。为了讲解方便,我以一段典型的本地HTML代码为例。
3.1 初始化浏览器并打开页面
我们先写一个简单的脚本,让Selenium启动浏览器并打开我们的测试页。
from selenium import webdriver
import os
import time
# 初始化Chrome浏览器驱动
# 如果chromedriver不在当前目录,需要指定路径,例如:webdriver.Chrome(executable_path=r'你的路径\chromedriver.exe')
browser = webdriver.Chrome()
# 获取当前脚本所在目录,并拼接出测试页面的绝对路径
current_dir = os.path.dirname(os.path.abspath(__file__))
url = 'file://' + os.path.join(current_dir, 'index.html') # 假设测试页面叫index.html
print('访问的页面地址:', url)
browser.get(url) # 打开页面
time.sleep(2) # 等待2秒,确保页面完全加载
这里有几个我踩过的坑提醒你:
time.sleep(2)是必要的。虽然Selenium有更智能的“显式等待”,但对于简单测试,sleep更直白。页面没加载完就截图,可能会截到空白。- 如果打开的是本地HTML文件,路径前面必须加
file://协议头。 - 如果遇到浏览器闪退或报错,十有八九是ChromeDriver版本不对,回去检查版本匹配。
3.2 截取原始背景图
页面加载好后,我们首先要找到那个显示背景图的Canvas或Div元素,并把它截图保存下来。通过浏览器开发者工具(F12)查看元素,找到它的唯一标识,比如IDjigsawCanvas。
# 定位到背景图画布元素并截图
canvas_element = browser.find_element_by_id('jigsawCanvas') # 根据你的页面实际情况修改ID
canvas_element.screenshot('origin.png')
print('原始背景图已保存为 origin.png')
这里直接用Selenium元素的screenshot()方法,比全屏截图再裁剪要精准得多,可以避免其他页面元素的干扰。
3.3 触发并截取带缺口的图片
接下来是关键一步:触发缺口图的生成。通常,你需要先与滑块元素进行交互(比如点击一下),缺口才会出现。
# 定位滑块按钮
slider = browser.find_element_by_id('jigsawCircle') # 根据你的页面实际情况修改ID
# 模拟点击一下滑块,触发缺口图片的显示
slider.click()
time.sleep(0.5) # 稍等片刻,让缺口图渲染完成
# 再次对同一个画布元素进行截图,这次得到的就是带缺口的图片
canvas_element.screenshot('after.png')
print('带缺口图片已保存为 after.png')
特别注意:这个click()操作和后续的time.sleep至关重要。有些验证码是在鼠标按下(mousedown)事件时触发,有些是在点击(click)时触发,需要根据实际情况调整。等待时间`0.# 1. 两数之和
题目
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例
示例 1:
输入:nums = [2,7,11,15], target = 9 输出:[0,1] 解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6 输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6 输出:[0,1]
提示
- 2 <= nums.length <= 104
- -109 <= nums[i] <= 109
- -109 <= target <= 109
- 只会存在一个有效答案
进阶
你可以想出一个时间复杂度小于 O(n2) 的算法吗?
解题思路
最简单的思路是暴力枚举,时间复杂度为O(n^2),空间复杂度为O(1)。
进阶的思路是用哈希表,遍历数组,对于每个元素,在哈希表中查找是否存在target - nums[i],如果存在,则返回结果,否则将当前元素存入哈希表。时间复杂度为O(n),空间复杂度为O(n)。
性能
执行用时:36 ms, 在所有 Python3 提交中击败了91.41%的用户
内存消耗:16.5 MB, 在所有 Python3 提交中击败了5.02%的用户
声明
来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/two-sum 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
更多推荐



所有评论(0)