共计 15513 个字符,预计需要花费 39 分钟才能阅读完成。
作者 | 江昱
起源 | Serverless 公众号
前言
Serverless 概念自被提出就倍受关注,尤其是近些年来 Serverless 焕发出了前所未有的生机,各畛域的工程师都在试图将 Serverless 架构与本身工作相结合,以获取到 Serverless 架构所带来的“技术红利”。
验证码 (CAPTCHA) 是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自动辨别计算机和人类的图灵测试)的缩写,是一种辨别用户是计算机还是人的公共全自动程序。能够避免歹意破解明码、刷票、论坛灌水,无效避免某个黑客对某一个特定注册用户用特定程序暴力破解形式进行一直地登陆尝试。实际上验证码是当初很多网站通行的形式,咱们利用比拟繁难的形式实现了这个性能。CAPTCHA 的问题由计算机生成并评判,然而这个问题只有人类能力解答,计算机是无奈解答的,所以答复出问题的用户就能够被认为是人类。说白了,验证码就是用来验证的码,验证是人拜访的还是机器拜访的“码”。
那么人工智能畛域中的验证码辨认与 Serverless 架构会碰撞出哪些火花呢?本文将通过 Serverless 架构和卷积神经网络(CNN)算法,实现验证码辨认性能。
浅谈验证码
验证码的倒退,能够说是十分迅速的,从开始的单纯数字验证码,到起初的数字 + 字母验证码,再到起初的数字 + 字母 + 中文的验证码以及图形图像验证码,单纯的验证码素材曾经越来越多了。从验证码的状态来看,也是各不相同,输出、点击、拖拽以及短信验证码、语音验证码……
Bilibili 的登录验证码就包含了多种模式,例如滑动滑块进行验证:
例如,通过顺次点击文字进行验证:
而百度贴吧、知乎、以及 Google 等相干网站的验证码又各不相同,例如抉择正着写的文字、抉择包含指定物体的图片以及按程序点击图片中的字符等。
验证码的辨认可能会依据验证码的类型而不太统一,当然最简略的验证码可能就是最原始的文字验证码了:
即使是文字验证码,也是存在很多差别的,例如简略的数字验证码、简略的数字 + 字母验证码、文字验证码、验证码中包含计算、简略验证码中减少一些烦扰成为简单验证码等。
验证码辨认
1. 简略验证码辨认
验证码辨认是一个古老的钻研畛域,简略说就是把图片上的文字转化为文本的过程。最近几年,随着大数据的倒退,宽广爬虫工程师在反抗反爬策略时,对验证码的辨认要求也越来越高。在简略验证码的时代,验证码的辨认次要是针对文本验证码,通过图像的切割,对验证码每一部分进行裁剪,而后再对每个裁剪单元进行类似度比照,取得最可能的后果,最初进行拼接,例如将验证码:
进行二值化等操作:
实现之后再进行切割:
切割实现再进行辨认,最初进行拼接,这样的做法是,针对每个字符进行辨认,相对来说是比拟容易的。
然而随着工夫的倒退,在这种简略验证码逐步无奈满足判断“是人还是机器”的问题时,验证码进行了一次小降级,即验证码下面减少了一些烦扰线,或者验证码进行了重大的扭曲,减少了强色块烦扰,例如 Dynadot 网站的验证码:
不仅有图像扭曲重叠,还有烦扰线和色块烦扰。这个时候想要辨认验证码,简略的切割辨认就很难取得良好的成果了,这时通过深度学习反而能够取得不错的成果。
2. 基于 CNN 的验证码辨认
卷积神经网络(Convolutional Neural Network,简称 CNN),是一种前馈神经网络,人工神经元能够响应四周单元,进行大型图像处理。卷积神经网络包含卷积层和池化层。
如图所示,左图是传统的神经网络,其根本构造是:输出层、隐含层、输入层。右图则是卷积神经网络,其构造由输出层、输入层、卷积层、池化层、全连贯层形成。卷积神经网络其实是神经网络的一种拓展,而事实上从构造上来说,奢侈的 CNN 和奢侈的 NN 没有任何区别(当然,引入了非凡构造的、简单的 CNN 会和 NN 有着比拟大的区别)。绝对于传统神经网络,CNN 在实际效果中让咱们的网络参数数量大大地缩小,这样咱们能够用较少的参数,训练出更加好的模型,典型的事倍功半,而且能够无效地防止过拟合。同样,因为 filter 的参数共享,即便图片进行了肯定的平移操作,咱们照样能够辨认出特色,这叫做“平移不变性”。因而,模型就更加持重了。
1)验证码生成
验证码的生成是十分重要的一个步骤,因为这一部分的验证码将会作为咱们的训练集和测试集,同时最终咱们的模型能够辨认什么类型的验证码,也是和这部分无关。
# coding:utf-8 | |
import random | |
import numpy as np | |
from PIL import Image | |
from captcha.image import ImageCaptcha | |
CAPTCHA_LIST = [eve for eve in "0123456789abcdefghijklmnopqrsruvwxyzABCDEFGHIJKLMOPQRSTUVWXYZ"] | |
CAPTCHA_LEN = 4 # 验证码长度 | |
CAPTCHA_HEIGHT = 60 # 验证码高度 | |
CAPTCHA_WIDTH = 160 # 验证码宽度 | |
randomCaptchaText = lambda char=CAPTCHA_LIST, size=CAPTCHA_LEN: "".join([random.choice(char) for _ in range(size)]) | |
def genCaptchaTextImage(width=CAPTCHA_WIDTH, height=CAPTCHA_HEIGHT, save=None): | |
image = ImageCaptcha(width=width, height=height) | |
captchaText = randomCaptchaText() | |
if save: | |
image.write(captchaText, './img/%s.jpg' % captchaText) | |
return captchaText, np.array(Image.open(image.generate(captchaText))) | |
print(genCaptchaTextImage(save=True)) |
通过上述代码,能够生成简略的中英文验证码:
2)模型训练
模型训练的代码如下(局部代码来自网络)。
util.py 文件,次要是一些提取进去的私有办法:
# -*- coding:utf-8 -*- | |
import numpy as np | |
from captcha_gen import genCaptchaTextImage | |
from captcha_gen import CAPTCHA_LIST, CAPTCHA_LEN, CAPTCHA_HEIGHT, CAPTCHA_WIDTH | |
# 图片转为黑白,3 维转 1 维 | |
convert2Gray = lambda img: np.mean(img, -1) if len(img.shape) > 2 else img | |
# 验证码向量转为文本 | |
vec2Text = lambda vec, captcha_list=CAPTCHA_LIST: ''.join([captcha_list[int(v)] for v in vec]) | |
def text2Vec(text, captchaLen=CAPTCHA_LEN, captchaList=CAPTCHA_LIST): | |
"""验证码文本转为向量""" | |
vector = np.zeros(captchaLen * len(captchaList)) | |
for i in range(len(text)): | |
vector[captchaList.index(text[i]) + i * len(captchaList)] = 1 | |
return vector | |
def getNextBatch(batchCount=60, width=CAPTCHA_WIDTH, height=CAPTCHA_HEIGHT): | |
"""获取训练图片组""" | |
batchX = np.zeros([batchCount, width * height]) | |
batchY = np.zeros([batchCount, CAPTCHA_LEN * len(CAPTCHA_LIST)]) | |
for i in range(batchCount): | |
text, image = genCaptchaTextImage() | |
image = convert2Gray(image) | |
# 将图片数组一维化 同时将文本也对应在两个二维组的同一行 | |
batchX[i, :] = image.flatten() / 255 | |
batchY[i, :] = text2Vec(text) | |
return batchX, batchY | |
# print(getNextBatch(batch_count=1)) |
model_train.py 文件,次要是进行模型训练。在该文件中,定义了模型的根本信息,例如该模型是三层卷积神经网络,原始图像大小是 60160,在第一次卷积后变为 60160,第一池化后变为 3080;第二次卷积后变为 3080,第二次池化后变为 1540;第三次卷积后变为 1540,第三次池化后变为 7 20。通过三次卷积和池化后,原始图片数据变为 720 的立体数据,同时我的项目在进行训练的时候,每隔 100 次进行一次数据测试,计算一次准确度:
# -*- coding:utf-8 -*- | |
import tensorflow.compat.v1 as tf | |
from datetime import datetime | |
from util import getNextBatch | |
from captcha_gen import CAPTCHA_HEIGHT, CAPTCHA_WIDTH, CAPTCHA_LEN, CAPTCHA_LIST | |
tf.compat.v1.disable_eager_execution() | |
variable = lambda shape, alpha=0.01: tf.Variable(alpha * tf.random_normal(shape)) | |
conv2d = lambda x, w: tf.nn.conv2d(x, w, strides=[1, 1, 1, 1], padding='SAME') | |
maxPool2x2 = lambda x: tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME') | |
optimizeGraph = lambda y, y_conv: tf.train.AdamOptimizer(1e-3).minimize(tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(labels=y, logits=y_conv))) | |
hDrop = lambda image, weight, bias, keepProb: tf.nn.dropout(maxPool2x2(tf.nn.relu(conv2d(image, variable(weight, 0.01)) + variable(bias, 0.1))), keepProb) | |
def cnnGraph(x, keepProb, size, captchaList=CAPTCHA_LIST, captchaLen=CAPTCHA_LEN): | |
"""三层卷积神经网络""" | |
imageHeight, imageWidth = size | |
xImage = tf.reshape(x, shape=[-1, imageHeight, imageWidth, 1]) | |
hDrop1 = hDrop(xImage, [3, 3, 1, 32], [32], keepProb) | |
hDrop2 = hDrop(hDrop1, [3, 3, 32, 64], [64], keepProb) | |
hDrop3 = hDrop(hDrop2, [3, 3, 64, 64], [64], keepProb) | |
# 全连贯层 | |
imageHeight = int(hDrop3.shape[1]) | |
imageWidth = int(hDrop3.shape[2]) | |
wFc = variable([imageHeight * imageWidth * 64, 1024], 0.01) # 上一层有 64 个神经元 全连贯层有 1024 个神经元 | |
bFc = variable([1024], 0.1) | |
hDrop3Re = tf.reshape(hDrop3, [-1, imageHeight * imageWidth * 64]) | |
hFc = tf.nn.relu(tf.matmul(hDrop3Re, wFc) + bFc) | |
hDropFc = tf.nn.dropout(hFc, keepProb) | |
# 输入层 | |
wOut = variable([1024, len(captchaList) * captchaLen], 0.01) | |
bOut = variable([len(captchaList) * captchaLen], 0.1) | |
yConv = tf.matmul(hDropFc, wOut) + bOut | |
return yConv | |
def accuracyGraph(y, yConv, width=len(CAPTCHA_LIST), height=CAPTCHA_LEN): | |
"""偏差计算图,正确值和预测值,计算准确度""" | |
maxPredictIdx = tf.argmax(tf.reshape(yConv, [-1, height, width]), 2) | |
maxLabelIdx = tf.argmax(tf.reshape(y, [-1, height, width]), 2) | |
correct = tf.equal(maxPredictIdx, maxLabelIdx) # 判断是否相等 | |
return tf.reduce_mean(tf.cast(correct, tf.float32)) | |
def train(height=CAPTCHA_HEIGHT, width=CAPTCHA_WIDTH, ySize=len(CAPTCHA_LIST) * CAPTCHA_LEN): | |
"""cnn 训练""" | |
accRate = 0.95 | |
x = tf.placeholder(tf.float32, [None, height * width]) | |
y = tf.placeholder(tf.float32, [None, ySize]) | |
keepProb = tf.placeholder(tf.float32) | |
yConv = cnnGraph(x, keepProb, (height, width)) | |
optimizer = optimizeGraph(y, yConv) | |
accuracy = accuracyGraph(y, yConv) | |
saver = tf.train.Saver() | |
with tf.Session() as sess: | |
sess.run(tf.global_variables_initializer()) # 初始化 | |
step = 0 # 步数 | |
while True: | |
batchX, batchY = getNextBatch(64) | |
sess.run(optimizer, feed_dict={x: batchX, y: batchY, keepProb: 0.75}) | |
# 每训练一百次测试一次 | |
if step % 100 == 0: | |
batchXTest, batchYTest = getNextBatch(100) | |
acc = sess.run(accuracy, feed_dict={x: batchXTest, y: batchYTest, keepProb: 1.0}) | |
print(datetime.now().strftime('%c'), 'step:', step, 'accuracy:', acc) | |
# 准确率满足要求,保留模型 | |
if acc > accRate: | |
modelPath = "./model/captcha.model" | |
saver.save(sess, modelPath, global_step=step) | |
accRate += 0.01 | |
if accRate > 0.90: | |
break | |
step = step + 1 | |
train() |
当实现了这部分之后,咱们能够通过本地机器对模型进行训练,为了晋升训练速度,我将代码中的 accRate 局部设置为:
if accRate > 0.90: | |
break |
也就是说,当准确率超过 90% 之后,零碎就会主动进行,并且保留模型。
接下来能够进行训练:
训练工夫可能会比拟长,训练实现之后,能够依据后果绘图,查看随着 Step 的减少,准确率的变动曲线:
横轴示意训练的 Step,纵轴示意准确率
3. 基于 Serverless 架构的验证码辨认
将下面的代码局部进行进一步整合,依照函数计算的标准进行编码:
# -*- coding:utf-8 -*- | |
# 外围后端服务 | |
import base64 | |
import json | |
import uuid | |
import tensorflow as tf | |
import random | |
import numpy as np | |
from PIL import Image | |
from captcha.image import ImageCaptcha | |
# Response | |
class Response: | |
def __init__(self, start_response, response, errorCode=None): | |
self.start = start_response | |
responseBody = {'Error': {"Code": errorCode, "Message": response}, | |
} if errorCode else {'Response': response} | |
# 默认减少 uuid,便于前期定位 | |
responseBody['ResponseId'] = str(uuid.uuid1()) | |
print("Response:", json.dumps(responseBody)) | |
self.response = json.dumps(responseBody) | |
def __iter__(self): | |
status = '200' | |
response_headers = [('Content-type', 'application/json; charset=UTF-8')] | |
self.start(status, response_headers) | |
yield self.response.encode("utf-8") | |
CAPTCHA_LIST = [eve for eve in "0123456789abcdefghijklmnopqrsruvwxyzABCDEFGHIJKLMOPQRSTUVWXYZ"] | |
CAPTCHA_LEN = 4 # 验证码长度 | |
CAPTCHA_HEIGHT = 60 # 验证码高度 | |
CAPTCHA_WIDTH = 160 # 验证码宽度 | |
# 随机字符串 | |
randomStr = lambda num=5: "".join(random.sample('abcdefghijklmnopqrstuvwxyz', num)) | |
randomCaptchaText = lambda char=CAPTCHA_LIST, size=CAPTCHA_LEN: "".join([random.choice(char) for _ in range(size)]) | |
# 图片转为黑白,3 维转 1 维 | |
convert2Gray = lambda img: np.mean(img, -1) if len(img.shape) > 2 else img | |
# 验证码向量转为文本 | |
vec2Text = lambda vec, captcha_list=CAPTCHA_LIST: ''.join([captcha_list[int(v)] for v in vec]) | |
variable = lambda shape, alpha=0.01: tf.Variable(alpha * tf.random_normal(shape)) | |
conv2d = lambda x, w: tf.nn.conv2d(x, w, strides=[1, 1, 1, 1], padding='SAME') | |
maxPool2x2 = lambda x: tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME') | |
optimizeGraph = lambda y, y_conv: tf.train.AdamOptimizer(1e-3).minimize(tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(labels=y, logits=y_conv))) | |
hDrop = lambda image, weight, bias, keepProb: tf.nn.dropout(maxPool2x2(tf.nn.relu(conv2d(image, variable(weight, 0.01)) + variable(bias, 0.1))), keepProb) | |
def genCaptchaTextImage(width=CAPTCHA_WIDTH, height=CAPTCHA_HEIGHT, save=None): | |
image = ImageCaptcha(width=width, height=height) | |
captchaText = randomCaptchaText() | |
if save: | |
image.write(captchaText, save) | |
return captchaText, np.array(Image.open(image.generate(captchaText))) | |
def text2Vec(text, captcha_len=CAPTCHA_LEN, captcha_list=CAPTCHA_LIST): | |
"""验证码文本转为向量""" | |
vector = np.zeros(captcha_len * len(captcha_list)) | |
for i in range(len(text)): | |
vector[captcha_list.index(text[i]) + i * len(captcha_list)] = 1 | |
return vector | |
def getNextBatch(batch_count=60, width=CAPTCHA_WIDTH, height=CAPTCHA_HEIGHT): | |
"""获取训练图片组""" | |
batch_x = np.zeros([batch_count, width * height]) | |
batch_y = np.zeros([batch_count, CAPTCHA_LEN * len(CAPTCHA_LIST)]) | |
for i in range(batch_count): | |
text, image = genCaptchaTextImage() | |
image = convert2Gray(image) | |
# 将图片数组一维化 同时将文本也对应在两个二维组的同一行 | |
batch_x[i, :] = image.flatten() / 255 | |
batch_y[i, :] = text2Vec(text) | |
return batch_x, batch_y | |
def cnnGraph(x, keepProb, size, captchaList=CAPTCHA_LIST, captchaLen=CAPTCHA_LEN): | |
"""三层卷积神经网络""" | |
imageHeight, imageWidth = size | |
xImage = tf.reshape(x, shape=[-1, imageHeight, imageWidth, 1]) | |
hDrop1 = hDrop(xImage, [3, 3, 1, 32], [32], keepProb) | |
hDrop2 = hDrop(hDrop1, [3, 3, 32, 64], [64], keepProb) | |
hDrop3 = hDrop(hDrop2, [3, 3, 64, 64], [64], keepProb) | |
# 全连贯层 | |
imageHeight = int(hDrop3.shape[1]) | |
imageWidth = int(hDrop3.shape[2]) | |
wFc = variable([imageHeight * imageWidth * 64, 1024], 0.01) # 上一层有 64 个神经元 全连贯层有 1024 个神经元 | |
bFc = variable([1024], 0.1) | |
hDrop3Re = tf.reshape(hDrop3, [-1, imageHeight * imageWidth * 64]) | |
hFc = tf.nn.relu(tf.matmul(hDrop3Re, wFc) + bFc) | |
hDropFc = tf.nn.dropout(hFc, keepProb) | |
# 输入层 | |
wOut = variable([1024, len(captchaList) * captchaLen], 0.01) | |
bOut = variable([len(captchaList) * captchaLen], 0.1) | |
yConv = tf.matmul(hDropFc, wOut) + bOut | |
return yConv | |
def captcha2Text(image_list): | |
"""验证码图片转化为文本""" | |
with tf.Session() as sess: | |
saver.restore(sess, tf.train.latest_checkpoint('model/')) | |
predict = tf.argmax(tf.reshape(yConv, [-1, CAPTCHA_LEN, len(CAPTCHA_LIST)]), 2) | |
vector_list = sess.run(predict, feed_dict={x: image_list, keepProb: 1}) | |
vector_list = vector_list.tolist() | |
text_list = [vec2Text(vector) for vector in vector_list] | |
return text_list | |
x = tf.placeholder(tf.float32, [None, CAPTCHA_HEIGHT * CAPTCHA_WIDTH]) | |
keepProb = tf.placeholder(tf.float32) | |
yConv = cnnGraph(x, keepProb, (CAPTCHA_HEIGHT, CAPTCHA_WIDTH)) | |
saver = tf.train.Saver() | |
def handler(environ, start_response): | |
try: | |
request_body_size = int(environ.get('CONTENT_LENGTH', 0)) | |
except (ValueError): | |
request_body_size = 0 | |
requestBody = json.loads(environ['wsgi.input'].read(request_body_size).decode("utf-8")) | |
imageName = randomStr(10) | |
imagePath = "/tmp/" + imageName | |
print("requestBody:", requestBody) | |
reqType = requestBody.get("type", None) | |
if reqType == "get_captcha": | |
genCaptchaTextImage(save=imagePath) | |
with open(imagePath, 'rb') as f: | |
data = base64.b64encode(f.read()).decode() | |
return Response(start_response, {'image': data}) | |
if reqType == "get_text": | |
# 图片获取 | |
print("Get pucture") | |
imageData = base64.b64decode(requestBody["image"]) | |
with open(imagePath, 'wb') as f: | |
f.write(imageData) | |
# 开始预测 | |
img = Image.open(imageName) | |
img = img.resize((160, 60), Image.ANTIALIAS) | |
img = img.convert("RGB") | |
img = np.asarray(img) | |
image = convert2Gray(img) | |
image = image.flatten() / 255 | |
return Response(start_response, {'result': captcha2Text([image])}) |
在这个函数局部,次要包含两个接口:
• 获取验证码:用户测试应用,生成验证码
• 获取验证码辨认后果:用户辨认应用,辨认验证码
这部分代码,所须要的依赖内容如下:
tensorflow==1.13.1 | |
numpy==1.19.4 | |
scipy==1.5.4 | |
pillow==8.0.1 | |
captcha==0.3 |
另外,为了更加简略的来体验,提供测试页面,测试页面的后盾服务应用 Python Web Bottle 框架:
# -*- coding:utf-8 -*- | |
import os | |
import json | |
from bottle import route, run, static_file, request | |
import urllib.request | |
url = "http://" + os.environ.get("url") | |
@route('/') | |
def index(): | |
return static_file("index.html", root='html/') | |
@route('/get_captcha') | |
def getCaptcha(): | |
data = json.dumps({"type": "get_captcha"}).encode("utf-8") | |
reqAttr = urllib.request.Request(data=data, url=url) | |
return urllib.request.urlopen(reqAttr).read().decode("utf-8") | |
@route('/get_captcha_result', method='POST') | |
def getCaptcha(): | |
data = json.dumps({"type": "get_text", "image": json.loads(request.body.read().decode("utf-8"))["image"]}).encode("utf-8") | |
reqAttr = urllib.request.Request(data=data, url=url) | |
return urllib.request.urlopen(reqAttr).read().decode("utf-8") | |
run(host='0.0.0.0', debug=False, port=9000) |
该后端服务,所需依赖:
bottle==0.12.19
前端页面代码:
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title> 验证码辨认测试零碎 </title> | |
<link href="https://www.bootcss.com/p/layoutit/css/bootstrap-combined.min.css" rel="stylesheet"> | |
<script> | |
var image = undefined | |
function getCaptcha() {const xmlhttp = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"); | |
xmlhttp.open("GET", '/get_captcha', false); | |
xmlhttp.onreadystatechange = function () {if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {image = JSON.parse(xmlhttp.responseText).Response.image | |
document.getElementById("captcha").src = "data:image/png;base64," + image | |
document.getElementById("getResult").style.visibility = 'visible' | |
} | |
} | |
xmlhttp.setRequestHeader("Content-type", "application/json"); | |
xmlhttp.send();} | |
function getCaptchaResult() {const xmlhttp = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"); | |
xmlhttp.open("POST", '/get_captcha_result', false); | |
xmlhttp.onreadystatechange = function () {if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {document.getElementById("result").innerText = "辨认后果:" + JSON.parse(xmlhttp.responseText).Response.result | |
} | |
} | |
xmlhttp.setRequestHeader("Content-type", "application/json"); | |
xmlhttp.send(JSON.stringify({"image": image})); | |
} | |
</script> | |
</head> | |
<body> | |
<div class="container-fluid" style="margin-top: 10px"> | |
<div class="row-fluid"> | |
<div class="span12"> | |
<center> | |
<h3> | |
验证码辨认测试零碎 | |
</h3> | |
</center> | |
</div> | |
</div> | |
<div class="row-fluid"> | |
<div class="span2"> | |
</div> | |
<div class="span8"> | |
<center> | |
<img src=""id="captcha"/> | |
<br><br> | |
<p id="result"></p> | |
</center> | |
<fieldset> | |
<legend> 操作:</legend> | |
<button class="btn" onclick="getCaptcha()"> 获取验证码 </button> | |
<button class="btn" onclick="getCaptchaResult()" id="getResult" style="visibility: hidden"> 辨认验证码 | |
</button> | |
</fieldset> | |
</div> | |
<div class="span2"> | |
</div> | |
</div> | |
</div> | |
</body> | |
</html> |
筹备好代码之后,开始编写部署文件:
Global: | |
Service: | |
Name: ServerlessBook | |
Description: Serverless 图书案例 | |
Log: Auto | |
Nas: Auto | |
ServerlessBookCaptchaDemo: | |
Component: fc | |
Provider: alibaba | |
Access: release | |
Extends: | |
deploy: | |
- Hook: s install docker | |
Path: ./ | |
Pre: true | |
Properties: | |
Region: cn-beijing | |
Service: ${Global.Service} | |
Function: | |
Name: serverless_captcha | |
Description: 验证码辨认 | |
CodeUri: | |
Src: ./src/backend | |
Excludes: | |
- src/backend/.fun | |
- src/backend/model | |
Handler: index.handler | |
Environment: | |
- Key: PYTHONUSERBASE | |
Value: /mnt/auto/.fun/python | |
MemorySize: 3072 | |
Runtime: python3 | |
Timeout: 60 | |
Triggers: | |
- Name: ImageAI | |
Type: HTTP | |
Parameters: | |
AuthType: ANONYMOUS | |
Methods: | |
- GET | |
- POST | |
- PUT | |
Domains: | |
- Domain: Auto | |
ServerlessBookCaptchaWebsiteDemo: | |
Component: bottle | |
Provider: alibaba | |
Access: release | |
Extends: | |
deploy: | |
- Hook: pip3 install -r requirements.txt -t ./ | |
Path: ./src/website | |
Pre: true | |
Properties: | |
Region: cn-beijing | |
CodeUri: ./src/website | |
App: index.py | |
Environment: | |
- Key: url | |
Value: ${ServerlessBookCaptchaDemo.Output.Triggers[0].Domains[0]} | |
Detail: | |
Service: ${Global.Service} | |
Function: | |
Name: serverless_captcha_website |
整体的目录构造:
| - src # 我的项目目录 | |
| | - backend # 我的项目后端,外围接口 | |
| | - index.py # 后端外围代码 | |
| | - requirements.txt # 后端外围代码依赖 | |
| | - website # 我的项目前端,便于测试应用 | |
| | - html # 我的项目前端页面 | |
| | - index.html # 我的项目前端页面 | |
| | - index.py # 我的项目前端的后盾服务(bottle 框架)| | - requirements.txt # 我的项目前端的后盾服务依赖 |
实现之后,咱们能够在我的项目目录下,进行我的项目的部署:
s deploy
部署实现之后,关上返回的页面地址:
点击获取验证码,即可在线生成一个验证码:
此时点击辨认验证码,即可进行验证码辨认:
因为模型在训练的时候,填写的指标准确率是 90%,所以能够认为在海量同类型验证码测试之后,整体的准确率在 90% 左右。
总结
Serverless 倒退迅速,通过 Serverless 做一个验证码辨认工具,我感觉这是一个十分酷的事件。在将来的数据采集等工作中,有一个柔美的验证码辨认工具是十分必要的。当然验证码品种很多,针对不同类型的验证码辨认,也是一项十分有挑战性的工作。