爬取2020软科中国大学排名
爬取2020软科中国大学排名

爬取2020软科中国大学排名

1.前言

因为这几天在学爬虫相关知识,正好课程有个实例,爬取软科中国大学排名,然后以表格形式输出,但是因为是前几年的课程,实例对应的网页源代码也不一样了,正好拿来练练手,提高实战能力。


2.案例分析

2.1.分析目标站点

首先,爬取站点要遵守robots协议,而查看后发现本站并没有相应的robots协议,而我们爬取时只进行了一次get请求,与用户行为相似,并不会对服务器造成影响,所以并不用考虑这一点,加上此站资源公开,并未加入反爬措施,所以难度不会太大。

先给出目标站点url地址:

https://www.shanghairanking.cn/rankings/bcur/2020

打开后如下:

方框及向下区域即要爬取的目标区域

找到目标区域后我们单击鼠标右键选择查看网页源代码,在源代码中找到目标区域的源代码,显然这是一个表格,所以我们寻找<tr><td>标签所在位置

这里要说一下,由于第一行的排名,学校名称,省市等内容是固定的已知元素,所以并不需要爬取,可以直接用格式化字符串输出为表格第一行,这样同时也可以为后面爬取的数据的排序做一个定位基准

可以看到,每个区块以序号1,2…开头,一个区块包含多个<td>标签,以</tr>结束,对应原表单的一行

由于我们爬取时进行查找是从上向下的,所以我们要找到tr标签的父元素,由于不考虑首行<th>,而又因为序号1是第一行第一个单元格,所以我们在序号1的上一行进行查看,即源代码图中第40行,向后拉取,在行末查看标签头

可以看到tr标签之前是tbody标签

前面都是以</…>结尾说明是上一个区块的结尾,下面就是下一个区块的开头,而tr是一行,td是一行内的一列,也就是一个单元格,所以<tbody>就是tr标签的父元素

翻到源代码尾部,同样可以看到tbody的闭合标签,验证我们的想法正确

分析完网页结构,就可以来写代码了。


2.2.代码分析

先调用requests库和bs4库中的BeautifulSoup

  1. from bs4 import BeautifulSoup
  2. import requests

然后用requests库对网页进行爬取,用Beautifulsoup对HTML进行解析

  1. r = requests.get("https://www.shanghairanking.cn/rankings/bcur/2020")
  2. r.encoding = r.apparent_encoding    //使网页按照自己的编码方式进行解码
  3. html = r.text
  4. soup = BeautifulSoup(html, "html.parser")

在进行数据解析前我们先格式化输出表头,每个单元格都采用居中对齐的方法

  1. print("{:^5}\t{:^10}\t{:^5}\t{:^5}\t{:^5}\t{:^5}".format("排名","学校名称","省市","类型","总分","办学层次"))

由于在进行数据分析的时候我们需要一个列表来存储数据,所以我们先在外部定义一个列表ls

  1. ls = []

然后我们对HTML里需要的数据进行查找,先使用一个for循环遍历tbody子代的每一个tr标签,同时我们设置一个计数器cnt对遍历次数计数

  1. for tr in soup.find('tbody').children:
  2.     cnt = 0

再嵌套一个循环,遍历tr标签内每一个td标签,然后用.contsents把td标签的内容以列表形式输出

  1. for td in tr:
  2.     a = td.contents[0]

为什么我们只输出td返回的列表内的第一个元素呢,用print(td.contents)就会知道它的完整输出结果是这样的

通过观察可以发现,有效信息都在第一个元素,所以我们只输出第一个元素

到这里可能大家已经发现,现在有两个问题:1.如何提取列表中的有效信息2.对于含有<a>链接标签的信息该如何提取。那我们先来解决第一个问题

由于还没学正则表达式,所以对于标签内的元素我还不能直接提取,但是我也不会这么轻易放弃,我又想到可以用.split()函数或者.replace()函数对元素进行再划分,观察发现,如果使用.split(” “),利用空格划分,得到的列表需要删除无效数据后,再进行一次划分。

而如果使用.replace(“\n”,””),再使用一次.replace(” “,””),把无效元素替换成空,这样两次替换就可以直接得到有效数据,所以我们选择.replace()方法。

同时我们将得到的数据存入ls列表内,最后也是通过列表输出

  1. b = a.replace("\n", "")
  2. ls.append(b.replace(" ",""))

接着我们来解决第二个问题,如何处理<a>标签?

对于<a>标签,我们可以看到有效数据在标签内部,所以我们使用.string方法获取字符串内容。

  1. ls.append(a.string)

通过观察可以发现,每一行有6个td标签,而a标签总是在第2个td标签里,如果我们一次性将所有数据存入列表,再一次性输出,那我们就要构造数列来判断a标签在所有数据中的位置,这样就让问题复杂化了。

所以为了简化问题,我们可以采用存入一行输出一行的方法,这样a标签永远是在第二个位置。在向列表存入数据前,我们使用if语句,判断当前位置是否为1(列表从0开始计数),不是就正常处理数据,是就采用对<a>标签的处理办法。然后我们让cnt+1,记录当前位置。

  1. if cnt != 1:
  2.     b = a.replace("\n", "")
  3.     ls.append(b.replace(" ",""))
  4. else:
  5.     ls.append(a.string)
  6. cnt += 1

在遍历完一个<tr>标签后,我们直接输出当前tr标签的有效内容,然后将ls列表清空,准备进行下一次遍历,下一次遍历开始时,循环头部的cnt=0会让cnt重新开始计数

  1. print("{:^5}\t{:^10}\t{:^5}\t{:^5}\t{:^5}\t{:^5}".format(ls[0],ls[1],ls[2],ls[3],ls[4],ls[5]))
  2. ls.clear()

这样我们就爬取成功了,21行代码就可以把所有数据爬取下来,最后附上完整代码和运行结果

  1. from bs4 import BeautifulSoup
  2. import requests
  3.  
  4. r = requests.get("https://www.shanghairanking.cn/rankings/bcur/2020")
  5. r.encoding = r.apparent_encoding
  6. html = r.text
  7. soup = BeautifulSoup(html, "html.parser")
  8. print("{:^5}\t{:^10}\t{:^5}\t{:^5}\t{:^5}\t{:^5}".format("排名","学校名称","省市","类型","总分","办学层次"))
  9. ls = []
  10. for tr in soup.find('tbody').children:
  11.     cnt = 0
  12.     for td in tr:
  13.         a = td.contents[0]
  14.         if cnt != 1:
  15.             b = a.replace("\n", "")
  16.             ls.append(b.replace(" ",""))
  17.         else:
  18.             ls.append(a.string)
  19.         cnt += 1
  20.     print("{:^5}\t{:^10}\t{:^5}\t{:^5}\t{:^5}\t{:^5}".format(ls[0],ls[1],ls[2],ls[3],ls[4],ls[5]))
  21.     ls.clear()
这里只截取了前30条,全部数据有567条