写验证码识别脚本这事说难不难说容易也真容易翻车。pytesseract是Python里调用Tesseract OCR引擎最方便的封装十几行代码就能把一张干净验证码里的字符读出来但真正放到实际场景里识别率会瞬间被噪点、扭曲、干扰线和字体变形教做人。这篇文章我把从环境搭建到图像预处理、再到参数调优和常见问题排查的完整过程都梳理一遍所有代码都是我实际跑过的。适合刚入门Python、想用OCR做简单验证码识别的同学参考。先说结论pytesseract从来不是万能的它擅长的是“相对规整”的字符识别。你拿它去识别那种五颜六色、字符扭曲成麻花的验证码大概率会失败。但如果你的目标是自动化测试、自己项目里的验证码识别、或者批量处理内部系统的验证码让它干活是完全够用的。这篇文章会告诉你哪些验证码它能搞定哪些搞不定以及万一搞不定的时候还能用什么办法补救。1. 验证码识别的基本思路与pytesseract选型1.1 验证码到底是什么识别难点在哪验证码本质上是一张带有干扰信息的图片图片里有一串字符服务器期望你正确读出这串字符然后提交。按照干扰强度大致可以分成几档第一档白底黑字、字体规整、无干扰线顶多有点倾斜。这种是最友好的Tesseract开箱即用识别率可以到95%以上。第二档有少量噪点、字符间距不明显、字体稍微艺术化。这种需要做灰度化、二值化、去噪点识别率能到80%左右。第三档字符扭曲、粘连、带彩色背景、有干扰线甚至字符旋转。这种已经是Tesseract的苦战区域识别率可能只有40%-60%需要结合字符分割和模板匹配甚至直接上深度学习。很多人一上来就抱怨“pytesseract识别率太低”但大部分情况不是工具太弱而是没做预处理。OCR引擎吃进去的是像素喂给它一张乱七八糟的图它自然吐不出好结果。理解这一点后面所有操作都是围绕“把图片整理成OCR引擎最喜欢的样子”展开的。1.2 为什么选pytesseract而不是其他方案选型这个问题我当时的考量很直接方案优点缺点适用场景pytesseract Tesseract免费、开源、部署简单、中文社区资料多对复杂扭曲验证码识别率有限规整或轻度干扰的验证码在线OCR API识别率高、省事收费、依赖网络、有隐私风险不想自己调优、量不大深度学习目标检测识别识别率天花板最高需要标注数据、训练环境、显卡验证码形态固定且量很大商业打码平台全自动、准确率稳定按次收费、账号体系、合规风险紧急情况、规模生产我最终选择pytesseract核心原因有三个第一它零门槛。一条pip install pytesseract加一个Tesseract安装包5分钟就能跑通第一版适合快速验证“这个验证码到底能不能被OCR识别”。第二它是本地运行。图片不需要上传到任何第三方服务器对内部系统、隐私数据来说更稳妥也不用担心网络抖动和接口配额。第三它可定制。Tesseract支持多种OCR引擎模式、白名单配置、页面分割模式配合OpenCV的预处理很多看起来“很复杂”的验证码其实还有救。当然我也要泼盆冷水如果是那种旋转角度很大的字符、或者加了严重扭曲变形的验证码pytesseract基本无能为力别浪费时间。遇到那种情况建议直接考虑深度学习方案或者接打码平台认清边界比硬扛更重要。1.3 需要准备哪些环境与依赖我的运行环境是Windows 10 Python 3.10下面这几个依赖缺一不可Python 3.8以上版本建议3.9或3.10太老的版本对pytesseract新版本兼容性不友好。Tesseract OCR本体注意它是个独立软件不是pip包。Windows用户需要下载安装包装完还要记住安装路径。pytesseract库它是Python和Tesseract之间的封装层通过调用Tesseract的命令行接口完成识别。OpenCV-Python库用来做图像预处理。也许你会问“pytesseract不能直接识别吗”能但直接识别只对第一档验证码有效后面的操作全靠OpenCV来配合。Pillow库pytesseract读取图片需要Pillow支持虽然装OpenCV的时候通常会带一个但最好显式声明出来避免版本混乱。注意Tesseract和pytesseract是两个东西。Tesseract是C实现的OCR引擎pytesseract只是Python调用它的桥梁。忘记装Tesseract本体是新手最常见的坑。2. 环境搭建与图像预处理三板斧2.1 安装Python、Tesseract OCR与pytesseract先说Tesseract本体。Windows用户去GitHub的UB-Mannheim/tesseract页面下载安装包安装的时候我建议勾选“Additional language data”里的简体中文——虽然验证码通常只需要识别英文和数字但谁也不能保证你哪天不需要中文识别。安装路径选择上我习惯装到C:\Program Files\Tesseract-OCR因为后面配置路径时好记默认路径就行。装完之后验证一下tesseract --version能输出版本号就说明装好了。接着装Python库pip install pytesseract opencv-python pillow然后写一个最简单的调用import pytesseract from PIL import Image img Image.open(captcha.png) code pytesseract.image_to_string(img, config--psm 7) print(code)如果直接跑Windows下大概率会报TesseractNotFoundError。原因就是pytesseract不知道Tesseract装在哪。你需要在脚本里显式指定路径pytesseract.pytesseract.tesseract_cmd rC:\Program Files\Tesseract-OCR\tesseract.exe这一步做完才算真正打通了环境。在VS Code里配置Python环境的朋友我多说一句直接在settings.json里把解释器指向你的虚拟环境再确认pytesseract和opencv确实装在了这个环境里而不是全局环境。Visual Studio Code经常出现“代码编辑器里import成功命令行跑失败”的情况十有八九是虚拟环境串了。2.2 图像预处理灰度化、二值化、去噪点环境通了之后先别急着识别。大多数验证码图片都不是理想的规整图直接送进OCR的结果基本是乱码。这个时候OpenCV的三板斧就派上用场了。第一步灰度化。验证码经常是彩色背景彩色文字OCR不关心颜色只关心字符形状。把彩色图变成灰度图可以减少色彩信息对算法判断的干扰。代码很简单import cv2 img cv2.imread(captcha.png) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)第二步二值化。灰度图每个像素的取值是0到255二值化就是让每个像素只取0或255两种值。这样图片就变成纯黑白的字符是白底黑字或者黑底白字OCR引擎对这种图的识别率最高。一般用自适应阈值或者固定阈值thresh cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]这里THRESH_BINARY_INV是把字符变成白色、背景变成黑色因为Tesseract对白字黑底的识别效果通常更好。THRESH_OTSU是自适应阈值会根据图片灰度分布自动算出一个最合适的阈值省得我们手动试。第三步去噪点。验证码里常见的小噪点、细线在二值化之后仍然存在。最简单的办法是用中值滤波denoised cv2.medianBlur(thresh, 3)也可以用形态学操作比如开运算先腐蚀再膨胀可以把细小的噪点抹掉。如果干扰线比较重还可以试试cv2.morphologyEx配合合适的卷积核。把这三步走完原本五颜六色带噪点的验证码就变成一张干净的黑白字符图。拿它再喂给pytesseract识别率会有质的提升。我实测过一个白底彩色噪点的验证码直接识别几乎全错预处理之后能到70%以上。提示二值化参数没有唯一答案。你可以先把处理后的图片保存到本地肉眼看看字符是否完整、是否断线、是否粘连。看不清字符的参数组合识别率一定上不去。2.3 分割与字符定位的常用技巧如果预处理后识别率还是不行下一步就该考虑字符分割了。所谓分割就是把一行字符拆成单个字符再逐个识别。为什么要分割因为Tesseract对整行粘连字符的识别能力有限分开识别反而更准。OpenCV里找字符轮廓是最常用的分割方法contours, _ cv2.findContours(denoised, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)拿到轮廓之后可以用cv2.boundingRect得到每个字符的外接矩形然后按x坐标从左到右排序依次裁剪出来。裁剪出来的单个字符图片可以放大、填充边界再喂给Tesseract识别配置--psm 10表示“识别单个字符”。这里有个坑如果字符之间是粘连的findContours会把两个字符并成一个轮廓。这时候要么尝试腐蚀操作把粘连处断开要么根据宽度判断“这个轮廓是不是包含了两个字符”如果是就在中间位置硬切一刀。硬切的办法很粗暴但有时候很有效就是算一下轮廓宽度和单个字符平均宽度的比值比值接近2就切中间。字符分割是个手工活不同验证码有不同的切法。我个人的建议是如果预处理整行识别已经能到80%以上就别折腾分割了。分割算法容易引入新问题比如把字符切残缺、误把干扰线当字符反而降低整体识别率。分割是在整行识别实在不行的情况下才用的兜底方案。3. 手把手写一个识别脚本3.1 最小可用代码识别一张干净验证码先把整个流程串起来我们写一个最小可用的脚本。假设验证码图片是干净的白底黑字没有明显干扰import pytesseract from PIL import Image # 如果是Windows记得配置Tesseract路径 pytesseract.pytesseract.tesseract_cmd rC:\Program Files\Tesseract-OCR\tesseract.exe img Image.open(captcha.png) code pytesseract.image_to_string(img, config--psm 7 --oem 3) # 清洗识别结果去掉多余空白和换行 code code.strip().replace( , ) print(code)--psm 7表示“把图片当作一行文本”这是验证码识别最常用的配置。--oem 3表示使用默认的LSTM引擎新版本Tesseract默认就是这个写不写都行。这里还有个细节验证码识别结果经常混进去一些竖线、点号之类的杂讯。我一般会在输出前做一层清洗只保留字母和数字import re code re.sub(r[^a-zA-Z0-9], , code)清洗之后结果就干净很多。3.2 传入参数、批量处理与目录遍历实际使用中你不太可能只识别一张图。更常见的需求是把一批验证码图片丢进一个文件夹脚本逐个识别最后汇总结果。这时候可以用argparse给脚本传入参数没必要把路径写死在代码里。import argparse import os import pytesseract import cv2 pytesseract.pytesseract.tesseract_cmd rC:\Program Files\Tesseract-OCR\tesseract.exe def preprocess(image_path): img cv2.imread(image_path) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) _, thresh cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU) return thresh def recognize(image_path): processed preprocess(image_path) # pytesseract可以直接读numpy数组也可以先转成PIL Image code pytesseract.image_to_string(processed, config--psm 7) return code.strip() if __name__ __main__: parser argparse.ArgumentParser(description验证码批量识别工具) parser.add_argument(--input, -i, requiredTrue, help输入图片文件或文件夹路径) parser.add_argument(--output, -o, defaultresult.txt, help结果输出文件) args parser.parse_args() path args.input if os.path.isfile(path): files [path] else: files [os.path.join(path, f) for f in os.listdir(path) if f.lower().endswith((.png, .jpg, .jpeg, .bmp))] with open(args.output, w, encodingutf-8) as f: for file in files: result recognize(file) f.write(f{file}\t{result}\n) print(f{file} - {result})这里面有两个小设计可以学习。第一个是--input既能接文件又能接文件夹脚本适应性更强。第二个是结果写进文件的同时也在终端打印方便实时观察。有时候你会遇到验证码不是一张图片文件而是接口返回的JSON里有一段base64编码的图片数据。比如你用抓包工具看到响应数据长这样{captcha: data:image/png;base64,iVBORw0KGgo...}这时候不需要保存成文件再识别直接在脚本里解码import base64 import numpy as np import cv2 base64_str iVBORw0KGgo... # 去掉data:image/png;base64,前缀 img_data base64.b64decode(base64_str) img_array np.frombuffer(img_data, np.uint8) img cv2.imdecode(img_array, cv2.IMREAD_COLOR)把这段逻辑接上脚本就能直接从接口响应里识别验证码省去中间落盘再读盘的环节效率高很多。3.3 识别率提升白名单、页面分割模式与多尝试Tesseract最被人低估的两个参数是--psm和-c tessedit_char_whitelist。白名单的作用是告诉Tesseract“你只能输出我指定的字符”。验证码基本都是大小写字母和数字但Tesseract经常会识别出各种标点符号比如把1识别成l小写L、把0识别成O。设置白名单之后这类错误会显著减少因为非法字符直接被过滤掉了。config --psm 7 -c tessedit_char_whitelistABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789注意白名单只对Tesseract的输出做约束并不能保证识别准确。但它确实能避免一些很离谱的错误输出。页面分割模式--psm是个值得反复试的参数。常见的几个模式参数含义使用场景--psm 6假设是统一字体大小的文本块整段文字--psm 7只有一行文本一行验证码--psm 8单个单词一个单词验证码--psm 10单个字符分割后的单字符--psm 13原始行不带处理特殊保存的行我自己的经验是验证码优先试--psm 7如果识别率不行可以试试--psm 8。有些字符间距较大的验证码按单词模式识别反而更好。还有一种提高识别率的方式是“多尺度识别”。先把原图识别一次再把图片放大两倍识别一次甚至放大三倍取结果中出现次数最多的那个作为最终结果。这不是什么高深算法但实践证明放大图片对很多小字体验证码有奇效。import cv2 import pytesseract from collections import Counter scale 2 enlarged cv2.resize(thresh, None, fxscale, fyscale, interpolationcv2.INTER_CUBIC) text pytesseract.image_to_string(enlarged, config--psm 7)当然放大也会把噪点放大。所以放大之前要确保预处理已经把噪点清理干净了。4. 常见问题与排查技巧实录4.1 TesseractNotFoundError与路径配置这个报错是新手遇得最多的。它出现的原因很简单pytesseract在运行时找不到tesseract.exe。排查步骤先确认Tesseract本体装了没有。在命令行里跑tesseract --version如果提示“不是内部或外部命令”说明Tesseract没有加入PATH环境变量。记住Tesseract的安装路径比如C:\Program Files\Tesseract-OCR\tesseract.exe在脚本里显式指定import pytesseract pytesseract.pytesseract.tesseract_cmd rC:\Program Files\Tesseract-OCR\tesseract.exe如果你用的是macOS或Linux路径通常是/usr/bin/tesseract或者/usr/local/bin/tesseract用which tesseract查一下就行。有个坑要注意rC:\Program Files\Tesseract-OCR\tesseract.exe这种写法前面一定要加r否则Python会把\t当成制表符、把\P当成分隔符路径就废了。如果你不想加r就得写成C:\\Program Files\\Tesseract-OCR\\tesseract.exe。还有一个偷懒但非常实用的办法在系统环境变量里把Tesseract的安装目录加到Path里这样脚本里就不用写路径了。缺点是换一台机器跑还得重新配写死在代码里反而更可移植。4.2 识别结果乱码、空字符串的排查识别结果是一堆乱七八糟的字符或者干脆是空字符串这是最常见的第二个问题。根据我的经验按照下面这个顺序排查基本能定位第一步检查图片质量。把预处理后的图片保存到本地看一眼。如果图片黑糊糊一片字符都连在一起或者断的断、残的残OCR肯定识别不好。此时回头调二值化阈值和滤波参数。第二步检查字符颜色。刚才预处理时用了THRESH_BINARY_INV它的作用是把字符变成白色、背景变黑色。但有些验证码本身就是深色背景浅色文字这时候反转反而合适有些是浅色背景深色文字反转后字符变白、背景变黑效果更好。你可以在脚本里把THRESH_BINARY_INV换成THRESH_BINARY试一下对比效果。第三步检查缩放比例。有些验证码的字符很小直接识别很难。把图片放大2到3倍再识别命中率会明显提升。注意放大要用INTER_CUBIC比默认插值效果更好。第四步检查页面分割模式。如果你用的是--psm 7但识别结果很差试试--psm 6或者--psm 8。不同模式对图片的假设不同有时候仅仅换一个模式结果就完全不同。第五步检查语言包。Tesseract默认用英文语言包识别但如果你给它的图片里既有中文又有英文识别结果会乱七八糟。如果验证码确定是纯数字或纯英文就用英文包如果包含中文就去Tesseract官网下载中文语言包然后指定langchi_sim。4.3 遇到干扰线极强的验证码怎么办有些验证码的干扰线特别粗、特别多预处理都很难清理干净。我遇到这种情况通常这么处理先用颜色过滤。干扰线往往颜色和字符颜色不同可以在彩色空间里根据颜色范围把干扰线像素过滤掉。比如验证码字符是红色、干扰线是灰色那我们就只保留接近红色的像素。import cv2 import numpy as np img cv2.imread(captcha.png) hsv cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # 设定红色的HSV范围 lower_red np.array([0, 50, 50]) upper_red np.array([10, 255, 255]) mask cv2.inRange(hsv, lower_red, upper_red) result cv2.bitwise_and(img, img, maskmask)然后对过滤后的图片再做灰度化和二值化。这种方法对“固定颜色字符任意颜色干扰”的验证码有奇效。如果颜色过滤不管用那就考虑形态学处理。用一个稍微大一点的卷积核做开运算可以把细长的干扰线侵蚀掉。但开运算也可能把字符的一部分抹掉需要多试几个卷积核尺寸。干扰线实在解决不了的还有一个思路不处理干扰线而是把图片切成单字符。单个字符区域里的干扰线往往只有一小段影响会小很多。配合之前的轮廓分割方法逐个字符识别再拼接往往比整行识别更稳。最后如果以上方法都试过了识别率还是上不去我建议直接停止优化。验证码识别到一定程度后继续堆算法投入产出比很低。你可以考虑把验证码送到打码平台花钱买准确率。用深度学习方案自己训练一个专用于这种验证码的识别模型。从业务侧想办法比如调整自动化策略减少遇到验证码的频率。识别验证码的手段还有很多但工具永远是为业务服务的。不要在单一环节上死磕。5. 进阶玩法与工程化落地5.1 封装成独立的命令行工具写好的识别脚本不要只放在一个文件里我建议封装成一个可复用的模块再把识别逻辑和入口逻辑分开。这样可以随时在别的项目里导入使用。# captcha_solver.py import cv2 import pytesseract import numpy as np class CaptchaSolver: def __init__(self, tesseract_pathNone): if tesseract_path: pytesseract.pytesseract.tesseract_cmd tesseract_path def preprocess(self, image): gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) _, thresh cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU) return thresh def solve_from_file(self, path): image cv2.imread(path) return self.solve_from_image(image) def solve_from_image(self, image): processed self.preprocess(image) config --psm 7 -c tessedit_char_whitelistABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 code pytesseract.image_to_string(processed, configconfig) return code.strip()这样封装完外部调用变成solver CaptchaSolver(rC:\Program Files\Tesseract-OCR\tesseract.exe) code solver.solve_from_file(captcha.png)逻辑清晰也方便以后加缓存、加日志、加扩展。5.2 打包成exe时别忘带上Tesseract很多朋友喜欢用PyInstaller把Python脚本打包成exe这样不用装Python也能跑。我要提醒一个坑pytesseract只是调用Tesseract命令行的壳子打包exe的时候并不会把Tesseract本体一起打进去。所以目标机器上还得安装Tesseract或者你把Tesseract的整个目录一起分发出去。如果想把Tesseract目录和环境一起带上可以在PyInstaller的spec文件里添加外部文件pyinstaller -F captcha_solver.py --add-data C:\Program Files\Tesseract-OCR;.\tesseract然后在代码里通过sys._MEIPASS找到解压出来的Tesseract路径。这个操作有点绕但对做工具分发的人来说是必须掌握的。打包完之后目标机器上如果没有注册系统级的环境变量记得在代码里用相对路径指定tesseract_cmd。否则就会出现“本地运行正常打包出来就报TesseractNotFoundError”的经典问题。5.3 后续还能怎么扩展如果用pytesseract把简单验证码的识别流程跑通了后续可以往这几个方向扩展接入接口自动化验证码识别只是其中一环识别出验证码后拼接到登录请求里实现全自动登录。建立样本反馈机制把识别失败的验证码图片保存到一个文件夹里定期人工标注一两次积累样本后训练自己的模型。结合深度学习做兜底先用pytesseract快速识别识别置信度低的时候再走深度学习模型。双层策略既快又准但需要一点工程能力。我个人实际测试下来的体会是pytesseract对于验证码识别这件事真正能稳定发挥的场景是“字符清晰、背景干净、干扰轻微”的验证码。遇到这种场景配置好预处理和OCR参数识别率能到90%以上。而常见的反识别手段一上识别率就会断崖式下跌。所以别神化它但也别太快否定它——用对了地方它就是一把顺手的小刀。最后再分享一个小技巧不管你的验证码识别脚本多完善测试阶段一定要拿至少几百张真实验证码图片跑一遍统计识别率。不要根据一张两张图的表现下结论。识别率统计脚本很简单把正确结果人工记录一下和OCR输出比对就行。这个数据能帮你判断当前方案到底适不适用也方便后续优化时对比效果。验证码识别这条路本质是在和对抗做平衡你想办法多一点识别率就高一点。