Python自动化破解WIPO六宫格验证码:图像处理与网络请求实战
1. 项目概述与核心痛点最近在做一个专利数据分析的小工具需要从世界知识产权组织WIPO的专利数据库里批量抓取一些公开数据。本来以为用Python的requests库写个脚本模拟登录后按部就班爬取就完事了结果第一步就卡在了登录验证码上。WIPO用的不是简单的数字字母验证码而是一个六宫格拼图验证码需要用户把打乱的图片碎片拖拽到正确位置才能通过。这种交互式验证码对于自动化脚本来说简直就是一道“叹息之墙”。手动操作一两次还行但成百上千次的登录请求根本不可能。这个项目标题“搞定WIPO专利站那个烦人的六宫格验证码”精准地戳中了所有需要自动化处理WIPO数据的研究员、分析师和开发者的痛点。它不是一个简单的字符识别OCR问题而是一个涉及图像处理、位置分析和模拟交互的综合挑战。本文将详细复盘我是如何利用Python生态中成熟的requests库进行网络请求并结合PILPillow图像处理库从分析、拆解到最终自动化通过这个验证码的全过程。无论你是爬虫新手还是遇到过类似交互式验证码的老手相信这套思路和代码都能给你带来直接的参考价值。2. 验证码机制深度分析与破解思路2.1 六宫格验证码的交互原理首先我们必须理解对手。WIPO的六宫格验证码通常由以下部分组成一个完整的、作为背景的参考图这张图是完整的、未被分割的。一个被分割成3x2六宫格并打乱顺序的拼图区域用户需要将打乱的碎片拖拽回正确位置。一个用于放置碎片的“画布”或“目标区域”。其验证逻辑通常不是前端完成的简单位置比对而是会将用户最终的排列顺序一个索引数组提交到后端进行校验。因此我们的核心目标不是模拟鼠标拖拽的图形界面操作那会非常复杂且不稳定而是计算出正确的碎片排列顺序并将这个顺序数组通过HTTP请求提交给服务器。2.2 技术方案选型与工具链确定基于以上分析我们的技术栈非常清晰网络请求层requests。用于获取验证码图片、提交验证结果。它比urllib更友好会话Session管理方便能自动处理Cookies这对于保持登录状态至关重要。图像处理层PIL(Pillow)。Python图像处理的事实标准。我们需要用它来下载图片、裁剪碎片、比对图像相似度。辅助计算numpy。虽然PIL本身能进行一些像素操作但涉及矩阵计算和相似度度量时numpy的数组操作效率极高。我们可以利用numpy将图片转换为数组进行计算。为什么不直接用Selenium模拟拖拽这是一个常见的思路。使用Selenium等浏览器自动化工具确实可以模拟人的所有操作包括拖拽。但缺点非常明显效率极低、资源占用高、不稳定。每个验证码都需要启动或复用浏览器实例渲染页面执行JavaScript速度慢且容易被反爬机制检测。而requestsPIL的方案是纯HTTP协议层面的操作速度快、资源消耗小更接近“机器与机器的对话”是更优雅和专业的解决方案。3. 核心步骤拆解与实操实现整个自动化流程可以分解为以下几个关键步骤我们将逐一实现。3.1 步骤一获取验证码原始素材首先我们需要从WIPO登录页面获取验证码图片。通常这类图片的URL会在页面HTML中或者通过一个单独的API接口返回。import requests from PIL import Image import io # 创建一个会话用于保持Cookies session requests.Session() # 假设我们先访问登录页面获取必要的令牌或Cookies login_page_url https://patentscope.wipo.int/search/zh/guestLogIn.jsf response session.get(login_page_url) # 这里可能需要解析页面获取一些隐藏的form字段如viewState等具体需分析页面源码 # 获取验证码图片的URL这个URL需要实际分析网络请求获得 # 例如通过浏览器开发者工具在“网络”选项卡中查找图片请求 captcha_image_url https://patentscope.wipo.int/some/path/captcha.jpg # 下载验证码图片 captcha_response session.get(captcha_image_url) if captcha_response.status_code 200: # 将二进制内容转换为PIL Image对象 full_image Image.open(io.BytesIO(captcha_response.content)) full_image.save(full_captcha.png) # 保存下来便于查看 print(完整验证码图片已下载) else: print(下载验证码图片失败)注意验证码图片的URL可能带有动态参数如时间戳_t123456789以防止缓存也可能需要特定的Referer或Cookie才能访问。务必使用session.get()来保持会话一致性并通过开发者工具仔细分析真实的请求头。3.2 步骤二图像预处理与碎片提取下载的图片通常包含了完整的参考图和打乱的碎片图。我们需要将它们分离开。假设服务器返回的是一张长图上半部分是完整参考图下半部分是打乱的3x2网格碎片。def split_captcha_image(full_image): 将下载的完整图片切割成参考图和碎片列表。 假设图片尺寸为 宽300px高400px上半部分200px是参考图下半部分200px是3x2的碎片网格。 width, height full_image.size ref_height height // 2 # 参考图高度 # 1. 提取完整参考图 reference_image full_image.crop((0, 0, width, ref_height)) reference_image.save(reference.png) # 2. 提取打乱的碎片网格 grid_image full_image.crop((0, ref_height, width, height)) grid_image.save(shuffled_grid.png) # 3. 将网格均匀切割成6个碎片 # 假设网格也是 300x200那么每个碎片是 100x100 tile_width width // 3 tile_height (height - ref_height) // 2 tiles [] for row in range(2): # 2行 for col in range(3): # 3列 left col * tile_width upper row * tile_height right left tile_width lower upper tile_height tile grid_image.crop((left, upper, right, lower)) tiles.append(tile) tile.save(ftile_{row}_{col}.png) # 保存每个碎片便于调试 return reference_image, tiles # 执行切割 reference_img, shuffled_tiles split_captcha_image(full_image) print(f切割出{len(shuffled_tiles)}个碎片)3.3 步骤三计算正确排列顺序这是最核心的一步。我们需要为每一个打乱的碎片在完整参考图中找到它原本的位置。思路将完整参考图也虚拟地切割成3x2的网格得到6个“目标位置”。然后对于每一个打乱的碎片计算它与6个目标位置图像的相似度。相似度最高的那个位置就是该碎片的正确归属。关键如何计算图像相似度对于这种经过裁剪、颜色一致的碎片均方误差MSE或结构相似性指数SSIM是很好的选择。这里我们使用更简单的MSE。import numpy as np def mse(imageA, imageB): 计算两幅图像之间的均方误差 (Mean Squared Error)。值越小越相似。 # 确保图像尺寸相同 if imageA.size ! imageB.size: imageB imageB.resize(imageA.size) # 转换为numpy数组并计算差值 err np.sum((np.array(imageA).astype(float) - np.array(imageB).astype(float)) ** 2) err / float(imageA.size[0] * imageA.size[1]) return err def find_correct_order(reference_img, shuffled_tiles, grid_shape(3, 2)): 找出碎片的正确排列顺序。 :param reference_img: 完整的参考图片 :param shuffled_tiles: 打乱的碎片列表 :param grid_shape: 网格形状 (列数 行数) :return: 一个列表表示 shuffled_tiles 中每个碎片对应的正确位置索引 (0-5) cols, rows grid_shape tile_width reference_img.width // cols tile_height reference_img.height // rows total_tiles cols * rows # 1. 从参考图中生成所有目标位置的模板碎片 target_tiles [] for r in range(rows): for c in range(cols): left c * tile_width upper r * tile_height right left tile_width lower upper tile_height target_tile reference_img.crop((left, upper, right, lower)) target_tiles.append(target_tile) # 2. 为每个打乱的碎片寻找最匹配的目标位置 correct_order [] used_target_indices set() # 防止重复匹配在MSE方法中可能不需要但更严谨 for shuffled_tile in shuffled_tiles: best_match_idx -1 lowest_mse float(inf) for idx, target_tile in enumerate(target_tiles): # 如果该目标位置已被占用跳过可选取决于算法 # if idx in used_target_indices: # continue current_mse mse(shuffled_tile, target_tile) if current_mse lowest_mse: lowest_mse current_mse best_match_idx idx correct_order.append(best_match_idx) # used_target_indices.add(best_match_idx) # 标记该位置已被使用 # 3. 验证correct_order 应该是一个0-5的排列且不重复 if sorted(correct_order) ! list(range(total_tiles)): print(警告匹配结果可能不正确顺序列表不是0-5的完整排列。) print(f匹配到的顺序: {correct_order}) return correct_order # 计算正确顺序 correct_order_indices find_correct_order(reference_img, shuffled_tiles) print(f计算出的碎片正确位置索引顺序: {correct_order_indices}) # 例如输出可能是 [2, 0, 4, 5, 1, 3]表示第一个碎片应放在索引2的位置第二个放索引0...实操心得在实际操作中WIPO的碎片边缘可能有半透明的阴影或边框这会影响MSE计算。一个有效的技巧是在比对前先将图片转换为灰度图image.convert(L)并裁剪掉边缘的几个像素例如tile.crop((2,2,tile.width-2, tile.height-2))只比对核心图像区域这样可以显著提高匹配准确率。3.4 步骤四模拟提交验证结果得到正确的顺序数组后我们需要模拟浏览器向服务器提交这个结果。这需要分析登录表单的提交请求。使用浏览器开发者工具F12在WIPO登录页面输入错误信息并完成验证码拖拽然后点击登录。在“网络”选项卡中找到最终的登录提交请求通常是POST请求。仔细查看该请求的载荷Payload你会发现除了用户名、密码还有一个字段可能叫captchaOrder、puzzleSequence或tilePositions它的值是一个类似[2,0,4,5,1,3]的JSON字符串或逗号分隔的字符串。我们的任务就是用requests模拟这个POST请求。# 假设我们从页面解析出了登录表单的其他必要字段如 viewState, j_idt... login_post_url https://patentscope.wipo.int/search/zh/loginAction.jsf # 构建提交的数据 # 注意所有字段名和格式必须和浏览器提交的一模一样 payload { loginForm:userId: your_username, loginForm:password: your_password, loginForm:captchaOrder: str(correct_order_indices).replace( , ), # 例如[2,0,4,5,1,3] javax.faces.ViewState: 获取到的viewState值, loginForm:loginButton: 登录, # 有时需要提交触发按钮的name # ... 可能还有其他隐藏字段 } # 添加必要的请求头特别是Content-Type和Referer headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: login_page_url, Content-Type: application/x-www-form-urlencoded, } # 使用同一个session提交 response session.post(login_post_url, datapayload, headersheaders) # 检查响应 if response.status_code 200: # 判断是否登录成功可以检查响应内容是否包含登录成功的标识或者后续请求是否正常 if 欢迎 in response.text or dashboard in response.text: print(登录成功) # 此时session已包含登录后的cookies可用于后续数据抓取 else: print(登录可能失败请检查验证码或账户信息。) print(response.text[:500]) # 打印部分响应内容用于调试 else: print(f请求失败状态码{response.status_code})4. 工程化优化与异常处理一个健壮的脚本不能只处理理想情况。以下是几个关键的优化点和避坑指南。4.1 增加容错与重试机制网络请求和图像匹配都可能失败。import time from requests.exceptions import RequestException def solve_captcha_with_retry(session, max_retries3): for attempt in range(max_retries): try: # 1. 获取验证码图片 captcha_img fetch_captcha_image(session) if not captcha_img: raise Exception(获取验证码图片失败) # 2. 分割图片 ref_img, tiles split_captcha_image(captcha_img) # 3. 计算顺序 order find_correct_order(ref_img, tiles) # 4. 简单验证顺序有效性 if len(set(order)) ! 6: print(f第{attempt1}次尝试匹配的顺序有重复重试...) time.sleep(1) continue return order # 成功则返回 except (RequestException, IOError, ValueError) as e: print(f第{attempt1}次尝试出错{e}) time.sleep(2) # 等待后重试 print(f经过{max_retries}次尝试仍失败。) return None4.2 处理图像差异与匹配失败颜色模式确保比对的两张图片颜色模式一致如都转为RGB。尺寸微调由于裁剪可能存在1像素误差可以在比对前将图片resize到统一尺寸。多算法验证如果MSE方法在某些情况下不准可以引入第二种算法如比较图像直方图的相关性进行交叉验证只有两种算法都认为匹配时才确认。4.3 会话Session管理务必使用requests.Session()对象来管理整个流程。它自动处理Cookies使得获取验证码和提交登录的请求处于同一会话上下文中这是登录成功的关键。5. 常见问题与排查技巧实录在实际部署中你可能会遇到以下问题问题现象可能原因排查与解决思路获取验证码图片返回403错误请求头不完整缺少Referer或特定CookieIP被临时限制。1. 用开发者工具复制浏览器请求的所有Headers特别是Cookie,Referer,User-Agent添加到session.get()的headers参数中。2. 在首次访问登录页和获取验证码之间增加短暂延时time.sleep(1)。3. 考虑使用更高质量的代理IP。图像匹配顺序总是错误1. 切割坐标计算错误。2. 碎片有边框/阴影干扰。3. 参考图和碎片图区域假设错误。1.保存中间图片把reference.png、shuffled_grid.png和每个tile_*.png都保存下来用图片查看器确认切割是否正确。2.预处理图像尝试将图片转为灰度、二值化或裁剪掉边缘5像素后再进行比对。3.手动分析用一次成功的登录对比浏览器收到的图片和你脚本下载的图片确认结构是否一致。登录请求返回错误页面但状态码是2001. 提交的Payload字段不全或格式错误。2. 缺少关键的隐藏表单字段如javax.faces.ViewState。3. 验证码已过期。1.仔细对比Payload用开发者工具查看浏览器成功登录时提交的所有参数一个不漏地复制到脚本中。注意字段名和值的格式是JSON字符串还是逗号分隔。2.动态获取字段像ViewState这样的字段每次请求都会变必须从登录页面的HTML源码中实时解析提取。3.保证时效性从获取验证码到提交登录时间间隔不宜过长最好在30秒内。偶尔成功经常失败服务器端有随机性的反爬策略或匹配算法在特定图片下不准。1.增加匹配置信度检查计算最佳匹配和次佳匹配的MSE差值如果差值太小如小于阈值则认为匹配不可信触发重试。2.引入人工复核环节对于匹配置信度低的将图片保存下来稍后人工处理或标记。一个关键的调试技巧在开发阶段将每次失败的请求和响应内容包括Headers、Payload、Response Text以及对应的验证码图片都保存到本地文件。通过对比成功和失败的日志你能快速定位问题所在。这个项目从看到验证码时的“头皮发麻”到最终稳定运行核心在于将一个人机交互问题精准地分解为图像识别和网络协议两个可编程解决的子问题。requests负责精准的协议模拟PIL负责解决图像的位置还原两者结合绕开了对前端交互的复杂模拟直击验证码验证的最终逻辑——提交一个正确的顺序数组。这套方法不仅适用于WIPO其思路也可以迁移到其他类似的拼图、滑块验证码上关键在于逆向分析出它最终提交给服务器的验证参数是什么。希望这份详细的复盘能帮你扫清自动化数据采集路上的这个常见障碍。
