前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Python验证码生成

Python验证码生成

作者头像
行云博客
发布2022-05-11 15:28:35
发布2022-05-11 15:28:35
1.3K00
代码可运行
举报
文章被收录于专栏:行云博客行云博客
运行总次数:0
代码可运行

Python程序中生成验证码并不算特别复杂,但需要三方库Pillow的支持(PIL的分支),因为要对验证码图片进行旋转、扭曲、拉伸以及加入干扰信息来防范那些用OCR(光学文字识别)破解验证码的程序。

下面的代码封装了生成验证码图片的功能,大家可以直接用这些代码来生成图片验证码,不要“重复发明轮子”。

代码语言:javascript
代码运行次数:0
运行
复制
"""
图片验证码
"""
import os
import random

from io import BytesIO

from PIL import Image
from PIL import ImageFilter
from PIL. ImageDraw import Draw
from PIL. ImageFont import truetype


class Bezier ( object ):
    """贝塞尔曲线"""

    def __init__ ( self ):
        self. tsequence = tuple ( [t / 20.0 for t in range ( 21 ) ] )
        self. beziers = { }

    def make_bezier ( self , n ):
        """绘制贝塞尔曲线"""
        try:
            return self. beziers [n ]
        except KeyError:
            combinations = pascal_row (n - 1 )
            result = [ ]
            for t in self. tsequence:
                tpowers = (t ** i for i in range (n ) )
                upowers = ( ( 1 - t ) ** i for i in range (n - 1 , - 1 , - 1 ) )
                coefs = [c * a * b for c , a , b in zip (combinations ,
                                                      tpowers , upowers ) ]
                result. append (coefs )
            self. beziers [n ] = result
            return result


