接单日记验证码图片生成

Posted A-L-Kun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了接单日记验证码图片生成相关的知识,希望对你有一定的参考价值。

接单日记(四)验证码图片生成

此为一个实验报告,故遵守实验报告的格式。

一、 实验目的

  1. 熟悉Python第三方库pillow的安装方法
  2. 熟悉Python第三方库pillow的简单使用
  3. 理解验证码图片的生成原理
  4. 理解Python内置模块string的函数方法,及其属性

二、 实验内容

编写程序,绘制一个验证码图片,要求图片的背景为白色,同时该验证码有随机四位字母或者数字组成,在图片上还需要有干扰线,可以不需要干扰点。

三、 程序及结果

1、 运行程序

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# @author: A.L.Kun
# @file: Verification.py
# @time: 2023/4/19 15:15
from random import choice, randint
from PIL import Image, ImageDraw, ImageFont


class Verification:
    """生成验证码图片的类"""

    def __init__(self, n, draw_line, size, font_path, bgcolor, path):
        self.draw_line = draw_line  # 是否添加干扰线
        self.n = n  # 生成即为验证码
        self.lis_all_code = []  # 存储获得的字母和数字
        self.getAllCode()  # 调用方法生成
        self.bgcolor = bgcolor  # 背景颜色
        # print(self.lis_all_code)  # 检测生成的字母和数字
        self.ret = ""  # 保存生成的验证码
        self.size = size  # 设置大小
        self.font = font_path  # 设置字体
        self.save_path = path  # 存储的路径

    def getAllCode(self):
        """获得所有字母和数字,存储在列表中"""
        for i in range(48, 58):
            self.lis_all_code.append(chr(i))
        for i in range(65, 91):
            self.lis_all_code.append(chr(i))
            self.lis_all_code.append(chr(i + 32))

    def add_line(self, pen):
        """添加干扰线,要传入画笔"""
        for i in range(randint(4, 8)):  # 随机生成四到八条干扰线
            # 颜色随机
            randColor = (randint(0, 255), randint(0, 255), randint(0, 255))
            # 起点随机
            start = (randint(0, self.size[0]), randint(0, self.size[1]))
            # 终点随机
            end = (randint(0, self.size[0]), randint(0, self.size[1]))
            # 画一条线
            pen.line([start, end], fill=randColor)

    def add_point(self, pen):
        """添加干扰点,传入画笔"""
        for i in range(randint(20, 24)):  # 生成随机个干扰点
            # 颜色随机
            randColor = (randint(0, 255), randint(0, 255), randint(0, 255))
            # 起点随机
            point = (randint(0, self.size[0]), randint(0, self.size[1]))
            pen.point(point, fill=randColor)

    def getImg(self):
        """获取验证码图片"""
        img = Image.new("RGBA", self.size, self.bgcolor)  # 创建一个图片
        pen = ImageDraw.Draw(img)  # 创建一个画笔
        font = ImageFont.truetype(self.font, 25)  # 实例化字体
        for i in range(self.n):  # 把文字写入图片中
            string = choice(self.lis_all_code)  # 生成随机的验证码
            self.ret += string  # 添加到结果中
            font_color = (randint(0, 255), randint(0, 255), randint(0, 255))  # 颜色随机
            # 验证码的初始横轴偏移量
            x_start = randint(2, 5)
            # 验证码的初始纵轴偏移量
            y_start = 0
            x = x_start + i * (self.size[0] // self.n)  # 获取每个字横坐标的位置
            y = randint(y_start, self.size[1] // 2) - 3  # 获取每个字纵坐标的位置
            pen.text([x, y], string, font=font, fill=font_color)  # 写入每一个字
        if self.draw_line:  # 如果要加干扰点线
            self.add_line(pen)
            self.add_point(pen)
        return img

    def main(self):
        """入口函数"""
        img = self.getImg()  # 获取图片
        img.save(self.save_path)  # 保存图片,因为里面有四色通道,所以要保存为png格式
        ret = self.ret
        self.ret = ""  # 重置 self.ret 的值
        return ret  # 同时要返回随机的字符串


if __name__ == "__main__":
    c = Verification(4, True, (100, 36), r\'C:\\Windows\\Fonts\\simfang.ttf\', (255, 255, 255), "verify.png")
    print(c.main())

2、 运行结果

验证码识别与生成类API调用的代码示例合集:六位图片验证码生成四位图片验证码生成简单验证码识别等

以下示例代码适用于 www.apishop.net 网站下的API,使用本文提及的接口调用代码示例前,您需要先申请相应的API服务。

  1. 六位图片验证码生成:包括纯数字、小写字母、大写字母、大小写混合、数字+小写、数字+大写、数字+大小写等情况。
  2. 四位图片验证码生成:包括纯数字、小写字母、大写字母、大小写混合、数字+小写、数字+大写、数字+大小写等情况。
  3. 简单验证码识别:验证码类型 : 数字+字母, 纯英文, 纯数字,计算题
  4. 英数_验证码识别:纯数字,纯英文,数字+英文
  5. 中英数_验证码识别:英文、数字、中文或混合型

**API Shop(apishop.net)提供多达50款的常用第三方API,可以从github上下载代码示例合集:https://github.com/apishop/All-APIs**

以上接口均包含PHP、Python、C#和Java等四种语言的代码示例,以 识别中英数验证码 API为例:

(1)基于PHP的 识别中英数验证码 API服务请求的代码示例

<?php
$method = "POST";
$url = "https://api.apishop.net/common/checkcode/recognizeVerifyCodeComplicate";
$headers = NULL;
$params = array(
    "convertJPG" => "", //是否转换成jpg格式(有少量png或gif图转成jpg格式后识别率明显提高,但并不是所有png或gif转成jpg后都会提高识别率。) 此字段为1时表示需要把图片转为jpg格式,其他值不做转换操作。
    "imgBASE64" => "", //图片文件的base64字符串。图片大小需要小于100KB。
    "codeType" => "", //验证码类型,最大长度为10.(10:任意长度数字(成功率有所降低),11~19:1~9位数字,20:任意长度英文(成功率有所降低),21~29:1~9位英文,30:任意长度英数混合(成功率有所降低),31~39:1~9位英数混合,40:任意长度汉字(成功率有所降低),41~49:1~9位汉字,50:任意长度中英数混合(成功率有所降低)),如“34”代表四位英数混合 
);

$result = apishop_curl($method, $url, $headers, $params);
If ($result) {
    $body = json_decode($result["body"], TRUE);
    $status_code = $body["statusCode"];
    If ($status_code == "000000") {
        //状态码为000000, 说明请求成功
        echo "请求成功:" . $result["body"];
    } else {
        //状态码非000000, 说明请求失败
        echo "请求失败:" . $result["body"];
    }
} else {
    //返回内容异常,发送请求失败,以下可根据业务逻辑自行修改
    echo "发送请求失败";
}

/**
 * 转发请求到目的主机
 * @param $method string 请求方法
 * @param $URL string 请求地址
 * @param null $headers 请求头
 * @param null $param 请求参数
 * @return array|bool
 */
function apishop_curl(&$method, &$URL, &$headers = NULL, &$param = NULL)
{
    // 初始化请求
    $require = curl_init($URL);
    // 判断是否HTTPS
    $isHttps = substr($URL, 0, 8) == "https://" ? TRUE : FALSE;
    // 设置请求方式
    switch ($method) {
        case "GET":
            curl_setopt($require, CURLOPT_CUSTOMREQUEST, "GET");
            break;
        case "POST":
            curl_setopt($require, CURLOPT_CUSTOMREQUEST, "POST");
            break;
        default:
            return FALSE;
    }
    if ($param) {
        curl_setopt($require, CURLOPT_POSTFIELDS, $param);
    }
    if ($isHttps) {
        // 跳过证书检查
        curl_setopt($require, CURLOPT_SSL_VERIFYPEER, FALSE);
        // 检查证书中是否设置域名
        curl_setopt($require, CURLOPT_SSL_VERIFYHOST, 2);
    }
    if ($headers) {
        // 设置请求头
        curl_setopt($require, CURLOPT_HTTPHEADER, $headers);
    }
    // 返回结果不直接输出
    curl_setopt($require, CURLOPT_RETURNTRANSFER, TRUE);
    // 重定向
    curl_setopt($require, CURLOPT_FOLLOWLOCATION, TRUE);
    // 把返回头包含再输出中
    curl_setopt($require, CURLOPT_HEADER, TRUE);
    // 发送请求
    $response = curl_exec($require);
    // 获取头部长度
    $headerSize = curl_getinfo($require, CURLINFO_HEADER_SIZE);
    // 关闭请求
    curl_close($require);
    if ($response) {
        // 返回头部字符串
        $header = substr($response, 0, $headerSize);
        // 返回体
        $body = substr($response, $headerSize);
        // 过滤隐藏非法字符
        $bodyTemp = json_encode(array(
            0 => $body
        ));
        $bodyTemp = str_replace("\ufeff", "", $bodyTemp);
        $bodyTemp = json_decode($bodyTemp, TRUE);
        $body = trim($bodyTemp[0]);
        // 将返回结果头部转成数组
        $respondHeaders = array();
        $header_rows = array_filter(explode(PHP_EOL, $header), "trim");
        foreach ($header_rows as $row) {
            $keylen = strpos($row, ":");
            if ($keylen) {
                $respondHeaders[] = array(
                    "key" => substr($row, 0, $keylen),
                    "value" => trim(substr($row, $keylen + 1))
                );
            }
        }
        return array(
            "headers" => $respondHeaders,
            "body" => $body
        );
    } else {
        return FALSE;
    }
}

(2)基于Python的 识别中英数验证码 API服务请求的代码示例

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# 测试环境: python2.7
# 安装requests依赖 => pip install requests/ easy_install requests

# 导入requests依赖
import requests
import json
import sys

reload(sys)
sys.setdefaultencoding(‘utf-8‘)

def apishop_send_request(method, url, params=None, headers=None):
    ‘‘‘
    转发请求到目的主机
    @param method str 请求方法
    @param url str 请求地址
    @param params dict 请求参数
    @param headers dict 请求头
    ‘‘‘
    method = str.upper(method)
    if method == ‘POST‘:
        return requests.post(url=url, data=params, headers=headers)
    elif method == ‘GET‘:
        return requests.get(url=url, params=params, headers=headers)
    else:
        return None

method = "POST"
url = "https://api.apishop.net/common/checkcode/recognizeVerifyCodeComplicate"
headers = None
params = {          
    "convertJPG":"", #是否转换成jpg格式(有少量png或gif图转成jpg格式后识别率明显提高,但并不是所有png或gif转成jpg后都会提高识别率。) 此字段为1时表示需要把图片转为jpg格式,其他值不做转换操作。
    "imgBASE64":"", #图片文件的base64字符串。图片大小需要小于100KB。
    "codeType":"", #验证码类型,最大长度为10.(10:任意长度数字(成功率有所降低),11~19:1~9位数字,20:任意长度英文(成功率有所降低),21~29:1~9位英文,30:任意长度英数混合(成功率有所降低),31~39:1~9位英数混合,40:任意长度汉字(成功率有所降低),41~49:1~9位汉字,50:任意长度中英数混合(成功率有所降低)),如“34”代表四位英数混合
}
result = apishop_send_request(method=method, url=url, params=params, headers=headers)
if result:
    body = result.text
    response = json.loads(body)
    status_code = response["statusCode"]
    if (status_code == ‘000000‘):
        # 状态码为000000, 说明请求成功
        print(‘请求成功:%s‘ % (body,))
    else:
        # 状态码非000000, 说明请求失败
        print(‘请求失败: %s‘ % (body,))
else:
    # 返回内容异常,发送请求失败
    print(‘发送请求失败‘)

(3)基于C#的 识别中英数验证码 API服务请求的代码示例

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Web.Script.Serialization;

namespace apishop_sdk
{
class Program
{
    /**
     * 转发请求到目的主机
     * @param method string 请求方法
     * @param url string 请求地址
     * @param params Dictionary<string,string> 请求参数
     * @param headers Dictionary<string,string> 请求头
     * @return string
    **/
    static string apishop_send_request(string method, string url, Dictionary<string, string> param, Dictionary<string, string> headers)
    {
        string result = string.Empty;
        try
            {
                string paramData = "";
                if (param != null && param.Count > 0)
                {
                    StringBuilder sbuilder = new StringBuilder();
                    foreach (var item in param)
                    {
                        if (sbuilder.Length > 0)
                        {
                            sbuilder.Append("&");
                        }
                        sbuilder.Append(item.Key + "=" + item.Value);
                    }
                    paramData = sbuilder.ToString();
                }
                method = method.ToUpper();
                if (method == "GET")
                {
                    url = string.Format("{0}?{1}", url, paramData);
                }
                HttpWebRequest wbRequest = (HttpWebRequest)WebRequest.Create(url);
                if (method == "GET")
                {
                    wbRequest.Method = "GET";
                }
                else if (method == "POST")
                {
                    wbRequest.Method = "POST";
                    wbRequest.ContentType = "application/x-www-form-urlencoded";
                    wbRequest.ContentLength = Encoding.UTF8.GetByteCount(paramData);
                    using (Stream requestStream = wbRequest.GetRequestStream())
                    {
                        using (StreamWriter swrite = new StreamWriter(requestStream))
                        {
                            swrite.Write(paramData);
                        }
                    }
                }

                HttpWebResponse wbResponse = (HttpWebResponse)wbRequest.GetResponse();
                using (Stream responseStream = wbResponse.GetResponseStream())
                {
                    using (StreamReader sread = new StreamReader(responseStream))
                    {
                        result = sread.ReadToEnd();
                    }
                }
            }
            catch
            {
                return "";
            }
            return result;
        }
        class Response
        {
            public string statusCode;
        }
        static void Main(string[] args)
        {
            string method = "POST";
            string url = "https://api.apishop.net/common/checkcode/recognizeVerifyCodeComplicate";
            Dictionary<string, string> param = new Dictionary<string, string>();            
            param.Add("convertJPG", ""); //是否转换成jpg格式(有少量png或gif图转成jpg格式后识别率明显提高,但并不是所有png或gif转成jpg后都会提高识别率。) 此字段为1时表示需要把图片转为jpg格式,其他值不做转换操作。
    param.Add("imgBASE64", ""); //图片文件的base64字符串。图片大小需要小于100KB。
    param.Add("codeType", ""); //验证码类型,最大长度为10.(10:任意长度数字(成功率有所降低),11~19:1~9位数字,20:任意长度英文(成功率有所降低),21~29:1~9位英文,30:任意长度英数混合(成功率有所降低),31~39:1~9位英数混合,40:任意长度汉字(成功率有所降低),41~49:1~9位汉字,50:任意长度中英数混合(成功率有所降低)),如“34”代表四位英数混合
        
            Dictionary<string, string> headers = null;
            string result = apishop_send_request(method, url, param, headers);
            if (result == "")
            {
                //返回内容异常,发送请求失败
                Console.WriteLine("发送请求失败");
                return;
            }

            Response res = new JavaScriptSerializer().Deserialize<Response>(result);
            if (res.statusCode == "000000")
            {
                //状态码为000000, 说明请求成功
                Console.WriteLine(string.Format("请求成功: {0}", result));
            }
            else
            {
                //状态码非000000, 说明请求失败
                Console.WriteLine(string.Format("请求失败: {0}", result));
            }
            Console.ReadLine();
        }
    }
}

(4)基于Java的 识别中英数验证码 API服务请求的代码示例

package net.apishop.www.controller;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import com.alibaba.fastjson.JSONObject;

/**
* httpUrlConnection访问远程接口工具
*/
public class Api
{
    /**
    * 方法体说明:向远程接口发起请求,返回字节流类型结果
    * param url 接口地址
    * param requestMethod 请求方式
    * param params 传递参数     重点:参数值需要用Base64进行转码
    * return InputStream 返回结果
    */
    public static InputStream httpRequestToStream(String url, String requestMethod, Map<String, String> params){
        InputStream is = null;
        try{
            String parameters = "";
            boolean hasParams = false;
            // 将参数集合拼接成特定格式,如name=zhangsan&age=24
            for (String key : params.keySet()){
                String value = URLEncoder.encode(params.get(key), "UTF-8");
                parameters += key + "=" + value + "&";
                hasParams = true;
            }
            if (hasParams){
                parameters = parameters.substring(0, parameters.length() - 1);
            }
            // 请求方式是否为get
            boolean isGet = "get".equalsIgnoreCase(requestMethod);
            // 请求方式是否为post
            boolean isPost = "post".equalsIgnoreCase(requestMethod);
            if (isGet){
                url += "?" + parameters;
            }
            URL u = new URL(url);
            HttpURLConnection conn = (HttpURLConnection) u.openConnection();
            // 请求的参数类型(使用restlet框架时,为了兼容框架,必须设置Content-Type为“”空)
            conn.setRequestProperty("Content-Type", "application/octet-stream");
            //conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
            // 设置连接超时时间
            conn.setConnectTimeout(50000);
            // 设置读取返回内容超时时间
            conn.setReadTimeout(50000);
            // 设置向HttpURLConnection对象中输出,因为post方式将请求参数放在http正文内,因此需要设置为ture,默认false
            if (isPost){
                conn.setDoOutput(true);
            }
            // 设置从HttpURLConnection对象读入,默认为true
            conn.setDoInput(true);
            // 设置是否使用缓存,post方式不能使用缓存
            if (isPost){
                conn.setUseCaches(false);
            }
            // 设置请求方式,默认为GET
            conn.setRequestMethod(requestMethod);
            // post方式需要将传递的参数输出到conn对象中
            if (isPost){
                DataOutputStream dos = new DataOutputStream(conn.getOutputStream());
                dos.writeBytes(parameters);
                dos.flush();
                dos.close();
            }
            // 从HttpURLConnection对象中读取响应的消息
            // 执行该语句时才正式发起请求
            is = conn.getInputStream();
        }catch(UnsupportedEncodingException e){
            e.printStackTrace();
        }catch(MalformedURLException e){
            e.printStackTrace();
        }catch(IOException e){
            e.printStackTrace();
        }
        return is;
    }

    public static void main(String args[]){
        String url = "https://api.apishop.net/common/checkcode/recognizeVerifyCodeComplicate";
        String requestMethod = "POST";
        Map<String, String> params = new HashMap<String, String>();         
        params.put("convertJPG", ""); //是否转换成jpg格式(有少量png或gif图转成jpg格式后识别率明显提高,但并不是所有png或gif转成jpg后都会提高识别率。) 此字段为1时表示需要把图片转为jpg格式,其他值不做转换操作。
    params.put("imgBASE64", ""); //图片文件的base64字符串。图片大小需要小于100KB。
    params.put("codeType", ""); //验证码类型,最大长度为10.(10:任意长度数字(成功率有所降低),11~19:1~9位数字,20:任意长度英文(成功率有所降低),21~29:1~9位英文,30:任意长度英数混合(成功率有所降低),31~39:1~9位英数混合,40:任意长度汉字(成功率有所降低),41~49:1~9位汉字,50:任意长度中英数混合(成功率有所降低)),如“34”代表四位英数混合   
        String result = null;
        try{
            InputStream is = httpRequestToStream(url, requestMethod, params);
            byte[] b = new byte[is.available()];
            is.read(b);
            result = new String(b);
        }catch(IOException e){
            e.printStackTrace();
        }
        if (result != null){
            JSONObject jsonObject = JSONObject.parseObject(result);
            String status_code = jsonObject.getString("statusCode");
            if (status_code == "000000"){
                // 状态码为000000, 说明请求成功
                System.out.println("请求成功:" + jsonObject.getString("result"));
            }else{
                // 状态码非000000, 说明请求失败
                System.out.println("请求失败:" + jsonObject.getString("desc"));
            }
        }else{
            // 返回内容异常,发送请求失败,以下可根据业务逻辑自行修改
            System.out.println("发送请求失败");
        }
    }
}

以上是关于接单日记验证码图片生成的主要内容,如果未能解决你的问题,请参考以下文章

接单日记SMTP发送邮件

接单日记:理解浮点数运算的误差

跟我学算法- tensorflow 卷积神经网络训练验证码

单点登录CAS使用记:为登录页面加上验证码

如何用PHP生成验证码

验证码识别与生成类API调用的代码示例合集:六位图片验证码生成四位图片验证码生成简单验证码识别等