Python數據可視化及網頁抓取項目實戰


一年的計划在春天。到2020年春天,這種流行病可能會改變許多人的計划。比如,三四月份是傳統企業招聘的高峰期之一。許多英俊的年輕人去拜訪岳母,勸他們在新年買房。職場和房地產市場有“三金四銀”之說。然而,這是真的嗎?

最近我又學了Python(為什么是又呢?因為我學的時候忘了,哈哈),為什么不簡單地驗證一下呢?畢竟,數據不會說謊。

主要流程:

  1. 以房地產市場為分析對象,與公司目前的業務有一定的關系。

  2. 從武漢市住房保障和房屋管理局網站獲取新建商品房公開交易統計數據。

  3. 閱讀數據並將其可視化,並對圖表進行簡要分析,得出初步結論。

首先給大家展示一下最終的可視化數據圖:

商品住宅成交統計數據(武漢

1、獲取數據

首先,使用“為人類設計的HTTP庫”請求從房管局網站獲取包含公共交易統計數據的HTML頁面。數據分為日統計和月統計。然后利用HTML和XML處理庫lxml對HTML頁面內容進行解析。分析之后,通過適當的XPath提取所需的數據。

一開始,我的想法是閱讀每日數據,並分別計算每個月的數據。在爬行之后,我發現每月的統計數據就在目錄頁面旁邊(笑聲和淚水)。不過,Jpg每月公布的數據只到2019年11月,這還不足以支撐整個兩年。因此,2019年12月的數據是根據每日統計數據(發布至2020年1月23日)計算得出的。正如所料,生命不可能徒勞無功:)

import requests
import lxml.html
import html
import time

import db_operator

def get_all_monthly_datas():
    """按月獲取所有成交數據"""
    # 索引頁(商品住房銷售月度成交統計)
    index_url = 'http://fgj.wuhan.gov.cn/spzfxysydjcjb/index.jhtml'
    max_page = get_max_page(index_url)
    if max_page > 0:
        print('共 ' + str(max_page) + ' 頁,' + '開始獲取月度數據..\n')
        for index in range(1, max_page + 1):
            if index >= 2:
                index_url = 'http://fgj.wuhan.gov.cn/spzfxysydjcjb/index_' + str(index) + '.jhtml'
            detail_urls = get_detail_urls(index, index_url)
            for detail_url in detail_urls:
                print('正在獲取月度統計詳情:' + detail_url)
                monthly_detail_html_str = request_html(detail_url)
                if monthly_detail_html_str:
                    find_and_insert_monthly_datas(monthly_detail_html_str)
    else:
        print('總頁數為0。')


def request_html(target_url):
    """請求指定 url 頁面"""
    headers = {
        'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Mobile Safari/537.36',
    }
    html_response = requests.get(target_url, headers=headers)
    html_bytes = html_response.content
    html_str = html_bytes.decode()
    return html_str


def get_max_page(index_url) -> int:
    """從索引頁中獲取總頁數"""
    print('獲取總頁數中..')
    index_html_str = request_html(index_url)
    selector = lxml.html.fromstring(index_html_str)
    max_page_xpath = '//div[@class="whj_padding whj_color pages"]/text()'
    result = selector.xpath(max_page_xpath)
    if result and len(result) > 0:
        result = result[0]
        index_str = result.replace('\r', '').replace('\n', '').replace('\t', '')
        max_page = index_str.split('\xa0')[0]
        max_page = max_page.split('/')[1]
        return int(max_page)
    return 0


def get_detail_urls(index, index_url):
    """獲取統計數據詳情頁 url 列表"""
    print('正在獲取統計列表頁面數據:' + index_url + '\n')
    index_html_str = request_html(index_url)
    selector = lxml.html.fromstring(index_html_str)
    # 提取 url 列表。
    # 疑問:這里使用 '//div[@class="fr hers"]/ul/li/a/@href' 期望應該能提取到更准確的數據,但是結果為空
    detail_urls_xpath = '//div/ul/li/a/@href'
    detail_urls = selector.xpath(detail_urls_xpath)
    return detail_urls

2、保存數據

獲取數據后,需要保存,以便后續的數據處理和增量更新。在這里,我們使用mongodb(一個接近python的文檔數據庫)來存儲數據。

踩坑:對於 macOS 系統網上許多 MongoDB 安裝說明已經失效,需要參考 mongodb/homebrew-brew 引導安裝。

啟動服務后就可以寫入數據:

from pymongo import MongoClient
from pymongo import collection
from pymongo import database

client: MongoClient = MongoClient()
db_name: str = 'housing_deal_data'
col_daily_name: str = 'wuhan_daily'
col_monthly_name: str = 'wuhan_monthly'
database: database.Database = client[db_name]
col_daily: collection = database[col_daily_name]
col_monthly: collection = database[col_monthly_name]


