linux---天气爬虫
代码概述
这段代码实现了一个天气查询系统,支持实时天气、未来天气和历史天气查询。用户可以通过终端菜单选择查询类型,并输入城市名称来获取相应的天气信息。程序通过 TCP 连接发送 HTTP 请求,并解析返回的 JSON 数据来展示天气信息。
#include "main.h"#include "cJSON.h"
#define BUFFER_SIZE 30000
#define TEMP_BUFFER_SIZE 4096#define COLOR_FIELD "\033[34m" // 字段蓝色
#define COLOR_VALUE "\033[36m" // 值青色
#define COLOR_WARN "\033[33m" // 警告黄色
#define COLOR_RESET "\033[0m" // 重置颜色
#define COLOR_HEAD "\033[35m" // 提示头颜色char weather_inquiry[4][1024] =
{{"实时天气"},{"未来天气"},{"历史天气"},{"退出查询"},
};int pos_start = 0;
int pos_end = 4;
int focus = 0;//选择的焦点
int menu_flag = 1;
char city[512];//储存城市名字
int sockfd;int getch(void)
{struct termios oldt, newt;int ch;int esc_mode = 0;unsigned long key = 0;//获取终端属性信息tcgetattr(STDIN_FILENO, &oldt);newt = oldt;//设置非阻塞模式newt.c_lflag &= ~(ICANON | ECHO);newt.c_cc[VMIN] = 0;//读取最小字符newt.c_cc[VTIME] = 1;//等待时间10/1秒//修改new中的ECHO和ICANON参数,使得new为不回显输入内容//设置终端信息tcsetattr(STDIN_FILENO, TCSANOW, &newt);//组合多字节按键for(int i = 0;i < 3;i++){ch = getchar();if(ch == EOF)break;key = (key << 8)|(ch & 0xFF);}//用完之后,恢复原来的终端属性tcsetattr(STDIN_FILENO, TCSANOW, &oldt);switch(key){case 0x1B5B41: return KEY_UP;case 0x1B5B42: return KEY_DOWN;case 0x0A: return KEY_ENTER;default: return (key&0xFF);//返回首个有效字符 }
}void View_two(void)
{if(menu_flag == 2){int i = 0;printf("\033[10;35H");printf("| 天气查询 |\n");printf("\033[0m");for(i = pos_start;i <pos_end;i++){if(i == focus){printf("\033[%d;50H",i+12);printf("|\033[30;43m%2d.%s\033[0m\n",i+1,weather_inquiry[i]);}else{printf("\033[%d;50H",12+i);printf("|%2d.%-20s\n",i+1,weather_inquiry[i]);}}}return;
}void city_name(void)
{ scanf("%s",city);while(getchar()!= '\n');printf("\033[0m");if(strncmp(city,"quit",4) == 0){exit(0);printf("感谢使用!\n");}
}#if 1
void view(void)
{if(menu_flag == 1){printf("\033[15;55H");printf("\033[;31m");printf("输入quit退出\n");printf("\033[10;25H");printf("\033[;33m");printf("~欢迎来到天气查询系统~\n");printf("\033[0m");printf("\033[12;25H");printf("\033[;34m");printf("请输入你要查询的城市:");// 清空城市名memset(city, 0, sizeof(city));city_name();menu_flag = 2;system("clear");}else if(menu_flag == 2){View_two();}return;
}#endifvoid CreateTcpClient(void)//创建TCP
{int ret = 0;sockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd < 0){perror("sockfd fail");return;}struct sockaddr_in seraddr;bzero(&seraddr,sizeof(seraddr));seraddr.sin_family = AF_INET;seraddr.sin_port = htons(PORT);seraddr.sin_addr.s_addr = inet_addr(IP);if(connect(sockfd,(struct sockaddr*)&seraddr,sizeof(seraddr)) < 0){perror("connect fail");return;}
}//发送HTTP请求(参数:天气类型)
void SendHttpRequest(int sockfd,char *head)
{char tmpbuff[4096] = {0};int written = 0;const char *template ="GET %s HTTP/1.1\r\n""Host: api.k780.com\r\n""User-Agent: WeatherClient/1.0\r\n""Accept: */*\r\n""Connection: close\r\n" // 改为短连接"\r\n"; // 必须的空行// 安全格式化(限制最大长度)written = snprintf(tmpbuff,sizeof(tmpbuff),template,head);if (written >= sizeof(tmpbuff)) {fprintf(stderr, "Request too large (max %zd bytes)\n", sizeof(tmpbuff));return;}//发送请求ssize_t sent = send(sockfd,tmpbuff,written,0);if(sent != written){perror("send fail");return;}
}void RecvSendWeather(void)
{int fd = 0;char buff[BUFFER_SIZE] = {0}; // 确保初始化为全0char cmpbuff[BUFFER_SIZE] = {0}; // 用于存储处理后的数据char tmpbuff[TEMP_BUFFER_SIZE] = {0}; // 临时缓冲区ssize_t nsize = 0; // recv函数接收的数据大小char *ptmp = NULL; // 临时指针char *pstart = NULL; // 数据起始位置指针char *pend = NULL; // 数据结束位置指针fd = open("recv.txt", O_RDWR | O_CREAT | O_TRUNC, 0666);while(1){memset(tmpbuff, 0, sizeof(tmpbuff)); // 清空临时缓冲区nsize = recv(sockfd, tmpbuff, sizeof(tmpbuff), 0);if(nsize <= 0){break;}strncat(buff, tmpbuff, (size_t)nsize); // 将接收到的数据追加到buff中if(strstr(tmpbuff, "0\r\n") != NULL)break;}//查找数据正文的起始位置ptmp = strstr(buff, "\r\n\r\n");if(ptmp != NULL){ptmp += 4; //跳过"\r\n\r\n"ptmp = strstr(ptmp, "\r\n");if(ptmp != NULL){ptmp += 2;pstart = ptmp; //标记数据起始位置pend = strstr(ptmp, "\r\n"); //查找数据结束位置if(pend != NULL){strncat(cmpbuff, pstart, pend - pstart);}}}write(fd, cmpbuff, strlen(cmpbuff)); // 将处理后的数据写入文件close(fd);close(sockfd);
}void real_time_weather()
{int ret = -1;int fd = -1;char *buffer = NULL;ssize_t bytes_read = 0;CreateTcpClient(); // 创建新的连接if(!(buffer = malloc(BUFFER_SIZE))){perror("malloc fail");return;}if((fd = open("recv.txt", O_RDONLY)) == -1){perror("open fail");return;}if((bytes_read = read(fd, buffer, BUFFER_SIZE)) <= 0){fprintf(stderr, "Read failed or empty file\n");return;}buffer[bytes_read] = '\0'; // 确保字符终止cJSON *root = cJSON_Parse(buffer);if(!root){fprintf(stderr, "JSON parse failed\n");return;}// 使用结构体定义字段映射struct FieldMapping {const char *json_key;const char *display_name;int required; // 1表示必须字段} fields[] = {{"days", "日期", 1},{"week", "星期", 1},{"citynm", "城市", 1},{"temperature_curr", "当前温度", 1},{"temp_high", "最高温度", 0},{"temp_low", "最低温度", 0},{"weather", "天气状况", 1},{"humidity", "当前湿度", 0},{"humi_high", "最大湿度", 0},{"humi_low", "最小湿度", 0},{"wind", "风向", 1},{"winp", "风力", 1},{"aqi", "PM2.5", 0}};cJSON *result = cJSON_GetObjectItem(root, "result");if(!result){fprintf(stderr, "Missing result object\n");cJSON_Delete(root);return;}// 遍历所有字段定义for(size_t i = 0; i < sizeof(fields)/sizeof(fields[0]); i++){cJSON *item = cJSON_GetObjectItem(result, fields[i].json_key);printf(COLOR_FIELD"%-8s: "COLOR_RESET, fields[i].display_name);if(item && item->valuestring){printf(COLOR_VALUE"%s\n"COLOR_RESET, item->valuestring);}else{printf(COLOR_WARN"N/A\n"COLOR_RESET);if(fields[i].required){fprintf(stderr, "Missing required field: %s\n", fields[i].json_key);cJSON_Delete(root);return;}}}ret = 0;free(buffer);cJSON_Delete(root);close(fd);
}void future_weather()
{int ret = -1;int fd = -1;char *buffer = NULL;ssize_t bytes_read = 0;CreateTcpClient(); // 创建新的连接if(!(buffer = malloc(BUFFER_SIZE))){perror("malloc fail");return;}if((fd = open("recv.txt",O_RDONLY)) == -1){perror("open fail");return;}if((bytes_read = read(fd,buffer,BUFFER_SIZE)) <= 0){fprintf(stderr, "Read failed or empty file\n");return;}buffer[bytes_read] = '\0';//确保字符终止cJSON *root = cJSON_Parse(buffer);if(!root){fprintf(stderr, "JSON parse failed\n");return;}// 使用结构体定义字段映射struct FieldMapping {const char *json_key;const char *display_name;int required; // 1表示必须字段} fields[] = {{"days", "日期", 1},{"week", "星期", 1},{"citynm", "城市", 1},{"temp_high", "最高温度", 0},{"temp_low", "最低温度", 0},{"weather", "天气状况", 1},{"wind", "风向", 1},{"winp", "风力", 1},};cJSON *result = cJSON_GetObjectItem(root,"result");if(!result){fprintf(stderr, "Missing result object\n");cJSON_Delete(root);}int i = 0;cJSON *item;cJSON_ArrayForEach(item,result){printf(COLOR_HEAD"=============第%d天气预报=============\n"COLOR_RESET,i+1);// 遍历所有字段定义for (size_t j = 0; j < sizeof(fields)/sizeof(fields[0]); j++) {cJSON *field = cJSON_GetObjectItem(item, fields[j].json_key);printf(COLOR_FIELD"%-8s: "COLOR_RESET, fields[j].display_name);if (field && field->valuestring) {printf(COLOR_VALUE"%s\n"COLOR_RESET, field->valuestring);} else {printf(COLOR_WARN"N/A\n"COLOR_RESET);if (fields[j].required) {fprintf(stderr, "Missing required field: %s\n", fields[j].json_key);}}}i++;} cJSON_Delete(root);free(buffer);close(fd);
}void historical_weather()
{
}void MenuChoose(void)
{int key = getch();char tmpbuff[1024]={0};//ESC按键处理if(key == KEY_ESC){if(menu_flag == 2){menu_flag = 1;//切换到菜单一system("clear");}}switch(key){case KEY_UP:if(focus > pos_start){system("clear");focus--;}break;case KEY_DOWN:if(focus < pos_end){system("clear");focus++;}break;case KEY_ENTER:switch(focus){case 0:system("clear");CreateTcpClient();sprintf(tmpbuff,"/?app=weather.today&weaid=%s&appkey=%s&sign=%s",city,API_KEY,API_SIGN);SendHttpRequest(sockfd,tmpbuff);RecvSendWeather();real_time_weather();if (sockfd > 0) {close(sockfd);sockfd = -1;}break;case 1:system("clear");CreateTcpClient();sprintf(tmpbuff,"http://api.k780.com/?app=weather.future&weaid=%s&appkey=%s&sign=%s&format=json",city,API_KEY,API_SIGN);SendHttpRequest(sockfd,tmpbuff);RecvSendWeather();future_weather();break;case 2:break;case 3:printf("感谢使用!\n");exit(0);break;} }
}int main(int argc, const char *argv[])
{system("clear");while(1){view();MenuChoose();}return 0;
}
演示视频
天气查询演示视频
一、函数总览
全局变量
-
weather_inquiry[4][1024]:存储菜单选项,包括实时天气、未来天气、历史天气和退出查询。 -
pos_start和pos_end:定义菜单显示的起始和结束位置。 -
focus:当前用户选择的菜单焦点。 -
menu_flag:标志当前显示的菜单层级,1表示主菜单,2表示二级菜单。 -
city[512]:存储用户输入的城市名称。 -
sockfd:TCP 套接字文件描述符。
第二部分:输入处理和菜单显示
getch() 函数
-
功能:捕捉用户按键输入,支持方向键和回车键。
-
实现:
-
使用
termios修改终端属性,禁用回显和行缓冲。 -
捕捉按键组合,识别上方向键(
KEY_UP)、下方向键(KEY_DOWN)和回车键(KEY_ENTER)。 -
恢复终端属性。
-
View_two() 函数
-
功能:显示二级菜单,包括实时天气、未来天气、历史天气和退出查询。
-
实现:
-
根据
focus的值,高亮显示当前选中的菜单项。 -
使用终端控制字符(如
\033)设置文字颜色和位置。
-
city_name() 函数
-
功能:获取用户输入的城市名称,并检查是否为退出命令。
-
实现:
-
使用
scanf获取输入,并清除输入缓冲区。 -
如果输入为
quit,程序退出。
-
view() 函数
-
功能:根据
menu_flag的值,显示主菜单或二级菜单。 -
实现:
-
主菜单显示欢迎信息,并提示用户输入城市名称。
-
二级菜单显示天气查询选项。
-
第三部分:TCP 连接和 HTTP 请求
CreateTcpClient() 函数
-
功能:创建一个 TCP 套接字,并连接到指定的服务器。
-
实现:
-
使用
socket创建套接字。 -
设置服务器地址和端口。
-
使用
connect建立连接。
-
SendHttpRequest() 函数
-
功能:发送 HTTP GET 请求。
-
实现:
-
使用
snprintf格式化请求头,包括目标 URL 和必要头部信息。 -
使用
send发送请求。
-
第四部分:数据接收和处理
RecvSendWeather() 函数
-
功能:接收服务器响应,并提取数据正文。
-
实现:
-
使用
recv循环接收数据,直到遇到空行(0\r\n)。 -
查找数据正文的起始和结束位置,提取并存储到文件中。
-
第五部分:实时天气查询
real_time_weather() 函数
-
功能:解析实时天气 JSON 数据,并展示相关信息。
-
实现:
-
从文件读取 JSON 数据。
-
使用
cJSON解析 JSON 数据。 -
定义字段映射结构体,遍历字段并显示对应值。
-
检查必填字段是否存在,若缺失则报错。
-
第六部分:未来天气查询
future_weather() 函数
-
功能:解析未来天气 JSON 数据,并展示相关信息。
-
实现:
-
从文件读取 JSON 数据。
-
使用
cJSON解析 JSON 数据。 -
定义字段映射结构体,遍历字段并显示对应值。
-
遍历 JSON 数组,逐条显示未来天气信息。
-
第七部分:菜单选择和主程序
MenuChoose() 函数
-
功能:处理用户按键输入,实现菜单导航和功能选择。
-
实现:
-
根据按键调整
focus值,实现菜单上下导航。 -
处理回车键,根据选中项执行对应功能:
-
实时天气查询:构造 URL 并发送请求,调用
real_time_weather。 -
未来天气查询:构造 URL 并发送请求,调用
future_weather。 -
退出程序:退出并显示感谢信息。
-
-
main() 函数
-
功能:主程序入口,循环显示菜单并处理用户输入。
-
实现:
-
清屏并初始化终端。
-
循环调用
view和MenuChoose,实现菜单显示和交互。
-
二、重点函数
RecvSendWeather() 函数
void RecvSendWeather(void)
{int fd = 0;char buff[BUFFER_SIZE] = {0}; // 确保初始化为全0char cmpbuff[BUFFER_SIZE] = {0}; // 用于存储处理后的数据char tmpbuff[TEMP_BUFFER_SIZE] = {0}; // 临时缓冲区ssize_t nsize = 0; // recv函数接收的数据大小char *ptmp = NULL; // 临时指针char *pstart = NULL; // 数据起始位置指针char *pend = NULL; // 数据结束位置指针fd = open("recv.txt", O_RDWR | O_CREAT | O_TRUNC, 0666);while(1){memset(tmpbuff, 0, sizeof(tmpbuff)); // 清空临时缓冲区nsize = recv(sockfd, tmpbuff, sizeof(tmpbuff), 0);if(nsize <= 0){break;}strncat(buff, tmpbuff, (size_t)nsize); // 将接收到的数据追加到buff中if(strstr(tmpbuff, "0\r\n") != NULL)break;}//查找数据正文的起始位置ptmp = strstr(buff, "\r\n\r\n");if(ptmp != NULL){ptmp += 4; //跳过"\r\n\r\n"ptmp = strstr(ptmp, "\r\n");if(ptmp != NULL){ptmp += 2;pstart = ptmp; //标记数据起始位置pend = strstr(ptmp, "\r\n"); //查找数据结束位置if(pend != NULL){strncat(cmpbuff, pstart, pend - pstart);}}}write(fd, cmpbuff, strlen(cmpbuff)); // 将处理后的数据写入文件close(fd);close(sockfd);
}
1. 初始化与文件准备
- 定义缓冲区
buff(存储完整数据)、cmpbuff(存储处理后的数据)和tmpbuff(临时接收数据)。 - 创建/清空文件
recv.txt用于保存最终数据。
2. 循环接收数据
- 通过
recv从套接字sockfd接收数据到tmpbuff。 - 将每次接收的数据追加到主缓冲区
buff中。 - 终止条件:当收到包含
"0\r\n"的数据时,认为传输结束,退出循环。
3. 数据解析
- 定位数据起始点:
- 查找首个
\r\n\r\n,跳过HTTP头部。 - 继续查找下一个
\r\n,跳过可能的分块长度字段(如5\r\n)。
- 查找首个
- 提取数据正文:
- 从
pstart到下一个\r\n之间的内容被视为有效数据,复制到cmpbuff。
- 从
4. 保存与清理
- 将处理后的数据写入文件
recv.txt。 - 关闭文件描述符和套接字。
潜在问题与改进建议
-
缓冲区溢出风险
- 问题:
buff和cmpbuff是固定大小,未检查strncat是否越界。 - 建议:增加长度检查,或改用动态内存分配。
- 问题:
-
分块编码处理不完整
- 问题:代码假设数据仅包含一个分块(如
5\r\nhello\r\n0\r\n),可能漏掉多分块数据。 - 建议:循环解析所有分块,处理格式如
长度\r\n数据\r\n。
- 问题:代码假设数据仅包含一个分块(如
-
结束标志可靠性
- 问题:若
0\r\n被拆分成多次接收(如0\r和\n),可能无法正确退出。 - 建议:在
buff中全局搜索0\r\n,而非仅检查tmpbuff。
- 问题:若
real_time_weather() 函数
void real_time_weather()
{int ret = -1;int fd = -1;char *buffer = NULL;ssize_t bytes_read = 0;CreateTcpClient(); // 创建新的连接if(!(buffer = malloc(BUFFER_SIZE))){perror("malloc fail");return;}if((fd = open("recv.txt", O_RDONLY)) == -1){perror("open fail");return;}if((bytes_read = read(fd, buffer, BUFFER_SIZE)) <= 0){fprintf(stderr, "Read failed or empty file\n");return;}buffer[bytes_read] = '\0'; // 确保字符终止cJSON *root = cJSON_Parse(buffer);if(!root){fprintf(stderr, "JSON parse failed\n");return;}// 使用结构体定义字段映射struct FieldMapping {const char *json_key;const char *display_name;int required; // 1表示必须字段} fields[] = {{"days", "日期", 1},{"week", "星期", 1},{"citynm", "城市", 1},{"temperature_curr", "当前温度", 1},{"temp_high", "最高温度", 0},{"temp_low", "最低温度", 0},{"weather", "天气状况", 1},{"humidity", "当前湿度", 0},{"humi_high", "最大湿度", 0},{"humi_low", "最小湿度", 0},{"wind", "风向", 1},{"winp", "风力", 1},{"aqi", "PM2.5", 0}};cJSON *result = cJSON_GetObjectItem(root, "result");if(!result){fprintf(stderr, "Missing result object\n");cJSON_Delete(root);return;}// 遍历所有字段定义for(size_t i = 0; i < sizeof(fields)/sizeof(fields[0]); i++){cJSON *item = cJSON_GetObjectItem(result, fields[i].json_key);printf(COLOR_FIELD"%-8s: "COLOR_RESET, fields[i].display_name);if(item && item->valuestring){printf(COLOR_VALUE"%s\n"COLOR_RESET, item->valuestring);}else{printf(COLOR_WARN"N/A\n"COLOR_RESET);if(fields[i].required){fprintf(stderr, "Missing required field: %s\n", fields[i].json_key);cJSON_Delete(root);return;}}}ret = 0;free(buffer);cJSON_Delete(root);close(fd);
}
亮点分析补充与优化建议
1. 模块化设计
- 优势:
将天气查询功能封装为独立函数real_time_weather(),逻辑边界清晰,符合“高内聚”原则。 - 改进空间:
- 单一职责原则:当前函数承担了网络连接、文件操作、JSON解析、数据展示等多个职责,可进一步拆分为子函数(如
fetch_weather_data()、parse_json()、display_weather())。 - 依赖解耦:文件
recv.txt作为数据传递媒介,属于硬编码依赖,建议改用内存直接传递(如通过函数参数传递数据缓冲区)。
- 单一职责原则:当前函数承担了网络连接、文件操作、JSON解析、数据展示等多个职责,可进一步拆分为子函数(如
2. 动态内存管理
- 优势:
使用malloc动态分配内存,避免栈溢出风险,适应大数据场景。 - 改进空间:
- 内存泄漏风险:错误处理路径中未完全释放内存(如
malloc成功但open失败时未free(buffer)),需用goto或cleanup标签统一释放资源。 - 缓冲区溢出:
read(fd, buffer, BUFFER_SIZE)未检查bytes_read是否等于BUFFER_SIZE,可能导致数据截断或越界。
- 内存泄漏风险:错误处理路径中未完全释放内存(如
3. 错误处理
- 优势:
覆盖文件操作、内存分配、JSON解析等关键错误点,避免程序崩溃。 - 改进空间:
- 错误码统一管理:
ret变量未被实际使用,建议通过返回值或全局错误码明确错误类型。 - 错误信息分级:区分“致命错误”(如必填字段缺失)与“警告信息”(如可选字段缺失),避免冗余报错。
- 错误码统一管理:
4. 数据解析
- 优势:
使用cJSON库解析复杂JSON数据,通过结构体FieldMapping实现字段映射,扩展性强。 - 改进空间:
- 字段类型校验:未检查JSON字段实际类型(如数值型字段可能被误读为字符串),需增加
cJSON_IsString/cJSON_IsNumber等校验。 - 嵌套结构处理:若JSON层级更深(如
result下包含嵌套对象),需支持递归解析。
- 字段类型校验:未检查JSON字段实际类型(如数值型字段可能被误读为字符串),需增加
5. 用户输出
- 优势:
ANSI颜色代码提升可读性,字段映射表确保输出完整性。 - 改进空间:
- 跨平台兼容性:Windows终端默认不支持ANSI颜色,需通过条件编译适配。
- 格式化输出:使用固定宽度(
%-8s)可能导致长字段显示不全,建议动态调整列宽。
难点分析与解决方案
1. JSON数据解析
- 核心挑战:
- 字段路径依赖性强(如直接访问
root->result->temperature_curr),若服务器返回结构变化,需修改代码。 - 未处理特殊字符(如转义字符
\")或Unicode编码(如\u4e2d\u6587)。
- 字段路径依赖性强(如直接访问
- 解决方案:
- 使用
cJSON_GetObjectItemCaseSensitive避免大小写敏感问题。 - 通过
cJSON_Print标准化输出,确保转义字符正确处理。
- 使用
2. 网络通信
- 核心挑战:
CreateTcpClient()实现未展示,可能隐藏连接超时、重试等逻辑缺失。- 未处理HTTP协议细节(如状态码检查、分块传输编码)。
- 解决方案:
- 封装HTTP客户端库(如
libcurl)简化网络操作。 - 增加超时机制和重试逻辑,提升鲁棒性。
- 封装HTTP客户端库(如
3. 文件操作
- 核心挑战:
- 文件读写与网络通信耦合,可能导致数据不同步(如文件未及时刷新)。
- 解决方案:
- 使用内存映射文件(
mmap)或管道(pipe)替代临时文件。 - 增加文件锁(
flock)避免多进程竞争。
- 使用内存映射文件(
future_weather() 函数
void future_weather()
{int ret = -1;int fd = -1;char *buffer = NULL;ssize_t bytes_read = 0;CreateTcpClient(); // 创建新的连接if(!(buffer = malloc(BUFFER_SIZE))){perror("malloc fail");return;}if((fd = open("recv.txt",O_RDONLY)) == -1){perror("open fail");return;}if((bytes_read = read(fd,buffer,BUFFER_SIZE)) <= 0){fprintf(stderr, "Read failed or empty file\n");return;}buffer[bytes_read] = '\0';//确保字符终止cJSON *root = cJSON_Parse(buffer);if(!root){fprintf(stderr, "JSON parse failed\n");return;}// 使用结构体定义字段映射struct FieldMapping {const char *json_key;const char *display_name;int required; // 1表示必须字段} fields[] = {{"days", "日期", 1},{"week", "星期", 1},{"citynm", "城市", 1},{"temp_high", "最高温度", 0},{"temp_low", "最低温度", 0},{"weather", "天气状况", 1},{"wind", "风向", 1},{"winp", "风力", 1},};cJSON *result = cJSON_GetObjectItem(root,"result");if(!result){fprintf(stderr, "Missing result object\n");cJSON_Delete(root);}int i = 0;cJSON *item;cJSON_ArrayForEach(item,result){printf(COLOR_HEAD"=============第%d天气预报=============\n"COLOR_RESET,i+1);// 遍历所有字段定义for (size_t j = 0; j < sizeof(fields)/sizeof(fields[0]); j++) {cJSON *field = cJSON_GetObjectItem(item, fields[j].json_key);printf(COLOR_FIELD"%-8s: "COLOR_RESET, fields[j].display_name);if (field && field->valuestring) {printf(COLOR_VALUE"%s\n"COLOR_RESET, field->valuestring);} else {printf(COLOR_WARN"N/A\n"COLOR_RESET);if (fields[j].required) {fprintf(stderr, "Missing required field: %s\n", fields[j].json_key);}}}i++;} cJSON_Delete(root);free(buffer);close(fd);
}
功能概述
该函数用于获取并展示未来多天的天气预报数据,整体流程如下:
- 建立TCP连接获取数据(依赖
CreateTcpClient())。 - 从文件
recv.txt读取JSON格式的天气数据。 - 解析JSON数据并格式化输出未来多天的天气信息。
亮点与改进建议
1. 核心亮点
-
数据遍历逻辑
使用cJSON_ArrayForEach遍历天气预报的多个结果,支持动态数量的天气预报条目,灵活性较高。cJSON_ArrayForEach(item, result) {printf("第%d天天气预报", i+1);// 遍历字段并输出 } -
字段映射复用
复用FieldMapping结构体定义字段映射关系,与real_time_weather()保持一致性,降低维护成本。 -
用户交互友好
通过颜色区分标题、字段名和数值,输出层次清晰:printf(COLOR_HEAD"=============第%d天气预报=============\n"COLOR_RESET, i+1);
2. 潜在问题与改进
问题1:内存与资源泄漏
-
风险点
- 文件打开失败(
open返回-1)时,未释放malloc分配的buffer。 - JSON解析失败时,未关闭文件描述符
fd。
- 文件打开失败(
-
修复建议
使用统一清理逻辑确保资源释放:void future_weather() {int fd = -1;char *buffer = NULL;cJSON *root = NULL;// 初始化代码...// 错误处理跳转标签cleanup:if (buffer) free(buffer);if (root) cJSON_Delete(root);if (fd != -1) close(fd); }
问题2:JSON类型安全性
-
风险点
直接访问field->valuestring,若字段值为数值类型(如温度),会导致空指针或错误输出。// 错误示例:若temp_high为数值,valuestring为NULL printf("%s", field->valuestring); -
修复建议
增加类型检查,支持数值和字符串:if (cJSON_IsString(field)) {printf("%s", field->valuestring); } else if (cJSON_IsNumber(field)) {printf("%d", field->valueint); }
问题3:数据完整性风险
-
风险点
假设result为数组类型,若服务器返回非数组数据(如空对象),程序会错误遍历。cJSON *result = cJSON_GetObjectItem(root, "result"); cJSON_ArrayForEach(item, result); // 若result非数组,崩溃! -
修复建议
验证数据类型:if (!cJSON_IsArray(result)) {fprintf(stderr, "Invalid result format: expected array\n");goto cleanup; }
问题4:代码冗余
-
风险点
future_weather与real_time_weather存在大量重复代码(如文件操作、JSON解析),违反DRY原则。 -
修复建议
抽象公共逻辑为独立函数:// 公共函数:读取文件到缓冲区 char* read_weather_data(const char *filename) {int fd = open(filename, O_RDONLY);// 读取并返回buffer... }// 公共函数:解析并打印天气字段 void print_weather_fields(cJSON *item, struct FieldMapping *fields, size_t count) {// 遍历字段并输出... }
难点与解决方案
1. 动态数组遍历
- 难点
需处理未知数量的天气预报条目,且每个条目需完整解析字段。 - 方案
使用cJSON_ArrayForEach宏安全遍历数组,避免手动索引越界。
2. 多层级JSON解析
- 难点
若JSON结构复杂(如嵌套对象),需递归解析。 - 方案
设计递归解析函数,处理嵌套结构:void parse_nested(cJSON *node, int depth) {if (cJSON_IsObject(node)) {cJSON_ArrayForEach(child, node) {parse_nested(child, depth + 1);}} }
相关文章:
linux---天气爬虫
代码概述 这段代码实现了一个天气查询系统,支持实时天气、未来天气和历史天气查询。用户可以通过终端菜单选择查询类型,并输入城市名称来获取相应的天气信息。程序通过 TCP 连接发送 HTTP 请求,并解析返回的 JSON 数据来展示天气信息。 #in…...
STM32如何精准控制步进电机?
在工业自动化、机器人控制等场合,步进电机以其高精度、开环控制的特性得到了广泛应用。而在嵌入式系统中,使用STM32进行步进电机的精确控制,已成为开发者的首选方案之一。 本文将从嵌入式开发者的角度,深入探讨如何基于STM32 MCU…...
C语言:确定进制
题目: 6942对于十进制来说是错误的,但是对于13进制来说是正确的。即, 6(13) 9(13) 42(13), 而 42(13)4131213054(10)。 任务是写一段程序,读入三个整数p、q和 r,然后确定一个进制 B(2<B<40) 使得 p q r。 如果…...
[免费]微信小程序(图书馆)自习室座位预约管理系统(SpringBoot后端+Vue管理端)(高级版)【论文+源码+SQL脚本】
大家好,我是java1234_小锋老师,看到一个不错的微信小程序(图书馆)自习室座位预约管理系统(SpringBoot后端Vue管理端)(高级版),分享下哈。 项目视频演示 【免费】微信小程序(图书馆)自习室座位预约管理系统(SpringBoot后端Vue管理端)(高级版…...
STM32 Bootloader理解
STM32 Bootloader个人理解 stm32单片机启动时会先运行一个引导程序Bootloader,该程序可以判断单片机的启动方式,例如stm32f103单片机会利用 boot0 、boot1 两个引脚判断启动模式。判断完启动模式后,设置 SP地址 以及 PC 指针指向对应的地址。…...
Linux SSHD 启动失败:OpenSSL 版本不匹配问题分析与解决
文章目录 Linux SSHD 启动失败:OpenSSL 版本不匹配问题分析与解决问题分析解决方案方法 1:重启 SSH 服务方法 2:检查 sshd 依赖的 OpenSSL 版本方法 3:检查 OpenSSL 共享库方法 4:重新安装 OpenSSH 总结 Linux SSHD 启…...
SpringBoot实战(三十五)微服务集成OAuth2.0(UAA)
目录 一、知识回顾1.1 什么是 OAuth2 协议?1.2 OAuth2 的4个角色1.3 OAuth2 的3种令牌1.4 OAuth2 的5种认证方式1.5 OAuth2 内置接口地址 二、UAA介绍2.1 概述2.2 UAA的主要功能2.3 UAA 的应用场景 三、微服务集成3.1 集成示例介绍3.2 集成测试 一、知识回顾 在进行…...
K8s 1.27.1 实战系列(七)Deployment
一、Deployment介绍 Deployment负责创建和更新应用程序的实例,使Pod拥有多副本,自愈,扩缩容等能力。创建Deployment后,Kubernetes Master 将应用程序实例调度到集群中的各个节点上。如果托管实例的节点关闭或被删除,Deployment控制器会将该实例替换为群集中另一个节点上的…...
Spring Boot笔记(上)
01 概要 Spring Boot 是 Java 领域最流行的 快速开发框架,专为简化 Spring 应用的初始搭建和开发而设计。 一、Spring Boot 解决了什么问题? 传统 Spring 痛点 • 繁琐的 XML 配置 • 需要手动管理依赖版本 • 部署依赖外部 Web 服务器(如 …...
Mysql主从复制和Mysql高可用以及负载均衡配置
需要先配置MySQL主从复制,然后再在主MySQL服务器上配置MySQL Router。以下是详细说明和步骤: 1. 为什么需要先配置MySQL主从复制? MySQL主从复制是MySQL高可用性和负载均衡的基础,通过将数据从主服务器实时同步到从服务器&#…...
MySQL------存储引擎和用户和授权
9.存储引擎 1.两种引擎 MyISAM和InnoDB 2.两种区别 1.事务: MyISAM不支持事务 2.存储文件: innodb : frm、ibd MyISAM: frm、MYD、MYI 3.数据行锁定: MyISAM不支持 4.全文索引: INNODB不支持,所以MYISAM做select操作速度很快 5.外键约束: MyISAM…...
DeepSeek进阶应用(一):结合Mermaid绘图(流程图、时序图、类图、状态图、甘特图、饼图)
🌟前言: 在软件开发、项目管理和系统设计等领域,图表是表达复杂信息的有效工具。随着AI助手如DeepSeek的普及,我们现在可以更轻松地创建各种专业图表。 名人说:博观而约取,厚积而薄发。——苏轼《稼说送张琥》 创作者&…...
大白话react第十八章React 与 WebGL 项目的高级拓展与优化
大白话react第十八章React 与 WebGL 项目的高级拓展与优化 1. 实现 3D 模型的导入与动画 在之前的基础上,我们可以导入更复杂的 3D 模型,并且让这些模型动起来,就像在游戏里看到的角色和场景一样。这里我们使用 GLTF 格式的模型,…...
【WPF】Slider滑动方法(INotifyPropertyChanged、ValueChanged )响应速度对比分析
一、Slider基础用法 在 XAML 中添加一个 Slider 控件,并设置其基本属性: <Slider Minimum"0" <!-- 最小值 -->Maximum"100" <!-- 最大值 -->Value"50" <!-- 初始值 -->Width&quo…...
DeepSeek未来发展趋势:开创智能时代的新风口
DeepSeek未来发展趋势:开创智能时代的新风口 随着人工智能(AI)、深度学习(DL)和大数据的飞速发展,众多创新型技术已经逐渐走向成熟,而DeepSeek作为这一领域的新兴力量,正逐步吸引越…...
1-003:MySQL 的索引类型有哪些?
MySQL 中的索引类型主要分为以下几类,每种索引都有不同的适用场景和优化查询的作用: 1. 按存储结构分类 ① 聚簇索引(Clustered Index) 特点: InnoDB 引擎的 主键索引 就是 聚簇索引。数据与索引存储在一起ÿ…...
从0开始的操作系统手搓教程24——完成我们的键盘驱动子系统
目录 所以,我们现来说说转义字符 我们需要如何处理扫描码 当键入的是双字符键时 当键入的是字母键时 下一篇 我们下面来看看我们的键盘驱动子系统是一个怎么个事情。 驱动程序,你可以认为是对硬件的一层封装。我们按照手册规格的规定姿势࿰…...
git大文件传输报错
简述 git传输大于25M的文件时会报错,需要使用 Git LFS进行文件传输。 Git LFS(Large File Storage)是 GitHub 推荐的方式,可以管理大文件而不会影响 Git 性能。 操作流程 # 安装 Git LFS git lfs install# 将 PDF 文件添加到 G…...
基础玩转物联网-4G模块如何快速实现与MQTT服务器通信
目录 1 前言 2 环境搭建 2.1 硬件准备 2.2 软件准备 2.3 硬件连接 2.4 检查驱动 3 连接MQTT服务器 3.1 创建MQTT监听Topic 3.2 打开配置工具读取基本信息 3.3 设置连接参数进行数据交互 4 总结 1 前言 MQTT(Message Queuing Telemetry Transport)是一种轻…...
使用Beanshell前置处理器对Jmeter的请求body进行加密
这里我们用HmacSHA256来进行加密举例: 步骤: 1.先获取请求参数并对请求参数进行处理(处理成String类型) //处理请求参数的两种方法: //方法一: //获取请求 Arguments args sampler.getArguments(); //转…...
Python入门3:类与面对对象
目录 类 一、类的概念 二、类的定义和使用 2.1 类的定义 2.2 实例化对象 三、类的属性和方法 3.1 属性 属性的类型: 补充--私有属性 属性的操作: 3.2 方法 方法的类型: 补充--私有方法 方法的操作 四、面对过程和面对对象 …...
mac本地部署Qwq-32b记录
导语 昨天看到阿里开源了Qwq-32b,号称性能可以媲美Deepseek-R1。今天晚上有空就在Mac上折腾了一下,使用ollma进行了部署,效果感觉还不错,特此记录。 环境 硬件 型号:Macbook M1 Pro 14寸内存:512G 环境…...
【病毒分析】熊猫烧香病毒分析及其查杀修复
目录 前言 一、样本概况 1.1 样本信息 1.2 测试环境及工具 1.3 分析目标 二、具体行为分析 2.1 主要行为 2.1.1 恶意程序对用户造成的危害 2.2 恶意代码分析 2.2.1 加固后的恶意代码树结构图(是否有加固) 2.2.2 恶意程序的代码分析片段 三、解决方案(或总结) 3.1 …...
【语料数据爬虫】Python实现将Json语料数据转换成Word文档
前言 本文是该专栏的第1篇,后面会持续分享Python爬虫采集各种语料数据的的干货知识,值得关注。 本专栏为笔者精心推出的“语料数据”爬虫专栏,特别适合需要写作素材的同学,该专栏文章以采集最新的“语料数据”为主,最终篇幅将涵盖【百万级语料数据】库。 值得一提的是,…...
警惕AI神话破灭:深度解析大模型缺陷与禁用场景指南
摘要 当前AI大模型虽展现强大能力,但其本质缺陷可能引发系统性风险。本文从认知鸿沟、数据困境、伦理雷区、技术瓶颈四大维度剖析大模型局限性,揭示医疗诊断、法律决策等8类禁用场景,提出可信AI建设框架与用户防护策略。通过理论分析与实操案…...
做到哪一步才算精通SQL
做到哪一步才算精通SQL-Structured Query Language 数据定义语言 DDL for StructCREATE:用来创建数据库、表、索引等对象ALTER:用来修改已存在的数据库对象DROP:用来删除整个数据库或者数据库中的表TRUNCATE:用来删除表中所有的行…...
leetcode454 四数相加
四数相加Ⅱ的解法可以将四数分为两组,即“分组 哈希”: 初始化哈希表。 分组:nums1 和 nums2 一组,nums3 和 nums4 一组。 分别对 nums1 和 nums2 进行遍历,将所有 nums1 和 nums2 的值的和作为哈希表的 key&#x…...
RoboVQA:机器人多模态长范围推理
23 年 11 月来自 Google Deepmind 的论文“RoboVQA: Multimodal Long-Horizon Reasoning for Robotics”。 本文提出一种可扩展、自下而上且本质多样化的数据收集方案,该方案可用于长期和中期的高级推理,与传统的狭窄自上而下的逐步收集相比,…...
C 语言数据结构(二):顺序表和链表
目录 1. 线性表 2. 顺序表 2.1 概念及结构 2.1.1 静态顺序表(不常用) 2.1.2 动态顺序表(常用) 编辑 2.2 练习 2.2.1 移除元素 2.2.2 删除有序数组中的重复项 2.2.3 合并两个有序数组 2.3 顺序表存在的问题 3. 链表 …...
无公网IP也能远程控制Windows:Linux rdesktop内网穿透实战
文章目录 前言1. Windows 开启远程桌面2. Linux安装rdesktop工具3. Win安装Cpolar工具4. 配置远程桌面地址5. 远程桌面连接测试6. 设置固定远程地址7. 固定地址连接测试 前言 如今远程办公已经从一种选择变成了许多企业和个人的必修课,而如何在Linux系统上高效地访…...
