Python 图片验证码库推荐与实践指南
网罗开发(小红书、快手、视频号同名)
大家好,我是展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。
图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。
展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!
文章目录
前言
最近在做一个 Web 项目的时候,需要添加验证码功能来防止恶意注册和暴力破解。刚开始想着自己写一个简单的验证码生成器,但发现要考虑的东西还挺多的:图片生成、字符扭曲、干扰线、过期时间、安全性等等。后来发现其实有很多现成的库可以用,但选择哪个库、怎么集成到项目中,又是一个问题。
相信很多 Python 开发者都遇到过类似的问题:项目需要验证码功能,但不知道选哪个库,也不知道怎么集成。今天我们就来聊聊 Python 中常用的图片验证码库,以及如何在实际项目中应用它们。
为什么需要验证码
在深入讨论具体的库之前,我们先聊聊为什么需要验证码,以及在实际开发中会遇到哪些痛点。
常见的安全问题
现在的 Web 应用面临很多安全威胁,验证码是其中一种重要的防护手段:
恶意注册:很多网站都会遇到恶意注册的问题,有人用脚本批量注册账号,占用服务器资源,甚至用来发送垃圾信息。如果没有验证码,这些脚本可以轻松地自动化注册流程。
暴力破解:对于登录功能,如果没有验证码,攻击者可以用脚本尝试大量的用户名密码组合。虽然现在很多系统都有登录失败次数限制,但验证码可以进一步增加攻击成本。
接口滥用:很多 API 接口如果没有验证码保护,可能会被恶意调用,比如发送短信验证码、发送邮件等。这些接口如果被滥用,不仅会消耗资源,还可能产生费用。
爬虫防护:虽然验证码不能完全阻止爬虫,但可以增加爬虫的成本。对于一些简单的爬虫,验证码就能起到很好的防护作用。
开发中的痛点
在实际开发中,实现验证码功能会遇到很多痛点:
图片生成复杂:如果要自己实现验证码生成,需要考虑很多细节:字体选择、字符扭曲、干扰线、干扰点、颜色搭配等等。这些细节处理不好,验证码要么太简单容易被识别,要么太复杂用户体验不好。
安全性问题:验证码的安全性是一个大问题。如果验证码太简单,容易被 OCR 识别;如果验证码太复杂,用户体验不好。而且还要考虑验证码的过期时间、一次性使用、防止重放攻击等问题。
框架集成:不同的 Web 框架(Flask、Django、FastAPI 等)集成验证码的方式不一样,需要针对性地适配。而且还要考虑前后端分离的场景,验证码如何通过 API 返回。
用户体验:验证码的用户体验也很重要。如果验证码看不清,用户会抱怨;如果验证码刷新不方便,用户会烦躁。而且现在很多用户习惯使用移动端,验证码在小屏幕上的显示效果也要考虑。
维护成本:如果自己实现验证码功能,后续的维护成本也不低。比如要更新字体、调整样式、修复 bug 等等。而使用现成的库,可以降低维护成本。
主流图片验证码库推荐
根据当前的技术趋势,下面是最常用且好用的图片验证码库,以及它们的特点和适用场景。
captcha:Python 原生库,推荐度高
captcha 是一个由 Google 开发维护的 Python 库,GitHub 上有 1.2k+ stars。它的特点是简单易用,支持自定义,适合各种 Python Web 框架。
优点:
- 简单易用,API 设计清晰
- 支持自定义图片大小、字体、颜色等
- 不依赖特定的 Web 框架,可以在 Flask、Django、FastAPI 等框架中使用
- 由 Google 维护,代码质量有保障
缺点:
- 功能相对简单,不支持复杂的验证码样式
- 安全性相对较低,容易被 OCR 识别
适用场景:
- 内部系统或中小型项目
- 对安全性要求不是特别高的场景
- 需要快速集成验证码功能的项目
基本使用:
from captcha.image import ImageCaptcha # 创建验证码图像 image = ImageCaptcha(width=280, height=90) data = image.generate('1234') image.write('1234','out.png')这个库的使用非常简单,只需要几行代码就能生成验证码图片。但需要注意的是,它生成的验证码相对简单,安全性不是特别高。
django-simple-captcha:Django 专属方案
django-simple-captcha 是专门为 Django 框架设计的验证码库,GitHub 上有 1.6k+ stars。它的特点是 Django 集成度最高,开箱即用。
优点:
- 与 Django 深度集成,使用非常方便
- 支持 Django Forms,可以直接在表单中使用
- 功能完善,支持多种验证码样式
- 社区活跃,文档完善
缺点:
- 仅限 Django 项目使用
- 样式相对固定,自定义程度有限
适用场景:
- Django 项目
- 需要快速集成验证码功能的 Django 应用
- 不需要太多自定义的场景
基本使用:
# settings.py INSTALLED_APPS =['captcha',]# models.pyfrom django import forms from captcha.fields import CaptchaField classContactForm(forms.Form): captcha = CaptchaField()这个库最大的优势就是与 Django 的集成非常好,如果你用的是 Django 框架,这个库是最佳选择。
kaptcha:Java 转 Python 实现
kaptcha 是模仿 Java 版 Kaptcha 的 Python 实现,功能相对强大。
优点:
- 功能强大,支持多种验证码样式
- 可以生成复杂的验证码图片
缺点:
- 文档相对较少
- 社区活跃度不高
- 使用相对复杂
适用场景:
- 需要复杂验证码样式的项目
- 对 Java Kaptcha 熟悉的开发者
商业方案:滑动验证码和行为验证码
除了开源的库,还有一些商业方案,比如极验、腾讯云验证码、阿里云验证码等。这些方案通常提供滑动验证码、行为验证码等更高级的验证方式。
极验(geetest):
- 识别率高,安全性强
- 支持多种验证方式(滑动、点选、语音等)
- 有免费额度,超出后收费
腾讯云验证码:
- 智能验证,多种形式
- 与腾讯云服务集成
- 按调用次数收费
阿里云验证码:
- 风险识别,无感验证
- 与阿里云服务集成
- 按调用次数收费
适用场景:
- 对安全性要求很高的商业项目
- 有预算支持的项目
- 需要高级验证方式的场景
Python 项目引入指南
下面我们来看看如何在实际项目中引入和使用这些验证码库。
基础安装配置
首先,我们需要安装相应的库:
# 安装 captcha 库(Flask/FastAPI 通用) pip install captcha pillow # 安装 django-simple-captcha(Django 项目) pip install django-simple-captcha pillow 是 Python 的图像处理库,captcha 库依赖它来生成图片。如果你用的是 Django,还需要安装 django-simple-captcha。
方案一:使用 captcha 库(Flask/FastAPI 通用)
如果你用的是 Flask 或 FastAPI,可以使用 captcha 库。下面是一个完整的 Flask 示例:
from flask import Flask, request, session, make_response from captcha.image import ImageCaptcha import random import io app = Flask(__name__) app.secret_key ='your-secret-key'defgenerate_captcha():"""生成验证码"""# 生成随机验证码文本(排除易混淆字符) chars ='ABCDEFGHJKLMNPQRSTUVWXYZ23456789' captcha_text =''.join(random.choices(chars, k=4))# 创建验证码图像 image = ImageCaptcha(width=120, height=40) data = image.generate(captcha_text)# 保存验证码到 session session['captcha']= captcha_text return data @app.route('/captcha')defget_captcha():"""获取验证码图片""" image_data = generate_captcha() response = make_response(image_data.getvalue()) response.headers['Content-Type']='image/png'# 防止缓存,确保每次请求都是新的验证码 response.headers['Cache-Control']='no-cache, no-store, must-revalidate' response.headers['Pragma']='no-cache' response.headers['Expires']='0'return response @app.route('/verify', methods=['POST'])defverify_captcha():"""验证用户输入""" user_input = request.form.get('captcha','').upper() server_captcha = session.get('captcha','')if user_input == server_captcha:# 验证成功后清除验证码,防止重复使用 session.pop('captcha',None)return{'success':True,'message':'验证码正确'}else:return{'success':False,'message':'验证码错误'},400这个方案的关键点:
- 生成验证码:使用
ImageCaptcha生成图片,并将验证码文本保存到 session - 返回图片:通过 HTTP 响应返回图片,并设置合适的响应头防止缓存
- 验证输入:从 session 中读取验证码,与用户输入进行比较
- 安全性:验证成功后清除 session 中的验证码,防止重复使用
方案二:Django 项目集成 django-simple-captcha
如果你用的是 Django,使用 django-simple-captcha 会更方便:
第一步:配置 settings.py
# settings.py INSTALLED_APPS =['captcha',]# 验证码设置 CAPTCHA_LENGTH =4# 字符数 CAPTCHA_TIMEOUT =5# 过期时间(分钟) CAPTCHA_NOISE_FUNCTIONS =('captcha.helpers.noise_null',) CAPTCHA_IMAGE_SIZE =(120,40)第二步:在 form 中使用
# forms.pyfrom django import forms from captcha.fields import CaptchaField classLoginForm(forms.Form): username = forms.CharField() password = forms.CharField(widget=forms.PasswordInput) captcha = CaptchaField()第三步:视图使用
# views.pyfrom django.shortcuts import render from.forms import LoginForm deflogin_view(request):if request.method =='POST': form = LoginForm(request.POST)if form.is_valid():# 验证通过,处理登录逻辑 username = form.cleaned_data['username'] password = form.cleaned_data['password']# ... 登录逻辑else: form = LoginForm()return render(request,'login.html',{'form': form})第四步:模板中使用
<!-- login.html --><formmethod="post"> {% csrf_token %} {{ form.as_p }} <inputtype="submit"value="登录"></form>这个方案的优势是集成度非常高,Django 会自动处理验证码的生成、验证等逻辑,你只需要在表单中添加一个字段就行。
方案三:高级自定义验证码
如果你需要更复杂的验证码样式,可以基于 PIL/Pillow 自己实现:
from captcha.image import ImageCaptcha from PIL import Image, ImageDraw, ImageFont, ImageFilter import random import string import io classAdvancedCaptcha:def__init__(self, width=160, height=60): self.width = width self.height = height self.font_size =40defgenerate_text(self, length=4):"""生成验证码文本(排除易混淆字符)""" chars = string.ascii_uppercase + string.digits exclude_chars ={'0','O','1','I','L'} chars =[c for c in chars if c notin exclude_chars]return''.join(random.choices(chars, k=length))defcreate_image(self, text):"""创建验证码图像(添加干扰)"""# 创建画布 image = Image.new('RGB',(self.width, self.height),(255,255,255)) draw = ImageDraw.Draw(image)# 添加随机干扰点for _ inrange(200): x = random.randint(0, self.width) y = random.randint(0, self.height) draw.point((x, y), fill=self._random_color(150,250))# 添加随机干扰线for _ inrange(5): x1 = random.randint(0, self.width) y1 = random.randint(0, self.height) x2 = random.randint(0, self.width) y2 = random.randint(0, self.height) draw.line([(x1, y1),(x2, y2)], fill=self._random_color(100,200), width=1)# 绘制文字try: font = ImageFont.truetype('arial.ttf', self.font_size)except: font = ImageFont.load_default()# 文字扭曲效果for i, char inenumerate(text):# 每个字符随机偏移 x =20+ i *35+ random.randint(-5,5) y =5+ random.randint(-5,5) draw.text((x, y), char, font=font, fill=self._random_color(20,120))# 添加滤镜效果 image = image.filter(ImageFilter.SMOOTH_MORE)# 转换为字节流 img_byte_arr = io.BytesIO() image.save(img_byte_arr,format='PNG') img_byte_arr = img_byte_arr.getvalue()return img_byte_arr, text def_random_color(self, low, high):"""生成随机颜色"""return(random.randint(low, high), random.randint(low, high), random.randint(low, high))这个方案的优势是可以完全自定义验证码的样式,但实现复杂度也更高。
最佳实践建议
在实际项目中,除了基本的验证码功能,我们还需要考虑很多细节。
安全性增强
添加过期时间:验证码不应该永久有效,应该设置过期时间。比如 5 分钟后自动失效:
import time from flask import session defset_captcha_session(text): session['captcha']= text session['captcha_time']= time.time()defverify_captcha_with_timeout(user_input, timeout=300):# 5分钟过期if'captcha'notin session or'captcha_time'notin session:returnFalseif time.time()- session['captcha_time']> timeout:# 清理过期验证码 session.pop('captcha',None) session.pop('captcha_time',None)returnFalsereturn user_input.upper()== session['captcha'].upper()一次性使用:验证码应该是一次性的,验证成功后立即清除,防止重复使用。
大小写不敏感:验证码验证时应该忽略大小写,提升用户体验。
防止重放攻击:每次验证后都应该清除验证码,防止攻击者重复使用同一个验证码。
前端集成示例
前端集成验证码时,需要考虑用户体验:
<!-- HTML前端代码 --><formid="login-form"><inputtype="text"name="username"placeholder="用户名"><inputtype="password"name="password"placeholder="密码"><div><inputtype="text"name="captcha"placeholder="验证码"><imgid="captcha-img"src="/captcha"onclick="this.src='/captcha?'+Date.now()"style="cursor:pointer;vertical-align:middle;"><ahref="javascript:;"onclick="refreshCaptcha()">换一张</a></div><buttontype="submit">登录</button></form><script>functionrefreshCaptcha(){// 通过添加时间戳参数强制刷新 document.getElementById('captcha-img').src ='/captcha?'+ Date.now();}</script>关键点:
- 点击图片刷新:用户可以点击验证码图片来刷新
- 换一张链接:提供明确的刷新入口
- 防止缓存:通过添加时间戳参数防止浏览器缓存
生产环境建议
在生产环境中,我们还需要考虑更多问题:
频率限制:对验证码请求进行 IP 限制,比如 60 秒内最多 5 次。这样可以防止恶意请求:
from flask import request from functools import wraps import time # 简单的内存缓存(生产环境建议用 Redis) request_cache ={}defrate_limit(max_requests=5, window=60):defdecorator(f):@wraps(f)defwrapper(*args,**kwargs): ip = request.remote_addr now = time.time()if ip in request_cache: requests =[r for r in request_cache[ip]if now - r < window]iflen(requests)>= max_requests:return{'error':'请求过于频繁'},429 requests.append(now) request_cache[ip]= requests else: request_cache[ip]=[now]return f(*args,**kwargs)return wrapper return decorator @app.route('/captcha')@rate_limit(max_requests=5, window=60)defget_captcha():# ... 生成验证码验证码多样性:可以混合使用数字、字母、算术验证码等,增加破解难度。
日志记录:记录验证失败次数,如果某个 IP 连续失败多次,可以临时封禁。
前后端分离:如果前后端分离,API 可以返回 base64 格式的验证码:
import base64 @app.route('/api/captcha')defget_captcha_api(): image_data, text = generate_captcha() base64_data = base64.b64encode(image_data.getvalue()).decode()# 保存验证码到 session 或 Redis session['captcha']= text return{'image':f'data:image/png;base64,{base64_data}','expires_in':300# 过期时间(秒)}CDN 缓存:静态验证码图片可以考虑 CDN 缓存,但要注意防止缓存导致的问题。
商业方案对比
对于对安全性要求很高的商业项目,可以考虑使用商业验证码方案。下面是几个主流方案的对比:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 自建 captcha | 免费、可控、无第三方依赖 | 安全性较低、需自己维护 | 内部系统、中小项目 |
| django-simple-captcha | Django 集成好、功能完善 | 仅限 Django、样式固定 | Django 项目 |
| 极验/腾讯云 | 安全性高、智能验证、防破解 | 收费、第三方依赖 | 对安全性要求高的商业项目 |
自建方案:适合内部系统或中小型项目,成本低但安全性相对较低。
Django 方案:适合 Django 项目,集成方便但灵活性有限。
商业方案:适合对安全性要求很高的商业项目,安全性高但需要付费。
实际应用场景
让我们看看几个实际应用场景,了解如何在不同情况下选择合适的方案。
场景一:内部管理系统
对于内部管理系统,对安全性要求不是特别高,可以选择简单的方案:
# 使用 captcha 库,简单快速from captcha.image import ImageCaptcha from flask import Flask, session, make_response app = Flask(__name__) app.secret_key ='your-secret-key'@app.route('/captcha')defget_captcha(): image = ImageCaptcha(width=120, height=40) captcha_text =''.join(random.choices('0123456789', k=4)) data = image.generate(captcha_text) session['captcha']= captcha_text response = make_response(data.getvalue()) response.headers['Content-Type']='image/png'return response 这种场景下,简单的数字验证码就够用了,用户体验也比较好。
场景二:用户注册登录
对于用户注册登录功能,需要平衡安全性和用户体验:
# 使用更复杂的验证码,但不要太难classLoginCaptcha:defgenerate(self):# 使用字母+数字,排除易混淆字符 chars ='ABCDEFGHJKLMNPQRSTUVWXYZ23456789' captcha_text =''.join(random.choices(chars, k=4))# 生成带干扰的验证码图片# ...这种场景下,需要一定的安全性,但也不能太复杂影响用户体验。
场景三:API 接口保护
对于 API 接口,特别是发送短信、邮件等会产生费用的接口,需要更强的保护:
# 使用商业方案或更复杂的验证码# 可以考虑滑动验证码、行为验证码等这种场景下,建议使用商业方案,或者实现更复杂的验证码逻辑。
场景四:前后端分离项目
对于前后端分离的项目,需要返回 base64 格式的验证码:
@app.route('/api/captcha')defget_captcha_api(): image_data, text = generate_captcha() base64_data = base64.b64encode(image_data).decode()# 保存到 Redis(推荐)或 session redis_client.setex(f'captcha:{session_id}',300, text)return{'image':f'data:image/png;base64,{base64_data}','expires_in':300}这种场景下,需要考虑验证码的存储和验证方式。
总结
选择验证码库时,需要根据项目需求来决定:
快速开发:Django 项目用 django-simple-captcha,非 Django 项目用 captcha。
高安全性需求:考虑商业方案(极验、腾讯云验证码)。
完全自定义:基于 PIL/Pillow + captcha 自行开发。
最简单的起步方案:
# 最小化实现 pip install captcha pillow # 生成验证码from captcha.image import ImageCaptcha image = ImageCaptcha() data = image.generate('1234')withopen('captcha.png','wb')as f: f.write(data.getvalue())关键点总结:
- 选择合适的库:根据项目框架和需求选择合适的库
- 安全性考虑:添加过期时间、一次性使用、频率限制等
- 用户体验:平衡安全性和用户体验,不要过度复杂
- 生产环境:考虑日志记录、频率限制、前后端分离等
希望这篇文章能帮助你选择合适的验证码库,并在实际项目中正确使用它们!
完整可运行 Demo 代码
下面是一个完整的 Flask 示例,展示了如何在实际项目中使用验证码:
from flask import Flask, request, session, make_response, render_template_string from captcha.image import ImageCaptcha import random import time import io app = Flask(__name__) app.secret_key ='your-secret-key-change-in-production'# HTML 模板 HTML_TEMPLATE =""" <!DOCTYPE html> <html> <head> <title>验证码示例</title> <style> body { font-family: Arial, sans-serif; max-width: 500px; margin: 50px auto; padding: 20px; } .form-group { margin-bottom: 15px; } label { display: block; margin-bottom: 5px; } input[type="text"], input[type="password"] { width: 100%; padding: 8px; box-sizing: border-box; } .captcha-group { display: flex; align-items: center; gap: 10px; } .captcha-img { cursor: pointer; border: 1px solid #ddd; padding: 5px; } .refresh-link { color: #007bff; text-decoration: none; } .refresh-link:hover { text-decoration: underline; } button { background-color: #007bff; color: white; padding: 10px 20px; border: none; cursor: pointer; } .message { margin-top: 10px; padding: 10px; border-radius: 4px; } .success { background-color: #d4edda; color: #155724; } .error { background-color: #f8d7da; color: #721c24; } </style> </head> <body> <h2>登录示例</h2> <form method="POST" action="/login"> <div> <label>用户名:</label> <input type="text" name="username" required> </div> <div> <label>密码:</label> <input type="password" name="password" required> </div> <div> <label>验证码:</label> <div> <input type="text" name="captcha" required> <img src="/captcha" onclick="refreshCaptcha()" alt="验证码"> <a href="javascript:;" onclick="refreshCaptcha()">换一张</a> </div> </div> <button type="submit">登录</button> </form> {% if message %} <div> {{ message }} </div> {% endif %} <script> function refreshCaptcha() { document.getElementById('captcha-img').src = '/captcha?' + Date.now(); } </script> </body> </html> """defgenerate_captcha_text(length=4):"""生成验证码文本(排除易混淆字符)""" chars ='ABCDEFGHJKLMNPQRSTUVWXYZ23456789'return''.join(random.choices(chars, k=length))defgenerate_captcha():"""生成验证码图片""" captcha_text = generate_captcha_text()# 创建验证码图像 image = ImageCaptcha(width=120, height=40) data = image.generate(captcha_text)# 保存验证码到 session(带时间戳) session['captcha']= captcha_text session['captcha_time']= time.time()return data @app.route('/')defindex():"""首页"""return render_template_string(HTML_TEMPLATE)@app.route('/captcha')defget_captcha():"""获取验证码图片""" image_data = generate_captcha() response = make_response(image_data.getvalue()) response.headers['Content-Type']='image/png'# 防止缓存 response.headers['Cache-Control']='no-cache, no-store, must-revalidate' response.headers['Pragma']='no-cache' response.headers['Expires']='0'return response defverify_captcha(user_input, timeout=300):"""验证验证码(带过期时间)"""if'captcha'notin session or'captcha_time'notin session:returnFalse,'验证码已过期,请刷新'# 检查是否过期(5分钟)if time.time()- session['captcha_time']> timeout: session.pop('captcha',None) session.pop('captcha_time',None)returnFalse,'验证码已过期,请刷新'# 验证(忽略大小写)if user_input.upper()!= session['captcha'].upper():returnFalse,'验证码错误'# 验证成功后清除验证码(一次性使用) session.pop('captcha',None) session.pop('captcha_time',None)returnTrue,'验证成功'@app.route('/login', methods=['GET','POST'])deflogin():"""登录处理"""if request.method =='POST': username = request.form.get('username','') password = request.form.get('password','') captcha_input = request.form.get('captcha','')# 验证验证码 is_valid, message = verify_captcha(captcha_input)ifnot is_valid:return render_template_string( HTML_TEMPLATE, message=message, message_type='error')# 这里处理实际的登录逻辑# 示例:简单的用户名密码验证if username =='admin'and password =='123456':return render_template_string( HTML_TEMPLATE, message='登录成功!', message_type='success')else:return render_template_string( HTML_TEMPLATE, message='用户名或密码错误', message_type='error')return render_template_string(HTML_TEMPLATE)if __name__ =='__main__': app.run(debug=True)使用说明:
- 安装依赖:
pip install flask captcha pillow - 运行应用:
python app.py - 访问应用:
打开浏览器访问http://localhost:5000 - 测试登录:
- 用户名:
admin - 密码:
123456 - 验证码:输入图片中显示的验证码
这个 Demo 展示了:
- 验证码生成:使用
captcha库生成验证码图片 - 验证码验证:带过期时间的一次性验证
- 前端集成:点击图片或链接刷新验证码
- 用户体验:清晰的错误提示和成功提示
你可以根据实际需求修改和扩展这个示例。