南京二手房成交數據分析


數據來源

數據頁面: 鏈家網南京(https://nj.lianjia.com/chengjiao/

鏈家網數據量很大,這里只用南京的二手房成交數據。

如下圖:
鏈家南京二手房成交

數據采集

鏈家網的頁面數據比較整齊,采集很簡單,為了避免影響別人使用,只采集的南京的二手房成交數據, 采集頻率也很低,總共花了一下午才采集完所有數據。

我主要采集以下 9 個數據,沒有采集房屋的圖片。

  1. 小區名稱和房屋概要
  2. 房屋朝向和裝修情況
  3. 成交日期
  4. 成交價格(單位: 萬元)
  5. 樓層等信息
  6. 成交單價
  7. 房屋優勢
  8. 掛牌價格
  9. 成交周期

爬蟲技術爭議比較多,詳細的過程就不多說了,采集完的數據我放在以下地址:https://databook.top/data/b2b49fff-ede4-4ce5-9d96-08c616d1e481/detail
已經整理成 csv 格式,需要的可以下載了用來做數據分析實驗。(數據截止到 2021/03/30)

數據采集的注意點

鏈家網的數據采集有個注意的地方,雖然打開這個網頁(https://nj.lianjia.com/chengjiao/), 我們看到目前共找到 8 萬多套成交房源,
但是鏈家網只顯示 100 頁的數據,每頁 30 條,也就是最多一次查詢出 3000 條數據。

所以,為了采集所有的數據,需要設置多種檢索條件,保證每次搜索的數據不超過 3000 條。 8 萬多條數據大概要設置 30 來種不同的搜索條件。
如下圖,我主要根據區域,售價和戶型來檢索的,也就是按區域如果超過 3000,再按售價,售價還超出再按戶型。 用這 3 個條件基本就夠了。

采集時分類

數據清理

合並和去重

采集的數據是根據不同搜索條件來的,所以有很多個 csv 文件。 csv 格式是統一的,先用 shell 腳本進行數據的合並和去重,我是按照南京的不同的區來合並數據的。

采集的時候我已經按照不同的區把數據放在不同的文件夾了。合並數據腳本示例是如下:

d="merged-files"
sed "" 建鄴區/*.csv > ${d}/建鄴區.csv

這里合並用的 sed 命令,沒有用如下 cat 命令:

d="merged-files"
cat 建鄴區/*.csv > ${d}/建鄴區.csv

cat 命令有個問題,前一個文件的最后一行會和下一個文件的第一行合並成一行。

合並之后就是去重:假設第一步合並后的文件都在 merged-files 文件夾下

d="merged-files"
for f in `ls ${d}/`
do
    sort -u ${d}/${f} -o uniq-${f}
done

格式化

采集到的原始數據是如下格式:

一品驪城 2室1廳 71平米,南 | 精裝,2020.09.05,78,中樓層(共5層) 板樓,10916元/平,,掛牌82萬,成交周期134天

可以看出,除了成交價(78)是正常的數字,單價(10916 元/平),掛牌價(掛牌 82 萬),成交周期(成交周期 134 天)等都是數字和文字混合。 這些字段需要將數字剝離出來才能進行后續的分析。

我是通過一個簡單的 golang 程序來格式化原始數據,然后生成新的 csv。

func handleData(line []string) TradedHouse {
  var houseData TradedHouse
  fmt.Printf("record: %v\n", line)
  // 1. 小區名稱和房屋概要
  var arr = strings.Split(line[0], " ")
  houseData.Name = arr[0]
  houseData.HouseType = arr[1]
  if len(arr) > 2 {
    houseData.HouseArea = gutils.ParseFloat64WithDefault(strings.TrimRight(arr[2], "平米"), 0.0)
  }

  // 2. 房屋朝向和裝修情況
  arr = strings.Split(line[1], " | ")
  houseData.HouseDirection = arr[0]
  houseData.HouseDecoration = arr[1]

  // 3. 成交日期
  houseData.TradingTime = line[2]
  // 4. 成交價格(單位: 萬元)
  houseData.TradingPrice = gutils.ParseFloat64WithDefault(line[3], 0.0)
  // 5. 樓層等信息
  houseData.FloorInfo = line[4]
  // 6. 成交單價
  houseData.UnitPrice = gutils.ParseFloat64WithDefault(strings.TrimRight(line[5], "元/平"), 0.0)
  // 7. 房屋優勢
  houseData.Advance = line[6]
  // 8. 掛牌價格
  if len(line) > 7 {
    houseData.ListedPrice = gutils.ParseFloat64WithDefault(strings.TrimRight(strings.TrimLeft(line[7], "掛牌"), "萬"), 0.0)
  }
  // 9. 成交周期
  if len(line) > 8 {
    houseData.SellingTime, _ = strconv.Atoi(strings.TrimRight(strings.TrimLeft(line[8], "成交周期"), "天"))
  }

  return houseData
}

轉換后的 csv 格式如下:

一品驪城,2室1廳,精裝,中樓層(共5層) 板樓,71,10916,82,78,134,2020.09.05,南,

數值部分都分離出來了,可以進入數據分析的步驟了。

數據分析

最后的分析步驟使用的 python 腳本,主要使用 python 的 numpy 和 pandas 庫。

下面分析了 2019~2020 南京各區二手房的每個月的銷售套數,成交總額以及成交單價。

銷售套數

# -*- coding: utf-8 -*-
import os

import numpy as np
import pandas as pd


def read_csv(fp):
    # 讀取2列 col9: 成交時間
    # 其中成交時間進行處理:從 2020.01.01 ==> 2020.01
    data = pd.read_csv(
        fp,
        usecols=[9],
        header=None,
        names=["time"],
        converters={"time": lambda s: s[:7]},
    )
    data_mask = data["time"].str.contains("2019|2020")
    data = data[data_mask]
    data["count"] = 1
    return data.groupby("time")


def write_csv(fp, data):
    data.to_csv(fp)


def main():
    # 讀取csv數據
    csv_path = "../liangjia-go/output/converter"
    output_path = "./成交數量統計.csv"
    files = list(
        map(
            lambda f: os.path.join(csv_path, f + ".csv"),
            [
                "南京鼓樓區",
                "南京建鄴區",
                "南京江寧區",
                "南京溧水區",
                "南京六合區",
                "南京浦口區",
                "南京棲霞區",
                "南京秦淮區",
                "南京玄武區",
                "南京雨花台區",
            ],
        )
    )

    allData = None
    for f in files:
        data = read_csv(f)
        data = data.sum()
        data["area"] = os.path.basename(f).strip(".csv").strip("南京")
        print(data)
        if allData is None:
            allData = data
        else:
            allData = allData.append(data)

    write_csv(output_path, allData)


if __name__ == "__main__":
    main()

成交總額

# -*- coding: utf-8 -*-
import os

import numpy as np
import pandas as pd


def read_csv(fp):
    # 讀取2列 col9: 成交時間, col7: 成交價格(萬元)
    # 其中成交時間進行處理:從 2020.01.01 ==> 2020.01
    data = pd.read_csv(
        fp,
        usecols=[7, 9],
        header=None,
        names=["value", "time"],
        converters={"time": lambda s: s[:7]},
    )
    data_mask = data["time"].str.contains("2019|2020")
    data = data[data_mask]
    return data.groupby("time")


def write_csv(fp, data):
    data.to_csv(fp)


def main():
    # 讀取csv數據,提取成交價格(col 7)
    csv_path = "../liangjia-go/output/converter"
    output_path = "./成交額統計.csv"
    files = list(
        map(
            lambda f: os.path.join(csv_path, f + ".csv"),
            [
                "南京鼓樓區",
                "南京建鄴區",
                "南京江寧區",
                "南京溧水區",
                "南京六合區",
                "南京浦口區",
                "南京棲霞區",
                "南京秦淮區",
                "南京玄武區",
                "南京雨花台區",
            ],
        )
    )

    allData = None
    for f in files:
        data = read_csv(f)
        data = data.sum()
        data["area"] = os.path.basename(f).strip(".csv").strip("南京")
        print(data)
        if allData is None:
            allData = data
        else:
            allData = allData.append(data)

    # 萬元 => 元
    allData["value"] = allData["value"] * 10000
    write_csv(output_path, allData)


if __name__ == "__main__":
    main()

成交單價

# -*- coding: utf-8 -*-
import os

import numpy as np
import pandas as pd


def read_csv(fp):
    # 讀取2列 col9: 成交時間, col5: 成交單價(元/平米)
    # 其中成交時間進行處理:從 2020.01.01 ==> 2020.01
    data = pd.read_csv(
        fp,
        usecols=[5, 9],
        header=None,
        names=["value", "time"],
        converters={"time": lambda s: s[:7]},
    )
    data_mask = data["time"].str.contains("2019|2020")
    data = data[data_mask]
    return data.groupby("time")


def write_csv(fp, data):
    data.to_csv(fp)


def main():
    # 讀取csv數據,提取成交價格(col 7)
    csv_path = "../liangjia-go/output/converter"
    output_path = "./成交單價統計.csv"
    files = list(
        map(
            lambda f: os.path.join(csv_path, f + ".csv"),
            [
                "南京鼓樓區",
                "南京建鄴區",
                "南京江寧區",
                "南京溧水區",
                "南京六合區",
                "南京浦口區",
                "南京棲霞區",
                "南京秦淮區",
                "南京玄武區",
                "南京雨花台區",
            ],
        )
    )

    allData = None
    for f in files:
        data = read_csv(f)
        data = data.mean()
        data["area"] = os.path.basename(f).strip(".csv").strip("南京")
        print(data)
        if allData is None:
            allData = data
        else:
            allData = allData.append(data)

    write_csv(output_path, allData)


if __name__ == "__main__":
    main()

分析結果展示

分析后生成的 csv,我寫了另外一個工具,可以直接轉換成小視頻。
工具是基於 antv G2ffmpeg 做的,還不是很成熟,以后會發布到官網上,同時在博客中詳細介紹。

生成的視頻已經放在我的視頻號了,感興趣可以看看。
databook 視頻號

總結

雖然上面的數據量不是很大,但這是我平時做一次數據分析的的整個過程(從數據采集到可視化展示)。

  1. 采集的部分使用的方式比較雜,根據具體情況看,有時我用 python 或者 golang 寫爬蟲,有時用現成的工具,比如八爪魚之類的。
  2. 采集之后對數據的初步整理,我基本上是用 shell,強大的 shell 命令可以極大的減少代碼的編寫。
  3. 對數據的精細化整理,我一般用 golang,開發效率和執行效率都高且便於對接各種存儲(上面的例子只是簡單的生成 csv)。
  4. 數據的分析我一般用 python,這個不用多說了,現成的分析庫實在太強大。建議安裝 miniconda,我另一個博客有介紹:debian10下miniconda環境配置
  5. 最后的分析結果展示,也有很多現成的工具,我選擇了用 antv 家族的庫來自己實現(主要是想試試能不能做一些差異化的展示)。


免責聲明!

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



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