class Captcha ( object ):
    """验证码"""

    def __init__ ( self , width , height , fonts = None , color = None ):
        self._image = None
        self._fonts = fonts if fonts else \
            [ os. path. join ( os. path. dirname (__file__ ) , 'fonts' , font )
              for font in [ 'ArialRB.ttf' , 'ArialNI.ttf' , 'Georgia.ttf' , 'Kongxin.ttf' ] ]
        self._color = color if color else random_color ( 0 , 200 , random. randint ( 220 , 255 ) )
        self._width , self._height = width , height

    @ classmethod
    def instance (cls , width = 200 , height = 75 ):
        prop_name = f '_instance_{width}_{height}'
        if not hasattr (cls , prop_name ):
            setattr (cls , prop_name , cls (width , height ) )
        return getattr (cls , prop_name )

    def background ( self ):
        """绘制背景"""
        Draw ( self._image ). rectangle ( [ ( 0 , 0 ) , self._image. size ] ,
                                    fill =random_color ( 230 , 255 ) )

    def smooth ( self ):
        """平滑图像"""
        return self._image. filter (ImageFilter. SMOOTH )

    def curve ( self , width = 4 , number = 6 , color = None ):
        """绘制曲线"""
        dx , height = self._image. size
        dx / = number
        path = [ (dx * i , random. randint ( 0 , height ) )
                for i in range ( 1 , number ) ]
        bcoefs = Bezier ( ). make_bezier (number - 1 )
        points = [ ]
        for coefs in bcoefs:
            points. append ( tuple ( sum ( [coef * p for coef , p in zip (coefs , ps ) ] )
                                for ps in zip (*path ) ) )
        Draw ( self._image ). line (points , fill =color if color else self._color , width =width )

    def noise ( self , number = 50 , level = 2 , color = None ):
        """绘制扰码"""
        width , height = self._image. size
        dx , dy = width / 10 , height / 10
        width , height = width - dx , height - dy
        draw = Draw ( self._image )
        for i in range (number ):
            x = int ( random. uniform (dx , width ) )
            y = int ( random. uniform (dy , height ) )
            draw. line ( ( (x , y ) , (x + level , y ) ) ,
                      fill =color if color else self._color , width =level )

    def text ( self , captcha_text , fonts , font_sizes = None , drawings = None , squeeze_factor = 0.75 , color = None ):
        """绘制文本"""
        color = color if color else self._color
        fonts = tuple ( [truetype (name , size )
                        for name in fonts
                        for size in font_sizes or ( 65 , 70 , 75 ) ] )
        draw = Draw ( self._image )
        char_images = [ ]
        for c in captcha_text:
            font = random. choice (fonts )
            c_width , c_height = draw. textsize (c , font =font )
            char_image = Image. new ( 'RGB' , (c_width , c_height ) , ( 0 , 0 , 0 ) )
            char_draw = Draw (char_image )
            char_draw. text ( ( 0 , 0 ) , c , font =font , fill =color )
            char_image = char_image. crop (char_image. getbbox ( ) )
            for drawing in drawings:
                d = getattr ( self , drawing )
                char_image = d (char_image )
            char_images. append (char_image )
        width , height = self._image. size
        offset = int ( (width - sum ( int (i. size [ 0 ] * squeeze_factor )
                                  for i in char_images [:- 1 ] ) -
                      char_images [- 1 ]. size [ 0 ] ) / 2 )
        for char_image in char_images:
            c_width , c_height = char_image. size
            mask = char_image. convert ( 'L' ). point ( lambda i: i * 1.97 )
            self._image. paste (char_image ,
                        (offset , int ( (height - c_height ) / 2 ) ) ,
                        mask )
            offset + = int (c_width * squeeze_factor )

    @ staticmethod
    def warp (image , dx_factor = 0.3 , dy_factor = 0.3 ):
        """图像扭曲"""
        width , height = image. size
        dx = width * dx_factor
        dy = height * dy_factor
        x1 = int ( random. uniform (-dx , dx ) )
        y1 = int ( random. uniform (-dy , dy ) )
        x2 = int ( random. uniform (-dx , dx ) )
        y2 = int ( random. uniform (-dy , dy ) )
        warp_image = Image. new (
            'RGB' ,
            (width + abs (x1 ) + abs (x2 ) , height + abs (y1 ) + abs (y2 ) ) )
        warp_image. paste (image , ( abs (x1 ) , abs (y1 ) ) )
        width2 , height2 = warp_image. size
        return warp_image. transform (
            (width , height ) ,
            Image. QUAD ,
            (x1 , y1 , -x1 , height2 - y2 , width2 + x2 , height2 + y2 , width2 - x2 , -y1 ) )

    @ staticmethod
    def offset (image , dx_factor = 0.1 , dy_factor = 0.2 ):
        """图像偏移"""
        width , height = image. size
        dx = int ( random. random ( ) * width * dx_factor )
        dy = int ( random. random ( ) * height * dy_factor )
        offset_image = Image. new ( 'RGB' , (width + dx , height + dy ) )
        offset_image. paste (image , (dx , dy ) )
        return offset_image

    @ staticmethod
    def rotate (image , angle = 25 ):
        """图像旋转"""
        return image. rotate ( random. uniform (-angle , angle ) ,
                            Image. BILINEAR , expand = 1 )

    def generate ( self , captcha_text = '' , fmt = 'PNG' ):
        """生成验证码(文字和图片)"""
        self._image = Image. new ( 'RGB' , ( self._width , self._height ) , ( 255 , 255 , 255 ) )
        self. background ( )
        self. text (captcha_text , self._fonts ,
                  drawings = [ 'warp' , 'rotate' , 'offset' ] )
        self. curve ( )
        self. noise ( )
        self. smooth ( )
        image_bytes = BytesIO ( )
        self._image. save (image_bytes , format =fmt )
        return image_bytes. getvalue ( )


def pascal_row (n = 0 ):
    """生成Pascal三角第n行"""
    result = [ 1 ]
    x , numerator = 1 , n
    for denominator in range ( 1 , n // 2 + 1 ):
        x * = numerator
        x / = denominator
        result. append (x )
        numerator - = 1
    if n & 1 == 0:
        result. extend ( reversed (result [:- 1 ] ) )
    else:
        result. extend ( reversed (result ) )
    return result


def random_color (start = 0 , end = 255 , opacity = 255 ):
    """获得随机颜色"""
    red = random. randint (start , end )
    green = random. randint (start , end )
    blue = random. randint (start , end )
    if opacity is None:
        return red , green , blue
    return red , green , blue , opacity

说明:上面的代码在生成验证码图片时用到了三种字体文件,使用上面的代码时需要添加字体文件到应用目录下的fonts目录中。


行云博客 - 免责申明 本站提供的一切软件、教程和内容信息仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我联系处理。敬请谅解!

本文链接:https://www.xy586.top/7617.html

转载请注明文章来源:行云博客 » Python验证码生成

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2000-04-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档