資料來源
資料頁面: 鏈家網南京(https://nj.lianjia.com/chengjiao/)
鏈家網資料量很大,這里只用南京的二手房成交資料,
如下圖:

資料采集
鏈家網的頁面資料比較整齊,采集很簡單,為了避免影響別人使用,只采集的南京的二手房成交資料, 采集頻率也很低,總共花了一下午才采集完所有資料,
我主要采集以下 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 = https://www.cnblogs.com/wang_yb/archive/2021/04/15/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 = https://www.cnblogs.com/wang_yb/archive/2021/04/15/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 = https://www.cnblogs.com/wang_yb/archive/2021/04/15/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 = https://www.cnblogs.com/wang_yb/archive/2021/04/15/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 = https://www.cnblogs.com/wang_yb/archive/2021/04/15/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 = https://www.cnblogs.com/wang_yb/archive/2021/04/15/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 = https://www.cnblogs.com/wang_yb/archive/2021/04/15/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 = https://www.cnblogs.com/wang_yb/archive/2021/04/15/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 = https://www.cnblogs.com/wang_yb/archive/2021/04/15/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 = https://www.cnblogs.com/wang_yb/archive/2021/04/15/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 = https://www.cnblogs.com/wang_yb/archive/2021/04/15/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 = https://www.cnblogs.com/wang_yb/archive/2021/04/15/data
else:
allData = allData.append(data)
write_csv(output_path, allData)
if __name__ =="__main__":
main()
分析結果展示
分析后生成的 csv,我寫了另外一個工具,可以直接轉換成小視頻,
工具是基于 antv G2 和 ffmpeg 做的,還不是很成熟,以后會發布到官網上,同時在博客中詳細介紹,
生成的視頻已經放在我的視頻號了,感興趣可以看看,

總結
雖然上面的資料量不是很大,但這是我平時做一次資料分析的的整個程序(從資料采集到可視化展示),
- 采集的部分使用的方式比較雜,根據具體情況看,有時我用 python 或者 golang 寫爬蟲,有時用現成的工具,比如八爪魚之類的,
- 采集之后對資料的初步整理,我基本上是用 shell,強大的 shell 命令可以極大的減少代碼的撰寫,
- 對資料的精細化整理,我一般用 golang,開發效率和執行效率都高且便于對接各種存盤(上面的例子只是簡單的生成 csv),
- 資料的分析我一般用 python,這個不用多說了,現成的分析庫實在太強大,建議安裝 miniconda,我另一個博客有介紹:debian10下miniconda環境配置
- 最后的分析結果展示,也有很多現成的工具,我選擇了用 antv 家族的庫來自己實作(主要是想試試能不能做一些差異化的展示),
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/276561.html
標籤:其他
上一篇:jdbc資料庫連接方式迭代
