目的:将网页上的表格获取出来。
我们采用新冠肺炎的数据举例,网址如下:
https://www.worldometers.info/coronavirus/
浏览该网页后,我们想获取下图的表格数据。
首先引入用于正则表达式的 RE 包或用于获取网页信息的 urllib 包。
import reimport urllib
接下来从网址中读源代码并转成字符串需要以下三步:
完整的代码如下:
link = "https://www.worldometers.info/coronavirus/"f = urllib.request.urlopen(link)myfile = f.read()info = myfile.decode("utf-8") print(info)
但是这个字符串太长了,我无法找到从 info 字符串里找到上面 Table 源代码所在的地方。这时我们找网页源码来看,操作有三步:
整个操作如下面动图所示:
第一步 - 获取整个 Table 的字符串
锁定好位置,其实我们只需知道表示 Table 代码的首尾的若干字符即可,以 <table 开始,以 </table> 结束,中间无数个(可用 * 表示)字符(可用通配符 . 表示)。
定义其模式 pat 如下,并用 findall 获取整个 Table 的字符串,返回是个列表,索引 0 位置的字符串。
pat = r'<table.*</table>'table_str = re.findall(pat, info)table_str[0]
该字符串还是很长,但至少已经缩减到 Table 层面了,Table 无非就是由若干行组成的嘛,让我们把注意力放在每行的代码上。
第二步 - 获取 Table 中每行的字符串
细看一下,我们发现一个规律,即每行代码以 <tr> 开始,以 </tr> 结束,如下图所示。
那定义其模式就简单了,r'<tr.*?</tr>',关键点是这个问号 ?,它代表是非贪婪模式匹配,即以尽可能少的方式来匹配,这样我们就可以把 Table 中多行就找出来。代码如下:
row_pat = r'<tr.*?</tr>'row_str = re.findall(row_pat, table_str[0])print( len(row_str) )row_str
返回的结果是一个包含 128 个元素的列表(表示这个 Table 有 128 行),接下来就需要把 Table 每一行的元素一一取出。
第三步 - 获取每行字符串中的各种信息
我们来看看表格,发现所有行分三种模式:
再看这三种类型的行对应的源代码
第一行
中间行
最后一行
设计他们的模式,并用 compile 函数创建带特定模式的对象。
first_row_pat = r'<th width=.+?>(.+?)<br />(.+?)</th>'mid_row_pat = r'<td style=.+?>(.+?)</td>'last_row_pat = r'<td>(?:<strong>|)(.+?)(?:</strong>|)</td>'
first_row_obj = re.compile(first_row_pat)mid_row_obj = re.compile(mid_row_pat)last_row_obj = re.compile(last_row_pat)
将每行获取出来的元素存在列表中,起名为 table。
table = []
for i, row in enumerate(row_str): if i == 0: table.append(first_row_obj.findall(row)) elif i == len(row_str)-1: table.append(last_row_obj.findall(row)) else: table.append(mid_row_obj.findall(row))table
结果基本正确,有三个问题:
我们要进一步处理以上三个问题,首先处理超链接和斜体(第 2 和 3 问题),代码如下:
pat1 = r'<a.+?>(.+?)</a>'pat2 = r'<span.+?>(.+?)</span>'
table1 = table.copy()for i, row in enumerate(table): if i !=0 and i !=len(table1)-1: if row[0].startswith(' <a'): table1[i][0] = re.findall(pat1, row[0])[0] elif row[0].startswith(' <span'): table1[i][0] = re.findall(pat2, row[0])[0] else: table1[i][0] = table1[i][0].strip()table1
最后将结果转换成数据帧(DataFrame),用 Pandas。
第四步 - 整理成 DataFrame
先引入 Pandas 包,并把 table1 转成 DataFrame。
import pandas as pd
df = pd.DataFrame(table1)df
结果无敌难看,有两点要改进:
先搞定行标签。
df = df.set_index(0)df
再搞定列标签。
new_header = df.iloc[0]df = df[1:]df.columns = new_headerdf
看起来完美,除了左上角有个讨厌的 (Country, Other) 和 0,它们分别是列标签名称和行标签名称,改成自己喜欢的就行。
df.index.name = 'Region'df.columns.name = ''df
现在看起来真的完美了,等等,列标签里还有讨厌的 ‘% ',打印出来看看:
names = [i[0]+' '+i[1] for i in df.columns.values]names
['Total Cases',
'% of All Cases',
'New Cases',
'Total Deaths',
'New Deaths',
'Total Recovered',
'New Recovered',
'Active Cases',
'Serious, Critical',
'Death Rate',
'Tot Cases/ 1M pop']
将 ‘% ' 换成空白符就行了。
names = [name.replace(' ', ' ') for name in names]names
['Total Cases',
'% of All Cases',
'New Cases',
'Total Deaths',
'New Deaths',
'Total Recovered',
'New Recovered',
'Active Cases',
'Serious, Critical',
'Death Rate',
'Tot Cases/ 1M pop']
再更新 DataFrame 的列标签。