一、導入庫與全局配置
python
運行
import json
import datetime
import time
import requests
from sqlalchemy import create_engine
import csv
import pandas as pd
作用:
- 引入數據解析、網絡請求、時間處理、數據庫操作等所需庫。
requests
:發送 HTTP 請求獲取網頁數據。sqlalchemy
:連接和操作 MySQL 數據庫。pandas
:處理 CSV 文件和數據清洗。
潛在問題:
- 未處理
requests
的超時(可能導致程序卡死)。 - 數據庫密碼直接寫死在代碼中(存在安全風險)。
二、核心爬取函數?scraw(code)
python
運行
def scraw(code):url = f'http://www.nmc.cn/rest/weather?stationid={code}&_=1675259309000'response = requests.get(url, headers=headers)try:data = json.loads(response.text)info = data['data']passed = data['data']['passedchart']real = data['data']['real']tempchart = data['data']['tempchart']predict = data['data']['predict']['detail']# 解析24小時天氣數據并寫入CSVfor i in passed:csv.writer(csv_obj).writerow([names[inx], ...])# 解析實時天氣數據并寫入CSVcsv.writer(csv_obj2).writerow([names[inx], ...])# 解析7天溫度數據并寫入CSVfor i in tempchart:csv.writer(csv_obj3).writerow([names[inx], ...])# 解析預報數據并寫入CSVfor i in predict:csv.writer(csv_obj4).writerow([names[inx], ...])except:print(f'{code}爬取失敗')
功能拆解:
-
URL 構造:
- 拼接城市代碼(
stationid
)和時間戳參數(_
),可能用于防止緩存。 - 問題:時間戳硬編碼(
1675259309000
),未動態生成,可能導致請求失效。
- 拼接城市代碼(
-
數據解析:
- 通過
json.loads()
解析 JSON 響應,提取passedchart
(歷史數據)、real
(實時數據)等字段。 - 風險:假設 JSON 結構固定,若網站接口變更會導致解析失敗(需添加容錯處理)。
- 通過
-
CSV 寫入:
- 循環寫入不同類型數據到 4 個 CSV 文件(
data24h.csv
、dataday.csv
等)。 - 問題:
names[inx]
依賴全局變量inx
,多線程環境下可能引發線程安全問題。
- 循環寫入不同類型數據到 4 個 CSV 文件(
三、降雨量爬取函數?scraw_rain24h()
?&?scraw_rain1h()
python
運行
def scraw_rain24h():url = f'http://www.nmc.cn/rest/real/rain/hour24/{date}?_={times}'csv_obj5 = open('csv/rain24h.csv', 'w', ...)response = requests.get(url, headers=headers)data = json.loads(response.text)raindata = data['data']['data']for i in raindata:csv.writer(csv_obj5).writerow([i[0]+i[1], i[5]])csv_obj5.close()def scraw_rain1h():# 邏輯與scraw_rain24h()類似,僅URL和CSV文件不同
關鍵細節:
- URL 參數:
date
由主程序生成(格式為YYYYMMDD08
),times
為當前時間戳(動態生成)。 - 數據結構:降雨量數據通過
i[0]+i[1]
拼接城市名(假設i[0]
為省,i[1]
為市),i[5]
為降雨量。 - 問題:未處理城市名重復或異常數據(如
i[0]
或i[1]
為空)。
四、數據庫存儲函數?save()
python
運行
def save():DB_STRING = 'mysql+pymysql://root:mysql@127.0.0.1:3306/tianqi'engine = create_engine(DB_STRING)# 讀取CSV文件df = pd.read_csv("csv/data24h.csv")df2 = pd.read_csv("csv/dataday.csv")# ... 讀取其他CSV文件# 數據清洗df = df.drop('24h降雨量', axis=1)df2 = df2[df2['體感溫度'] != 9999]df3 = df3[df3['最高溫度'] != 9999]# 寫入數據庫df.to_sql('24h', con=engine, if_exists='replace', index=False)# ... 寫入其他DataFrame
功能說明:
-
數據庫連接:
- 使用 SQLAlchemy 創建數據庫引擎,連接本地 MySQL 的
tianqi
數據庫。 - 風險:密碼
mysql
硬編碼,需通過環境變量或配置文件管理。
- 使用 SQLAlchemy 創建數據庫引擎,連接本地 MySQL 的
-
數據清洗:
- 刪除無效列(如
24h降雨量
)和值為9999
的行(假設9999
為錯誤值)。 - 問題:清洗邏輯分散,未統一處理(如其他 CSV 文件可能也存在無效值)。
- 刪除無效列(如
-
數據寫入:
- 使用
to_sql
批量寫入,if_exists='replace'
會覆蓋表數據(可能導致歷史數據丟失)。
- 使用
五、主程序邏輯(if __name__ == '__main__'
)
python
運行
if __name__ == '__main__':df = pd.read_csv('csv/citycode.csv')codes = df.code.tolist()names = df.城市.tolist()date = time.strftime('%Y%m%d', time.gmtime()) + '08'times = int(time.time() * 1000)headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; ...)'}# 初始化CSV文件csv_obj = open('csv/data24h.csv', 'w', ...)# ... 初始化其他CSV文件for inx, i in enumerate(codes):scraw(i)print(f"{names[inx]}爬取完畢")# 關閉CSV文件csv_obj.close()# ... 關閉其他CSV文件scraw_rain24h()scraw_rain1h()save()
流程分析:
-
準備階段:
- 讀取城市代碼表(
citycode.csv
),獲取codes
(城市代碼)和names
(城市名)。 - 生成
date
(當前日期 +08
,可能為北京時間時區調整)和times
(毫秒級時間戳)。
- 讀取城市代碼表(
-
爬取階段:
- 循環調用
scraw(i)
爬取每個城市的數據,依賴全局變量inx
和names
。 - 問題:未控制爬取頻率(可能觸發網站反爬機制),建議添加
time.sleep()
。
- 循環調用
-
收尾階段:
- 關閉 CSV 文件句柄(需確保在異常情況下也能關閉,建議用
with
語句)。 - 爬取降雨量數據并保存到數據庫。
- 關閉 CSV 文件句柄(需確保在異常情況下也能關閉,建議用
六、整體問題總結與改進方向
模塊 | 問題 | 改進建議 |
---|---|---|
爬取邏輯 | 硬編碼時間戳、未處理反爬 | 動態生成時間戳,添加請求頭(如Referer )、限制爬取頻率 |
異常處理 | 全局except 捕獲,無詳細日志 | 細化異常類型,使用logging 模塊記錄錯誤信息 |
資源管理 | CSV 文件未用with 語句,可能泄漏資源 | 改用with open(...) as f 管理文件 |
數據安全 | 數據庫密碼硬編碼 | 使用環境變量(如os.getenv() )或配置文件 |
代碼可維護性 | 全局變量耦合嚴重,邏輯分散 | 將功能封裝為類,分離爬取、解析、存儲邏輯 |
擴展性 | 難以為新城市或數據類型擴展 | 設計可配置的爬取規則和字段映射 |
通過分塊優化,可顯著提升代碼的健壯性、可維護性和安全性,同時降低對目標網站的影響。
?完整代碼:
import json
import datetime
import time
import requests
from sqlalchemy import create_engine
import csv
import pandas as pddef scraw(code):# 發送 HTTP 請求,獲取網頁內容url = f'http://www.nmc.cn/rest/weather?stationid={code}&_=1675259309000'response = requests.get(url, headers=headers)try:data = json.loads(response.text)info = data['data']# 24小時天氣情況passed = data['data']['passedchart']# 一天real = data['data']['real']# 最近七天最高低溫度tempchart = data['data']['tempchart']# 預測predict = data['data']['predict']['detail']for i in passed:humidity = i['humidity'] # 相對濕度pressure = i['pressure'] # 空氣壓力rain1h = i['rain1h'] #rain24h = i['rain24h'] #temperature = i['temperature'] # 溫度windDirection = i['windDirection']windSpeed = i['windSpeed']time = i['time']tempDiff = i['tempDiff'] # 體感溫度csv.writer(csv_obj).writerow([names[inx],humidity, pressure, rain1h, rain24h, temperature, windDirection, windSpeed, time, tempDiff])csv.writer(csv_obj2).writerow([names[inx],datetime.datetime.now().date(), real['weather']['airpressure'], real['weather']['feelst'],real['weather']['humidity'], real['weather']['info'], real['weather']['rain'], real['weather']['temperature'],real['wind']['direct'], real['wind']['power'], real['wind']['speed']])for i in tempchart:time = i['time']max_temp = i['max_temp']min_temp = i['min_temp']csv.writer(csv_obj3).writerow([names[inx],time, max_temp, min_temp])for i in predict:date = i['date']temperatureday = i['day']['weather']['temperature']temperaturenight = i['night']['weather']['temperature']wind = i['day']['wind']['direct']csv.writer(csv_obj4).writerow([names[inx],date, temperatureday, temperaturenight, wind])except:print(f'{code}爬取失敗')def scraw_rain24h():url = f'http://www.nmc.cn/rest/real/rain/hour24/{date}?_={times}'csv_obj5 = open('csv/rain24h.csv', 'w', encoding="utf-8",newline='')response = requests.get(url, headers=headers)data = json.loads(response.text)print(data)raindata = data['data']['data']csv.writer(csv_obj5).writerow(["城市",'降雨量'])for i in raindata:csv.writer(csv_obj5).writerow([i[0] +i[1], i[5]])print('爬取數據完畢')csv_obj5.close()def scraw_rain1h():url = f'http://www.nmc.cn/rest/real/rain/hour1/{date}?_={times}'csv_obj6 = open('csv/rain1h.csv', 'w', encoding="utf-8", newline='')response = requests.get(url, headers=headers)data = json.loads(response.text)raindata = data['data']['data']csv.writer(csv_obj6).writerow(["城市", '降雨量'])for i in raindata:csv.writer(csv_obj6).writerow([i[0] + i[1], i[5]])print('爬取數據完畢')csv_obj6.close()def save():# 存入數據庫DB_STRING = 'mysql+pymysql://root:mysql@127.0.0.1:3306/tianqi'engine = create_engine(DB_STRING)df = pd.read_csv("csv/data24h.csv")df2 = pd.read_csv("csv/dataday.csv")df3 = pd.read_csv("csv/tempchart.csv")df4 = pd.read_csv("csv/predict.csv")df5 = pd.read_csv("csv/rain24h.csv")df6 = pd.read_csv("csv/rain1h.csv")#刪除不正常值# 刪除部分列值等于9999的行df = df.drop('24h降雨量',axis=1)df2 = df2[df2['體感溫度'] != 9999]df3 = df3[df3['最高溫度'] != 9999]df.to_sql('24h', con=engine, if_exists='replace',index=False)df2.to_sql('day', con=engine, if_exists='replace',index=False)df3.to_sql('tempchart', con=engine, if_exists='replace',index=False)df4.to_sql('predict', con=engine, if_exists='replace',index=False)df5.to_sql('rain24h', con=engine, if_exists='replace',index=False)df6.to_sql('rain1h', con=engine, if_exists='replace',index=False)print('保存數據庫完畢')if __name__ == '__main__':df = pd.read_csv('csv/citycode.csv')codes = df.code.tolist()names = df.城市.tolist()#北京# codes = [54511]# names = ['北京']date = time.strftime('%Y%m%d', time.gmtime()) +'08'times = int(time.time() * 1000)# # 設置請求頭部信息,避免被識別為爬蟲headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'}csv_obj = open('csv/data24h.csv', 'w', encoding="utf-8",newline='')csv_obj2 = open('csv/dataday.csv', 'w', encoding="utf-8", newline='')csv_obj3 = open('csv/tempchart.csv', 'w', encoding="utf-8", newline='')csv_obj4 = open('csv/predict.csv', 'w', encoding="utf-8", newline='')csv.writer(csv_obj).writerow(["城市","相對濕度", "氣壓", "一小時降雨量","24h降雨量", "溫度", "風向", "風速","時間",'體感溫度'])csv.writer(csv_obj2).writerow(["城市","日期","氣壓", '體感溫度',"相對濕度","天氣情況","一小時降雨量","溫度", "風向", "風強度","風速"])csv.writer(csv_obj3).writerow(["城市","日期","最高溫度", '最低溫度'])csv.writer(csv_obj4).writerow(["城市","日期","白天溫度", '夜晚溫度',"風向"])for inx,i in enumerate(codes):scraw(i)print(f"{names[inx]}爬取完畢")csv_obj.close()csv_obj2.close()csv_obj3.close()csv_obj4.close()scraw_rain24h()scraw_rain1h()save()
?
import csv
import json
import requests
# 設置請求頭部信息,避免被識別為爬蟲headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'}# 發送 HTTP 請求,獲取網頁內容url = 'http://www.nmc.cn/rest/province/all?_=1678112903659'
response = requests.get(url, headers=headers)
data = json.loads(response.text)
csv_obj = open('allcsv/citycode.csv', 'w', encoding="utf-8", newline='')
csv.writer(csv_obj).writerow(['城市','code'])
for i in data:code = i['code']url = f'http://www.nmc.cn/rest/province/{code}?_=1677854971362'response = requests.get(url, headers=headers)data = json.loads(response.text)for x in data:csv.writer(csv_obj).writerow([x['city'], x['code']])csv_obj.close()