pycaptcha

pycaptcha是python的开源图片验证码库,很基础,但也记录一下吧。

1. 目录结构
下载pycaptcha,基本目录结构如下:
.
| -- Base.py: 定义BaseCaptcha,提供Factory接口供外面生成、验证验证码
| -- data: 用来存放数据的目录,验证码字体、验证码背景图片以及验证码词库都存在该目录下
| | -- fonts
| | -- pictures
| | -- words
|-- File.py: 定义RandomFileFactory,用来在data目录中获取随机的字体、背景图片和词语
|-- __init__.py: 作为一种良好的习惯,__init__.py不定义任何东西,仅import
|-- Visual: 顾名思义,提供生成图片验证码功能,最关键的部分
| |-- Backgrounds.py: 定义了几种验证码背景,比如CroppedImage、RandomDots
| |-- Base.py: 定义ImageCaptcha,把验证码图片的背景、文字、扭曲操作抽象成Layer
| |-- Distortions.py:扭曲验证码图片,提供了WigglyBlocks和SineWarp两种
| |-- __init__.py
| |-- Pictures.py: 定义ImageFactory,继承RandomFileFactory,用来在data/pictures文件夹选背景
| |-- Tests.py:通过继承ImageFactory实现三个具体验证码类型,通过重写getLayers()方法实现
| `-- Text.py:定义FontFactory用来选择随机字体,TextLayer用来生成验证码文字layer
`-- Words.py: WordList封装了验证码的词库,从data/words目录的指定词库选择一个词语作为验证码

2. 文件随机选择
File.py定义了RandomFileFactory,后面选择字体、单词和背景图片的class都继承自该类。
dataDir = os.path.join(os.path.split(os.path.abspath(__file__))[0], "data")
class RandomFileFactory(object):
extensions = []
basePath = "."

def __init__(self, *fileList):
self.fileList = fileList
self._fullPaths = None

dataDir指向data文件夹,默认数据是存在该目录。
extensions在具体情景下指定,比如选择图片可能是.png/.jpg,选择字体是.ttf。
fileList指定搜索文件的起始目录列表,_findFullPaths()方法会迭代fileList,把所有符合extensions的文件都加入self._fullPaths,然后提供pick()函数,通过random.choice(self._fullPaths)随机选择。
def _findFullPaths(self):
"""From our given file list, find a list of full paths to files"""
paths = []
for name in self.fileList:
path = os.path.join(dataDir, self.basePath, name)
if os.path.isdir(path):
for content in os.listdir(path):
if self._checkExtension(content):
paths.append(os.path.join(path, content))
else:
paths.append(path)
return paths

具体使用:
class ImageFactory(File.RandomFileFactory):
extensions = [".png", ".jpeg"]
basePath = "pictures"

# 选择data/abstract/目录下的随机背景图片
abstract = ImageFactory("abstract")

单词的选择没使用RandomFileFactory,而是直接指定文件名,然后加载所有的单词到一个list,同样通过random.choice(list)选择。存在一些过滤,比如单词的最大长度最小长度。

3. 验证码
先看BaseCaptcha:
class BaseCaptcha(object):
minCorrectSolutions = 1
maxIncorrectSolutions = 0

def __init__(self):
self.solutions = []
self.valid = True
self.id = randomIdentifier()
self.creationTime = time.time()

验证码有对应的solution,这里的solution竟然是一个列表。同时,验证码有时效性。
addSolution(self, solution)
testSolutions(self, solutions):大部分应用solutions都只包含一个答案吧。
这个两个方法很简单,略过。

提供了一个Captcha的工厂Factory用来封装验证码的获取、验证:
class Factory(object):
def __init__(self, lifetime=60*15):
self.lifetime = lifetime
self.storedInstances = {}

