爬蟲實戰+資料分析:全國消費支出分析及未來預測

努力的小雨發表於2024-03-25

在本篇文章中,爬蟲的講解不僅僅侷限於爬蟲本身,還會引申至另一個重要領域:資料分析。對我們而言,爬蟲的核心價值實際上在於獲取資料,一旦獲得了資料,接下來必然是要加以利用。資料分析便是其中關鍵一環,因此在爬蟲的講解之後,我們將會稍作涉及與資料分析相關的知識要點。

今天主要任務是爬取全國消費資料,然後根據過去十年的資料進行深入分析,以便進行未來兩年的消費預測。廢話不多說,讓我們直接開始吧。

全國消費資料

要獲取全國的消費資料,最好前往國家資料統計局進行查詢。因此,在使用爬蟲時,應當謹慎操作,避免對伺服器造成負荷過大的影響。在成功獲取資料後,應當及時儲存,而不是過度頻繁地請求資料,以免導致伺服器癱瘓。在開始分析頁面之前,先確認所需的全國消費資料是否已被提供,然後按照常規操作,在頁面下方進行搜尋,以確定資料展示形式是靜態頁面還是透過ajax請求獲取的。

image

為什麼在這裡我搜尋的是數字而非文字?這是因為該請求返回到瀏覽器時處於亂碼狀態,因此為了演示,我選擇了數字作為示例,效果是一樣的。一旦找到請求,處理起來就很簡單了,我們只需複製URL,前往線上網站進行處理,然後將程式碼複製出來即可。如果線上網站有不清楚的地方,可以參考前幾章的文章。

資料抓取

直接看下爬蟲程式碼:

import requests
import re

strdata_code_map = {}
wdcode_name_map = {}
def get_data():
    global strdata_code_map,wdcode_name_map
    headers = {
        'Accept': 'application/json, text/javascript, */*; q=0.01',
        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
        'Connection': 'keep-alive',
        'Cookie': 'wzws_sessionid=oGX46GqAMTIzLjE3Mi40OS4yMDKBZDk0YTI3gmZjNWVlMQ==; u=6; experience=show; JSESSIONID=bANUmkmAc_F_FOy-dM-8VqxHEea-dpa39By6stbh14v9_aYXN7HM!1314454129',
        'Referer': 'https://data.stats.gov.cn/easyquery.htm?cn=C01',
        'Sec-Fetch-Dest': 'empty',
        'Sec-Fetch-Mode': 'cors',
        'Sec-Fetch-Site': 'same-origin',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0',
        'X-Requested-With': 'XMLHttpRequest',
        'sec-ch-ua': '"Chromium";v="122", "Not(A:Brand";v="24", "Microsoft Edge";v="122"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"Windows"',
    }

    params = {
        'm': 'QueryData',
        'dbcode': 'hgnd',
        'rowcode': 'zb',
        'colcode': 'sj',
        'wds': '[]',
        'dfwds': '[{"wdcode":"zb","valuecode":"A0A04"}]',
        'k1': '1710816989823',
        'h': '1',
    }

    response = requests.get('https://data.stats.gov.cn/easyquery.htm', params=params,   headers=headers, verify=False)
    # 解析JSON資料
    response_data = response.json()
    
    # 提取datanodes中的strdata和code對映資料列表
    datanodes = response_data['returndata']['datanodes']
    for node in datanodes:
        input_str = node['code']
        match = re.search(r'\.(\w+)_sj\.(\d+)', input_str)

        if match:
            part1 = match.group(1)  # A0A0401
            part2 = match.group(2)  # 2023
            if 'year' not in strdata_code_map:
                strdata_code_map['year'] = []
            if part2 not in strdata_code_map['year']:
                strdata_code_map['year'].append(part2) 
            if part1 not in strdata_code_map:
                strdata_code_map[part1] = []
            strdata_code_map[part1].append(node['data']['strdata'])
    print(strdata_code_map)
    # 提取wdnodes中code和name對映列表
    wdnodes = response_data['returndata']['wdnodes']
    wdcode_name_map = {node['code']: node['name'] for node in wdnodes[0]['nodes']}
    print(wdcode_name_map)

