通用技术 【转载】另类 UI 识别元素方法——12306 售票网站新版验证码识别对抗

陈恒捷 · 2015年03月19日 · 最后由 81—1 回复于 2016年02月19日 · 2482 次阅读

原文地址:http://linux.im/2015/03/17/12306-new-captcha.html

12306 的抢票软件从技术上说其实和 UI 自动化测试很相似。在此转载一篇《12306 售票网站新版验证码识别对抗》的文章,让大家了解一些另类的 UI 识别元素方法,开阔一下思维

简单解释一下这个文章的主要内容:
识别问题:图片验证中的图片是动态生成的,数据量大到到你无法用类似爬虫的方式一一关联图像与文字内容
解决探索:采用 OCR 图像识别来识别文字,用识图软件把图像转化为文字(不要吃惊,google 一直有研究这个,如何把图片转化为正确的文字描述。文中的关键字识别只是它的初级阶段)
最终解决:使用 Python 的 PIL 库进行 OCR 识别(成功率基本达标),使用百度识图 API 进行图像与关键字的转换,通过特殊算法解决 OCR 识别结果与图像关键字之间的模糊匹配。最终成功率达到可以产品化的程度。

近段时间 12306 订票网站验证码升级为用户识别图像内容,然后选取符合条件的图片为验证码,比如这样:

不少媒体新闻大呼抢票工具集体失效、12306 终极验证码等新闻,这种验证码的推出有好同样也有坏处:机器识别困难,同样人眼识别也轻松不到哪里去。

用这种方式作为验证码最大的担忧就是怕脚本或人工对其图片进行爬虫遍历,然后将所有的图片保存后与关键字进行对比并关联入库,当然前提是这些图片都是静态的。

12306 验证码究竟是静态还是动态,昨晚对这个疑问进行了实践:http://linux.im/2015/03/17/12306-captcha-md5-go.html ,简单的说测试后发现这整张图片是在服务器后端动态生成的,所以不难理解为什么生成验证码页面时会比较慢。

同样上午我们又进行了第二个实践,将整张验证码中的八张图像拆分为 8 张小图然后进行感知 hash 处理,获得样本总数 72225 张,不重复的图库为 15478 张,重复最高为 869 次,绘制成图如下:

既然不是静态的图像(对比过近 10w 条图像 hash),那我们就不浪费功夫爬取静态图片进行数据关联入库了,但我们仍然需要 “破” 掉这个验证码,没有什么理由。

最后,下文出现的所有的片段代码将会开源,无需担心。

关键字识别

验证码流程:

  • 验证码提问
  • 选择答案(多选)

例如上面的验证码图,他是一整张图片,识别其关键字首先要对关键字区域进行图像截取,随后识别成文字。

这里使用 Python 的 PIL 图像处理库来进行区域的选择:

def imgCut():
    pic_file = downloadImg()
    pic_path = "./12306_pic/%s.jpg" % pic_file
    pic_text_path = './12306_pic/%s_text.jpg' % pic_file
    pic_obj = Image.open(pic_path)
    box = (120,0,290,25)
    region = pic_obj.crop(box)
    region.save(pic_text_path)
    print '[*] Picture Text Picture: {}'.format(pic_text_path)
    return pic_path, pic_text_path

imgGut 函数首先会下载这张验证码大图(其中包括提示字、关键字、8 张图片等),然后保存至 ./12306_pic/ 目录进行存储,随后使用 PIL 库对图像的 (120,0,290,25) 区域切割,也就是获取关键字图像区域。

现在我们已经能够将验证码下载并切割出想要的关键字区域了,下面我们要识别关键字,然后转换为文本文字。

使用一些开源的光学字符识别模块应该就能进行识别,但这不方便使用者运行,所以我选择了一款在线网站 OCR 识别,他能够对你上传的图像(我们刚刚切割好的图像)进行文字识别转换,当然准确率并没有那么高,一定得记住这一点!

这里贴出部分代码,功能实现(传入图像返回关键字的文本内容):

upload_pic_url = "http://cn.docs88.com/pdftowordupload2.php"
filename_tmp = filename.split('/')[-1]
pic_text_content = open(filename).read()
para = {'Filename': filename_tmp,
       'sourcename': filename_tmp,
       'sourcelanguage': 'cn',
       'desttype': 'txt',
       'Upload': 'Submit Query',}
upload_pic = requests.post(upload_pic_url, data=para, files={"Filedata" : open(filename, 'rb')})
text_result_url = 'http://cn.docs88.com/' + upload_pic.content[3:]
text_result = requests.get(text_result_url)
return text_result.content

我们运行试试效果:

[+] Download Picture: https://kyfw.12306.cn/otn/passcode...
[*] Picture Text Picture: ./12306_pic/1426580454_text.jpg
[*] Text: 衬 衫

[+] Download Picture: https://kyfw.12306.cn/otn/passcod...
[*] Picture Text Picture: ./12306_pic/1426580454_text.jpg
[*] Text: )帽子

[+] Download Picture: https://kyfw.12306.cn/otn/passcod...
[*] Picture Text Picture: ./12306_pic/1426580454_text.jpg
[*] Text: 春联

效果还不错,足够我们测试使用,还记得他的准确率吗?

巧妙的图像识别

