python3 爬虫实现抓取工信部备案信息

大家好,我是阿文,今天给大家讲解一下如何使用 python 来查询工信部域名备案信息,那么先讲下为什么不直接查询而是要用这种手段爬呢?

首先,工信部这个网站也不知道是不是找外包做的,里面各种嵌套表格(现在网站基本都是 div+css 结构),这个还能忍,最不能忍的是他那个验证码,我十次输入只有一次对的。

它的验证码长这样的,6位数英文+数字随机组合

而工作中又需要经常去查询客户域名是否备案与否,一些第三方网站查询有可能不准确,比如读取的是缓存数据。所以最准确的还是去工信部查询。那么只好自己写个爬虫去爬下数据咯。

验证码明明是对的,他却说错了,验证的时候都校验通过,提交的时候却提示错误,屎一样的网站。

所需要的环境和库

python 版本

我这里是用的 python3.7(只要是 python3 应该都问题不大)

  • import requests
  • import fateadm_api
  • from bs4 import BeautifulSoup

辅助工具

  • postman (主要是用来模拟请求的)

最终结果

先看一眼最终结果

➜  get_domain_info git:(master) ✗ python3 get_beian.py
AFOV58
{'name': '杭州网易质云科技有限公司', 'nature': '企业', 'icp_number': '浙ICP备17006647号-2', 'web_name': '网易云', 'domain': 'www.163yun.com', 'check_data': '2018-07-17'}
➜  get_domain_info git:(master) ✗ python3 get_beian.py
{'name': '方文俊', 'nature': '个人', 'icp_number': '浙ICP备15018780号-3', 'web_name': '阿文codeing', 'domain': 'www.awen.me', 'check_data': '2016-12-14'}
➜  get_domain_info git:(master) ✗ 

如图所示

里面包含了名称、属性、备案号、域名、审核时间等信息,一般你买的接口也就返回这些数据。那些封装好的接口其实也是去爬工信部的网站来获取数据,其实,这个里面最难的是验证码识别,我试了 python 的 PIL 库调用tesserocr 去处理验证码效果不是很好,参考文章python 识别验证码
于是调用第三方接口,这些第三方接口基本都是基于 TensorFlow 来通过机器学习模拟训练的,识别率特别高,几乎每一张验证码都能正确识别。

不得不佩服,工信部这样的网站得配合机器学习来食用,门槛着实是高。就像 12306 一样,设计那么复杂的验证码其实就是为了防止被爬虫爬。然而现在有了机器学习之后,通过让机器不断的训练自我学习,这些复杂的玩意已经很轻松的就被破解了。

话不多说,我们继续。。

分析请求步骤

第一步,首先我们打开工信部的查询备案网站 http://www.miitbeian.gov.cn/publish/query/indexFirst.action ,然后用 chrome 打开开发者菜单,切换到 network,然后点击获取验证码,可以看到有一个请求

我们仔细看下这个请求的请求信息,包括请求 url、请求头和请求参数

Request URL: http://www.miitbeian.gov.cn/getVerifyCode?40