def insert_monthly_data(year_month, monthly_commercial_house):
    """寫入月度統計數據"""
    query = {'year_month': year_month}
    existed_row = col_monthly.find_one(query)
    try:
        monthly_commercial_house_value = int(monthly_commercial_house)
    except:
        if existed_row:
            print('月度數據已存在 =>')
            col_monthly.delete_one(query)
            print('已刪除:月度成交數不符合期望。\n')
        else:
            print('忽略:月度成交數不符合期望。\n')
    else:
        print(str({year_month: monthly_commercial_house_value}))
        item = {'year_month': year_month,
                'commercial_house': monthly_commercial_house_value,}
        if existed_row:
            print('月度數據已存在 =>')
            new_values = {'$set': item}
            result = col_monthly.update_one(query, new_values)
            print('更新數據成功:' + str(item) + '\n' + 'result:' + str(result) + '\n')
        else:
            result = col_monthly.insert_one(item)
            print('寫入數據成功:' + str(item) + '\n' + 'result:' + str(result) + '\n

由於在實際應用中提取數據的限制不夠嚴格,一些臟數據是在早期編寫的,所以除了正常的插入和更新外,還有一個清理臟數據的try異常。

3、讀取數據

數據采集保存完成后,使用mongodb圖形用戶界面工具Robo 3T檢查並確認數據完整,基本符合預期。

接下來從數據庫讀取數據:

def read_all_monthly_datas():
    """從數據庫讀取所有月度統計數據"""
    return {"2018年": read_monthly_datas('2018'),
            "2019年": read_monthly_datas('2019'),}


def read_monthly_datas(year: str) -> list:
    """從數據庫讀取指定年份的月度統計數據"""
    query = {'year_month': {'$regex': '^' + year}}
    result = col_monthly.find(query).limit(12).sort('year_month')

    monthly_datas = {}
    for data in result:
        year_month = data['year_month']
        commercial_house = data['commercial_house']
        if commercial_house > 0:
            month_key = year_month.split('-')[1]
            monthly_datas[month_key] = data['commercial_house']

    # 如果讀取結果小於 12,即有月度數據缺失,則嘗試讀取每日數據並計算出該月統計數據
    if len(monthly_datas) < 12:
        for month in range(1, 13):
            month_key = "{:0>2d}".format(month)
            if month_key not in monthly_datas.keys():
                print('{}年{}月 數據缺失..'.format(year, month_key))
                commercial_house = get_month_data_from_daily_datas(year, month_key)
                if commercial_house > 0:
                    monthly_datas[month_key] = commercial_house
    return monthly_datas


def get_month_data_from_daily_datas(year: str, month: str) -> int:
    """從每日數據中計算月度統計數據"""
    print('從每日數據中獲取 {}年{}月 數據中..'.format(year, month))
    query = {'year_month_day': {'$regex': '^({}-{})'.format(year, month)}}
    result = col_daily.find(query).limit(31)
    sum = 0
    for daily_data in result:
        daily_num = daily_data['commercial_house']
        sum += daily_num
    print('{}年{}月數據:{}'.format(year, month, sum))
    return sum

可見,在讀取月度數據的方法中,要檢查數據是否完整,如果數據缺失,則要從日常數據中讀取並計算相關邏輯。

4、數據可視化

因為這只是一個簡單查看數據總體趨勢的練習,所以您不想繪制稍微復雜的圖表。使用圖表庫Matplotlib繪制簡單的統計圖表:

import matplotlib.pyplot as plt
import html_spider
import db_operator

def generate_plot(all_monthly_datas):
    """生成統計圖表"""
    # 處理漢字未正常顯示問題
    # 還需要手動下載 SimHei.ttf 字體並放到 /venv/lib/python3.7/site-packages/matplotlib/mpl-data/fonts 目錄下)
    plt.rcParams['font.sans-serif'] = ['SimHei']
    plt.rcParams['font.family'] = 'sans-serif'

    # 生成統計圖表
    fig, ax = plt.subplots()
    plt.title(u"商品住宅成交統計數據(武漢)", fontsize=20)
    plt.ylabel(u"成交量", fontsize=14)
    plt.xlabel(u"月份", fontsize=14)
    for year, monthly_datas in all_monthly_datas.items():
        ax.plot(list(monthly_datas.keys()), list(monthly_datas.values()), label=year)
    ax.legend()
    plt.show()


# 爬取網頁數據(並寫入數據庫)
# html_spider.get_all_daily_datas()
html_spider.get_all_monthly_datas()
# 讀取數據,生成統計圖表
generate_plot(db_operator.read_all_monthly_datas())

執行完畢繪制生成的就是開始貼出的數據圖。

5、簡要分析

結合圖表中近兩年的數據曲線,可以直觀地看出,近兩年來,每年上半年都在上升。隨着丈母娘的壓力逐漸降低到年中應該買的,買不到的也不急,數據會回落,然后隨着下半年,另一批准備看丈母娘的補品又會開始上漲。具體來說,2月份是全年最低的(估計是因為寒假),然后穩步上升到8月份左右。9月份會回落,然后再回升(除2018年7月外,還有明顯回落,所以我們需要檢查當時政策調控貸款等方面是否有調整影響)。

至於3、4月份,都是上漲的區域,但全年的高峰實際上出現在年底和年中。可以看出,從復蘇的角度來看,“金山銀絲”的觀點有一定的依據,但不一定從高峰期的角度來看。

最后,我沒有得出一個更為肯定的結論:是真是假。可能有很多事情沒有一個明確的答案:)

最后

2019年仍明顯高於2018年。不要太擔心房地產市場的下跌,因為擔心是沒有用的~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM