Pandas DataFrame格式化及按单场赛事而非全局排名问题
体育赛事爬虫数据输出问题解决方案
问题概述
爬虫抓取多场体育赛事数据生成CSV时,存在两个未解决的需求:
- 排名计算:需按单场赛事(而非全局)计算
Odds Ranking、Official Rating Ranking等字段的排名,当前全局排名不符合预期; - CSV格式优化:每场赛事数据后需添加空行并重复表头,而非仅在文件末尾添加空行。
当前核心代码片段:
for item in course_racecard: for link in a_tag: race_link = (times.find_previous('a')['href']) full_url = urljoin(url, race_link) for item in entries: for horse_info in item.find_all(class_='horse_extended'): li = item.find_all("li") trainer = li[0].text.strip() jockey = li[1].text.strip() data.append({ "Date": date, "Course": race_course, "Times": times.text, #"URL": full_url, "Title": race_title_simple, "Distance": distance, "Runners": number_of_runners, "Age Group": age_group, "Class": race_class_simple, "Going": going, "Surface": surface, "Horse": horse_name, "Trainer": trainer, "Jockey": jockey, "Age": age_simple, "Weight": weight, "Official Rating": rating_simple, "Form": simple_horse_form, "Exchange Back Odds": odds_int, "weight in stone": weight_in_stone, "Timeform Favourites": "", }) df = pd.DataFrame(data) df['Going'] = df['Going'].str.replace('Going: ', '') df['Surface'] = df['Surface'].str.replace('Surface: ', '') df['Distance'] = df['Distance'].str.replace('Distance: ', '') df['Age Group'] = df['Age Group'].str.replace('Age: ', '') df['Runners'] = df['Runners'].str.replace('Runners: ', '') df['Odds Ranking'] = df['Exchange Back Odds'].rank(ascending=False) df['Official Rating Ranking'] = df['Official Rating'].rank(ascending=True) df['Weight Ranking'] = df['weight in stone'].rank(ascending=True)
解决方案
1. 按单场赛事计算排名
当前代码对整个DataFrame做全局排名,需通过赛事唯一标识字段分组实现单场赛事内的排名计算。
优化后代码:
# 收集所有数据到data列表后,一次性生成DataFrame(移到所有循环外,提升效率) df = pd.DataFrame(data) # 数据清洗 df['Going'] = df['Going'].str.replace('Going: ', '') df['Surface'] = df['Surface'].str.replace('Surface: ', '') df['Distance'] = df['Distance'].str.replace('Distance: ', '') df['Age Group'] = df['Age Group'].str.replace('Age: ', '') df['Runners'] = df['Runners'].str.replace('Runners: ', '') # 定义能唯一区分单场赛事的字段组合(根据实际数据调整) race_identifier = ['Date', 'Course', 'Times', 'Title'] # 按单场赛事分组计算排名 df['Odds Ranking'] = df.groupby(race_identifier)['Exchange Back Odds'].rank(ascending=False, method='min') df['Official Rating Ranking'] = df.groupby(race_identifier)['Official Rating'].rank(ascending=True, method='min') df['Weight Ranking'] = df.groupby(race_identifier)['weight in stone'].rank(ascending=True, method='min')
method='min':处理相同数值的排名逻辑,确保相同值获得相同的最高排名,可根据需求替换为'max'(相同值取最低排名)或'first'(按出现顺序排名);- 注意:
race_identifier的字段组合必须能唯一标识单场赛事,避免不同赛事被错误归为一组。
2. CSV格式优化:每场赛事后加空行和表头
直接使用df.to_csv()无法实现该格式,需逐赛事处理并写入文件,精确控制每行内容。
实现代码:
import csv output_path = 'race_results.csv' header = df.columns.tolist() race_groups = df.groupby(race_identifier) with open(output_path, 'w', newline='', encoding='utf-8') as f: writer = csv.writer(f) first_group = True for _, group_df in race_groups: if not first_group: # 非首个赛事:先写空行,再写表头 writer.writerow([]) writer.writerow(header) else: # 首个赛事:只写一次表头 writer.writerow(header) first_group = False # 写入当前赛事的所有数据行 for _, row in group_df.iterrows(): writer.writerow(row.tolist())
- 该方式用
csv.writer精确控制输出,自动处理特殊字符(如逗号)的引号包裹,避免CSV格式错误; - 代码逻辑自动跳过最后一场赛事后的空行和表头,避免冗余内容。
额外效率优化
当前代码在horse_info循环内反复执行df = pd.DataFrame(data),每次添加一条数据就重新生成DataFrame,效率极低。建议:
- 保留
data.append()收集所有数据; - 把
df = pd.DataFrame(data)及后续的清洗、排名代码移到所有循环的最外层,仅执行一次。
内容的提问来源于stack exchange,提问作者MattTrehearn
相关产品推荐
相关产品推荐

