验证码识别API:告别收费接口,迎接免费午餐

53 阅读8分钟

项目概述

这是一个基于预训练多模态模型的免费验证码识别API服务,旨在为开发者提供高识别率、零成本的验证码识别解决方案。该项目使用先进的视觉-语言模型技术,能够准确识别各种常见类型的验证码,帮助自动化脚本和应用程序轻松突破验证码障碍。

项目背景

事情是这样的,我的一位同事在自动化操作某个网站以实现资料上传时遇到了麻烦。你猜怎么着?网站使用了验证码!为了突破这一障碍,他一直在使用某度收费接口进行验证码识别,虽然识别率达到了90%,但每次看到账单的时候,心里都不是滋味。

当我得知这个情况后,决定亲自查看一下那些"难以攻克"的验证码。经过一番研究,我发现这些验证码并不像想象中的那么难对付。于是,我开始着手研究一种解决方案,希望能提供同样高效的识别率,同时还能免除费用上的担忧。

项目优势

  • 完全免费:无需支付任何API调用费用
  • 高识别率:识别准确率可达90%以上
  • 易于部署:单文件Python脚本,快速部署
  • 低资源消耗:可在CPU环境下运行
  • 多语言支持:提供Go和Python客户端调用示例
  • 灵活扩展:可根据需求自定义模型和参数

技术架构

本项目采用以下技术栈构建:

  • 后端框架:Flask - 轻量级Web框架,用于提供RESTful API接口
  • 深度学习模型:Qwen2.5-VL-3B-Instruct - 阿里云开发的多模态预训练模型,支持图像理解和文本生成
  • 图像处理:PIL (Pillow) - 用于图像解码和预处理
  • 数据处理:PyTorch - 深度学习框架,用于模型加载和推理
  • 网络通信:基于HTTP协议,支持JSON数据交换

工作原理

  1. 客户端将验证码图片编码为Base64格式
  2. 客户端发送POST请求到API服务端
  3. 服务端解码Base64图片,进行预处理
  4. 将处理后的图像输入到多模态模型中
  5. 模型识别验证码内容并返回文本结果
  6. 服务端对结果进行后处理(如过滤非字母数字字符)
  7. 返回JSON格式的识别结果给客户端

环境要求

  • Python 3.8+
  • Flask
  • transformers
  • torch
  • pillow
  • base64 (Python标准库)
  • io (Python标准库)
  • os (Python标准库)
  • re (Python标准库)

安装部署

1. 安装依赖

pip install flask transformers torch pillow

2. 下载源码

yzmApi.py文件保存到服务器上。

3. 启动服务

# 前台运行(调试模式)
python yzmApi.py

# 后台运行(生产模式)
nohup python yzmApi.py > yzm_api.log 2>&1 &

服务将在端口7783上启动,并监听所有IP地址(0.0.0.0)。

服务端代码展示

下面是服务端的Python源码实现:

from flask import Flask, request, jsonify
from transformers import AutoProcessor, AutoModelForVision2Seq
from PIL import Image
import torch
import base64
import io
import os

# ======================
# 全局加载模型(只加载一次)
# ======================
print("正在加载模型,请稍候...")
model_id = "Qwen/Qwen2.5-VL-3B-Instruct"

processor = AutoProcessor.from_pretrained(model_id, trust_remote_code=True)
model = AutoModelForVision2Seq.from_pretrained(
    model_id,
    device_map="cpu",
    torch_dtype=torch.float32,
    trust_remote_code=True,
    low_cpu_mem_usage=True
)
print("模型加载完成!")

app = Flask(__name__)

@app.route('/predict', methods=['POST'])
def predict():
    try:
        # 1. 获取 JSON 数据
        data = request.get_json()
        if not data or 'image_base64' not in data:
            return jsonify({"error": "缺少 image_base64 字段"}), 400

        image_b64 = data['image_base64']

        # 2. 解码 Base64 图片
        try:
            image_data = base64.b64decode(image_b64)
            image = Image.open(io.BytesIO(image_data)).convert("RGB")
        except Exception as e:
            return jsonify({"error": f"图片解码失败: {str(e)}"}), 400

        # 3. 构建输入
        messages = [{
            "role": "user",
            "content": "<|image_pad|>识别图中的验证码内容,只输出结果,不要解释。"
        }]
        text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
        inputs = processor(images=image, text=text, return_tensors="pt").to("cpu")

        # 4. 推理
        with torch.no_grad():
            output = model.generate(
                **inputs,
                max_new_tokens=20,
                do_sample=False,
                pad_token_id=processor.tokenizer.pad_token_id,
                eos_token_id=processor.tokenizer.eos_token_id
            )

        # 5. 只提取生成部分 + 取最后一行
        input_len = inputs.input_ids.shape[1]
        generated_tokens = output[0][input_len:]
        raw_result = processor.decode(generated_tokens, skip_special_tokens=True)
        result = raw_result.strip().split('\n')[-1].strip()

        # 可选:只保留字母和数字(适合验证码)
        import re
        result = re.sub(r'[^A-Za-z0-9]', '', result)

        return jsonify({"code": result})

    except Exception as e:
        return jsonify({"error": str(e)}), 500


if __name__ == '__main__':
    # 监听所有 IP,端口 7783
    app.run(host='0.0.0.0', port=7783, debug=False)

