1. 程式人生 > >用Python和Pandas以及爬蟲技術統計歷史天氣

用Python和Pandas以及爬蟲技術統計歷史天氣

背景

最近在計劃明年從北京rebase到深圳去,所以最近在看深圳的各個方面。去年在深圳呆過一段時間,印象最深的是,深圳總是突然就下雨,還下好大的雨。對於我這種從小在南方長大但是後面又在北京呆了2年多的人來說,熟悉而又無奈。

今天早上本來想隨便瀏覽瀏覽一個天氣網站,看看深圳的歷史天氣如何的,但是,一不小心發現,這家網站竟然直接能用API來抓資料,這~~~還不抓一波,省的自己一個月一個月地看。

先上最後的效果圖:

所有的code都在我的GitHub上:boydfd

下面從幾個方面講一講我是怎麼做的:

  1. 爬取資料
  2. 用pandas顯示資料
  3. 功能擴充套件
  4. 遇到的坑

爬取資料

先是在http://tianqi.2345.com上面瀏覽了一下深圳的6月份天氣。然後發現點切換月份的時候,網址沒有變,那應該有請求API吧,看看這個API長啥樣吧。

發現返回值就是純JS程式碼,那就解析一下吧:

  1. 去掉var =和最後的;
  2. 用到demjson解析成Python的List[Dict]物件。
  3. 轉成pandas的DataFrame
  4. 加上我們的date欄位
date = '201905'
weather = requests.get('http://tianqi.2345.com/t/wea_history/js/{date}/59493_{date}.js'.format(date=date)).text.split('=')[1][:-1]
weather = demjson.decode(weather)['tqInfo']
df = pd.DataFrame(weather)
df['month'] = date

結果是這樣的:

用Pandas顯示資料

太多雨天

我們可以看到,有各種雷陣雨啊,陰轉雨啊,雨轉陰之類的,這樣看到的天氣太雜了,所以我就統一了一下,按照雨、多雲、陰、晴的順序來排序,先出出現的關鍵詞優先順序更高。

寫一個函式來處理之:

rain = '雨'
rain_index = ' ' + rain
cloudy = '多雲'
cloudy_index = ' ' + cloudy
overcast = '陰'
overcast_index = ' ' + overcast
sunny = '晴'
sunny_index = ' ' + sunny
def weath_category(row):
    tianqi = row['tianqi']
    if tianqi.find(rain) != -1:
        return rain_index
    if tianqi.find(overcast) != -1:
        return overcast_index
    if tianqi.find(cloudy) != -1:
        return cloudy_index
    return sunny_index

多個月的資料

一個月的資料不夠啊,我們想要很多個月的資料,那就寫得函式來生成月份吧。

def date_generate(start, end):
    start = datetime.strptime(start, '%Y%m')
    end = datetime.strptime(end, '%Y%m')
    while True:
        next_start = start + relativedelta(months=1)
        yield start.strftime('%Y%m')
        if next_start > end:
            break
        start = next_start

畫圖

分好類,爬了多個月份的資料,就剩最終的畫圖部分了。使用Pandas提供給我們的函式,可以很容易就畫出圖來。

def plot_weather(start, end):
    df = read_weather(start, end).dropna().reset_index()
    df['weather'] = df.apply(weath_category, axis=1)
    
    from pylab import rcParams
    rcParams['figure.figsize'] = 40, 10
    weather_df = df.groupby(['month', 'weather']).aqi.count().unstack().reset_index()
    weather_df.plot.bar(x='month', y=[rain_index, overcast_index, cloudy_index, sunny_index])

功能擴充套件

現在只能收集到一個月的資料,想收集多個月的資料,還都自己去頁面上找城市代表的code是啥,太低效了。

這個網站這麼容易爬,那就再試試能不能找到呼叫code的API。

啊哦,一不小心找到了所有的code,哈哈哈。

那就在JS裡面提取一下。

  1. 先把所有的JS程式碼都複製到瀏覽器的console裡, 結果長這樣:

  1. 將其轉換成字串。
provqx.flatMap(a => a).join('|')
  1. 在Python裡處理它。
def line_to_city_code(line):
    return line.split(' ')[1].split('-')

def get_city_to_code():
    city_code_list = list(map(line_to_city_code, city_code.split('|')))
    return {city_code[0]: city_code[1] for city_code in city_code_list if len(city_code) == 2}

這樣我們就拿到所有的code了,只需要輸入城市,開始時間,結束時間,一張漂亮的圖就出來了,我還寫了個類稍微封裝了一下,只需要這樣就能使用了:

Weather('深圳').plot_weather('201701', '201906')

遇到的坑

以前在電腦裡面處理過一次,就是matplotlib畫圖中文亂碼的事情,這次換了新電腦又碰到了。所以又搞了一次,

大概的步驟可以參考https://www.jianshu.com/p/8ed59ac76c06

我為了以防下次再經歷一次,就寫了個指令碼自動處理這件事,目前只支援macOS和Python3。
指令碼也在我的GitHub:bash,

直接執行下面的bash指令碼就可以解決這個問題:

curl -o- https://raw.githubusercontent.com/boydfd/one_step_solve/master/matplotlib_chinese.sh | bash