证监会近日出台了更为严格的风控监管法规,因而我得到了新的需求——重做风险评测,最后一项是要统计用户的居住地。产品设计是在用户填写之前,按照用户的身份证号码将用户居住地的默认值设为出生地。
实现是比较简单的,身份证前六位依次对应我们的省、市、县的行政代码,将各个行政代码与对应的地址以key-value(Python中的字典)的形式写成常量或者写入数据库,然后拿着身份证前六位查询即可。问题在于,需要先把行政代码拿到手,然后写为字典。
1.爬取行政区代码
先查找到国家最新公布的行政区划代码网站页面
我们可以拷贝进编辑器,但想要存为字典,就需要一行一行的手动修改格式。虽然也就3000多行吧,但身为一个程序员,哪怕是最初级的,也该有一个偷懒的心,更何况人生苦短我用Python。
下面开始爬取操作,这么一个简单的网页不需要复杂的爬虫,使用urllib2+BeautifulSoup4(py2的搭配,py3中应该为urllib.request+BeautifulSoup3)就可以了
先进入python.exe/ipython.exe
得到404,说明网站还是做了基本的安全措施,需要加点料。
在Chrome浏览器按F12进入调试模式,按F5刷新,在Network中查看网页中对html的Request,然后拿到Headers中的几个重要的值:Host、User-Agent、Cookie,然后添加到我们自己的request中
但这个时候拿到的req并不能直接用,如果print(req)的话,会得到满屏幕的乱码。此时req的内容,是网页的html源代码,等于在网页查看的Request对应的Response。仔细查看Response的内容,终于在第368行找到了我们的目标,拷贝到编辑器中查看
这时候该BeautifulSoup4出场了,bs4支持多种解析器:Python标准库(“html.parser”)、lxml HTML 解析器(“lxml”)、lxml XML 解析器(“xml”)、html5lib(“html5lib”),并提供了强大的遍历和搜索方法,具体查看文档。
这里使用Python标准库解析器+find_all( name , attrs)搜索标签即可
观察可知行政区编码最里层的标签是<span>, 属性为lang="EN-US"
下一步就是把110000从"bs4.element.Tag"中取出来,观察发现每条数据的格式都是一样的,倘若可以转为string,然后index('>'),再取偏移6位即可
如上图所示,一番尝试得到了所有的行政区代码,下一步就是取得对应行政区名称了。
同样是观察可知行政区名称最里层的标签为<span>,属性为style="font-family: 宋体"
此处开始换电脑了,所以截图不一样
得到的结果是Unicode字符串,但感觉似乎有点不对劲,将areas和codes打印出来对比来看一下,Unicode字符print出来就会显示中文
发现地址列表中存在空格的情况,再加以处理即可
现在剩下最后一步了,按字典的格式输出打屏幕上,然后拷贝编辑器中。
话说当时我在这里脑子抽抽了,卡了半个多小时,尝试过直接赋值为字典或者使用codes写入文件再读取出来写成字典,发现地址显示出来的全是Unicode字符,最终在旁人点醒下才跳出思维得到自己要的。
想要存入本地文件的话,需要使用codecs这个库,Unicode不能直接写入文件。
完整的代码如下:
import codecs
import urllib2
import sys
from bs4 import BeautifulSoup
url = 'http://www.stats.gov.cn/tjsj/tjbz/xzqhdm/201703/t20170310_1471429.html'
headers = {
'Host': 'www.stats.gov.cn',
'User-Agent': 'ozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36',
'Cookie': 'AD_RS_COOKIE=20083363; _gscu_1771678062=957855112bjaz048; _gscs_1771678062=95785511u2vn7148|pv:5; _gscbrs_1771678062=1; _trs_uv=f3pk_6_j35k2nll; _trs_ua_s_1=i5mz_6_j35k2nll',
}
try:
request = urllib2.Request(url, headers=headers)
response = urllib2.urlopen(request)
req = response.read()
except urllib2.URLError, e:
if hasattr(e, 'code'):
print(e.code)
if hasattr(e, 'reason'):
print(e.reason)
sys.exit(1)
soup = BeautifulSoup(req, "html.parser")
code_res = soup.find_all('span', attrs={'lang': 'EN-US'})
codes = [str(c)[19:25] for c in code_res]
area_res = soup.find_all('span', attrs={'style': 'font-family: 宋体'})
areas = [a.text.strip() for a in area_res if a.text.strip()]
# 按字典的书写格式输出,避免手动加引号
# for i in range(len(codes)):
# print ' "%s": u"%s",' % (codes[i], areas[i])
with codecs.open('./res.txt', 'w', 'utf-8') as f:
for i in range(len(codes)):
f.write(codes[i] + ':' + areas[i] + '\n')
2.通过身份证号码获取用户出生地信息
从网上可以查的18位身份证号码的编排规则,以及最后一位的校验规则
如下处理即可
# 一共3508条,不全给出
areacode = { # 截止2016年7月31日
"110000": u"北京市",
"110100": u"市辖区",
...
}
# 校验18位身份证号码的合法性
def vertify(id):
c = 0
ckeck_codes = '10X98765432' # 校验码
for (a, p) in zip(map(int, id[:-1]), range(17, 0, -1)):
w = (2 ** p) % 11
c += a * w
return id[-1] == ckeck_codes[c % 11]
# 获取出生地信息
def area(id):
province = id[0:2]
municipal = id[2:4]
county = id[4:6]
return (areacode[province + "0000"],
areacode[province + municipal + "00"],
areacode[province + municipal + county])