利用Python高效获取USDA食品营养数据:API分页处理详解

利用Python高效获取USDA食品营养数据:API分页处理详解

本教程详细讲解如何利用python和USDA食品数据中心API获取完整的食品营养数据。针对API默认50条结果的限制,我们将深入探讨API分页机制,包括pageSize和pageNumber参数的使用。通过迭代请求、优化代码结构及错误处理,本文将提供一个高效的解决方案,帮助用户克服API分页限制,全面抓取并处理所有可用的食品营养信息。

理解API分页机制

在使用外部api获取数据时,经常会遇到api对单次请求返回结果数量的限制。这是一种常见的机制,旨在防止单个请求消耗过多服务器资源,并提高api响应速度。usda食品数据中心(fdc)api也不例外,其默认情况下每次请求可能只返回50条结果。

要解决这个问题,关键在于查阅API的官方文档。通过仔细阅读API文档,我们可以发现FDC API支持分页查询,这意味着数据被分割成多个“页面”,每次请求只能获取一个页面的数据。文档中明确指出了两个关键参数:

  • pageSize:定义了每个页面返回结果的最大数量。默认值为50,但根据文档,最大可以设置为200。
  • pageNumber:指定要请求的页面编号。通过递增此参数,可以逐页获取所有数据。

API响应中通常会包含currentPage(当前页码)和totalPages(总页数)等信息,这些信息对于实现完整的数据遍历至关重要。

分页数据获取的Python实现

为了获取所有符合条件的食品营养数据,我们需要编写一个循环,在每次迭代中请求不同的页面,直到所有页面都被访问。以下是实现这一目标的Python代码示例,它对原始请求逻辑进行了优化和扩展。

优化后的api调用函数

import requests import JSon import pandas as pd  def get_all_usda_foods(query_term, api_key):     """     通过USDA食品数据中心API获取所有匹配的食品营养数据。     该函数处理API分页,并返回一个包含所有食品项的列表。      Args:         query_term (str): 查询的食品名称或关键词,例如 'raw'。         api_key (str): 您的USDA API密钥。      Returns:         list: 包含所有食品项字典的列表,如果请求失败则返回None。     """     all_foods = []     # 构造基础URL,设置pageSize为最大值200以减少请求次数     base_url = f"https://api.nal.usda.gov/fdc/v1/foods/search?api_key={api_key}&query={query_term}&pageSize=200"      with requests.Session() as session: # 使用requests.Session提高效率         try:             # 首次请求获取总页数             print(f"Fetching initial page for query: '{query_term}'...")             initial_response = session.get(base_url, timeout=10)             initial_response.raise_for_status() # 检查HTTP请求是否成功             api_response_data = initial_response.json()              if not api_response_data.get("foods"):                 print(f"No foods found for query: '{query_term}'.")                 return []              total_pages = api_response_data.get("totalPages", 1)             print(f"Total pages to retrieve: {total_pages}")              all_foods.extend(api_response_data["foods"]) # 添加第一页的数据              # 遍历剩余页面             for page_num in range(2, total_pages + 1):                 print(f"Fetching page {page_num}/{total_pages}...")                 page_url = f"{base_url}&pageNumber={page_num}"                 page_response = session.get(page_url, timeout=10)                 page_response.raise_for_status()                 page_data = page_response.json()                 all_foods.extend(page_data["foods"])          except requests.exceptions.HTTPError as http_err:             print(f"HTTP error occurred: {http_err} - Status Code: {http_err.response.status_code}")             return None         except requests.exceptions.ConnectionError as conn_err:             print(f"Connection error occurred: {conn_err}")             return None         except requests.exceptions.Timeout as timeout_err:             print(f"Request timed out: {timeout_err}")             return None         except requests.exceptions.RequestException as req_err:             print(f"An unexpected error occurred: {req_err}")             return None         except json.JSONDecodeError as json_err:             print(f"Error decoding JSON response: {json_err}")             print(f"Response content: {initial_response.text if 'initial_response' in locals() else 'N/A'}")             return None     return all_foods  # 替换为您的实际API密钥 API_KEY = "YOUR_USDA_API_KEY"  food_items = get_all_usda_foods("raw", API_KEY)  if food_items:     print(f"nSuccessfully retrieved {len(food_items)} food items.")     # 后续数据处理... else:     print("Failed to retrieve food items or no items found.") 

