上面的输出显示每一行都是用嵌入在每一行中的html标记打印出来的。这不是我们想要的。可以使用Beautiful Soup或正则表达式删除html标记。
str_cells = str(row_td)
cleantext = BeautifulSoup(str_cells,"lxml").get_text()
print(cleantext)
[14TH,INTEL TEAM M,04:43:23,00:58:59 - DANIELLE CASILLAS,01:02:06 - RAMYA MERUVA,01:17:06 - PALLAVI J SHINDE,01:25:11 - NALINI MURARI]:正则表达式才是高手风范)。
我们导入re(用于正则表达式)模块,下面的代码展示了如何构建一个正则表达式,该表达式查找< td > html标记中的所有字符,并为每个表行替换为空字符串。
首先,通过传递与re.compile()匹配的字符串来编译正则表达式。点、星号和问号(.*?) 将匹配一个开头的尖括号,后面跟着任何东西,后面跟着一个结尾的尖括号。它以非贪婪的方式匹配文本,也就是说,它匹配尽可能短的字符串。
如果省略问号,它将匹配第一个开始尖括号和最后一个结束尖括号之间的所有文本。
编译正则表达式后,可以使用re.sub()方法查找正则表达式匹配的所有子字符串,并用空字符串替换它们。
下面的完整代码生成一个空列表,提取每一行html标记之间的文本,并将其附加到指定的列表中。
import re
list_rows = []
for row in rows:
cells = row.find_all('td')
str_cells = str(cells)
clean = re.compile('<.*?>')
clean2 = (re.sub(clean,'',str_cells))
list_rows.append(clean2)
print(clean2)
type(clean2)
[14TH,01:25:11 - NALINI MURARI]
str
下一步是将列表转换为dataframe并使用panda快速查看前10行。
df = pd.DataFrame(list_rows) df.head(10)
0
0 [Finishers:,577]
1 [Male:,414]
2 [Female:,163]
3 []
4 [1,814,JARED WILSON,M,TIGARD,OR,00:36:21...
5 [2,573,NATHAN A SUSTERSIC,PORTLAND,...
6 [3,687,FRANCISCO MAYA,00:3...
7 [4,623,PAUL MORROW,BEAVERTON,00:38:...
8 [5,569,DEREK G OSBORNE,HILLSBORO,00...
9 [6,642,JONATHON TRAN,00:39...
数据处理和清理
dataframe不是我们想要的格式。为了清理它,您应该在逗号位置将“0”列分割为多个列,通常我们使用str.split()方法实现的。
df1 = df[0].str.split(',',expand=True)
df1.head(10)

这看起来清爽多了,但还有工作要做。dataframe的每行周围都有不需要的方括号。可以使用strip()方法删除列“0”上的左方括号。
df1[0] = df1[0].str.strip('[')
df1.head(10)

上表缺少表标头。您可以使用find_all()方法获得表标头。
col_labels = soup.find_all('th')
与处理表类似,您可以使用Beautiful Soup提取表标题的html标记之间的文本。
all_header = []
col_str = str(col_labels)
cleantext2 = BeautifulSoup(col_str,"lxml").get_text()
all_header.append(cleantext2)
print(all_header)
['[Place,Bib,Name,Gender,City,State,Chip Time,Chip Pace,Gender Place,Age Group,Age Group Place,Time to Start,Gun Time,Team]']
然后,您可以将标题列表转换为pandas dataframe。
df2 = pd.DataFrame(all_header) df2.head()
0
0 [Place,Chip T...
类似地,您可以在所有行的逗号位置将“0”列分割为多个列。
df3 = df2[0].str.split(',expand=True)
df3.head()

frames = [df3,df1]
df4 = pd.concat(frames)
df4.head(10)

下面显示了如何将第一行分配为表标头。
df5 = df4.rename(columns=df4.iloc[0])
df5.head()

至此,这个表的格式几乎完全正确。对于分析,您可以从以下数据的概述开始。
df5.info()df5.shape
Int64Index: 597 entries,0 to 595
Data columns (total 14 columns):
[Place 597 non-null object
Bib 596 non-null object
Name 593 non-null object
Gender 593 non-null object
City 593 non-null object
State 593 non-null object
Chip Time 593 non-null object
Chip Pace 578 non-null object
Gender Place 578 non-null object
Age Group 578 non-null object
Age Group Place 578 non-null object
Time to Start 578 non-null object
Gun Time 578 non-null object
Team] 578 non-null object
dtypes: object(14)
memory usage: 70.0+ KB
(597,14)
该表有597行和14列。您可以删除所有缺少值的行。
df6 = df5.dropna(axis=0,how='any')
另外,请注意如何将表头复制为df5中的第一行。 可以使用以下代码行删除它。
df7 = df6.drop(df6.index[0]) df7.head()

您可以通过重新命名'[Place' and ' Team]'列来执行更多的数据清理。Python对空格非常挑剔。确保在“Team]”中在引号之后加上空格。
df7.rename(columns={'[Place': 'Place'},inplace=True)
df7.rename(columns={' Team]': 'Team'},inplace=True)
df7.head()

最后的数据清理步骤包括删除“Team”列中的单元格的右括号。
df7['Team'] = df7['Team'].str.strip(']')
df7.head()

到这里为止我们花了一段很长时间,也得到了我们想要的dataframe。现在您可以进入令人兴奋的部分,开始绘制数据并计算有趣的统计数据。
数据分析和可视化
首先要回答的问题是,跑步者的平均完成时间(以分钟为单位)是多少?您需要将列“Chip Time”转换为几分钟形式。一种方法是首先将列转换为列表进行操作。
time_list = df7[' Chip Time'].tolist()# You can use a for loop to convert 'Chip Time' to minutestime_mins = []for i in time_list: h,m,s = i.split(':')
math = (int(h) * 3600 + int(m) * 60 + int(s))/60 time_mins.append(math)#print(time_mins)
下一步是将列表转换回dataframe,并立即为跑步者 Chip Time 创建一个新的列(“Runner_mins”)。
df7['Runner_mins'] = time_mins
df7.head()

下面的代码显示了,在dataframe中计算数字列的统计信息。
df7.describe(include=[np.number])
Runner_mins
count 577.000000
mean 60.035933
std 11.970623
min 36.350000
25% 51.000000
50% 59.016667
75% 67.266667
max 101.300000
有趣的是,所有跑步者的平均chip time 是大约60分钟。最快的10K跑者跑完36.35分钟,最慢的跑者跑完101.30分钟。
boxplot是另一个有用的工具,用于可视化汇总统计信息(最大值、最小值、中等值、第一四分位数、第三四分位数,包括异常值)。下面是在箱线图中显示的跑步者的数据汇总统计数据。为了实现数据可视化,可以方便地首先从matplotlib附带的pylab模块导入参数,并为所有图形设置相同的大小,以避免为每个图形设置相同的大小。
from pylab import rcParams
rcParams['figure.figsize'] = 15,5
df7.boxplot(column='Runner_mins')
plt.grid(True,axis='y')
plt.ylabel('Chip Time')
plt.xticks([1],['Runners'])
([],)

要回答的第二个问题是:跑者的完成时间是否服从正态分布?
下面是使用seaborn库绘制的跑步者chip times分布图。分布看起来几乎是正常的。
x = df7['Runner_mins']
ax = sns.distplot(x,hist=True,kde=True,rug=False,color='m',bins=25,hist_kws={'edgecolor':'black'})
plt.show()

第三个问题是关于不同年龄段的男性和女性是否有表现上的差异。
下面是男性和女性芯片时间的分布图。
f_fuko = df7.loc[df7[' Gender']==' F']['Runner_mins']
m_fuko = df7.loc[df7[' Gender']==' M']['Runner_mins']
sns.distplot(f_fuko,hist_kws={'edgecolor':'black'},label='Female')
sns.distplot(m_fuko,hist=False,label='Male')
plt.legend()

这一分布表明女性的平均速度比男性慢。您可以使用groupby()方法分别计算男性和女性的汇总统计信息,如下所示。
g_stats = df7.groupby(" Gender",as_index=True).describe()print(g_stats)
Runner_mins
count mean std min 25% 50%
Gender
F 163.0 66.119223 12.184440 43.766667 58.758333 64.616667
M 414.0 57.640821 11.011857 36.350000 49.395833 55.791667
75% max
Gender
F 72.058333 101.300000
M 64.804167 98.516667
所有女性和男性的平均芯片时间分别为约66分钟和约58分钟。
那么下面是男性和女性完成时间的并排箱线图比较。
df7.boxplot(column='Runner_mins',by=' Gender')plt.ylabel('Chip Time')plt.suptitle("")
C:UserssmasangoAppDataLocalContinuumanaconda3libsite-packages
umpycoreromnumeric.py:57: FutureWarning: reshape is deprecated and will raise in a subsequent release. Please use .values.reshape(...) instead
return getattr(obj,method)(*args,**kwds)Text(0.5,0.98,'')

结论
在本教程中,您使用Python执行了Web抓取。 您使用Beautiful Soup库来解析html数据并将其转换为可用于分析的表单。 您在Python中执行了数据清理并创建了有用的图表(箱形图,条形图和分布图),用Python的matplotlib和seaborn库来显示有趣的趋势。 在本教程之后,您应该能够使用Python轻松地从Web抓取数据,应用清理技术并从数据中提取有用的见解。 (编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|