Python爬虫-暨大考研报录比

关于学习爬虫这个事情,我和它的缘分起源于某位讨厌的老师的期末项目作业,那是个为了开拓学生自学能力的好老师嗯。emmmmm于是在掉了三天头发后,我还要被迫来写博客,感觉也是一种奇妙的体验,算是达成了“人生第一次写博客”成就吧,nice。

那么现在我来share一下为了python爬虫我这三天是如何学习的吧头发是如何掉的吧:( 

关于python爬虫,首先要了解的当然是python了,python都不会,就别提爬不爬的了。现在主要是python3比较流行,我学习所用的IDE JetBrains PyCharm 2017.1 x64也是python3 的。

至于python怎么学,诶这个。。。。当然是看书了,一本python3砖块书看完,绝对受益匪浅:)

好吧如果是看不下砖块书的话,而且你的小白指的是“有着其他语言的基础,只是没接触过python”这样的程度,那么,实用派推荐慕课网教程、廖雪峰教程、百度云盘里各种教程。我个人是在这里学的:慕课网:Python入门-廖雪峰

基本上半天到一天就可以刷完啦,至于填鸭式不填鸭式的就自己判断咯,我觉得我接受能力还可以的。不过这个是python2 的教程,不过都是相同的啦,就是有些地方可能不太一样,比如print后面的内容在pycharm里运行一定要加括号。。。。如果介意的话,就自己去各大程序员课程网站找一找咯,建议一边看教程一边在自己电脑的环境里运行一下。下面来开始说说爬虫。

首先,爬虫是什么?

        爬虫全程网络爬虫,又名网络蜘蛛。蜘蛛,顾名思义啊,就是沿着网爬爬爬,然后抓取在网上的食物。好比互联网就是一张巨大的蜘蛛网,它由许多张小网结成。然后网上的信息就相当于蜘蛛网上的苍蝇啊蚊子啊什么的。[这比喻好像有点奇怪但是很实际真的。。。不信你去研究一下蜘蛛网:)] 而我们要爬取的信息,就是这只蜘蛛,准备要吃什么了。

 

那么问题来了,我们怎么描述食物?这就涉及到我们平常浏览的网页了。

在用户浏览网页的过程中,我们可以看到形形色色的直观的信息比如价格啊,图片啊,账号啊,音频啊这些。这个过程其实就是用户输入网址之后,经过DNS服务器,找到服务器主机,向服务器发出一个请求,服务器经过解析之后,发送给用户的浏览器 HTML、JS、CSS 等文件,浏览器解析出来,用户便可以看到形形色色的图片了。

换句话说,我们看到的网页其实就是翻译过的代码:) 网页实质是由 HTML、CSS、JavaScript 代码构成的,HTML就好比苍蝇的种族、名字、结构什么的,css就是它的emmmmm颜色啊个头大小啊这些,JavaScript实现的就是这只苍蝇是横着飞还是竖着飞还蹦着来这些动作的事情。爬虫爬来的主要就是HTML中的东西,通过分析和过滤这些 HTML 代码,实现对图片、文字等资源的获取。

 

 

讲到这里,就再说一下网这个东西。每张网都有属于它的属性和地址,叫做URL。

 

 

URL,即统一资源定位符,也就是我们说的网址。下面贴一个百度来的内容:

URL的格式由三部分组成:
①第一部分是协议(或称为服务方式)。
②第二部分是存有该资源的主机IP地址(有时也包括端口号)。
③第三部分是主机资源的具体地址,如目录和文件名等。

爬虫爬取数据时必须要有一个目标的URL才可以获取数据,可以说,它是爬虫获取数据的基本依据。

总之呢,要爬哪张网,就复制那张网的URL,正常就是那个东西的这里:

下面演示一下用python爬取百度首页:

 

import urllib.request

response = urllib.request.urlopen("http://www.baidu.com")
print(response.read())

然后,你会看见输出结果长这个样子:

