利用爬虫自动查找重名的高校
一句话介绍 – 这是什么
利用Python+百度百科,自动查阅Excel表格中的学校是否因更名而重复出现,如“XX学院”在某一年更名为“XX大学”,但表格中同时出现了“XX学院”和“XX大学”,其实是同一所学校,我们要做的就是把这样的学校找出来。
一句话思路 – 做什么
思路很简单,利用Python爬虫遍阅百度百科,把每个学校原始的、曾经使用的、现在的名字都找出来并记下来,然后回到Excel表格中寻找这些名字中是否出现名字重复项,如果是的话说明重复项是更名前后的同个学校。
背景 – 为啥要做
虽然现在我是做运营,整天出卖自己的机智,可是偶尔动脑筋、写代码才是真爱。
老板说这里有个包含3000多个中国高校的Excel表格,要我们查漏补缺。我们多找出了100个左右的新的高校,那么问题来了:这100个可能是原数据没有、新增的,也可能与原数据的某个高校是同个学校,只是因学校更名的原因于是再次出现而已。
出于对技术的热情和对重复性工作的抵抗,于是撸了这份代码。
思路分解 – 怎么做
核心部分有这两个。
1、构建学校名称集合
假如我们现在有了这100个新的高校的所有名字集合,大概如下图
最左边是我们新增的学校的名称,右边大括号{}内的内容就是通过百科查找到的该学校原始的、曾经使用的、现在的所有名字的集合。名字集合里面有些误差,比如出现“政府批准同意将”等字眼,这是目前程序缺陷、无法精准打击的原因,但是对后面查重影响不大。
我把这个函数称为get_schools_dict(),这个函数返回一个字典(dict),这个字典的key就是我们新增的那100个学校,value就是找到的名称集合。这里利用的是的爬虫爬取百科页面+正则表达式提取“更名”、“原名”等关键词前后的带有“大学/学院/学校”的字眼的文字。通常这段文字就是学校名称或者一段包含有学校名称句子。
2、遍历查重
让程序遍历Excel表格,查找原数据3000多个高校名是否在我们构建的100个高校名称集合中出现。如果出现的话,说明是同个高校,保存下来。
这里我称该函数为compare(),它会返回一个列表,列表内有若干元组(tuple),每个元组就是重名学校的原名和现在的名字,大概如下
代码撸起
整个代码结构如下
各个函数作用分别是读取Excel表格、从表格中得到我们想要的学校名单、构造百科的url(用于爬虫爬取)、访问url并获取百科页面、构造学校名称集合、遍历查重。在主函数中,利用以上各函数,先读取表格,然后分别获取原始高校的名单和新的100个高校的名单,然后构建新100个高校的学校名称集合,最后通过compare比较得出重名项。
1、完成get_schools_dict()函数。
把新增100个高校名单传进来,我们逐一取出学校名字,借助前面的get_url()、get_page()函数来得到这个高校的百科页面,然后就是重头戏:正则提取学校名称。
这里分为三种。
第一种,抓取页面上的“更名(为)”后面的带有“大学/学院/学校”字眼的文字。如下图中,“更名为”后面的“河北唐山矿冶学院”、“河北矿冶学院”就被捕捉到了,这种方法一打一个准。
第二种,抓取页面上的“更名(为)”前面的带有“大学/学院/学校”字眼的文字。这种通常就会出现前面说的多余的字眼。不过没关系,能把XX大学/学院/学校抓下来就行了。
第三种,有些百科页面会说这个学校原名是什么,那就把“原名(为:)”后的带有“大学/学院/学校”字眼的文字抓取下来。
正则表达式的教程暂不赘述,具体可详见:
http://blog.csdn.net/michaelpp/article/details/9631979
’[\u4e00-\u9fa5]’表示匹配任何中文,’ |’表示“或”,能保证XX大学或者XX学院或者XX学校都能匹配到。
这里有个缺陷,就是有些页面上会的学校名称会带有双引号或者’《’、’》’等符号,那么我们的正则表达式可能就匹配不到了,于是我们在’[\u4e00-\u9fa5]’把这些可能出现的符号也加进去,变成’ [\“\”\《\》\u4e00-\u9fa5]’,用于过滤出学校名的正则表达式最终变成:
for school in schools_list: url = get_url(school) page_data = get_page(url) new_names = re.findall('更名[为\:]([\“\”\《\》\u4e00-\u9fa5]*?大学|[\“\”\《\》\u4e00-\u9fa5]*?学院|[\“\”\《\》\u4e00-\u9fa5]*?学校)',page_data) original_names = re.findall('([\“\”\《\》\u4e00-\u9fa5]*?大学|[\“\”\《\》\u4e00-\u9fa5]*?学院|[\“\”\《\》\u4e00-\u9fa5]*?学校)[\“\”\《\》\u4e00-\u9fa5]*?更名',page_data) first_name = re.findall('原名[为\:]([\“\”\《\》\u4e00-\u9fa5]*?大学|[\“\”\《\》\u4e00-\u9fa5]*?学院|[\“\”\《\》\u4e00-\u9fa5]*?学校)',page_data)
最后把找出来的这三种名称集中在一起,通过集合(set)的或操作(|=)可以有效的去除重复的名字,代码如下
names = new_names+original_names+first_name if len(names) != 0: schools.setdefault(school,set()) for name in names: schools[school] |= {name}
其中’schools.setdefault(school,set())’是指为schools这个字典创建一个item,key是school即我们要正在操作的这个学校名称,value是一个空的集合(set),这个集合再通过‘schools[school] |= {name}’把刚刚在百科上找到的那些名字都收揽了进来。
如果以上三句正则表达式中都找不到任何相关信息,即代码中的’names’为空,那么我们知道在百科中关于这个学校找不到任何“更名”、“原名”的信息,我们把这个学校认为是没改名过、确定是新增的高校。因此在’else’里面进行操作,如下
else: print (school,'从未更名') schools_not_changed.append(school)
把他打印出来,并添加到另一个列表中。至此该函数基本完成。
2、撸compare函数
如果从上一步得到的字典中拿出一个一个学校,比如’漳州卫生职业学院’,它的所有名字是这样的:
再从原数据中拿出一个学校名称,比如’漳州医学护理职业学院’,然后通过’in’这个语句分别判断’漳州医学护理职业学院’是否在'漳州卫生学校', '日福建省人民政府批准同意将漳州医学护理职业学院', '漳州卫生职业学院'这三个里面出现。
for school_new_name in schools_new_dict[school_new]: if school_original in school_new_name: print ('one school be found:',school_original,'->',school_new,'\n') schools_changed.append((school_original,school_new))
我们可以清楚看到,原名称’漳州医学护理职业学院’出现在名称集合的'日福建省人民政府批准同意将漳州医学护理职业学院'中。说明’漳州医学护理职业学院’与’漳州卫生职业学院’有一腿,即是同个学校的更名前后的两个名称。
这里用上了两个小变量来做标记。分别是reName和repeat。还是上面的例子,reName是指当’漳州医学护理职业学院’在'漳州卫生学校', '日福建省人民政府批准同意将漳州医学护理职业学院', '漳州卫生职业学院'的其中某一个出现的时候,reName标记为1,表示原始数据在新增的100个高校的其中某一个(即’漳州卫生职业学院’)找到了重名;而repeat是指,档’漳州医学护理职业学院’不仅在’漳州卫生职业学院’的名称集合中找到了重名,还可能在其他的100个高校名称比如‘广东开放大学’、‘广东南方职业学院’等这些学校里的名称集合也找到了重名,这种情况出现的时候,表明这几所大学可能是合并过,或者是其他更复杂的情况,应引起重视,于是我们找到repeat大于1的学校时应把它“抓”出来并且最终人工查阅、排除一下。
最终compar()函数函数代码如下
def compare(school_original_list,schools_new_dict): schools_changed = [] schools_repeat = [] for school_original in school_original_list: repeat = 0 for school_new in schools_new_dict: reName = 0 for school_new_name in schools_new_dict[school_new]: if school_original in school_new_name: reName = 1 if reName == 1: print ('one school be found:',school_original,'->',school_new,'\n') repeat += 1 schools_changed.append((school_original,school_new)) if repeat > 1: print ('\t',school_original,' repeat ',repeat,'\n') schools_repeat.append(school_original) return schools_changed,schools_repeat
3、其他函数
其他辅助性函数非常简单。
get_url()函数构建url。通过观察可以发现,每个高校的词条可由这样的结构组成:
’http://baike.baidu.com/item/’+’学校名称’
浏览器友好的把’ 万博科技职业学院’显示了出来,但在实际的url中这些中文是经过编码了的,实际的url应该是:
http://baike.baidu.com/item/%E4%B8%87%E5%8D%9A%E7%A7%91%E6%8A%80%E8%81%8C%E4%B8%9A%E5%AD%A6%E9%99%A2
可以看到‘%’后面一大堆东西就是中文编码后对应的符号,我们要提交如上格式的代码。
这里使用了urllib库的urllib. parse功能来编码中文、构建url,用法是:
‘parse.quote(word)’,其中word正是你想编码的中文内容。
最终代码如下
def get_url(word): url = 'http://baike.baidu.com/item/{}'.format(parse.quote(word)) return url
get_page()函数爬取页面。这里利用了requests这个第三方库。用自带的urllib也是可以的,只是requests用熟手了。
def get_page(url): crawler = requests.session() page = crawler.get(url).content page_data = page.decode() return page_data
get_table()主要读取Excel表格。也是利用了第三方库xlrd,使用非常简单。
def get_table(): data = xlrd.open_workbook('schools.xls') table = data.sheets()[0] nrows = table.nrows ncols = table.ncols return table,nrows,ncols
这里不仅返回了整个表,还返回了表的行数、列数等。
最后我们还可以写个函数,利用xlwt把更名了的学校的新名字自动写进Excel表格中。不过这里发现更名的其实不是特别多,且时间有限,暂时没有动手。那么全部代码基本到处为止就完成了。
改进空间
这代码其实很简单,也有不少缺陷。
一个缺陷就是各种情况没有考虑完全,比如有些百科页面是会出现‘<…>XX学院<…>更名为…’之类的想不到的情况,其中<>表示HTML语言,可能是为了给这个学校加粗、加颜色或者添加链接等;也比如整个页面没出现“更名”、“原名”这两个字眼,但是通过“创立”、“合并为”这些类似的字眼来说明这个学校的更名历史,这也是该程序暂时没能识别的。
改进的话要再考虑种种可能的情况,甚至可以尝试使用机器学校的方法来识别。这些就等有机会再慢慢改或者等向他人学习之后再来动工吧。