Accept: image/webp,image/apng,image/*,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cache-Control: no-cache
Connection: keep-alive
Cookie: __jsluid=eb9d0c3a04daf97b1958257fef1a5126; JSESSIONID=Tx2EwxeRj_u-RKvL0HSzpFdoxfEBE3y1pRvoePlozRoVYAPr7uE2!1649116120
DNT: 1
Host: www.miitbeian.gov.cn
Pragma: no-cache
Referer: http://www.miitbeian.gov.cn/icp/publish/query/icpMemoInfo_showPage.action
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36

多次刷新验证码,发现后面都是两位数的值,其实我发现就算这个40不变也没关系,照样能生成一个新的验证码。这个应该是防止有缓存。

第二步,好,现在试试输入验证码,观察下请求,发现有个验证验证码的请求,这里注意了,即使你验证码输入的是小写,他也会在请求时转成大写。

此外,你还需要记录下请求 URL、请求头和查询参数(Form Data) 里面的值,也就是大写的验证码。

第三步,我们输入域名,工信部查询的方式有很多种,这里只讲通过域名去查,其他的你把参数换了传给服务器也一样的,我们输入验证码后查询请求里面有一个 请求如下:

Request URL: http://www.miitbeian.gov.cn/icp/publish/query/icpMemoInfo_searchExecute.action

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cache-Control: no-cache
Connection: keep-alive
Content-Length: 144
Content-Type: application/x-www-form-urlencoded
Cookie: __jsluid=eb9d0c3a04daf97b1958257fef1a5126; JSESSIONID=k36E8uIkINZgWY2svrcyVR5TceIIiEclzOOeOJ7jzaVzXoILuPyo!1298008624
DNT: 1
Host: www.miitbeian.gov.cn
Origin: http://www.miitbeian.gov.cn
Pragma: no-cache
Referer: http://www.miitbeian.gov.cn/icp/publish/query/icpMemoInfo_searchExecute.action
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36

其 Form Data 里面的值如下:

siteName: 
condition: 1
siteDomain: awen.me
siteUrl: 
mainLicense: 
siteIp: 
unitName: 
mainUnitNature: -1
certType: -1
mainUnitCertNo: 
verifyCode: NNCX6O

重点关注 siteDomain 和 verifyCode 这两个值一个是域名一个是验证码,提交请求后,我们看到页面显示

包括序号、主办单位名称、单位性质、备案号、网站名称等等。我们看下对应请求的 response 信息,可以看到,他其实是将上述信息保存在一张 table 的 td 标签里面,这个 td 标签class 属性叫 bxy。也就是说我们最后只要把这个标签里面的值拿到就可以了。如果返回没有这个标签那就说明这个网站没有备案了。

好了,到这里,基本上整个查询过程就完成了。接下来我们开始写代码

代码

1.首先,我们导入几个库

import requests
import fateadm_api
from bs4 import BeautifulSoup
import random

其中 fateadm_api 是一个第三方打码平台的库,我之前自己实现过识别验证码 ,准确率不高。这个你也可以去换成其他的。BeautifulSoup 是用来解析 html 标签获取到最终的值。

然后,我们要创建一个 session ,因为请求过程中会带上session 。

requests_session = requests.session()

第一步,获取验证码,我们实现个方法,这个验证码干的事情就是去获取验证码并保存到本地,然后调用打码接口识别验证码。最后返回验证码信息

# 获取验证码进行解析,这里调用 http://www.fateadm.com/price.html 打码平台的接口获取验证码

def get_verify_code():
    url = "http://www.miitbeian.gov.cn/getVerifyCode?"+str(random.randint(1,100))

    headers = {
        'Accept': "image/webp,image/apng,image/*,*/*;q=0.8",
        'Accept-Encoding': "gzip, deflate",
        'Accept-Language': "zh-CN,zh;q=0.9,en;q=0.8",
        'Cache-Control': "no-cache",
        'Connection': "keep-alive",
        'DNT': "1",
        'Host': "www.miitbeian.gov.cn",
        'Pragma': "no-cache",
        'Referer': "http://www.miitbeian.gov.cn/icp/publish/query/icpMemoInfo_showPage.action",
        'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
    }

    response = requests_session.get(url, headers=headers)
    if response.status_code == 200:
        with open('code.jpg', 'wb') as file:
            file.write(response.content)
    # 识别验证码        
    verifyCode = fateadm_api.TestFunc('code.jpg')
    return verifyCode

注意所有请求头里面的 cookie 字段都去掉,因为 requests_session = requests.session() 会带 cookie 去请求,requests 库这个方法直接给我们维护了 session ,我们不需要去关心 cookie。你可以直接 print(response.headers) 打印响应头查看 set-cookie 信息。

第二步,校验验证码,这一步是模拟输入验证码后判断验证码是否输入正确,如正确会返回 True

# 模拟输入验证码后判断验证码是否输入正确

def check_verify_code(validateValue):
    url = "http://www.miitbeian.gov.cn/common/validate/validCode.action"

    payload = {"validateValue": validateValue}
    headers = {
        'content-type': "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW",
        'Accept': "application/json, text/javascript, */*",
        'Accept-Encoding': "gzip, deflate",
        'Accept-Language': "zh-CN,zh;q=0.9,en;q=0.8",
        'Cache-Control': "no-cache",
        'Connection': "keep-alive",
        'Content-Length': "20",
        'Content-Type': "application/x-www-form-urlencoded",
        'DNT': "1",
        'Host': "www.miitbeian.gov.cn",
        'Origin': "http://www.miitbeian.gov.cn",
        'Pragma': "no-cache",
        'Referer': "http://www.miitbeian.gov.cn/icp/publish/query/icpMemoInfo_showPage.action",
        'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
        'X-Requested-With': "XMLHttpRequest",
    }

    response = requests_session.post(url, data=payload, headers=headers)
    if response.status_code != 200:
        return
    resp_json = response.json()
    return resp_json['result']

第三步,查询,我们需要把服务器响应的 body 内的内容拿到并且进行解析 html 从中提取对于我们有价值的信息放入字典中。

# 模拟查询并反馈备案信息
# 例如:{'name': '杭州网易质云科技有限公司', 'nature': '企业', 'icp_number': '浙ICP备17006647号-2', 'web_name': '网易云', 'domain': 'www.163yun.com', 'check_data': '2018-07-17'}