這段程式碼解析了返回的JSON資料,提取了datanodes中的資料節點和wdnodes中的維度節點資訊。對於資料節點,透過正規表示式提取了每個節點的code屬性,解析出資料節點對應的strdata和code對映關係,並將這些資訊儲存到strdata_code_map字典中。對於維度節點,將每個節點的code和name屬性對映關係儲存到wdcode_name_map字典中。

資料分析

拿到資料後,我們立即對其進行資料分析。一般來說,在資料分析專案中,我們會首先利用Pandas庫載入資料,進行資料清洗和處理,然後使用Matplotlib庫進行資料視覺化,以便更深入地理解資料並有效展示結果。

不多說,直接看下程式碼:

import pandas as pd
import matplotlib.pyplot as plt

def get_now_plt():
    data = {
        'year': ['2023', '2022', '2021', '2020', '2019', '2018', '2017', '2016', '2015', '2014'],
        'A0A0401': ['26796', '24538', '24100', '21210', '21559', '19853', '18322', '17111', '15712', '14491'],
        'A0A0402': ['9.0', '-0.2', '12.6', '-4.0', '5.5', '6.2', '5.4', '6.8', '6.9', '7.5'],
        'A0A0403': ['12114', '10590', '10645', '9037', '9886', '8781', '7803', '7157', '6460', '5842'],
        'A0A0404': ['14.4', '-0.5', '17.8', '-8.6', '12.6', '12.5', '9.0', '10.8', '10.6', '11.4'],
        'A0A0405': ['7983', '7481', '7178', '6397', '6084', '5631', '5374', '5151', '4814', '4494'],
        'A0A0406': ['6.7', '4.2', '12.2', '5.1', '8.0', '4.8', '4.3', '7.0', '7.1', '8.9'],
        'A0A0407': ['1479', '1365', '1419', '1238', '1338', '1289', '1238', '1203', '1164', '1099'],
        'A0A0408': ['8.4', '-3.8', '14.6', '-7.5', '3.8', '4.1', '2.9', '3.3', '5.9', '7.0'],
        'A0A0409': ['6095', '5882', '5641', '5215', '5055', '4647', '4107', '3746', '3419', '3201'],
        'A0A040A': ['3.6', '4.3', '8.2', '3.2', '8.8', '13.1', '9.6', '9.6', '6.8', '6.7'],
        'A0A040B': ['1526', '1432', '1423', '1260', '1281', '1223', '1121', '1044', '951', '890'],
        'A0A040C': ['6.6', '0.6', '13.0', '-1.7', '4.8', '9.1', '7.4', '9.7', '6.9', '10.3'],
        'A0A040D': ['3652', '3195', '3156', '2762', '2862', '2675', '2499', '2338', '2087', '1869'],
        'A0A040E': ['14.3', '1.2', '14.3', '-3.5', '7.0', '7.1', '6.9', '12.0', '11.6', '14.9'],
        'A0A040F': ['2904', '2469', '2599', '2032', '2513', '2226', '2086', '1915', '1723', '1536'],
        'A0A040G': ['17.6', '-5.0', '27.9', '-19.1', '12.9', '6.7', '8.9', '11.2', '12.2', '9.9'],
        'A0A040H': ['2460', '2120', '2115', '1843', '1902', '1685', '1451', '1307', '1165', '1045'],
        'A0A040I': ['16.0', '0.2', '14.8', '-3.1', '12.9', '16.1', '11.0', '12.3', '11.5', '14.5'],
        'A0A040J': ['697', '595', '569', '462', '524', '477', '447', '406', '389', '358'],
        'A0A040K': ['17.1', '4.6', '23.2', '-11.8', '9.7', '6.8', '10.0', '4.4', '8.7', '10.3']
    }
    label = {
        'A0A0401': '居民人均消費支出',
        'A0A0402': '居民人均消費支出_比上年增長',
        'A0A0403': '居民人均服務性消費支出',
        'A0A0404': '居民人均服務性消費支出_比上年增長',
        'A0A0405': '居民人均食品菸酒支出',
        'A0A0406': '居民人均食品菸酒支出_比上年增長',
        'A0A0407': '居民人均衣著支出',
        'A0A0408': '居民人均衣著支出_比上年增長',
        'A0A0409': '居民人均居住支出',
        'A0A040A': '居民人均居住支出_比上年增長',
        'A0A040B': '居民人均生活用品及服務支出',
        'A0A040C': '居民人均生活用品及服務支出_比上年增長',
        'A0A040D': '居民人均交通通訊支出',
        'A0A040E': '居民人均交通通訊支出_比上年增長',
        'A0A040F': '居民人均教育文化娛樂支出',
        'A0A040G': '居民人均教育文化娛樂支出_比上年增長',
        'A0A040H': '居民人均醫療保健支出',
        'A0A040I': '居民人均醫療保健支出_比上年增長',
        'A0A040J': '居民人均其他用品及服務支出',
        'A0A040K': '居民人均其他用品及服務支出_比上年增長'
    }

    keys = list(data.keys())  # 獲取所有鍵並轉換為列表
    need_keys = []
    for i in range(1, len(keys), 2):
        need_keys.append(keys[i])
    # 資料
    years = data['year']

    # 繪製折線圖
    for key in range(0, len(need_keys), 2):
        plt.plot(years, [int(x) for x in data[need_keys[key]]], label=label[need_keys[key]], marker='o')

    plt.rcParams['font.sans-serif'] = ['SimHei']
    plt.xlabel('年份')
    plt.ylabel('消費支出(元)')
    plt.title('全國居民人均支出情況')
    plt.legend()
    plt.grid(True)
    plt.show()
    