是的,如果你没学过HTML,那么,这对你来说就是乱码:) (还好上学期我认真学了!)emmmmm如果没学过呢也没关系,还是那个办法,慕课网,搜索HTML基础教程:)

 

这里可以戳-->慕课网:HTML+CSS基础课程

 

这里重点还要关注一下form标签里的get属性和post属性,嗯大概也是半天到一天就可以了解那些基础标签了:)

 

 

Urllib库

 

 

 

如上有用到一个import urllib.request ,这就要提到爬虫的必备,urllib库了。这是爬取网站的第一步。

关于这个库,python 3.X版本是不需要安装urllib2包的,urllib和urllib2包集合成在一个包了,叫urllib.request,直接import 它,然后就可以调用它的诸多方法啦~

 

首先要用到的是Request它是URL请求的抽象类,主要作用是构造一个请求对象,用于发送网络请求,返回响应数据。实例如:request = urllib.request.Request("http://www.baidu.com")

 

 

 

 

它的作用其实就是在模拟我们正常上网的过程:输入网址-->经过DNS服务器-->找到服务器主机-->向服务器发出一个请求。在构建请求时还需要加入好多内容,通过构建一个request,服务器响应请求得到应答,返回给对象。(在我的理解里通俗点讲大概就是模拟我们上网搜索网址后敲下的回车键功能。)

请求完毕之后呢,要做的就是打开这个URL,向指定的url发送请求,然后返回服务器响应的类文件对象。

 

 

 

 resp=urllib.request.urlopen("request")用的就是这个:urlopen方法

urlopen在我的理解其实就是一个方法函数,调用它呢可以实现打开目标URL,返回赋值给一个对象,将从目标URL里返回的信息存放进去。它的参数分别为:

    url:要获得的目标URL,没有默认值,必须手动赋值传送;

    data:访问URL时要传送的数据,一般默认为空none,可省略传送;

    timeout:设置超时时间,也有默认值socket._GLOBAL_DEFAULT_TIMEOUT,同样的,可省略传送。

好了现在已经取得了装有所要的网页信息的resp这个对象了,如果想看resp这个对象里获得了什么信息怎么办呢?

print(response.read().decode('utf-8'))

 

这里就又涉及到response对象了。它有多个方法,这里要讲的是主要是read这个方法,显而易见,就是读取文件内容,然后返回HTML字符串类型。为了防止卡住,建议最好设置timeout参数。

里面还有一个decode() 方法,它是以 encoding 指定的编码格式解码字符串。默认编码为字符串编码。为了防止获得的信息与网页的编码不同,所以我们调用一下它,将获得的文件内容指定为utf-8编码,指定后打开就不会出现上面那种返回了很多看不懂的数据如十六进制的奇奇怪怪的东西,如果不知道编码可以去下载一个chardet库,使用方法网上有很多的,写的也很好,就不赘述了。主要我觉得没必要毕竟网页大多都是utf-8编码啦~如果不是utf-8的话,可以打开网页代码(右键单击会出现一个列表,最下面有个检查,点击它就会出现网页的源代码啦),如何找到head标签。再然后,找到meta标签里的content属性的charset。如图为gb2312:

然后print打印它一下就可以得到该网页的HTML代码啦,如图:    

如果想高级一点保证一下安全性,就调用urllib中的urllib.error模块再加一个异常判断。

 

 

 

 

URLError一般是因为没有网络连接或者server不存在。这种情况下,会产生一个reason属性,是一个tuple,包含了错误码和错误文本

URLError产生的原因主要有网络无连接,即本机无法上网;连接不到特定的服务器;服务器不存在。

 

URLError还有一个子类,HTTPError,即当你利用urlopen发出一个请求时,服务器会对应一个应答对象response,包含一个“状态码”。而其它不能处理的,urlopen会产生一个HTTPError,对应相应的状态码,比如我们数值的204,404,503以及其它编码(有兴趣自行百度),这些状态码表示HTTP协议所返回的响应的状态。