def do_request_beian(domain, verifyCode):
    url = "http://www.miitbeian.gov.cn/icp/publish/query/icpMemoInfo_searchExecute.action"
    payload = "siteName=&condition=1&siteDomain=" + domain + "&siteUrl=&mainLicense=&siteIp=&unitName=&mainUnitNature=-1&certType=-1&mainUnitCertNo=&verifyCode=" + verifyCode

    headers = {
        'content-type': "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW",
        'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
        'Accept-Encoding': "gzip, deflate",
        'Accept-Language': "zh-CN,zh;q=0.9,en;q=0.8",
        'Cache-Control': "no-cache",
        'Connection': "keep-alive",
        'Content-Length': "144",
        'Content-Type': "application/x-www-form-urlencoded",
        'DNT': "1",
        'Host': "www.miitbeian.gov.cn",
        'Origin': "http://www.miitbeian.gov.cn",
        'Pragma': "no-cache",
        'Referer': "http://www.miitbeian.gov.cn/icp/publish/query/icpMemoInfo_showPage.action",
        'Upgrade-Insecure-Requests': "1",
        'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36",
    }

    response = requests_session.post(url, data=payload, headers=headers)
    if response.status_code != 200:
        return
    html_context = response.text

    # 解析 html,获取对应的值存入 dict 中
    soup = BeautifulSoup(html_context, "html.parser")
    soup_msg = soup.find_all(name='td', attrs={'class': "bxy"})
    soup.prettify()
    icp_list = []
    for content in soup_msg:
        content = content.get_text()
        content_out = "".join(content.split())
        icp_list.append(content_out)
    icp_info = {"name": icp_list[0], "nature": icp_list[1], "icp_number": icp_list[2],
                "web_name": icp_list[3], "domain": icp_list[4], "check_data": icp_list[-2]}
    return icp_info

我们重点说下怎么解析,看下面这段代码:

# 解析 html,获取对应的值存入 dict 中
     soup = BeautifulSoup(html_context, "html.parser")
     soup_msg = soup.find_all(name='td', attrs={'class': "bxy"})
     soup.prettify()
     icp_list = []
     for content in soup_msg:
         content = content.get_text()
         content_out = "".join(content.split())
         icp_list.append(content_out)

我们获取到响应的 html 内容后要把他传入到BeautifulSoup中,然后通过 find_all 查找到 class 属性为 bxy 的 所有 td 标签。因为我们需要的信息就在这个 td 标签里面。拿到信息之后我们循环获取 td 标签中的值并对值进行处理,里面包含了 这种空格信息不是我们想要的,需要处理掉。
最后存入我们定义的 icp_list 中。

考虑到如果 soup_msg 为空的情况,我们捕获下异常,如果报异常了那就是没有未备案

try:
    soup_msg = soup.find_all(name='td', attrs={'class': "bxy"})
    icp_list = []
    for content in soup_msg:
        content = content.get_text()
        content_out = "".join(content.split())
        icp_list.append(content_out)
    icp_info = {"name": icp_list[0], "nature": icp_list[1], "icp_number": icp_list[2],
                "web_name": icp_list[3], "domain": icp_list[4], "check_data": icp_list[-2]}
    return icp_info
except IndexError:
    return '未备案'

或者如果list 为空就打印未备案

if len(soup_msg):
    icp_list = []
    for content in soup_msg:
        content = content.get_text()
        content_out = "".join(content.split())
        icp_list.append(content_out)
    icp_info = {"name": icp_list[0], "nature": icp_list[1], "icp_number": icp_list[2],
                "web_name": icp_list[3], "domain": icp_list[4], "check_data": icp_list[-2]}
    return icp_info
print("未备案")

最后我们在 main 方法里面调用者三个方法,并将验证码转为大写传入进行验证,如果值为 True 则执行查询。

if __name__ == '__main__':

    verify_code = str(get_verify_code()).upper()
    if check_verify_code(verify_code) is True:
        print(do_request_beian("awen.me", verify_code))

另外,我发现即使不判断验证码是否为 True 也可以查询

verify_code = str(get_verify_code()).upper()
# if check_verify_code(verify_code) is True:
print(do_request_beian("qingchan.me", verify_code))

大概就是这样样子。其实这个主要是学习和巩固了下 HTTP 的request 库的使用以及BeautifulSoup库的使用。老实讲,我还是比较喜欢那种返回的数据是 json 的网站,直接 json 解析就 ok,美滋滋。

github

地址:https://github.com/monkey-wenjun/get_icp_info/tree/master