刘功瑞的博客

有一天你突然惊醒,发现这一切,都只不过是一场梦。

从收集ICP备案域名到tensorflow实现卷积神经网络识别验证码


一.概述

最近想收集全市的ICP备案域名信息,想着从工信部的官网上抓包获取查询接口,但是发现有验证码,而且还很复杂,于是想从网上找一些免费的接口,找了半天也没有找到,所有接口都是收费的,价格虽然不高,几分钱查询一次,但是禁不住我查询量大。综合价格和各网站提供接口的方式发现,他们应该都是从工信部官网获取的数据,有一定的难度造成不免费提供,但收费的价格并不高,并且有多家网站可以提供,说明获取数据的难度不是特别大,依我的经验来看,价值几千块的定制化代码的学习成本应该不会超过一个月时间,所以我思考了很多种获取数据的方式,最终确定了技术方案应该是由机器识别验证码来获取数据。


二.实现细节

工信部ICP查询有两种方式:

使用备案号进行查询,验证码较长,6位

image.png


使用数据库自增id查询,验证码较短,4位

image.png

使用备案号查出列表后,点击详情的请求为第二种接口

image.png

image.png

id=99841901,数据库中大概有九千万条数据,只要我们可以自动输入验证码,我们可以把数据全部跑出来,测试了几条大部分id都是空数据。


机器学习识别验证码

接下来我们要使用tensorflow实现卷积神经网络,进行验证码识别。

在github上有很多相关框架,经过多番测试,最后我使用的是cnn_captcha

地址:https://github.com/nickliqian/cnn_captcha

将项目下载到本地后,先安装所有依赖包,使用清华镜像加快下载速度

pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

此代码没有标注python的版本,在经历各种报错折磨之后,得出结果运行环境为python3.6,注意不能是3.7,因为3.7没有tensorflow==1.7.0的版本,只有高版本。

然后经过我的几天测试,有独立显卡的使用gpu训练会提升很大的效率。(只有nvidia显卡可以使用gpu训练,用cpu训练的话,不需要安装cuda和cudnn)

安装gpu版本的tensorflow,需要在requirements.txt文件中修改 tensorflow==1.7.0tensorflow-gpu==1.7.0

还需要下载cuda9.0和cudnn7.0,注意版本不要下错,不然无法运行。

cuda9.0下载地址

https://developer.nvidia.com/cuda-toolkit-archive

cudnn7.0下载地址(需要英伟达的账号,懒的注册的可以找我单独给你)

https://developer.nvidia.com/rdp/cudnn-download

安装cuda时候可能由于环境冲突会遇到各种问题,自行百度解决。


安装完成后,就可以进入主题了。


第一步:数据集

我们想要让机器识别验证码,就先需要告诉他什么样是正确的,什么样是错误的,所以需要收集一批正确标注的验证码。

image.png

用于训练的验证码图片以 v35g_1583090000.png 格式命名(正确验证码_时间戳.后缀)。

我们要收集第一批用于训练的图片集,第一批数据集收集是最麻烦的,人工识别后给图片修改文件名效率太低,而且太累,我尝试使用免费的文字识别接口,我申请了一个百度的文字识别接口,每天50000次免费,虽然百度识别的正确率只有20%左右,但是对于我们来说足够收集几千张原始数据集了。

我写了一个代码自动下载ICP备案官网的验证码并调用百度接口来识别,识别正确后保存到本地。

# encoding:utf-8

import requests
import base64
import time
import os

# client_id 为官网获取的AK, client_secret 为官网获取的SK
# host = 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=XXXXXXXX&client_secret=XXXXXXXXXXXXXXXXXXX'
# response = requests.get(host)
# if response:
#     # print(response.json())
#     access_token = response.json()['access_token']
#     print access_token
access_token = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'

'''
通用文字识别
'''


def general_basic(filePath):
    request_url = "https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic"
    # 二进制方式打开图片文件
    f = open(filePath, 'rb')
    img = base64.b64encode(f.read())
    params = {"image": img, "language_type": "ENG"}
    # access_token = '[调用鉴权接口获取的token]'
    request_url = request_url + "?access_token=" + access_token
    headers = {'content-type': 'application/x-www-form-urlencoded'}
    response = requests.post(request_url, data=params, headers=headers)
    code = ''
    if response:
        result = response.json()
    print result
    for l in result['words_result']:
        code += l['words']
    final_code = ''
    for c in code:
        if c != ' ':
            final_code += c
    if len(final_code) != 4:
        return ''
    return final_code