用python中的try-except来捕获相应的异常,代码如下:
try:
        response = urllib.request.urlopen(request, timeout=15)    # 指定超时秒数,在规定时间内不返回结果,直接raise timeout
        print(response.read().decode('gb2312'))
    except urllib.request.HTTPError as e:
        print(e)
    except urllib.request.URLError as e:
        print (e)

       

       以上的输出仅仅只是把整个页面的HTML源代码输出来了,并没有为我们进行筛选那么这和我们直接在网页上按f12查看元素又能有什么区别呢?手动摊手)所以接下来我要说的就是如何筛选出我们想要的信息。

emmmmm由于我有个同学志向远大,励志于考研究生,当得知我要在期末做一个爬虫的时候她“哈哈哈哈哈哈哈哈哈哈哈”了半天然后要我给她爬各高校的考研报录比:)我翻了白眼然后怂怂的开始浏览起了各大高校考研网。然后又因为水平有限,我决定就先爬一个高校就可以了嗯。于是呢

百度中国考研网,找到报录比:

打开它,拖下去乌拉拉一堆高级院校(确认过眼神,都是我上不起的。。。)然后找到在茫茫学校里一眼看中暨南大学

点击它然后会发现自己进入了考研网里属于暨南大学的信息网

2017年暨南大学考研报录比,戳进去,看看上方的网址,那就是我们要的url

http://www.chinakaoyan.com/graduate/info/nid/208123/schoolID/398.shtml

然后就是一个考研信息列表,我们要提取的信息就是表格里的学院、专业、报考人数、上线人数、录取人数。知道要的是什么信息后,右击学院单元格选择的检查(HTML熟悉的可以直接查看网页源代码):

 然后接下来就是分析他的特性,然后提出我们想要的那个值,怎么提取,下面就来讲一下:

正则表达式

关于正则表达式,它的作用就是对字符串操作的一种逻辑公式,即事先定义好一些特定字符组合成一个“规则字符串”来对字符串进行一种过滤,提取出其中所需要的特定值。

换句话说,就是匹配对应的字符。它有特定的语法规则,具体的我个人是在网上自学的,菜鸟教程和有些博客写的都很详细的,以下是链接

>>Python爬虫入门七之正则表达式- 崔庆才

 

大概了解一下正则表达式语法后,我们代入这个代码的分析。

首先这个HTML中,肯定只有一个title标签,那么我们就可以通过正则表达式来返回这个标签夹起来的那个标题:

<title>(.*?)</title>

中间那个(.*?)就代表我们要的那个值啦。

里面涉及的re后面会讲。

好了继续看我们的网页代码,可以看到这个表格几乎都是td标签,所以不能像标题一样直接用标签来提取,所以我们要找到一个特殊的值。又因为,这里头的td标签几乎都一模一样,没有类名,各种特殊性都没有,可以说是很简洁了,简洁到找不到特殊的。。。。所以没有条件,我就只好自己创造特殊条件了。。。。

于是我就想,把学院这个属性作为我们想要提取的信息的索引(就是我希望我可以输入一个学院名称然后返回他的专业啊报考人数啊什么的),把学院作为突破点,将设置一个变量用来接受这个输入值,然后把这个变量放入正则表达式中,让它变得“特殊”起来。

print('请输入需要查询的学院全称(注意:不可出现错别字):')
xueyuan_input = input()#获取想查询的学院
正则表达式就可以表示为这样:
<td>'+xueyuan_input+'</td>

然后后面的专业,即学院的td标签之后的第二个td标签,表示为:

<td>'+xueyuan_input+'</td>.*?<td>.*?</td>.*?<td>(.*?)</td>

由于td标签在HTML中是过行的,而且,还有缩进,所以</td>和<td>中间要加上一个.*?或者.s+,前者如果导入re模块再添加上re.S,就几乎可以匹配所有字符串了。包括换行啊空格字符等。

之后的几个属性以此类推,就不列举了。

下面讲一下re模块

Python Re 模块