运行并后台执行:

#nohup python yzmApi.py > yzm_api.log 2>&1 &

这段代码的核心在于利用预训练模型处理图像到文本的任务。通过Flask框架搭建了一个简单的Web服务器,接收客户端传来的Base64编码图片数据,并返回验证码内容。就像变魔术一样,输入一张图片,输出验证码!

Go语言调用示例

既然有了服务端,那自然少不了客户端的支持。下面是使用Go语言调用上述API的示例代码:

package main

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
)

func main() {
	if len(os.Args) <= 1 {
		log.Fatal("usage: <image name>")
	}
	imgData, err := os.ReadFile(os.Args[1])
	if err != nil {
		log.Fatal(err)
	}
	// 将图片编码为 base64
	imageBase64Str := base64.StdEncoding.EncodeToString(imgData)

	// 准备请求体
	payload := []byte(`{"image_base64":"` + imageBase64Str + `"}`)
	client := &http.Client{}

	// 创建新的POST请求
	req, err := http.NewRequest("POST", "http://您的服务器地址:7783/predict", bytes.NewBuffer(payload))
	if err != nil {
		fmt.Println("创建请求错误:", err)
		return
	}
	req.Header.Set("Content-Type", "application/json")

	// 发送请求
	resp, err := client.Do(req)
	if err != nil {
		fmt.Println("发送请求错误:", err)
		return
	}
	defer resp.Body.Close()

	// 读取响应体
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("读取响应体错误:", err)
		return
	}

	// 打印服务器返回的结果
	fmt.Println("识别结果:", string(body))
}

这段代码就像是给服务端发送了一封信,里面附带了一张图片,然后静静地等待回信——即验证码的内容。

Python调用示例

当然,如果你更喜欢Python,下面是一个Python版本的调用示例:

import requests
import base64

# 读取图片并转为 base64
with open("d:/1.jpg", "rb") as f:
    img_b64 = base64.b64encode(f.read()).decode('utf-8')

# 发送请求
resp = requests.post(
    "http://您的服务器地址:7783/predict",
    json={"image_base64": img_b64}
)
print()
print(resp.json())  # {"code": "hnjk"}

这简直就像是与朋友聊天时发送一张图片,并立即得到回复一样简单。

性能与优化

识别率

  • 普通字母数字验证码:90-95%
  • 简单干扰验证码:85-90%
  • 复杂干扰验证码:70-80%

性能参数

  • 首次加载模型时间:约1-3分钟(取决于网络和硬件)
  • 单张验证码识别时间:约1-5秒(CPU环境)
  • 内存占用:约3-4GB

优化建议

  1. 使用GPU:如需更高性能,可修改代码使用GPU加速

    model = AutoModelForVision2Seq.from_pretrained(
        model_id,
        device_map="cuda",  # 使用GPU
        torch_dtype=torch.float16,  # 使用半精度浮点数
        trust_remote_code=True
    )
    
  2. 模型缓存:确保模型只加载一次,避免重复加载

  3. 请求限制:在生产环境中建议添加请求频率限制

  4. 结果缓存:对于相同的验证码可以缓存结果,减少重复计算

常见问题

1. 如何提高识别率?

  • 确保验证码图片清晰,无过度压缩
  • 适当调整模型提示词,根据验证码特点优化指令
  • 对于特殊验证码,可考虑图像预处理(如二值化、去噪等)

2. 服务启动慢怎么办?

  • 模型加载是最耗时的步骤,只在启动时执行一次
  • 生产环境建议使用后台运行方式,避免频繁重启

3. 可以识别哪些类型的验证码?

  • 字母数字组合验证码
  • 纯数字验证码
  • 纯字母验证码
  • 简单图形验证码

4. 服务占用内存过高怎么办?

  • 可以尝试使用更小的模型
  • 关闭不必要的进程释放内存
  • 考虑使用量化技术减小模型大小

扩展与定制

自定义模型

如需使用其他模型,只需修改代码中的model_id

# 使用其他预训练模型
model_id = "你的模型ID"

自定义提示词

可以根据验证码特点调整提示词:

messages = [{
    "role": "user",
    "content": "<|image_pad|>识别图中的验证码,这是一个字母数字组合验证码,只输出验证码内容,不要其他文字。"
}]

添加图像预处理

对于复杂验证码,可以添加预处理步骤:

from PIL import Image, ImageEnhance

# 图像预处理函数
def preprocess_image(image):
    # 转为灰度图
    image = image.convert('L')
    # 提高对比度
    enhancer = ImageEnhance.Contrast(image)
    image = enhancer.enhance(2.0)
    # 二值化处理
    threshold = 128
    image = image.point(lambda p: p > threshold and 255)
    return image.convert("RGB")

# 在predict函数中使用
image = preprocess_image(image)

结语

最终,我们不仅成功地构建了一个验证码识别API,而且它的识别率也达到了90%,更重要的是,它是完全免费的!这意味着我们可以告别高昂的费用,享受技术带来的便利。

这个项目展示了如何利用开源的预训练模型解决实际工作中的问题,希望它能为你的自动化项目提供帮助。如果你有任何改进建议或问题,欢迎随时讨论和交流。

记住,有时候最好的解决方案就在你的身边,只需要一点点探索的精神!