当前位置:首页>python>Python PIL 实现图形验证码

Python PIL 实现图形验证码

  • 2026-10-11 06:58:03
Python PIL 实现图形验证码

在Web登录、注册场景里,图形验证码是最基础的防刷手段。本文基于Pillow(PIL)从零实现动态验证码图片,包含随机字符、噪点、干扰文字、模糊滤镜,附带完整代码逐行讲解,可直接集成到Flask项目。

一、依赖准备

首先安装图像处理库 Pillow

pip install pillow

用到模块说明:

  • PIL.Image
    :创建画布
  • PIL.ImageDraw
    :绘制像素、文字
  • PIL.ImageFont
    :加载字体渲染文字
  • PIL.ImageFilter
    :图片模糊滤镜
  • random
    :生成随机字符、随机颜色、随机坐标
  • uuid
    :生成唯一文件名,避免图片重名覆盖

二、整体实现思路

  1. 随机生成 数字、大小写字母 验证码字符(4位)
  2. 创建空白RGB画布,底色浅灰色
  3. 绘制噪点像素,增加识别难度
  4. 循环渲染4位验证码字符,随机纵向偏移、深色文字
  5. 添加干扰文字,防止爬虫简单OCR识别
  6. 添加模糊滤镜,进一步提升破解门槛
  7. 使用uuid命名图片,保存到本地;项目中可把验证码字符串存入数据库,绑定session用于校验

三、代码

from uuid import uuid1from PIL import Image, ImageDraw, ImageFont, ImageFilterimport randomdef rnd_char():    ”””随机生成一个字符:数字 / 小写字母 / 大写字母”””    i = random.randint(1, 3)    if i == 1:# 小写字母 ASCII 97 ~ 122        an = random.randint(97, 122)    elif i == 2:# 大写字母 ASCII 65 ~ 90        an = random.randint(65, 90)    else:# 数字 ASCII 48 ~ 57        an = random.randint(48, 57)    return chr(an)def rnd_dis():    ”””随机干扰字符(和验证码共用一套规则)”””    return rnd_char()def rnd_color():    ”””噪点颜色:偏亮色系 (64~255),和背景区分”””    return (random.randint(64, 255),            random.randint(64, 255),            random.randint(64, 255))def rnd_color2():    ”””验证码文字颜色:深色 (32~127),保证辨识度”””    return (random.randint(32, 127),            random.randint(32, 127),            random.randint(32, 127))def create_code():# 设置图片尺寸:宽240,高60    width = 60 * 4    height = 60# 创建画布,RGB模式,浅灰色背景    image = Image.new('RGB', (width, height), (192, 192, 192))# 加载字体(注意替换为你本机可用字体路径!)    font = ImageFont.truetype('./font/CorporateS-RegularItalic.otf', 36)# 创建画笔对象    draw = ImageDraw.Draw(image)# 1. 批量绘制噪点像素,降低OCR识别率    for x in range(0, width, 20):        for y in range(0, height, 10):            draw.point((x, y), fill=rnd_color())# 2. 生成4位验证码字符串    code_str = ””    for t in range(4):        c = rnd_char()        code_str += c# 纵向随机偏移,避免文字水平对齐        h = random.randint(1, height - 30)# 横向均分位置,预留边距        w = width / 4 * t + 10        draw.text((w, h), c, font=font, fill=rnd_color2())# 3. 添加干扰字符    for j in range(0, width, 30):        dis_char = rnd_dis()        w = random.randint(0, width)        h = random.randint(1, height - 30)        draw.text((w, h), dis_char, font=font, fill=rnd_color())# 4. 高斯模糊滤镜    image = image.filter(ImageFilter.BLUR)# uuid生成唯一图片名称,防止文件覆盖    code_name = f'{uuid1()}.jpg'    save_dir = f'./{code_name}'    image.save(save_dir, 'jpeg')    print(f”验证码文本:{code_str},图片已保存 {save_dir}”)    return code_str, imageif __name__ == '__main__':    create_code()

说明

  1. 两套随机颜色函数    - rnd_color():噪点、干扰文字,亮度区间更高;    - rnd_color2():主体验证码文字,使用深色,保证人眼可读。
  2. 文字随机纵向偏移h = random.randint(1, height - 30)         如果所有文字在同一水平线,爬虫OCR极易批量识别,随机上下浮动提升难度。
  3. UUID命名图片uuid1()         生成带时间信息唯一ID,高并发场景不会出现图片覆盖问题。
  4. ⚠️ 字体踩坑提醒ImageFont.truetype("字体路径", 36)         Windows、Linux、Mac自带字体路径各不相同。    - Linux服务器经常缺少ttf字体,部署前需要安装字体;    - 建议把字体文件放到项目目录,使用相对路径。

四、生产项目流程

  1. 调用create_code(),拿到验证码字符串 code_str;
  2. 将 code_str 存入数据库/Redis,同时绑定用户sessionID,设置5分钟过期;
  3. 将图片直接返回给前端(Flask中可返回二进制流,不用落地保存文件,节省磁盘);
  4. 用户登录提交验证码时,后端根据session取出预存字符串,忽略大小写比对。

优化建议:不要每次都保存图片到硬盘!Flask项目里直接把图片转为二进制BytesIO返回给前端,避免大量验证码图片堆积占用磁盘。

Flask 流式返回

from flask import Flask, make_response, sessionfrom io import BytesIOfrom verification_code import create_codeapp = Flask(__name__)# session密钥,生产环境请使用复杂随机字符串app.secret_key = "secret_key_123456"@app.route("/verification_code")def verification_code():    """    获取图形验证码接口    """    # 调用生成函数,得到验证码文本、图片对象    code_str, code_img = create_code()    # 创建内存缓冲区    buf = BytesIO()    # 将图片写入内存缓冲区,格式jpeg    code_img.save(buf, 'jpeg')    # 从缓冲区取出二进制图片数据    image_data = buf.getvalue()    # 构造响应    ret = make_response(image_data)    # 设置响应头,告知浏览器这是图片    ret.headers['Content-Type'] = 'image/jpg'    # 将验证码存入session,统一转大写,实现大小写不敏感校验    session['img'] = code_str.upper()    return retif __name__ == '__main__':    app.run(debug=True)

解析

buf = BytesIO()code_img.save(buf, 'jpeg')image_data = buf.getvalue()
  1. BytesIO()
    :开辟一块内存充当虚拟文件;
  2. code_img.save(buf, 'jpeg')
    :PIL不再写入硬盘,直接把图片写入内存;
  3. buf.getvalue()
    :读取内存缓冲区中完整图片二进制数据。
ret = make_response(image_data)ret.headers['Content-Type'] = 'image/jpg'
  • make_response(image_data)
    :把图片二进制作为响应体;
  • Content-Type
     头部告诉浏览器:返回内容是jpg图片,浏览器直接渲染图片。
session['img'] = code_str.upper()

将正确验证码存入session,统一转为大写;用户提交时同样转大写对比,做到输入大小写均可通过校验。

登录验证码校验示例

from flask import request@app.route(”/login”, methods=[”POST”])def login():# 获取用户提交验证码    user_code = request.form.get(”code”, ””).upper()# 取出session保存的正确验证码    server_code = session.get(”img”)    if not server_code:        return ”验证码已过期,请刷新”    if user_code == server_code:        return ”验证码校验成功”    return ”验证码错误”

最新文章

随机文章