315
社区成员
发帖
与我相关
我的任务
分享| 这个作业属于哪个课程 | 软件工程实践-2023学年-W班社区-CSDN社区云 |
|---|---|
| 这个作业要求在哪里 | 软件工程实践第二次作业——个人实战-CSDN社区 |
| 这个作业的目标 | 在文章开头给出新建的Gitcode项目地址 详细阅读作业要求 完成代码编写并进行测试 撰写博客 描述解题思路 设计实现过程 关键代码展示 PSP表格 |
| 其他参考文献 | https://www.cnblogs.com/xinz/archive/2011/11/20/2255830.html |
## 仓库地址
ImBloodGirl / 222100306 · GitCode
| PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
| Planning | 计划 | ||
| • Estimate | • 估计这个任务需要多少时间 | 5 | 5 |
| Development | 开发 | ||
| • Analysis | • 需求分析 (包括学习新技术) | 20 | 660(这个时间都在学C++爬虫,还没学会......)+400(改用python在调爬虫bug...) |
| • Design Spec | • 生成设计文档 | 45 | 120 |
| • Design Review | • 设计复审 | 45 | 60 |
| • Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 28 | 60 |
| • Design | • 具体设计 | 45 | 70 |
| • Coding | • 具体编码 | 60 | 500(感觉大部分时间是在写爬虫......) |
| • Code Review | • 代码复审 | 30 | 300 |
| • Test | • 测试(自我测试,修改代码,提交修改) | 60 | 300 |
| Reporting | 报告 | ||
| • Test Repor | • 测试报告 | 30 | 45 |
| • Size Measurement | • 计算工作量 | 10 | 20 |
| • Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 45 | 60 |
| 合计 | 378 | 2620 |
首先,要对数据进行爬取。爬取后进行数据清洗。数据清洗后需要提取并整理出样例给出的数据格式。根据题目要求,我们需要获取选手的名字、性别、国籍、比赛项目、排名、成绩。
比赛结果 |世界游泳官方 (worldaquatics.com)
比赛运动员 |世界游泳官方 (worldaquatics.com)
文件读取函数
数据爬取
数据清洗
数据格式整理
文件输出函数
错误反馈处理
本来的数据库表结构是这样的:
| Column Name | Data Type | Description |
|---|---|---|
| id | Integer | Primary key, autoincrement |
| EventName | String(255) | Name of the event |
| DisciplineName | String(255) | Name of the discipline |
| EventResultDate | Date | Date of the event result |
| OverallRank | Integer | Overall rank of the athlete |
| Country | String(255) | Country of the athlete |
| Athlete | String(255) | Name of the athlete |
| Age | Integer | Age of the athlete |
| Points | Float | Points earned by the athlete |
| PtsBehind | Float | Points behind the leader |
| Column Name | Data Type | Description |
|---|---|---|
| id | Integer | Primary key, autoincrement |
| Country | String(255) | Country of the athlete |
| Athlete | String(255) | Name of the athlete |
| Gender | String(10) | Gender of the athlete (e.g., 'Male', 'Female') |
| DOB | Date | Date of birth of the athlete |
| Discipline | String(255) | Discipline of the athlete (e.g., 'Swimming', 'Athletics') |
| Column Name | Data Type | Description |
|---|---|---|
| id | Integer | Primary key, autoincrement |
| event_result_id | Integer | Foreign key, references the event result |
| DiveOrder | Integer | Order of the dive |
| DivePoints | Float | Points awarded for the dive |
【改进】但失败
所有不同项目是存放在同一个表下的,但是认为这样会使查询速度减慢,因此,如果改为以比赛项目名为名字存储数据,建立多个表。
修改失败,搞不了动态创建表。
因为C++的IDE问题难以修复,不能使用第三方库,只能把数据库文件存为csv,读取文件。
读取文件
识别文件内容
如果是 players,就调用输出选手所有数据的函数
如果是
result ...
,则进一步处理:
result 必须是独立的单词。
如果格式为 result ... detail,就调用输出详细比赛结果的函数。
如果格式为 result ...(不含 detail),就调用输出相应比赛结果的函数。
如果 result 后面的条件不符合要求,就输出 N/A。
如果无法识别是 players 还是 result,则输出 Error。
整理格式
详细思路见实现过程
Python语言,Pycharm软件
(1)根据结构建立表
# 创建数据库连接引擎
engine = create_engine('mysql+pymysql://root:123456@localhost:3306/swim_cmpt')
# 创建一个基类
Base = declarative_base()
# 定义比赛结果表的ORM类
class EventResult(Base):
__tablename__ = 'event_results'
id = Column(Integer, primary_key=True, autoincrement=True)
EventName = Column(String(255))
DisciplineName = Column(String(255))
EventResultDate = Column(Date)
OverallRank = Column(Integer)
Country = Column(String(255))
Athlete = Column(String(255))
Age = Column(Integer)
Points = Column(Float)
PtsBehind = Column(Float)
# 创建表
Base.metadata.create_all(engine)
# 创建一个Session类
Session = sessionmaker(bind=engine)
(2)根据含有各比赛id的url爬出此特定id
def get_competition_events():
url = "https://api.worldaquatics.com/fina/competitions/3337/events"
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0"
}
# 发送 GET 请求
response = requests.get(url, headers=headers)
# 检查请求是否成功
if response.status_code == 200:
# 解析 JSON 响应
data = response.json()
# print(data)
# 提取比赛信息
competitions = data.get('Sports', [])[0].get("DisciplineList",[])
print(competitions)
for competition in competitions:
competition_id = competition.get('Id')
competition_name = competition.get('DisciplineName')
# 构建比赛对应的URL
competition_url = f"https://api.worldaquatics.com/fina/events/{competition_id}"
# 获取比赛结果数据并处理
get_event_results(competition_url, competition_name)
else:
print("Failed to fetch data from the API")
print("获取比赛信息失败!")
类似以上,根据获取的特定id访问比赛具体信息url,爬取信息存入数据库
(3)将爬取到的信息存为csv
1. 解析数据文件
读取并解析athlete_info.csv文件,获取选手的全名、性别和国籍信息。
2. 实现主功能
根据input.txt中的指令内容,决定执行哪个功能。对于功能1,当内容为players时,调用输出所有选手信息的函数 outputPlayersData。
3. 输出格式
输出内容格式化为指定格式,并写入到output.txt文件中。每个选手的信息后面跟随一行 ----- 作为分隔符。
1. 解析数据文件
读取并解析event_results.csv文件,获取各个赛事的比赛结果。
2. 实现主功能
根据
input.txt
中的指令内容,决定执行哪个功能。
对于内容为
result ...
的指令:
result 必须是独立的单词。
如果格式为 result ... detail,调用输出详细比赛结果的函数 outputDetailedResult。
如果格式为 result ...(不含 detail),调用输出相应比赛结果的函数 outputEventResult。
3. 输出格式
输出内容格式化为指定格式,并写入到output.txt文件中。每个结果信息后面跟随一行 ----- 作为分隔符。
1. 详细比赛结果
读取并解析更详细的比赛结果文件,获取初赛、半决赛和决赛的详细得分。
2. 实现主功能
当指令内容为 result ... detail 时,调用输出详细比赛结果的函数 outputDetailedResult。
3. 输出格式
输出内容格式化为指定格式,并写入到output.txt文件中。每个详细结果信息后面跟随一行 ----- 作为分隔符。
1. 处理无法识别的指令
如果无法识别是 players 还是 result,则输出 Error,并写入到output.txt文件中。
2. 处理无效的赛事条件
如果 result 后面的条件不符合要求(由 isValidEvent 函数判断),则输出 N/A,并写入到output.txt文件中。
接口类:DivingChampionShshiy
运行命令:
g++ src\DWASearch.cpp src\DivingChampionship.cpp -o DWASearch
DWASearch src\input.txt src\output.txt src\data\athlete_info.csv src\data\event_results2.csv src\data\event_details.csv
接口功能:
处理命令:根据命令内容执行相应的操作。
验证事件:验证赛事类型的有效性。
验证命令:验证命令格式的有效性。
读取数据文件:读取不同格式的 CSV 数据文件。
格式化输出:格式化比赛结果并输出到文件。
#ifndef DIVING_CHAMPIONSHIP_H
#define DIVING_CHAMPIONSHIP_H
#include <string>
#include <vector>
#include <map>
// 结构体
struct Athlete {
std::string fullName;
std::string lastName;
std::string gender;
std::string country;
};
struct AthleteRank {
std::string preliminaryRank;
std::string semifinalRank;
std::string finalRank;
};
struct DetailedAthlete {
std::string fullName;
std::string rank;
std::string preliminaryScore;
std::string semifinalScore;
std::string finalScore;
};
struct EventResult {
int id;
std::string eventName;
std::string disciplineName;
std::string eventResultDate;
std::string heatName;
int overallRank;
std::string country;
std::string athlete;
std::string age;
double points;
std::string ptsBehind;
};
class DivingChampionship {
public:
// 处理命令,根据命令内容调用相应的处理函数
void processCommand(const std::string& command, const std::string& athleteInfoFile, const std::string& eventResultsFile, const std::string& diveScoresFile, const std::string& outputFile);
// 验证赛事类型的有效性
bool isValidEvent(const std::string& event);
// 验证命令格式的有效性
bool isValidResultCommand(const std::string& command);
private:
// 读取CSV文件并存储数据
void readCSV(const std::string& filename, std::vector<std::vector<std::string>>& data);
// 格式化得分
std::string formatScore(const std::vector<double>& scores);
// 提取选手的姓氏
std::string extractLastName(const std::string& fullName);
// 读取选手信息
std::vector<Athlete> readAthleteInfo(const std::string& filename);
// 读取赛事结果
std::vector<EventResult> readEventResults(const std::string& filename);
// 读取跳水得分
std::map<int, std::vector<double>> readDiveScores(const std::string& filename);
// 写入赛事结果到文件
void writeEventResult(const std::string& filename, const std::vector<EventResult>& results, const std::map<int, std::vector<double>>& diveScores);
// 写入选手信息到文件
void writeAthleteInfo(const std::string& filename, const std::vector<Athlete>& athletes);
// 写入详细赛事结果到文件
void writeDetailedEventResult(const std::string& filename, const std::vector<DetailedAthlete>& athletes);
// 转换字符串为小写
std::string toLower(const std::string& str);
// 输出错误信息到文件
void outputError(const std::string& outputFile);
// 输出N/A到文件
void outputNA(const std::string& outputFile);
};
#endif // DIVING_CHAMPIONSHIP_H
#include "DivingChampionship.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <unordered_set>
/**
* @brief 读取CSV文件并存储数据
*
* @param filename CSV文件名
* @param data 用于存储读取数据的二维字符串向量
*/
void DivingChampionship::readCSV(const std::string& filename, std::vector<std::vector<std::string>>& data) {
// 打开文件并读取内容
// 将每行数据分割成多个单词并存储到data中
}
/**
* @brief 格式化得分
*
* @param scores 跳水得分向量
* @return 格式化后的得分字符串
*/
std::string DivingChampionship::formatScore(const std::vector<double>& scores) {
// 遍历得分向量并计算总分
// 将得分和总分格式化为字符串
return formattedScore;
}
/**
* @brief 提取选手的姓氏
*
* @param fullName 选手全名
* @return 选手的姓氏
*/
std::string DivingChampionship::extractLastName(const std::string& fullName) {
// 分割全名字符串并提取最后一个单词作为姓氏
return lastName;
}
/**
* @brief 读取选手信息
*
* @param filename CSV文件名
* @return 选手信息向量
*/
std::vector<Athlete> DivingChampionship::readAthleteInfo(const std::string& filename) {
// 打开文件并读取选手信息
// 将读取的每行数据解析成Athlete结构体并存储到向量中
return athletes;
}
/**
* @brief 读取赛事结果
*
* @param filename CSV文件名
* @return 赛事结果向量
*/
std::vector<EventResult> DivingChampionship::readEventResults(const std::string& filename) {
// 打开文件并读取赛事结果
// 将读取的每行数据解析成EventResult结构体并存储到向量中
return results;
}
/**
* @brief 读取跳水得分
*
* @param filename CSV文件名
* @return 以赛事ID为键,得分向量为值的映射表
*/
std::map<int, std::vector<double>> DivingChampionship::readDiveScores(const std::string& filename) {
// 打开文件并读取跳水得分
// 将读取的每行数据解析成键值对并存储到映射表中
return diveScores;
}
/**
* @brief 写入赛事结果到文件
*
* @param filename 输出文件名
* @param results 赛事结果向量
* @param diveScores 跳水得分映射表
*/
void DivingChampionship::writeEventResult(const std::string& filename, const std::vector<EventResult>& results, const std::map<int, std::vector<double>>& diveScores) {
// 打开输出文件并将赛事结果和得分写入文件
}
/**
* @brief 写入选手信息到文件
*
* @param filename 输出文件名
* @param athletes 选手信息向量
*/
void DivingChampionship::writeAthleteInfo(const std::string& filename, const std::vector<Athlete>& athletes) {
// 打开输出文件并将选手信息写入文件
}
/**
* @brief 写入详细赛事结果到文件
*
* @param filename 输出文件名
* @param athletes 详细赛事结果向量
*/
void DivingChampionship::writeDetailedEventResult(const std::string& filename, const std::vector<DetailedAthlete>& athletes) {
// 打开输出文件并将详细赛事结果写入文件
}
/**
* @brief 转换字符串为小写
*
* @param str 输入字符串
* @return 转换后的字符串
*/
std::string DivingChampionship::toLower(const std::string& str) {
// 将输入字符串中的每个字符转换为小写
return lowerStr;
}
/**
* @brief 验证赛事类型的有效性
*
* @param event 赛事类型字符串
* @return 赛事类型是否合法
*/
bool DivingChampionship::isValidEvent(const std::string& event) {
// 检查赛事类型是否在预定义的有效赛事类型集合中
return valid;
}
/**
* @brief 验证命令格式的有效性
*
* @param command 命令字符串
* @return 命令格式是否合法
*/
bool DivingChampionship::isValidResultCommand(const std::string& command) {
// 检查命令格式是否符合预期,包括是否包含有效的赛事类型和详细标志
return valid;
}
/**
* @brief 处理命令,根据命令内容调用相应的处理函数
*
* @param command 命令字符串
* @param athleteInfoFile 选手信息文件名
* @param eventResultsFile 赛事结果文件名
* @param diveScoresFile 跳水得分文件名
* @param outputFile 输出文件名
*/
void DivingChampionship::processCommand(const std::string& command, const std::string& athleteInfoFile, const std::string& eventResultsFile, const std::string& diveScoresFile, const std::string& outputFile) {
if (command == "players") {
// 读取选手信息并写入输出文件
} else if (!isValidResultCommand(command)) {
// 输出N/A到文件
} else if (command.rfind("result ", 0) == 0 && command.find("detail") == std::string::npos) {
// 读取赛事结果并写入输出文件
} else if (command.rfind("result ", 0) == 0 && command.find("detail") != std::string::npos) {
// 读取详细赛事结果并写入输出文件
} else {
// 输出错误信息到文件
}
}
这里处理命令的逻辑来进行命令的识别/报错和分类。不同情况下的代码流。
bool isValidResultCommand(const std::string& command) {
if (command.rfind("result ", 0) != 0 && command.find("result") == 0) {
return false; // If it starts with "result" but not followed by space, it is an error
}
std::string eventType;
if (command.find(" detail") != std::string::npos) {
eventType = command.substr(7, command.find(" detail") - 7);
return isValidEvent(eventType);
} else {
eventType = command.substr(7);
return isValidEvent(eventType);
}
}
void processCommand(const std::string& command, std::ofstream& outFile) {
if (command == "players") {
outputPlayersData(outFile);
} else if (command.find("result") == 0) {
std::string event = command.substr(7); // 去掉 "result " 部分
std::string detailSuffix = "detail";
// 处理详细结果
if (event.size() > detailSuffix.size() &&
event.compare(event.size() - detailSuffix.size(), detailSuffix.size(), detailSuffix) == 0) {
std::string eventType = event.substr(0, event.size() - detailSuffix.size() - 1);
if (isValidEvent(eventType)) {
outputDetailedResult(outFile, eventType);
} else {
outputNA(outFile);
}
}
// 处理普通结果
else if (isValidEvent(event)) {
outputEventResult(outFile, event);
} else {
outputNA(outFile);
}
} else {
outputError(outFile);
}
}
bool isValidEvent(const std::string& event) {
static const std::vector<std::string> validEvents = {
"women 1m springboard",
"women 3m springboard",
"women 10m platform",
"women 3m synchronised",
"women 10m synchronised",
"men 1m springboard",
"men 3m springboard",
"men 10m platform",
"men 3m synchronised",
"men 10m synchronised"
};
return std::find(validEvents.begin(), validEvents.end(), event) != validEvents.end();
}
int main(int argc, char* argv[]) {
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " <input_file> <output_file>\n";
return 1;
}
std::ifstream inFile(argv[1]);
std::ofstream outFile(argv[2]);
if (!inFile) {
std::cerr << "Error opening input file\n";
return 1;
}
if (!outFile) {
std::cerr << "Error opening output file\n";
return 1;
}
std::string command;
while (std::getline(inFile, command)) {
processCommand(command, outFile);
}
return 0;
}
下面是合法条件的确定:
bool isValidEvent(const std::string& event) {
std::unordered_set<std::string> validEvents = {
"women 1m springboard", "women 3m springboard", "women 10m platform",
"women 3m synchronised", "women 10m synchronised", "men 1m springboard",
"men 3m springboard", "men 10m platform", "men 3m synchronised", "men 10m synchronised"
};
return validEvents.find(event) != validEvents.end();
}
先让程序正常读取文件。
1. 处理无法识别的指令
当输入文件中的指令既不是 players 也不是以 result 开头时,程序会输出 Error。这是为了确保程序能在遇到无效输入时,提供明确的反馈,而不会崩溃或产生不可预期的行为。
具体实现方法如下:
在 processCommand 函数中,首先检查指令是否为 players,如果是则调用 outputPlayersData 函数。
如果指令不是 players,则检查它是否以 result 开头。
如果既不是 players 也不是 result,则调用 outputError 函数,向输出文件写入 Error。
2. 处理无效的赛事条件
在处理以 result 开头的指令时,需要进一步检查指令的详细内容是否有效。具体的检查步骤如下:
如果指令格式为 result ... detail,即包含 detail 关键字,程序会提取出赛事条件并检查其有效性。如果条件无效,则输出 N/A。
如果指令格式为 result ...(不含 detail),程序同样会检查赛事条件的有效性。如果条件无效,也会输出 N/A。
为了实现上述检查,定义了一个辅助函数 isValidEvent 来验证赛事条件的有效性。这个函数包含了所有有效的赛事条件,并检查给定的条件是否在其中。
3. 处理文件读取和写入错误
为了确保程序在读取和写入文件时能够处理可能出现的错误,程序会在打开文件时检查文件是否成功打开。如果文件无法打开,程序将输出相应的错误信息,并终止执行。
具体实现方法如下:
在 main 函数中,尝试打开输入文件和输出文件。
如果输入文件无法打开,输出错误信息 Error opening input file。
如果输出文件无法打开,输出错误信息 Error opening output file。
原因:在csv中rank1的pst behind值为空,导致读取失败
尝试学习用C++爬取网页数据,实在不理解,尽管看了很多网课,大部分都是爬取图片,讲解者隐晦带着黄色的腔调令人不适。
所以我用Python爬取了网页数据,并用C++处理数据。
理想:我用python写过爬虫,无非是
请求网页,抓取html内容
用正则清洗数据
将数据安放到数据库
而已,用到的库大概有requests 或 urllib 库。
现实:但在 C++ 中抓取网页内容相对 Python 来说复杂一些。
因为 C++ 并没有内置像 Python 中requests 或 urllib 这样的方便的库 。
在 C++ 中,我需要使用一大堆第三方库来发送 HTTP 请求和处理响应。常用的库包括 cURL、libcurl、WinINet 等。
哎理想很丰满,现实很骨感,我以为爬虫是个很简单的作业,但是由于C++爬虫和Python有很大不同,还是得重新学习,学习爬虫的原理也是个曲折的过程......
其他:另外,我发现仅仅只熟悉一个编译器,是远远不够的,因为有时候编译器因为各种原因无法使用。
比如,我的Cion许可证过期了,需要邮件到校园邮箱验证,而我给邮箱管理员发的邮件一周都没回,网站也登不进去......
这时我想到可以用vscode,但发现自己并不熟悉库的安装,还要另外下载vspkg才能安装库......
学习原来是耗时最长的......
烦......
有些东西我们总是看起来“会”、“有思路”,做起来比预期时间要长得多,所以应该预留时间,未雨绸缪,才有望按时交付。
把所有困难的事情看作自我成长,就不会那么困难了。
比如一开始不知道数据到底要爬什么,虽然会用python爬虫,但是着手取数据的时候还是纠结半天。
现在知道看到作业给出两个链接,只要爬两个链接下相关的数据即可。不需要想太多复杂的。