代码解析与优化点

  1. requests.Session的使用: requests.Session对象可以在多次请求中保持某些参数(如cookies、请求头),并且会重用底层的TCP连接,这对于进行多次API请求(如分页)来说,可以显著提高效率和性能。

  2. pageSize参数优化: 我们将pageSize直接设置为API允许的最大值200。这样做可以减少需要发起的API请求总数,从而缩短数据获取时间。

  3. 分页逻辑

    立即学习Python免费学习笔记(深入)”;

    • 首先发起一个请求来获取第一页的数据,并从响应中提取totalPages(总页数)。
    • 将第一页的数据添加到all_foods列表中。
    • 然后,使用for循环从第2页开始(因为第1页已经获取),一直迭代到totalPages。
    • 在每次循环中,通过添加&pageNumber={page_num}参数来构造新的URL,并发送请求获取当前页的数据。
    • 将每页获取到的foods列表扩展到all_foods中。
  4. 健壮的错误处理

    • try…except块用于捕获各种可能发生的网络和API错误,例如requests.exceptions.HTTPError(HTTP状态码非200)、requests.exceptions.ConnectionError(连接失败)、requests.exceptions.Timeout(请求超时)以及json.JSONDecodeError(JSON解析失败)。
    • response.raise_for_status()是一个便捷的方法,如果HTTP请求返回了错误的状态码(如4xx或5xx),它会抛出一个HTTPError异常。

数据处理与导出

获取到完整的食品数据列表food_items后,下一步就是将其转换为结构化的数据格式(如Pandas DataFrame),并导出到excel文件。

# 假设food_items已经通过get_all_usda_foods函数获取 if food_items:     table_data = []     for food_item in food_items:         row = {             "Description": food_item.get("description", "N/A"),             "FDC_ID": food_item.get("fdcId", "N/A") # 添加FDC ID以便追踪         }          # 提取营养成分         for nutrient in food_item.get("foodNutrients", []):             nutrient_name = nutrient.get("nutrientName")             nutrient_value = nutrient.get("value")             if nutrient_name and nutrient_value is not None:                 row[nutrient_name] = nutrient_value         table_data.append(row)      # 创建DataFrame     df = pd.DataFrame(table_data)      # 打印一些信息,例如数据框的形状和前几行     print("nDataFrame created:")     print(f"Shape: {df.shape}")     print("First 5 rows:")     print(df.head())      # 导出到Excel     output_filename = 'usda_nutritional_facts_all.xlsx'     try:         df.to_excel(output_filename, index=False)         print(f"nData successfully exported to {output_filename}")     except Exception as e:         print(f"Error exporting to Excel: {e}") else:     print("No data to process or export.") 

在这个数据处理阶段,我们:

  1. 遍历food_items列表中的每一个食品字典。
  2. 为每个食品创建一个行字典,包含Description和FDC_ID等基本信息。
  3. 遍历foodNutrients列表,将每个营养成分的名称作为列名,其值作为单元格内容。
  4. 使用pd.DataFrame()将列表中的所有行字典转换为一个Pandas DataFrame。
  5. 最后,使用df.to_excel()方法将DataFrame导出为Excel文件,index=False表示不将DataFrame的索引写入Excel。

注意事项与最佳实践

  • API文档是黄金法则:在与任何API交互之前,务必仔细阅读其官方文档。它是理解API功能、参数、限制和错误代码的唯一权威来源。
  • 错误处理:始终在您的代码中包含健壮的错误处理机制。网络不稳定、API密钥失效、请求参数错误等都可能导致程序崩溃。良好的错误处理能提高程序的稳定性和用户体验。
  • API密钥安全:切勿将API密钥硬编码到公共代码仓库或客户端代码中。应通过环境变量、配置文件或秘密管理服务来安全地存储和访问API密钥。
  • 速率限制:某些API会有请求频率限制(rate limiting),即在一定时间内允许的请求次数。如果超出限制,API会返回错误。虽然USDA FDC API的文档中没有明确提及严格的速率限制,但在进行大量请求时仍需注意。如果遇到此类问题,可能需要引入延迟(time.sleep())来避免被封禁。
  • 数据量考量:如果获取的数据量非常大,一次性加载到内存中可能会导致内存溢出。在这种情况下,可以考虑分批处理数据,或直接将数据流式写入文件,而不是先全部收集到内存中再写入。

总结

通过本教程,我们深入探讨了如何利用Python克服USDA食品数据中心API的分页限制,从而获取完整的食品营养数据。核心在于理解API文档中关于pageSize和pageNumber参数的定义,并结合requests.Session进行高效的迭代请求。同时,我们也强调了错误处理、API密钥安全以及API文档的重要性,这些都是构建健壮、可靠的API客户端程序的关键要素。掌握这些技巧,您将能够更有效地从各类API中提取所需数据。

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享