这是个python自带的模块,它提供的是对正则表达式的支持,有许多个方法,上面正则表达式的那个链接里也有关于它的一些讲解,我就不一一列举他的方法,就讲一下常用的基本的方法,主要讲后面要用的。

re.compile(string[,flags])

compile方法,主要功能是返回一个对象pattern,pattern即一个匹配模式,可以理解为我们上面所说的正则表达式或者一个变量,为了获得这个模式,我们要compile一下它,就是这么简单。

后面的flag是可选参数,即选择匹配模式,取值可以使用按位或者运算符'|’表示同时生效。他的可选值有像re.I,re.L,还有上面提到的re.S,各有各的好用之处,同样的上面链接里有,不赘述。主要这里用的是re.S,主要是懒得打多点代码,为了方便复制粘贴匹配换行啦~

re.match(pattern,string[,flags])

这个方法就是从头开始匹配pattern了,如果无法匹配就返回none,匹配未结束就到达字符串的尾端,同样的,匹配失败,返回none。

也就是说,compile获取匹配模式后,它就是用这个匹配模式开始进行匹配的,它还有很多match这个对象的属性和方法,由于下面我没有用到,就不讲了吧。

re.search(pattern,string[,flags])

这个方法呢,与match很像,区别就是match只检测re是不是在字符串开始位置开始匹配,而search()则会扫描这个字符串进行查找。也就是说,好比一篇信件的阅读理解,同样想找‘hello’,match只能找到一开头就是‘hello’的那个,如果人家在前面写了个标题,它就只能否则返回none;而search,即便你加了标题,它也能扫描全篇阅读理解,可以找到标题后面的‘hello’。

re.findall(pattern,string[,flags])

这个函数,字面意思,很好理解,就是找到全部对应的‘hello’。也就是说不管是称呼里的‘hello’,还是后面正文里的:Tom say:"hello, Jerry."里的‘hello’,都可以被返回,然后以列表元素的形式存入。假设,把re.findall的返回赋给变量L,称呼里的‘hello’就是L[0],正文里的就是L[1]。我们可以用for循环依次输出它。

在后面的代码中,我几乎只用到了compile和findall。比如查询学院:

xueyuan = re.compile(r'<td>'+xueyuan_input+'</td>',re.S)#获取的学院输入正则表达式中匹配
xueyuan_name = xueyuan.findall(content)#在content中查找匹配的正则表达式
print (xueyuan_name)

呐,findall返回了所有出现经济学院的值。当然也可以把我上面的那个正则表达式里的td标签去掉,就不会返回多余的代码,而是一色清的‘经济学院’了。

 

介绍完re,来开始着手爬信息了

        专业和报考人数部分的匹配,也是基于这个xueyuan_name变量的。

#专业
zhuanye = re.compile(r'<td>'+xueyuan_input+'</td>.*?<td>.*?</td>.*?<td>(.*?)</td>',re.S)#专业不需要获取,其HTML中位于学院的标签后面
zhuanye_name = zhuanye.findall(content)#在content中查找匹配的zhuanye正则表达式
#报考人数
baokao = re.compile(r'<td>'+xueyuan_input+'</td>.*?<td>\d+</td>.*?<td.*?>\d+</td>.*?<td.*?>\d+</td>.*?<td.*?>\d+</td>.*?<td>(\d+)</td>',re.S)
baokao_num = baokao.findall(content)

运行一下:

看看HTML元素,然后发现苍了个天,代码不仅没有类名,还给你合并行了啊啊啊啊啊啊啊啊啊啊。所以由于合并了,后面的td个数不统一!不统一!!!

好了冷静,想一想,还是可以解决的。

首先,用range()设置一个zhuanye_name的长度的for循环:

1. 查找是否出现过rowspan这个属性,如果有,用变量x1来接受它返回的值!

2. 判断x1是不是空值,是就没问题了,完全不影响下面其它列的baokao_num值直接就结束啦;但是如果不是空值,就继续第三步