之前关于图像识别我在 Buzz 发表过相关文章:使用 CloudSight API 进行图像识别的 Python 脚本,这次我们不使用这个脚本,原因是虽然识别准确度较高但速度略慢,所以我并不是很钟爱这一套,恰巧知乎上有位朋友写了一篇利用百度识图来进行图像识别的文章及代码,Google 识图当然也不错,但刚好在这我们会用到,所以不必纠结。

  1. 分割验证码图像
  2. 丢进百度识图 API 函数
  3. 返回百度识图结果

横向两行,每行四个,然后对其进行图像识别并返回:

dict_list = {}
count = 0
for y in range(2):
    for x in range(4):
        count += 1
        im2 = get_sub_img(pic_path, x, y)
        result = baidu_stu_lookup(im2)
        dict_list[count] = result
        print (y,x), result

其中函数因文章长度原因暂不在这贴出,识别效果如下:

(0, 0) 冰雕|建筑夜景
(0, 1) 炸暑条|快餐
(0, 2) 灯塔|高塔
(0, 3) 汉堡|麦当劳薯条|开店
(1, 0) 运动外套|防护服|运动服
(1, 1) 银灰色|手机|移动版
(1, 2) 标书制作|规划
(1, 3) 手机

好,现在我们能够识别出关键字,也能识别出验证码 8 个图像了,我们还需要机器帮助我们确认,究竟选择哪几个图。

可能是它

前面两次提到使用的 OCR 在线识别准确度并没有那么高,所以为了方便程序能够聪明的帮我们思考这道选择题,我们进行结果伪分词对比。

首先将关键字进行拆分,然后循环对比结果,这样就能将未准确识别的文字忽略并识别相应识图结果,这里我将 8 个图像结果按照 1-8 区分,第一行从左到右(1-4),第二行(5-8):

if captcha_text.strip() > 2:
    print '\n[*] Maybe the result of the:'
    maybe_result = []
    for v in dict_list:
        for c in range(len(unicode(captcha_text.strip(), 'utf8'))):
            text = unicode(captcha_text, 'utf8')[c]
            if text in dict_list[v]:
                _str_res = '%s --- %s' % (v, dict_list[v])
                maybe_result.append(_str_res)
    for r in list(set(maybe_result)):
        print r
else:
    print '[-] False'

好了,这样一来就算识别率没有那么高我们也能尽可能的将答案寻找出来了,看下效果:


未结束

结束了吗? 其实没有。

我们使用脚本进行了大量的测试,成功率可喜的足够令一些邪恶的人做些事儿了,但验证码对抗一直在进行,当然也越来越有趣:)

文中完整代码链接:https://gist.github.com/Evi1m0/fbbdb1ba7c66cc4e1bb2

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 14 条回复 时间 点赞

天书一样啊,看都看不懂。。

丢第三方图库感觉更不靠谱啊。第三方获得的特征属性不一定是你需要的。
感觉如果图库不更新还有希望。图和关键字对应起来直接放数据库。
感觉限制条件应该有不少:
如果图库过大,就有搜索对比的大量成本。
如果图片更新了,那就更坑了。

上有政策下有对策,不是有几个应用一天以内就支持了这种图片识别了嘛!

额,我分享这个的目的不是想让大家讨论抢票软件本身,只是想让大家开阔一下思维,遇到用常规方法做不了的事情时就突破常规,用非常规方法来解决。
框架/工具用得多很容易局限在它们以内。框架只是辅助工具,如同这里的百度识图一样。我们的价值不是熟练使用各种框架/工具,而是能快速找到合适的方法、采用合适的工具、以尽量快和低成本的方式解决问题。
个人觉得,测试和开发的主要区别就在于测试更需要开阔的眼界和思维,能跳出编程固有的思路来解决问题。这也是我选择做测试的原因之一。

#2 楼 @yangchengtest
文中第一部分已经说明图和关键字对应起来放数据库走不通。。。图库量太大了。。。所以要用识图工具。
百度识图不是单纯的数据库比对,里面有识别算法的,当然也包含大数据。简单地说就是它能识别近乎于任意图片的对应关键字,这样即使你图库再大,再怎么更新也不怕。
附上文中提到的知乎的讨论链接:http://www.zhihu.com/question/19738567

@chenhengjie123 呵呵,下面是我个人理解,供参考:
对于问题 1,感知 HASH 只是最简单的图片识别技术。精度和准确性都有问题,可以这么说是把图转成 8*8 做对比基本没有实用价值。用 PIL 也别用这个算法。~
稍微复杂一点的有 OPENCV 提供的 SIFT 和 SURF 特征点计算(具体实现我没搞明白,肯定有大牛能轻松搞定的。。。)。我个人觉得如果有 GOOGLE,BAIDU 的初级搜图技术,12306 这种封闭的系统,图库必然是有限的,用大量的数据去测试获取原始数据,然后去整合数据,技术上这些应该都不是问题。~

#6 楼 @yangchengtest 对,你说的我都赞同,确实目前图片识别技术准确性还不能达到很高的实用价值,但在某些特定场景下(如文中的场景)可以起到很大的辅助作用。
我主要想分享一下它这种另类的解决思路(用较低成本达到较大的产出)而已。

楼主 神人

#7 楼 @chenhengjie123
昨天中午的配饭帖哎~
我觉得也是,讲的就是个思路,其中的环节自己搞还是用现成搞的都无所谓啦~最后能搞定就成~搞不定尝试下也好啊~只是他那个最下面的地址我开着 *** 也一直没打开略囧~

楼主 我要给你生孩子

#11 楼 @doria 神马情况……我只是转载的……

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册