当前位置: 首页 > article >正文

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_startpos_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() 函数

  • 功能:主程序入口,循环显示菜单并处理用户输入。

  • 实现

    • 清屏并初始化终端。

    • 循环调用 viewMenuChoose,实现菜单显示和交互。

二、重点函数

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
  • 关闭文件描述符和套接字。

潜在问题与改进建议

  1. 缓冲区溢出风险

    • 问题buffcmpbuff是固定大小,未检查strncat是否越界。
    • 建议:增加长度检查,或改用动态内存分配。
  2. 分块编码处理不完整

    • 问题:代码假设数据仅包含一个分块(如5\r\nhello\r\n0\r\n),可能漏掉多分块数据。
    • 建议:循环解析所有分块,处理格式如长度\r\n数据\r\n
  3. 结束标志可靠性

    • 问题:若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作为数据传递媒介,属于硬编码依赖,建议改用内存直接传递(如通过函数参数传递数据缓冲区)。
2. 动态内存管理
  • 优势
    使用malloc动态分配内存,避免栈溢出风险,适应大数据场景。
  • 改进空间
    • 内存泄漏风险:错误处理路径中未完全释放内存(如malloc成功但open失败时未free(buffer)),需用gotocleanup标签统一释放资源。
    • 缓冲区溢出read(fd, buffer, BUFFER_SIZE)未检查bytes_read是否等于BUFFER_SIZE,可能导致数据截断或越界。
3. 错误处理
  • 优势
    覆盖文件操作、内存分配、JSON解析等关键错误点,避免程序崩溃。
  • 改进空间
    • 错误码统一管理ret变量未被实际使用,建议通过返回值或全局错误码明确错误类型。
    • 错误信息分级:区分“致命错误”(如必填字段缺失)与“警告信息”(如可选字段缺失),避免冗余报错。
4. 数据解析
  • 优势
    使用cJSON库解析复杂JSON数据,通过结构体FieldMapping实现字段映射,扩展性强。
  • 改进空间
    • 字段类型校验:未检查JSON字段实际类型(如数值型字段可能被误读为字符串),需增加cJSON_IsString/cJSON_IsNumber等校验。
    • 嵌套结构处理:若JSON层级更深(如result下包含嵌套对象),需支持递归解析。
5. 用户输出
  • 优势
    ANSI颜色代码提升可读性,字段映射表确保输出完整性。
  • 改进空间
    • 跨平台兼容性:Windows终端默认不支持ANSI颜色,需通过条件编译适配。
    • 格式化输出:使用固定宽度(%-8s)可能导致长字段显示不全,建议动态调整列宽。

难点分析与解决方案
1. JSON数据解析
  • 核心挑战
    • 字段路径依赖性强(如直接访问root->result->temperature_curr),若服务器返回结构变化,需修改代码。
    • 未处理特殊字符(如转义字符\")或Unicode编码(如\u4e2d\u6587)。
  • 解决方案
    • 使用cJSON_GetObjectItemCaseSensitive避免大小写敏感问题。
    • 通过cJSON_Print标准化输出,确保转义字符正确处理。
2. 网络通信
  • 核心挑战
    • CreateTcpClient()实现未展示,可能隐藏连接超时、重试等逻辑缺失。
    • 未处理HTTP协议细节(如状态码检查、分块传输编码)。
  • 解决方案
    • 封装HTTP客户端库(如libcurl)简化网络操作。
    • 增加超时机制和重试逻辑,提升鲁棒性。
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);
}
功能概述

该函数用于获取并展示未来多天的天气预报数据,整体流程如下:

  1. 建立TCP连接获取数据(依赖CreateTcpClient())。
  2. 从文件recv.txt读取JSON格式的天气数据。
  3. 解析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_weatherreal_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---天气爬虫

代码概述 这段代码实现了一个天气查询系统&#xff0c;支持实时天气、未来天气和历史天气查询。用户可以通过终端菜单选择查询类型&#xff0c;并输入城市名称来获取相应的天气信息。程序通过 TCP 连接发送 HTTP 请求&#xff0c;并解析返回的 JSON 数据来展示天气信息。 #in…...

STM32如何精准控制步进电机?

在工业自动化、机器人控制等场合&#xff0c;步进电机以其高精度、开环控制的特性得到了广泛应用。而在嵌入式系统中&#xff0c;使用STM32进行步进电机的精确控制&#xff0c;已成为开发者的首选方案之一。 本文将从嵌入式开发者的角度&#xff0c;深入探讨如何基于STM32 MCU…...

C语言:确定进制