lifetime定义了验证码的有效时长,默认15分钟。
再看一下其他几个方法:
def new(self, cls, *args, **kwargs): 生成一个验证码,cls是验证码的类,都定义在Tests.py。每生成一个新的验证,都保存在storedInstance里。
def get(self, id): 通过id获取验证码
def clean(self): 迭代self.storedInstances,清除过时的验证码
def test(self, id, solutions): 测试验证码是否正确,检查之前先调用self.clean()清除所有过期验证码

说到验证码的存储,这里直接存在实例里,但在应用时量太大不靠谱,所以考虑实现,可能是:
1. 缓存:优点是不用手动clean()过期验证码了,没有持久化,但即便缓存系统奔溃了影响也不大。
2. 数据库: 真持久化了反而考虑有没有必要,假设网站崩溃,回复之后大量验证码也都超过过期时间(比如15分钟)了吧?
3. 文件:如果是测试,还不如例子里来的简单呢。


这里逻辑很清晰易懂。有点可以借鉴的就是验证码的id生成器:
def randomIdentifier(alphabet = string.ascii_letters + string.digits,
length = 24):
return "".join([random.choice(alphabet) for i in xrange(length)])


关于验证码的test()方法,现在有验证码A,客户端输入答案并提交,携带验证码A的id和输入的答案跑去服务器验证结果,如果验证失败,原来的验证码A还存在于服务器端。一般网站的做法是又返回一个新的验证码B,同时把B的id埋在form中,这样用户再输入,验证的便是另一个新的验证码B,而不能多次尝试验证码A的答案。

但,客户端可以把验证码A的id记录下来,第一次失败后,第二次还拿验证码A的id跑去验证,这样就可以实现对一个验证码的多次尝试了。

所以,建议把test()方法改造一下,每个验证码只尝试一次,无论结果正确与否,马上删除。

4. 生成图片验证码
这部分是验证码的最核心部分,也是最难的部分。验证码的作用就是防止spammer,如何生成人类肉眼容易识别和理解但程序通过图像处理难以识别的文字是一门学问。
先看看ImageCaptcha:
class ImageCaptcha(Captcha.BaseCaptcha):
defaultSize = (256,96)
def __init__(self, *args, **kwargs):
Captcha.BaseCaptcha.__init__(self)
self._layers = self.getLayers(*args, **kwargs)

ImageCaptcha继承了BaseCaptcha,defaultSize定义了验证码图片的尺寸大小。
_layers属性非常关键,来看getLayers方法便会发现只是返回空的列表[],这个方法定义验证码生成的具体算法,由子类来覆盖实现,后面详细讲。
getImage()方法用来获取验证码图片,它又调用了render()方法,而render方法没做任何实质性的工作,本质上调用了_renderList()方法,直接来看看这个方法,代码如下:
def _renderList(self, layers, img):
for i in layers:
if type(i) == tuple or type(i) == list:
img = self._renderList(i, img)
else:
img = i.render(img) or img
return img
layers参数就是getLayers()方法子类实现返回的,img则是验证码图片,新生成的:
img = Image.new("RGB", size)

_renderList()方法是,从子类获得Layers,然后,根据依次调用layer.render(img)方法,得到新的img,循环调用下去,最后得到的img就是最终的验证图片。
Layer是什么?验证码图片刚开始只是一张新生成的空白图片(img),然后经过添加背景、添加验证码文字、图片扭曲处理这三个步骤,得到最终的图片。这里的每个步骤都可以看做一个Layer,即抽象成对图片img的一个处理步骤。每个步骤的输入都是最开始的img对象,如何处理呢,不外img.paste()、img.transform()等几个PIL图像处理的常用方法,所以使用python做验证码还需要研究PIL。
另外,layer对象支持返回带list/tuple元素的列表对象。
这里getLayers()方法只返回[]的做法很妙,对Layer的抽象也很妙,参考设计模式的Strategy/Template Pattern。

下面看看如何生成背景图片、添加验证码文字、图片扭曲的具体算法就不介绍了,具体在:
背景图片生成算法:Backgrounds.py文件
添加验证码文字:Text.py(包括字体选择)
图片扭曲算法:Distortions.py(最核心的算法)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值