文章内容

2017/4/13 9:23:03,作 者: 黄兵

基于C#.NET实现通用的验证码识别组件(策略设计模式)

相信大家在开发过程中,基本都用到过验证码识别程序。一提到验证码识别,绝大多数兄弟想到的都是用C++的效率配上牛逼哄哄的二值化、边缘检测等算法来实现。但这种识别方式的依赖性太强,不可重用,无法扩展,假设对方稍微修改下验证码的变形算法(做过网站的都知道有多简单),可能你累死累活搞出来的识别程序就全部作废了。


这里讲个我们公司的例子,为了识别支付宝登录的验证码,公司花大价钱请了一位牛人B用C++写了个支付宝验证码识别的DLL并做了导出,供我们在.NET平台下直接调用。当我们项目开发快结束的时候,这货竟然完全无法识别了!我们研究发现,是因为支付宝对文字做了简单的变形。牛人B则落井下石的告知需另付钱才能继续识别,因此老板就把这重任交给了我。


作为热爱创造的苦逼程序猿,现在必须要拿出我们那渴望下班的热情,开始这漫漫的编码长路 ,首先提出我们的需求:

  1. 通用识别字母、数字、及混合验证码。

  2. 识别率必须要高(90%以上)。

  3. 识别速度要快(5秒内)。

  4. 支持异步及多线程并发识别。

  5. 调用简单,扩展性强。


可能大家看到这需求时已经产生疑问了,怎么可能有通用的验证码识别程序?一套代码怎么可能识别所有网站的验证码?今天我们就通过第三方打码平台来解决这个变态的需求。现在听我慢慢道来,我们按面向对象的方式来确定这个组件中应该有的对象:

  • 打码平台(Platform)

  • 平台帐号(Account)

  • 识别策略(Strategy)

  • 验证码识别器(Decoder)

首先,我们来说“打码平台(Platform)”,我们把验证码图片发送至打码平台,他们再通过大批量(24X7)在线的用户进行人工+机器识别,速度基本在1-5秒之间,完成后返回识别结果。现在来定义一个平台枚举集合,用作平台的随意切换:

接着,我们来说“平台帐号(Account)”,每个平台都至少需要一个普通帐号和一个开发者帐号,普通帐号用作登录、充值、识别用。开发者帐号用作生成软件ID、软件KEY。很多兄弟一听到要充值就不敢往下看了,其实价格真的很便宜,1元钱可以识别250次(多么销魂的数字)。下边定义了平台帐号类,用作向策略类中传递帐号信息,这些信息在你注册普通帐号和开发者帐号后才可以获得:

其次,我们再说“识别策略(Strategy)”,它主要封装与第三方平台的交互细节,为“验证码识别器”提供简单的操作方法,并支持识别策略的无限扩展(例如未来增加其他平台)。第一行定义了平台帐号信息字段;第二行定义了识别的具体方法,传入图片路径,并将验证码的识别结果返回为字符串。

然后,我们来说“验证码识别器(Decoder)”,它主要用来封装识别细节,支持平台切换,提供给外部开发人员调用。第一行定义了Task异步识别方法,传入验证码图片路径,通过异步编程解决IO、网络操作耗时的问题;第二行至第四行定义了三个事件,分别在验证码识别的启动、完成、错误时触发。

就这样,一个通用验证码识别组件就基本定义完成了,我知道你现在可能会说:#@¥@#¥#!@#!!这写的什么玩意?!但事实就是如此,从面向抽象的角度讲,确实已经完成了。只是我们还没有定对其进行实现,也就是说仅仅定义了规范,从应用上来讲并没有什么卵用。所以下边我们就来讲(IStrategy、IDecoder)这两个重要的接口如何实现。

识别策略(Strategy)的实现:

由上图得知,我们定义了一个若快平台的验证码识别策略来实现IStrategy接口。通过实现Recognize方法将帐号信息封装成字典随图片一起提交到第三方平台进行识别,并返回识别结果。同时我们还可以直接在构造函数中设置平台帐号。

验证码识别器(Decoder)的实现:

由上图得知,我们通过实现IDecoder的接口,利用反射获取运行时的策略,利用Task异步执行策略类中的Recognize方法,并在执行之前触发OnStart事件,执行之后触发OnCompleted事件,执行错误后触发OnError事件。同时获取到当前异步任务的线程ID及任务的执行时间并传递给事件。

现在我们新建一个控制台程序来使用这个组件:

下边我们同时识别三个验证码,大家可以看一下执行时间和准确率:


本文验证码组件源代码、实例代码下载地址:https://github.com/coldicelion/Captcha-Recognizer

分享到:

发表评论

评论列表