get_now_plt()

為了保持程式碼的流暢性,我先複製了資料並定義了兩個字典項,分別是label和data。字典label用於儲存每種資料型別的中文標籤,而字典data包含了各年份的不同消費支出資料,比如居民人均消費支出、居民人均服務性消費支出等。接著,我使用matplotlib.pyplot庫來繪製折線圖。在繪製過程中,我遍歷了need_keys列表,為每種資料型別繪製相應的折線圖,並新增了標籤和資料點。

image

當處理資料時,請確保注意到,如果需要顯示中文字元,您可能需要使用以下語句來設定字型以避免亂碼:plt.rcParams['font.sans-serif'] = ['SimHei']。此外,請注意我儲存的資料是按倒序排列的。

未來預測

當我們擁有近10年的資料時,實際上可以利用這些資料進行預測。在這方面有許多方法可供選擇,今天我們將探討ARIMA模型。ARIMA代表自迴歸(Autoregressive, AR)、差分(Integrated, I)和移動平均(Moving Average, MA)這三種技術的結合,是一種用於時間序列預測的統計模型。透過ARIMA模型,我們可以捕捉時間序列資料中的趨勢、季節性變化和週期性變化。

在本章中,我們僅僅是提供了一些基礎資訊,希望能夠引發你的興趣,具體的內容將在後續的章節中詳細展開。因此,接下來可以直接檢視程式碼部分:

import requests
import pandas as pd
import matplotlib.pyplot as plt
from statsmodels.tsa.arima.model import ARIMA
import re