3. 把这个str的x1强制变成int来运算,用range(0,int(x1))来继续设置一个for循环,x2为计数值。

4. 修改出现了合并单元格的下一行的baokao_num值。

代码:

for q in range(0, len(zhuanye_name)):
        x = re.compile(r'<td>' + str(zhuanye_name[q]) + '</td>\s+<td rowspan=.(\d+).>.*?' + str(baokao_num[q]), re.S)
        x1 = x.findall(content)
        if len(x1):
            for x2 in range(1, int(x1[0])):
                x3 = re.compile(r'<td>' + zhuanye_name[q + x2] + '</td>.*?<td>(\d+).*?', re.S).findall(content)
                baokao_num[q + x2] = x3[0]

运行一下:

      

      除了以上专业和报考人数这种写法,还有另一种写法。

前面有说过,findall返回的其实是一个列表,那我们就定义一个空列表,然后设一个zhuanye_name, 或者baokao_num长度的循环,把前面的zhuanye_name, 或者baokao_num列表的每个元素都提出来,作为上线人数和录取人数里正则表达式里的特殊值,然后用列表的append方法将获取的值逐个加进去。

shangxian_num= []#设置一个列表存放上线人数
luqu = []#设置一个列表存放录取人数的数据
for i in range(0,len(xueyuan_name)):
    shangxian = re.compile(r'<td>' + zhuanye_name[i] + '</td>.*?<td>.*?</td>.*?<td>.*?</td>.*?<td>.*?</td>.*?<td>.*?</td>.*?<td>(\d+)</td>', re.S).findall(content)
    shangxian_num.append(shangxian[0])
    luqu_num = re.compile(r'<td>' + zhuanye_name[i] + '</td>.*?<td>\d+</td>.*?<td>(\d+)</td>\s+<td>\D+</td>\s+</tr>',re.S).findall(content)
    luqu.append(luqu_num[0])
    print(i+1,'学院:'+xueyuan_input+'  ','专业:'+zhuanye_name[i]+'  ','报考人数:'+baokao_num[i]+'  ','上线人数:'+shangxian_num[i]+'  ','录取人数:'+luqu[i]+'  ')

试试其它学院怎么样。。。。

既然这样,这里就可能是正则表达式错误,那么,改一下咯。比如改成这样:

shangxian = re.compile(r'<td>' + baokao_num[i] + '</td>.*?<td>(\d+)</td>', re.S).findall(content)

OK了!那么试试其它学院怎么样。

what??????这什么情况????反着来折腾我?????改之前,经济学院不行,文学院OK;改完,经济学院OK了,文学院那边完蛋了。。。这俩学院有什么区别,为什么反着来???

这个。。。我想了半天,大概是因为用baokao_num作为特殊值用在正则表达式里,对于经济学院这个处于这个列表第一位的来说是OK的,因为前面和baokao_num这个数值一样的很少见。而文学院在列表后面一点,就会出现匹配错误的情况。

但是,为什么用zhuanye_name这么唯一的特殊值用在正则表达式里对于经济学院就不能用呢?看上去001,像是匹配到了下一行列表的学院代码去了。。。。也就是可能td标签个数没有准确。可是为什么对于文学院又是精准定位的呢?

这个。。。。。我到最后也没想明白,但是不影响我改bug嗯: ) 我大可以用if来判断输入值是经济学院还是其它学院,如果是经济学院,那就用baokao_num嘛。

    shangxian = re.compile(r'<td>' + zhuanye_name[i] + '</td>.*?<td>.*?</td>.*?<td>.*?</td>.*?<td>.*?</td>.*?<td>.*?</td>.*?<td>(\d+)</td>', re.S).findall(content)
    shangxian_num.append(shangxian[0])
    if xueyuan_input == '经济学院':
        shangxian = re.compile(r'<td>' + baokao_num[i] + '</td>.*?<td>(\d+)</td>', re.S).findall(content)
        shangxian_num[i] = shangxian[0]