headers = {
    "Host": "he.beian.miit.gov.cn",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36",
    "Accept": "image/webp,image/apng,image/*,*/*;q=0.8",
    "Referer": "http://he.beian.miit.gov.cn/icp/publish/query/icpMemoInfo_searchExecute.action",
    "Accept-Language": "zh-CN,zh;q=0.9,und;q=0.8"
}
cookie = ''
k = 0
for i in range(100000):
    url = "http://he.beian.miit.gov.cn/getDetailVerifyCode"
    r = requests.get(url + '?' + str(i + 1), headers=headers, cookies=cookie)
    if cookie == '':
        cookie = requests.utils.dict_from_cookiejar(r.cookies)
    filePath = 'D:/tmp/' + str(i) + '.png'
    with open(filePath, 'wb') as f:
        f.write(r.content)
    type = 1
    # 百度识别
    final_code = general_basic(filePath)
    if "<" in final_code or ">" in final_code:
        continue
    if final_code == '':
        continue
    # print final_code
    valid_url = "http://he.beian.miit.gov.cn/icp/publish/query/icpMemoInfo_login.action"
    valid_data = {
        "verifyCode": final_code,
        "id": "96136711",
        "siteName": "",
        "siteDomain": "",
        "siteUrl": "",
        "mainLicense": "",
        "siteIp": "",
        "unitName": "",
        "mainUnitNature": "-1",
        "certType": "-1",
        "mainUnitCertNo": "",
        "bindFlag": "0"
    }
    r2 = requests.post(valid_url, data=valid_data, headers=headers, cookies=cookie)
    if u"王敏" not in r2.text:
        try:
            os.remove(filePath)
        except:
            print u'出错了'
        continue
    else:
        dstDir = "C:/icp/sample/new_train/" + final_code.lower() + "_" + str(int(time.time())) + ".png"
        try:
            os.rename(filePath, dstDir)
        except:
            print u'出错了'
        k += 1
        print k
    # time.sleep(1)

运行一段时间之后,我们就保存了几千张正确的验证码,第一批训练数据集收集完成。


第二步:配置文件

配置文件使用如下配置,并创建出相关目录,注意 model_save_dir 是我们训练完成后模型存放的位置,相当于最终训练成果存放位置,指定的是文件名,不是目录

{
  "origin_image_dir": "sample/origin/",
  "new_image_dir": "sample/new_train/",
  "train_image_dir": "sample/train/",
  "test_image_dir": "sample/test/",
  "api_image_dir": "sample/api/",
  "online_image_dir": "sample/online/",
  "local_image_dir": "sample/local/",
  "model_save_dir": "C:/icp/model/1",
  "image_width": 200,
  "image_height": 60,
  "max_captcha": 4,
  "image_suffix": "png",
  "char_set": "0123456789abcdefghijklmnopqrstuvwxyz",
  "use_labels_json_file": false,
  "remote_url": "http://127.0.0.1:6100/captcha/",
  "cycle_stop": 20000,
  "acc_stop": 0.99,
  "cycle_save": 500,
  "enable_gpu": 1,
  "train_batch_size": 512,
  "test_batch_size": 100
}

第三步:验证和拆分数据集

首先需要创建和指定三个文件夹:origin,train,test,然后把第一步收集的数据集放到 ./sample/origin 目录下

使用命令校验原始图片集的尺寸和测试图片是否正常,并按照19:1的比例拆分出训练集和测试集

python3 verify_and_split_data.py

会有以下提示

>>> 开始校验目录:[sample/origin/]
开始校验原始图片集
原始集共有图片: 1001张
====以下1张图片有异常====
[第0张图片] [.DStore] [文件后缀不正确]
========end
开始分离原始图片集为:测试集(5%)和训练集(95%)
共分配1000张图片到训练集和测试集,其中1张为异常留在原始目录
测试集数量为:50
训练集数量为:950
>>> 开始校验目录:[sample/new_train/]
【警告】找不到目录sample/new_train/,即将创建
开始校验原始图片集
原始集共有图片: 0张
====以下0张图片有异常====
未发现异常(共 0 张图片)
========end
开始分离原始图片集为:测试集(5%)和训练集(95%)
共分配0张图片到训练集和测试集,其中0张为异常留在原始目录
测试集数量为:0
训练集数量为:0


此外,当你有新的样本需要一起训练,可以放在sample/new_train目录下,再次运行python3 verify_and_split_data.py即可。

需要注意的是,如果新的样本中有新增的标签,你需要把新的标签增加到char_set配置中或者labels.json文件中。


第四步:开始训练

创建好训练集和测试集之后,就可以开始训练模型了。训练的过程中会输出日志,日志展示当前的训练轮数、准确率和loss。此时的准确率是训练集图片的准确率,代表训练集的图片识别情况

例如:

第10次训练 >>> 
[训练集] 字符准确率为 0.03000 图片准确率为 0.00000 >>> loss 0.1698757857
[验证集] 字符准确率为 0.04000 图片准确率为 0.00000 >>> loss 0.1698757857

字符准确率和图片准确率的解释:

假设:有100张图片,每张图片四个字符,共400个字符。我们这里把任务拆分为为需要识别400个字符
字符准确率:识别400的字符中,正确字符的占比。
图片准确率:100张图片中,4个字符完全识别准确的图片占比。

这里不具体介绍tensorflow安装相关问题,直奔主题。
确保图片相关参数和目录设置正确后,执行以下命令开始训练:

python3 train_model.py

由于训练集中常常不包含所有的样本特征,所以会出现训练集准确率是100%而测试集准确率不足100%的情况,此时提升准确率的一个解决方案是增加正确标记后的负样本。

训练结束条件在配置文件中可以设置。


第五步:开启api接口

在我们第一次训练完成后,验证码已经有了一定的识别率,几千张的训练成果应该和百度的识别率差不多,我们可以自己开放api接口供别人调用了。

python3 webserver_recognize_api.py

开启api服务后,使用下面代码调用

url = "http://127.0.0.1:6000/b"
files = {'image_file': (image_file_name, open('captcha.jpg', 'rb'), 'application')}
r = requests.post(url=url, files=files)


返回的结果是一个json:

{
    'time': '1542017705.9152594',
    'value': 'g1as',
}


成功到达这步之后,我们已经可自给自足了,不需要再使用百度的接口了,使用我们自己开启的接口进行验证码识别,收集第二批、第三批数据集...。

使用本地接口收集数据集代码如下

# encoding:utf-8

import requests
import base64
import time
import os


def local_code(filePath):
    request_url = "http://127.0.0.1:6000/b"
    files = {'image_file': open(filePath, 'rb')}
    response = requests.post(request_url, files=files, headers=headers)
    code = ''
    if response:
        result = response.json()
        code = result['value']
    return code


headers = {
    "Host": "he.beian.miit.gov.cn",
    "User-Agent": "Mozilla/6.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36",
    "Accept": "image/webp,image/apng,image/*,*/*;q=0.8",
    "Referer": "http://he.beian.miit.gov.cn/icp/publish/query/icpMemoInfo_searchExecute.action",
    "Accept-Language": "zh-CN,zh;q=0.9,und;q=0.8"
}
cookie = ''
k = 0
for i in range(100):
    url = "http://he.beian.miit.gov.cn/getDetailVerifyCode"
    r = requests.get(url + '?' + str(i + 1), headers=headers, cookies=cookie)
    if cookie == '':
        cookie = requests.utils.dict_from_cookiejar(r.cookies)
    filePath = 'D:/tmp/' + str(i) + '.png'
    with open(filePath, 'wb') as f:
        f.write(r.content)
    final_code = local_code(filePath)
    valid_url = "http://he.beian.miit.gov.cn/icp/publish/query/icpMemoInfo_login.action"
    valid_data = {
        "verifyCode": final_code,
        "id": "96136711",
        "siteName": "",
        "siteDomain": "",
        "siteUrl": "",
        "mainLicense": "",
        "siteIp": "",
        "unitName": "",
        "mainUnitNature": "-1",
        "certType": "-1",
        "mainUnitCertNo": "",
        "bindFlag": "0"
    }
    r2 = requests.post(valid_url, data=valid_data, headers=headers, cookies=cookie)
    if u"王敏" not in r2.text:
        os.remove(filePath)
        print True
    else:
        dstDir = "D:/tmp/r/" + final_code.lower() + "_" + str(int(time.time())) + ".png"
        os.rename(filePath, dstDir)
        # print r2.text
        print False

第六步:提升识别率

经过我的测试,icp备案查询系统的验证码第一次训练时识别率20%左右,第二次30%左右,随着验证码图片的数量越来越多,识别率会越来越高。提高识别率的方法有很多,作为初学者,我用的最笨也最简单的方法,在不调整参数的情况下,收集足够多的训练样本,用来提升识别率,目前我已收集20万张验证码图片,在线识别率已经到了95%左右,本地识别率99%。如果你有更好的方法,可以联系我一起交流学习。


image.png


第七步:爬取ICP备案数据

验证码识别率已经达到了95%,可能比我手动输入的正确率都要高,爬取数据已经没有任何问题,全国的ICP备案域名数据量巨大,慢慢跑吧,代码就不放出来了。


三.总结

本次人工智能的学习只是我无意中接触到的,虽然学习过程让我痛苦无比(安装各种环境报错、系统损坏重装一次,丢失很多重要资料、测试识别率的提升方法、人肉标注验证码),耗费一周多的时间,但它的神奇令我印象深刻,之前渗透测试中我对验证码的方式就是手工测试几条弱口令就得了,这次经历让我大开眼界,真的是科技改变生活,学习才能得到真知。

发表评论:

Powered By Z-BlogPHP 1.5.2 Zero

Copyright www.liugongrui.com.All Rights Reserved.