题目&#xff1a; 6942对于十进制来说是错误的&#xff0c;但是对于13进制来说是正确的。即, 6(13) 9(13) 42(13)&#xff0c; 而 42(13)4131213054(10)。 任务是写一段程序&#xff0c;读入三个整数p、q和 r&#xff0c;然后确定一个进制 B(2<B<40) 使得 p q r。 如果…...

[免费]微信小程序(图书馆)自习室座位预约管理系统(SpringBoot后端+Vue管理端)(高级版)【论文+源码+SQL脚本】

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的微信小程序(图书馆)自习室座位预约管理系统(SpringBoot后端Vue管理端)(高级版)&#xff0c;分享下哈。 项目视频演示 【免费】微信小程序(图书馆)自习室座位预约管理系统(SpringBoot后端Vue管理端)(高级版…...

STM32 Bootloader理解

STM32 Bootloader个人理解 stm32单片机启动时会先运行一个引导程序Bootloader&#xff0c;该程序可以判断单片机的启动方式&#xff0c;例如stm32f103单片机会利用 boot0 、boot1 两个引脚判断启动模式。判断完启动模式后&#xff0c;设置 SP地址 以及 PC 指针指向对应的地址。…...

Linux SSHD 启动失败:OpenSSL 版本不匹配问题分析与解决

文章目录 Linux SSHD 启动失败&#xff1a;OpenSSL 版本不匹配问题分析与解决问题分析解决方案方法 1&#xff1a;重启 SSH 服务方法 2&#xff1a;检查 sshd 依赖的 OpenSSL 版本方法 3&#xff1a;检查 OpenSSL 共享库方法 4&#xff1a;重新安装 OpenSSH 总结 Linux SSHD 启…...

SpringBoot实战(三十五)微服务集成OAuth2.0(UAA)

目录 一、知识回顾1.1 什么是 OAuth2 协议&#xff1f;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 领域最流行的 快速开发框架&#xff0c;专为简化 Spring 应用的初始搭建和开发而设计。 一、Spring Boot 解决了什么问题&#xff1f; 传统 Spring 痛点 • 繁琐的 XML 配置 • 需要手动管理依赖版本 • 部署依赖外部 Web 服务器&#xff08;如 …...

Mysql主从复制和Mysql高可用以及负载均衡配置

需要先配置MySQL主从复制&#xff0c;然后再在主MySQL服务器上配置MySQL Router。以下是详细说明和步骤&#xff1a; 1. 为什么需要先配置MySQL主从复制&#xff1f; MySQL主从复制是MySQL高可用性和负载均衡的基础&#xff0c;通过将数据从主服务器实时同步到从服务器&#…...

MySQL------存储引擎和用户和授权

9.存储引擎 1.两种引擎 MyISAM和InnoDB 2.两种区别 1.事务&#xff1a; MyISAM不支持事务 2.存储文件: innodb : frm、ibd MyISAM: frm、MYD、MYI 3.数据行锁定: MyISAM不支持 4.全文索引: INNODB不支持&#xff0c;所以MYISAM做select操作速度很快 5.外键约束: MyISAM…...

DeepSeek进阶应用(一):结合Mermaid绘图(流程图、时序图、类图、状态图、甘特图、饼图)

&#x1f31f;前言: 在软件开发、项目管理和系统设计等领域&#xff0c;图表是表达复杂信息的有效工具。随着AI助手如DeepSeek的普及&#xff0c;我们现在可以更轻松地创建各种专业图表。 名人说&#xff1a;博观而约取&#xff0c;厚积而薄发。——苏轼《稼说送张琥》 创作者&…...

大白话react第十八章React 与 WebGL 项目的高级拓展与优化

大白话react第十八章React 与 WebGL 项目的高级拓展与优化 1. 实现 3D 模型的导入与动画 在之前的基础上&#xff0c;我们可以导入更复杂的 3D 模型&#xff0c;并且让这些模型动起来&#xff0c;就像在游戏里看到的角色和场景一样。这里我们使用 GLTF 格式的模型&#xff0c…...

【WPF】Slider滑动方法(INotifyPropertyChanged、ValueChanged )响应速度对比分析

一、Slider基础用法 在 XAML 中添加一个 Slider 控件&#xff0c;并设置其基本属性&#xff1a; <Slider Minimum"0" <!-- 最小值 -->Maximum"100" <!-- 最大值 -->Value"50" <!-- 初始值 -->Width&quo…...

DeepSeek未来发展趋势:开创智能时代的新风口