def get_feature_plt():
    
    # 資料
    data = {
        'year': [2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023],
        'A0A0403': [5842, 6460, 7157, 7803, 8781, 9886, 9037, 10645, 10590, 12114],
        'A0A0405': [4494, 4814, 5151, 5374, 5631, 6084, 6397, 7178, 7481, 7983],
        'A0A0407': [1099, 1164, 1203, 1238, 1289, 1338, 1238, 1419, 1365, 1479],
        'A0A0409': [3201, 3419, 3746, 4107, 4647, 5055, 5215, 5641, 5882, 6095],
        'A0A040B': [890, 951, 1044, 1121, 1223, 1281, 1260, 1423, 1432, 1526],
        'A0A040D': [1869, 2087, 2338, 2499, 2675, 2862, 2762, 3156, 3195, 3652],
        'A0A040F': [1536, 1723, 1915, 2086, 2226, 2513, 2032, 2599, 2469, 2904],
        'A0A040H': [1045, 1165, 1307, 1451, 1685, 1902, 1843, 2115, 2120, 2460],
        'A0A040J': [358, 389, 406, 447, 477, 524, 462, 569, 595, 697],
    }
    label = {
        'A0A0401': '居民人均消費支出',
        'A0A0402': '居民人均消費支出_比上年增長',
        'A0A0403': '居民人均服務性消費支出',
        'A0A0404': '居民人均服務性消費支出_比上年增長',
        'A0A0405': '居民人均食品菸酒支出',
        'A0A0406': '居民人均食品菸酒支出_比上年增長',
        'A0A0407': '居民人均衣著支出',
        'A0A0408': '居民人均衣著支出_比上年增長',
        'A0A0409': '居民人均居住支出',
        'A0A040A': '居民人均居住支出_比上年增長',
        'A0A040B': '居民人均生活用品及服務支出',
        'A0A040C': '居民人均生活用品及服務支出_比上年增長',
        'A0A040D': '居民人均交通通訊支出',
        'A0A040E': '居民人均交通通訊支出_比上年增長',
        'A0A040F': '居民人均教育文化娛樂支出',
        'A0A040G': '居民人均教育文化娛樂支出_比上年增長',
        'A0A040H': '居民人均醫療保健支出',
        'A0A040I': '居民人均醫療保健支出_比上年增長',
        'A0A040J': '居民人均其他用品及服務支出',
        'A0A040K': '居民人均其他用品及服務支出_比上年增長'
    }

    df = pd.DataFrame(data)
    df.set_index('year', inplace=True)
    need_keys = list(data.keys())  # 獲取所有鍵並轉換為列表
    for i in range(1, len(need_keys), 3):
        # 擬合ARIMA模型
        model = ARIMA(df[need_keys[i]], order=(1, 1, 1))  # 根據資料特點選擇合適的order
        model_fit = model.fit()

        # 進行未來預測
        future_years = [2024,2025]  # 假設預測未來兩年
        forecast = model_fit.forecast(steps=len(future_years))

        # 視覺化預測結果
        plt.plot(df.index, df[need_keys[i]], label=label[need_keys[i]])
        plt.plot(future_years, forecast, label='預測'+label[need_keys[i]], linestyle='--', marker='o')
    plt.rcParams['font.sans-serif'] = ['SimHei']
    plt.xlabel('年份')
    plt.ylabel('消費支出(元)')
    plt.title('未來兩年預測消費支出')
    plt.legend()
    plt.grid(True)
    plt.show()

在進行最佳化處理時,首先將暫存的資料轉換為Pandas的DataFrame格式,並將年份設為索引。接著,從資料中提取所需的鍵名,對每三個鍵進行ARIMA模型的擬合和預測,隨後透過視覺化展示了消費支出隨時間變化的趨勢圖,並呈現了未來兩年的預測資料。

image

總結

完美收官,本文是爬蟲實戰的最後一章了,所以儘管本文著重呈現爬蟲實戰,但其中有一大部分內容專注於資料分析。爬蟲只是整個過程的起點,其主要目的之一就是為後續資料分析等工作做好準備。透過對爬取的資料進行精確的清洗和分析,可以揭示其中隱藏的規律和趨勢,為決策提供有力支援。因此,爬蟲實戰並不僅僅是技術的展示,更是對資料價值的挖掘和充分利用。

還有一點需要特別強調的是,絕對不能利用這種方式從中謀取個人利益,比如搭建爬蟲網站等手段,這些行為是違法的。我想再次強調,在進行爬蟲操作時一定要遵守相關法律法規,儘量以學習為主,切勿觸犯法律。

相關文章