weatherget.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. import requests
  2. import pandas as pd
  3. import time
  4. import os
  5. import random
  6. # ============================================================
  7. # 配置
  8. # ============================================================
  9. START_DATE = "2020-01-01"
  10. END_DATE = "2026-08-31"
  11. # 输出目录
  12. OUTPUT_DIR = "weather_data"
  13. os.makedirs(OUTPUT_DIR, exist_ok=True)
  14. # 最终合并文件
  15. FINAL_FILE = "china_city_temperature_2020_2026.csv"
  16. # Open-Meteo 历史 API
  17. URL = "https://archive-api.open-meteo.com/v1/archive"
  18. # 城市
  19. cities = {
  20. "北京": (39.9042, 116.4074),
  21. "上海": (31.2304, 121.4737),
  22. "广州": (23.1291, 113.2644),
  23. "深圳": (22.5431, 114.0579),
  24. "成都": (30.5728, 104.0668),
  25. "武汉": (30.5928, 114.3055),
  26. "西安": (34.3416, 108.9398),
  27. "南京": (32.0603, 118.7969),
  28. "青岛": (36.0671, 120.3826),
  29. "哈尔滨": (45.8038, 126.5349),
  30. "乌鲁木齐": (43.8256, 87.6168),
  31. "昆明": (25.0389, 102.7183),
  32. }
  33. # ============================================================
  34. # 创建 Session
  35. # ============================================================
  36. session = requests.Session()
  37. session.headers.update({
  38. "User-Agent": "Mozilla/5.0 (Weather Data Collector)"
  39. })
  40. # ============================================================
  41. # 获取一个城市
  42. # ============================================================
  43. def get_city_weather(city, lat, lon, max_retries=6):
  44. output_file = os.path.join(
  45. OUTPUT_DIR,
  46. f"{city}_{START_DATE}_{END_DATE}.csv"
  47. )
  48. # --------------------------------------------------------
  49. # 如果已经下载成功,直接跳过
  50. # --------------------------------------------------------
  51. if os.path.exists(output_file):
  52. try:
  53. old = pd.read_csv(output_file)
  54. if len(old) > 3000:
  55. print(f"✓ {city} 已存在,跳过")
  56. return old
  57. except Exception:
  58. pass
  59. params = {
  60. "latitude": lat,
  61. "longitude": lon,
  62. "start_date": START_DATE,
  63. "end_date": END_DATE,
  64. "daily": (
  65. "temperature_2m_mean,"
  66. "temperature_2m_max,"
  67. "temperature_2m_min"
  68. ),
  69. "timezone": "Asia/Shanghai",
  70. # 长期历史数据使用 ERA5-Land
  71. "models": "era5_land",
  72. # 陆地网格选择
  73. "cell_selection": "land",
  74. # 温度单位
  75. "temperature_unit": "celsius",
  76. }
  77. for attempt in range(1, max_retries + 1):
  78. try:
  79. print(
  80. f"正在获取:{city} "
  81. f"(第 {attempt}/{max_retries} 次)"
  82. )
  83. response = session.get(
  84. URL,
  85. params=params,
  86. timeout=(15, 180)
  87. )
  88. response.raise_for_status()
  89. data = response.json()
  90. if data.get("error"):
  91. raise RuntimeError(
  92. data.get("reason", "Open-Meteo API error")
  93. )
  94. daily = data["daily"]
  95. df = pd.DataFrame({
  96. "date": daily["time"],
  97. "city": city,
  98. "mean_temp": daily["temperature_2m_mean"],
  99. "max_temp": daily["temperature_2m_max"],
  100. "min_temp": daily["temperature_2m_min"],
  101. })
  102. # ------------------------------------------------
  103. # 检查数据完整性
  104. # ------------------------------------------------
  105. expected_days = (
  106. pd.Timestamp(END_DATE)
  107. - pd.Timestamp(START_DATE)
  108. ).days + 1
  109. if len(df) < expected_days * 0.99:
  110. raise RuntimeError(
  111. f"数据数量异常:{len(df)} 条,"
  112. f"预期约 {expected_days} 条"
  113. )
  114. # ------------------------------------------------
  115. # 保存单城市文件
  116. # ------------------------------------------------
  117. df.to_csv(
  118. output_file,
  119. index=False,
  120. encoding="utf-8-sig"
  121. )
  122. print(
  123. f"✓ {city} 获取成功:"
  124. f"{len(df)} 天"
  125. )
  126. return df
  127. except Exception as e:
  128. print(
  129. f"✗ {city} 第 {attempt} 次失败:"
  130. f"{type(e).__name__}: {e}"
  131. )
  132. if attempt < max_retries:
  133. # 指数退避
  134. wait = min(
  135. 60,
  136. 5 * (2 ** (attempt - 1))
  137. )
  138. # 加一点随机时间,避免连续撞服务器
  139. wait += random.uniform(0, 3)
  140. print(
  141. f"等待 {wait:.1f} 秒后重试..."
  142. )
  143. time.sleep(wait)
  144. else:
  145. print(
  146. f"✗ {city} 最终失败,"
  147. f"后面会继续处理其他城市"
  148. )
  149. return None
  150. # ============================================================
  151. # 开始下载
  152. # ============================================================
  153. all_data = []
  154. failed = []
  155. for city, (lat, lon) in cities.items():
  156. df = get_city_weather(
  157. city,
  158. lat,
  159. lon
  160. )
  161. if df is not None:
  162. all_data.append(df)
  163. else:
  164. failed.append(city)
  165. # 城市之间稍微等待
  166. time.sleep(2)
  167. # ============================================================
  168. # 合并所有成功数据
  169. # ============================================================
  170. if all_data:
  171. result = pd.concat(
  172. all_data,
  173. ignore_index=True
  174. )
  175. result.to_csv(
  176. FINAL_FILE,
  177. index=False,
  178. encoding="utf-8-sig"
  179. )
  180. print("\n" + "=" * 60)
  181. print("下载完成")
  182. print("=" * 60)
  183. print(f"成功城市:{len(all_data)}")
  184. print(f"失败城市:{len(failed)}")
  185. print(f"总数据量:{len(result):,}")
  186. print(f"最终文件:{FINAL_FILE}")
  187. if failed:
  188. print("\n失败城市:")
  189. for city in failed:
  190. print(" -", city)
  191. else:
  192. print("没有成功获取任何城市数据")
  193. # ============================================================
  194. # 数据检查
  195. # ============================================================
  196. print("\n数据预览:")
  197. if all_data:
  198. print(result.head(10))
  199. print("\n每个城市数据量:")
  200. print(result.groupby("city").size())