DeepSeek未来发展趋势&#xff1a;开创智能时代的新风口 随着人工智能&#xff08;AI&#xff09;、深度学习&#xff08;DL&#xff09;和大数据的飞速发展&#xff0c;众多创新型技术已经逐渐走向成熟&#xff0c;而DeepSeek作为这一领域的新兴力量&#xff0c;正逐步吸引越…...

1-003:MySQL 的索引类型有哪些?

MySQL 中的索引类型主要分为以下几类&#xff0c;每种索引都有不同的适用场景和优化查询的作用&#xff1a; 1. 按存储结构分类 ① 聚簇索引&#xff08;Clustered Index&#xff09; 特点&#xff1a; InnoDB 引擎的 主键索引 就是 聚簇索引。数据与索引存储在一起&#xff…...

从0开始的操作系统手搓教程24——完成我们的键盘驱动子系统

目录 所以&#xff0c;我们现来说说转义字符 我们需要如何处理扫描码 当键入的是双字符键时 当键入的是字母键时 下一篇 我们下面来看看我们的键盘驱动子系统是一个怎么个事情。 驱动程序&#xff0c;你可以认为是对硬件的一层封装。我们按照手册规格的规定姿势&#xff0…...

git大文件传输报错

简述 git传输大于25M的文件时会报错&#xff0c;需要使用 Git LFS进行文件传输。 Git LFS&#xff08;Large File Storage&#xff09;是 GitHub 推荐的方式&#xff0c;可以管理大文件而不会影响 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&#xff08;Message Queuing Telemetry Transport&#xff09;是一种轻…...

使用Beanshell前置处理器对Jmeter的请求body进行加密

这里我们用HmacSHA256来进行加密举例&#xff1a; 步骤&#xff1a; 1.先获取请求参数并对请求参数进行处理&#xff08;处理成String类型&#xff09; //处理请求参数的两种方法&#xff1a; //方法一&#xff1a; //获取请求 Arguments args sampler.getArguments(); //转…...

Python入门3:类与面对对象

目录 类 一、类的概念 二、类的定义和使用 2.1 类的定义 2.2 实例化对象 三、类的属性和方法 3.1 属性 属性的类型&#xff1a; 补充--私有属性 属性的操作&#xff1a; 3.2 方法 方法的类型&#xff1a; 补充--私有方法 方法的操作 四、面对过程和面对对象 …...

mac本地部署Qwq-32b记录

导语 昨天看到阿里开源了Qwq-32b&#xff0c;号称性能可以媲美Deepseek-R1。今天晚上有空就在Mac上折腾了一下&#xff0c;使用ollma进行了部署&#xff0c;效果感觉还不错&#xff0c;特此记录。 环境 硬件 型号&#xff1a;Macbook M1 Pro 14寸内存&#xff1a;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大模型虽展现强大能力&#xff0c;但其本质缺陷可能引发系统性风险。本文从认知鸿沟、数据困境、伦理雷区、技术瓶颈四大维度剖析大模型局限性&#xff0c;揭示医疗诊断、法律决策等8类禁用场景&#xff0c;提出可信AI建设框架与用户防护策略。通过理论分析与实操案…...

做到哪一步才算精通SQL

做到哪一步才算精通SQL-Structured Query Language 数据定义语言 DDL for StructCREATE&#xff1a;用来创建数据库、表、索引等对象ALTER&#xff1a;用来修改已存在的数据库对象DROP&#xff1a;用来删除整个数据库或者数据库中的表TRUNCATE&#xff1a;用来删除表中所有的行…...

leetcode454 四数相加

四数相加Ⅱ的解法可以将四数分为两组&#xff0c;即“分组 哈希”&#xff1a; 初始化哈希表。 分组&#xff1a;nums1 和 nums2 一组&#xff0c;nums3 和 nums4 一组。 分别对 nums1 和 nums2 进行遍历&#xff0c;将所有 nums1 和 nums2 的值的和作为哈希表的 key&#x…...

RoboVQA:机器人多模态长范围推理

23 年 11 月来自 Google Deepmind 的论文“RoboVQA: Multimodal Long-Horizon Reasoning for Robotics”。 本文提出一种可扩展、自下而上且本质多样化的数据收集方案&#xff0c;该方案可用于长期和中期的高级推理&#xff0c;与传统的狭窄自上而下的逐步收集相比&#xff0c…...

C 语言数据结构(二):顺序表和链表

目录 1. 线性表 2. 顺序表 2.1 概念及结构 2.1.1 静态顺序表&#xff08;不常用&#xff09; 2.1.2 动态顺序表&#xff08;常用&#xff09; ​编辑 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. 固定地址连接测试 前言 如今远程办公已经从一种选择变成了许多企业和个人的必修课&#xff0c;而如何在Linux系统上高效地访…...