大家好,我是阿文,今天给大家讲解一下如何使用 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