学了scrapy框架之后,我重写了一个爬虫项目,并且已经爬好了所有数据。
传送门:https://www.imtrq.com/archives/2353
前言
其实在前几天,我就已经爬好了一份注册会计师的个人简介,当然,是按照我自己需要的事务所去查的,之后我也把数据分享给了我导和学姐们。
但是,在我处理自己的数据时,我越来越觉得不对劲,我的数据每合并一次就少掉一半,我还以为是自己操作的问题,苦恼了半天,后来我突然意识到:
审计师不一定是注册会计师啊!!!!!
这么简单的问题竟然被我忽略了,没错,就像我这样没过CPA的也可以去做审计,所以审计师 ≠ 注册会计师,那么我去注册会计师信息管理系统里面找的信息,肯定就不全了。
那么有没有办法挽救呢,答案肯定是有的,那就是:全!部!重!新!爬!一!遍!
看起来任务好像很重,但是,我这几天在处理数据(数以千计的缺失值)上打的白工比这个重多了……心累。
呃,也许有人还不知道去哪里查这些信息,我看了一些文献,都很草率地写着“注册会计师官网”,找了很多篇,终于有一篇点出了“中国注册会计师行业管理系统”,总算为我指明了方向。
网址在这:
http://cmispub.cicpa.org.cn/cicpa2_web/goto/nomsg/DNA_XH/default.shtml
注意:请到第一个数据库里面去查询,如果你去查第二个数据库,那么你也跟我之前犯了一样的错误啦。(当然,如果你本来就是只要注册会计师的信息,就当我没说,不过,我还要提醒一下,我是没找到什么办法可以从第二个数据库里一次性下载所有数据。)
还有一些很坑的地方:
- 网页一片空白?——哼哼!这是一个神奇的网站,只有用传统的ie浏览器才可以打开
- 解析出现乱码?——呵呵!本网页用GB2312编码!
- GB2312解码失败?——还有随机的GBK解码哦!
这都是我上一次爬数据遇到的坑,这次当然要补上,另外,我还要做这些些事:
- 收集会计师事务所的信息
- 收集从业人员的信息
- 收集注册会计师信息
- 收集注册会计师个人简介
第三第四条看起来像重复的,其实并没有重复,因为个人简介合信息列表是在不同的网页的,所以我分别采集到不同的数据库了。(咦,怎么好像还是重复的。)
嘛,我需要的暂时就是上面这些信息,然后我就 开 始 了。
采集
为什么不用Scrapy之类的爬虫框架呢,很简单,因为我不会。
为什么封装成类呢?呃,这并不是我不会面向对象哈,我用实例做多进程爬虫的时候总是出错,所以就没有这么做了。(咦,好像还是因为我不会?)
我做这些爬虫的时候一般分成两步,第一步是获取url列表,第二步就直接开搞了。
1 获取注册会计师事务所列表和url
好像事务所的序号和事务所id重复了,所以我就把代表特征值的“id”全都换成了“token”这个变量名。
好在我说的第一个数据库是可以用空值搜索的,而且一下子就列出了所有的事务所(竟然有9000多家,汗,应该把分所也列进去了);如果你选择直接去注册会计师信息系统里查,那么是不可以空值搜索的。
代码如下,我应该写了注释了噢,你也可以点击这里直接下载
import requests, pymongo, re, logging
from bs4 import BeautifulSoup
import multiprocessing as mp
#创建日志
logging.basicConfig(filename='AuditUnitIndex.log', level='DEBUG')
#连接数据库
client = pymongo.MongoClient('mongodb://localhost:27017')
unit = client['AuditUnit']
index = unit['index']
#设置链接和请求头
url = 'http://cmispub.cicpa.org.cn/cicpa2_web/OfficeIndexAction.do'
headers = {
'User-Agent':'Mozilla/5.0(WindowsNT10.0;WOW64;Trident/7.0;rv:11.0)likeGecko'
}
#抓取一页的数据
def get_one_page(pageNum):
params = {
'ascGuid': '00',
'isStock': '00',
'method': 'indexQuery',
'offAllcode': '',
'offName': '',
'pageNum': pageNum,
'pageSize': '',
'personNum': '',
'queryType': 1
}
r = requests.post(url, headers= headers, params=params)
try:
#尝试用GB2312解码
text = r.content.decode('gb2312')
except:
#解码失败则尝试用GBK解码
text = r.content.decode('gbk')
return text
#从一个页面上获取列表
def get_one_list(pageNum):
text = get_one_page(pageNum)
soup = BeautifulSoup(text, 'lxml')
li = soup.find_all('tr', "rsTr")
return li
#处理列表上的一条记录
def parse_one_page(pageNum):
li = get_one_list(pageNum)
for i in range(len(li)):
single = li[i].find_all('td')
piece = {
'id': single[0].string,
'code': single[1].string,
'token': re.findall('viewDetail\(\'(.*?)\'\,', str(single[2].a))[0],
'name': single[2].string,
'address': single[3].string,
'contact': single[4].string,
'number': single[5].string
}
index.insert_one(piece)
def work(pageNum):
try:
parse_one_page(pageNum)
logging.debug('Page {} Downloaded!'.format(pageNum))
except:
logging.debug('Page {} Failed, PLEASE CHECK!'.format(pageNum))
if __name__ == '__main__':
pageNum = [i for i in range(1,631)]
p = mp.Pool()
p.map_async(work, pageNum)
p.close()
p.join()
2 开始采集需要的信息
其实主要是两种信息:事务所和审计师,但我为了不出差错,暂时用四个数据库(集合)来存储。(都是MongoDB哦,虽然我也不太懂这个数据库,但是反正可以拿来存数据就是了。)
- AuditUnit——info:存储事务所信息
- AuditUnit——NwPer: 存储从业人员信息
- AuditUnit——CPA:存储注册会计师信息
- Auditors——Profile:存储注册会计师个人资料(从业人员
不配有 未公开个人资料)
代码如下,我好像也写注释了噢,你也可以点击这里直接下载
import requests, pymongo, re, logging, time
from bs4 import BeautifulSoup
import multiprocessing as mp
#生成日志
logging.basicConfig(filename='AuditCrawler.log', level='DEBUG')
#连接数据库
client = pymongo.MongoClient('mongodb://localhost:27017')
unit = client['AuditUnit']
auditors = client['Auditors']
#用以存储事务所的特征码
index = unit['index']
#存储爬取失败的事务所,后期调整
failed = unit['failed']
#用以存储事务所的信息
info = unit['info']
#用以存储事务所里的注册会计师
cpa = unit['CPA']
#用以存储事务所里的从业人员
NwPer = unit['NwPer']
#用以存储注册会计师详细资料
profile = auditors['profile']
#浏览器请求头
headers = {
'User-Agent':'Mozilla/5.0(WindowsNT10.0;WOW64;Trident/7.0;rv:11.0)likeGecko'
}
#获取事务所信息
#获取事务所详情页面
def get_unit_page(token):
url = 'http://cmispub.cicpa.org.cn/cicpa2_web/09/'+ token + '.shtml'
r = requests.post(url, headers= headers)
try:
#尝试用GB2312解码
text = r.content.decode('gb2312')
except:
#解码失败则尝试用GBK解码
text = r.content.decode('gbk')
return text
#从详情页面抽取信息存入数据库
def get_unit_info(token):
text = get_unit_page(token)
soup = BeautifulSoup(text, 'lxml')
td = soup.find('table', "detail_table")
tdl = td.find_all('td', "tdl")
tdc = td.find_all('td', "data_tb_content")
Unit_piece = {tdl[i].get_text('\n', '<br/>').replace('\n','').replace('\t', '').replace('(请点击)', ''): tdc[i].get_text('\n', '<br/>').replace('\n','').replace('\t', '').replace('(请点击)', '') for i in range(len(tdl))}
info.insert_one(Unit_piece)
#获取从业人员信息
#获取总页数
def get_NwPer_num(token):
url = 'http://cmispub.cicpa.org.cn/cicpa2_web/OfficeIndexAction.do'
params = {
'method': 'getEmployeeList',
'offGuid': token,
'pageNum': 1,
'pageSize': 10
}
r = requests.post(url, headers= headers, params=params)
try:
#尝试用GB2312解码
text = r.content.decode('gb2312')
except:
#解码失败则尝试用GBK解码
text = r.content.decode('gbk')
try:
page_num = int(re.findall('记录 / 共 (.*?) 页', text)[0])
except:
page_num = 0
return page_num
#获取从业人员列表页面
def get_NwPer_page(token, page):
url = 'http://cmispub.cicpa.org.cn/cicpa2_web/OfficeIndexAction.do'
params = {
'method': 'getEmployeeList',
'offGuid': token,
'pageNum': page,
'pageSize': 10
}
r = requests.post(url, headers= headers, params=params)
try:
#尝试用GB2312解码
text = r.content.decode('gb2312')
except:
#解码失败则尝试用GBK解码
text = r.content.decode('gbk')
return text
#从列表页面抽取信息存入数据库
def get_NwPer_info(token):
page_num = get_NwPer_num(token)
if page_num==0:
return 0
for page in range(1, page_num+1):
text = get_NwPer_page(token, page)
soup = BeautifulSoup(text, 'lxml')
td = soup.find('table', "detail_table").find_all('tr')
if len(td) < 2:
return 0
unit = td[0].td.get_text('\n', '<br/>').replace('\n','').replace('\t', '').replace('(从业人员)', '')
tdl = td[1].find_all('th')
trcs = td[2:]
for trc in trcs:
tdc = trc.find_all('td')
NwPer_piece = {tdl[i].get_text('\n', '<br/>').replace('\n','').replace('\t', ''): tdc[i].get_text('\n', '<br/>').replace('\n','').replace('\t', '') for i in range(len(tdl))}
NwPer_piece['所在事务所'] = unit
NwPer.insert_one(NwPer_piece)
#获取注册会计师信息
#获取总页数
def get_cpa_num(token):
url = 'http://cmispub.cicpa.org.cn/cicpa2_web/OfficeIndexAction.do'
params = {
'method': 'getPersons',
'offGuid': token,
'pageNum': 1,
'pageSize': 10
}
r = requests.post(url, headers= headers, params=params)
try:
#尝试用GB2312解码
text = r.content.decode('gb2312')
except:
#解码失败则尝试用GBK解码
text = r.content.decode('gbk')
try:
page_num = int(re.findall('记录 / 共 (.*?) 页', text)[0])
except:
page_num = 0
return page_num
#获取注册会计师列表页面
def get_cpa_page(token, page):
url = 'http://cmispub.cicpa.org.cn/cicpa2_web/public/query/swszs/%20/'+ token +'.html'
params = {
'method': 'getPersons',
'offGuid': token,
'pageNum': page,
'pageSize': 10
}
r = requests.post(url, headers= headers, params=params)
try:
#尝试用GB2312解码
text = r.content.decode('gb2312')
except:
#解码失败则尝试用GBK解码
text = r.content.decode('gbk')
return text
#从列表页面抽取信息存入数据库
def get_cpa_info(token):
page_num = get_cpa_num(token)
if page_num==0:
return 0
profile_tokens = []
for page in range(1, page_num+1):
text = get_cpa_page(token, page)
soup = BeautifulSoup(text, 'lxml')
td = soup.find_all('table', "detail_table")[1].find_all('tr')
if len(td) < 2:
return 0
unit = soup.find('table', "detail_table").find_all('tr')[0].td.get_text('\n', '<br/>').replace('\n','').replace('\t', '').replace('(注册会计师)', '')
tdl = td[0].find_all('th')
trcs = td[1:]
for trc in trcs:
tdc = trc.find_all('td')
cpa_piece = {tdl[i].get_text('\n', '<br/>').replace('\n','').replace('\t', ''): tdc[i].get_text('\n', '<br/>').replace('\n','').replace('\t', '') for i in range(len(tdl))}
cpa_piece['所在事务所'] = unit
cpa.insert_one(cpa_piece)
for tk in re.findall('getPerDetails\(\'(.*?)\',', text):
profile_tokens.append(tk)
return profile_tokens
#获取注册会计师个人简介
#获取个人简介页面
def get_profile_page(tk):
url = 'http://cmispub.cicpa.org.cn/cicpa2_web/07/'+ tk + '.shtml'
r = requests.post(url, headers= headers)
try:
#尝试用GB2312解码
text = r.content.decode('gb2312')
except:
#解码失败则尝试用GBK解码
text = r.content.decode('gbk')
return text
#从页面抽取信息
def get_one_profile(tk):
text = get_profile_page(tk)
soup = BeautifulSoup(text, 'lxml')
td = soup.find('table', "detail_table")
tdl = td.find_all('td', "tdl")
tdc = td.find_all('td', "data_tb_content")
profile_piece = {tdl[i].get_text('\n', '<br/>').replace('\n','').replace('\t', ''): tdc[i].get_text('\n', '<br/>').replace('\n','').replace('\t', '') for i in range(len(tdl))}
profile.insert_one(profile_piece)
#获取事务所所有的注册会计师的个人信息
def get_profile(token):
tks = get_cpa_info(token)
if tks == 0:
return
for tk in tks:
get_one_profile(tk)
def work(token):
try:
logging.debug('Processing TOKEN: {}'.format(token))
get_unit_info(token)
logging.debug('TOKEN: {} Unit Info Downloaded.'.format(token))
logging.debug('TOKEN: {} Downloading NwPer Info.'.format(token))
get_NwPer_info(token)
logging.debug('TOKEN: {} NwPer Info Downloaded.'.format(token))
logging.debug('TOKEN: {} Downloading CPA Info.'.format(token))
get_profile(token)
logging.debug('TOKEN: {} CPA Info Downloaded.'.format(token))
except:
#也许有网络错误的时候,再试一次
time.sleep(5)
try:
logging.debug('Processing TOKEN: {}'.format(token))
get_unit_info(token)
logging.debug('TOKEN: {} Unit Info Downloaded.'.format(token))
logging.debug('TOKEN: {} Downloading NwPer Info.'.format(token))
get_NwPer_info(token)
logging.debug('TOKEN: {} NwPer Info Downloaded.'.format(token))
logging.debug('TOKEN: {} Downloading CPA Info.'.format(token))
get_profile(token)
logging.debug('TOKEN: {} CPA Info Downloaded.'.format(token))
except:
logging.debug('TOKEN: {} Download Failed!.'.format(token))
failed.insert_one({'token': token})
token = []
for i in index.find():
token.append(i['token'])
p = mp.Pool()
p.map_async(work, token)
p.close()
p.join()
后记
我这个程序还在运行,也不知道会不会出什么bug就发上来了,呃,如果我三天后还没有记得把采集好的数据上传上来,那么这个程序一定是有bug的。
至于我为什么不放到GitHub上呢……
一是这个程序很小,不足挂齿。
二是像我这样(伪)不正经的人,是不会认认真真写README的。
Comments | 1 条评论
博主 傲娇的小基基
现在的问题是貌似爬一段时间就会被服务器拒绝连接,可恶,我看日志明明在增长,就一直没管,后来才发现一大半都是failed