好了,最后一个问题,关于报录比。虽然人家叫2017暨南大学考研报录比,然而我压根没看见这个比在哪里了。。。。。

没关系,我们自己算,反正有报考人数值也有录取人数值,把字符串转为数值类型,除一下就可以了嘛~

最后附上全部代码:

import urllib.request
import re

print('请输入需要查询的学院全称(注意:不可出现错别字):')
xueyuan_input = input()#获取想查询的学院

request = urllib.request.Request("http://www.chinakaoyan.com/graduate/info/nid/208123/schoolID/398.shtml")
try:
    response = urllib.request.urlopen(request, timeout=20)    # 指定超时秒数,在规定时间内不返回结果,直接raise timeout
    content = response.read().decode('gb2312')
except urllib.request.HTTPError as e:
    print(e)
except urllib.request.URLError as e:
    print (e)
# 标题
title = re.compile(r'<title>(.*?)</title>').findall(content)
print('-------------------------' + title[0] + '--------------------------------')
#查询所有出现学院名的语句
xueyuan = re.compile(r'<td>'+xueyuan_input+'</td>',re.S)#获取的学院输入正则表达式中匹配
xueyuan_name = xueyuan.findall(content)#在content中查找匹配的正则表达式
#专业
zhuanye = re.compile(r'<td>'+xueyuan_input+'</td>.*?<td>.*?</td>.*?<td>(.*?)</td>',re.S)#专业不需要获取,其HTML中位于学院的标签后面
zhuanye_name = zhuanye.findall(content)#在content中查找匹配的zhuanye正则表达式
#报考人数
baokao = re.compile(r'<td>'+xueyuan_input+'</td>.*?<td>\d+</td>.*?<td.*?>\d+</td>.*?<td.*?>\d+</td>.*?<td.*?>\d+</td>.*?<td>(\d+)</td>',re.S)
baokao_num = baokao.findall(content)

for q in range(0, len(zhuanye_name)):
        x = re.compile(r'<td>' + str(zhuanye_name[q]) + '</td>\s+<td rowspan=.(\d+).>.*?' + str(baokao_num[q]), re.S)
        x1 = x.findall(content)
        if len(x1):
            for x2 in range(1, int(x1[0])):
                x3 = re.compile(r'<td>' + zhuanye_name[q + x2] + '</td>.*?<td>(\d+).*?', re.S).findall(content)
                baokao_num[q + x2] = x3[0]

shangxian_num= []#设置一个列表存放上线人数
luqu = []#设置一个列表存放录取人数的数据
for i in range(0,len(xueyuan_name)):
    luqu_num = re.compile(r'<td>' + zhuanye_name[i] + '</td>.*?<td>\d+</td>.*?<td>(\d+)</td>\s+<td>\D+</td>\s+</tr>',
                          re.S).findall(content)
    luqu.append(luqu_num[0])
    shangxian = re.compile(r'<td>' + zhuanye_name[i] + '</td>.*?<td>.*?</td>.*?<td>.*?</td>.*?<td>.*?</td>.*?<td>.*?</td>.*?<td>(\d+)</td>', re.S).findall(content)
    shangxian_num.append(shangxian[0])
    if xueyuan_input == '经济学院':
        shangxian = re.compile(r'<td>' + baokao_num[i] + '</td>.*?<td>(\d+)</td>', re.S).findall(content)
        shangxian_num[i] = shangxian[0]
    print(i+1,'学院:'+xueyuan_input+'  ','专业:'+zhuanye_name[i]+'  ','报考人数:'+baokao_num[i]+'  ','上线人数:'+shangxian_num[i]+'  ','录取人数:'+luqu[i]+'  ','报录比:',int(baokao_num[i])/int(luqu[i]))

检验一下运行结果:

经济学院OK~~~~心情忐忑的再测试一下文学院:

nice!文学院OK!再看看其他学院怎么样:

啊哈哈哈哈哈哈哈哈哈哈哈哈终于完成作业了!happy ending!

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值