Python爬取北京地區蛋殼公寓資料,並進行資料視覺化處理
本文的文字及圖片來源於網路,僅供學習、交流使用,不具有任何商業用途,如有問題請及時聯絡我們以作處理。
前言
近期,蛋殼公寓“爆雷”事件持續發酵,期間因拖欠房東房租與租客退款,蛋殼公寓陷入討債風波,全國多地蛋殼公寓辦公區域出現大規模解約事件,而作為蛋殼公寓總部所在地北京,自然首當其衝。
為了應對大規模的解約,北京在全市已經設立了100多個蛋殼公寓矛盾糾紛接待點,包含了蛋殼公寓涉及到的12個區,這些接待點下沉到了街道甚至社群,以方便涉及蛋殼公寓事件的房東和租客諮詢和處理糾紛。
長租公寓暴雷,不少年輕人不得不流離失所,構成疫情下的另一個經濟寫照,事態何去何從,值得關注。本文從資料角度出發,爬取了蛋殼公寓北京區域共6025條公寓資料,清洗資料,並進行視覺化分析,為大家瞭解蛋殼公寓提供一個新的視角。
資料獲取
蛋殼公寓網頁結構相對簡單,資料結構統一,簡單的url翻頁構造即可。需要注意的是極少數網頁會返回404,需要新增判斷過濾掉。本文用request請求到資料,用xpath對返回的資料進行解析,最後以追加模式將資料儲存為csv檔案。爬蟲核心程式碼如下:
def get_danke(href): time.sleep(random.uniform(0, 1)) #設定延時,避免對伺服器產生壓力 response = requests.get(url=href, headers=headers) if response.status_code == 200: #部分網頁會跳轉404,需要做判斷res = response.content.decode('utf-8') div = etree.HTML(res) items = div.xpath("/html/body/div[3]/div[1]/div[2]/div[2]") for item in items: house_price=item.xpath("./div[3]/div[2]/div/span/div/text()")[0] house_area=item.xpath("./div[4]/div[1]/div[1]/label/text()")[0].replace('建築面積:約','').replace('㎡(以現場勘察為準)','') house_id=item.xpath("./div[4]/div[1]/div[2]/label/text()")[0].replace('編號:','') house_type=item.xpath("./div[4]/div[1]/div[3]/label/text()")[0].replace('\n','').replace(' ','').replace('戶型:','') house_floor=item.xpath("./div[4]/div[2]/div[3]/label/text()")[0].replace('樓層:','') house_postion_1=item.xpath("./div[4]/div[2]/div[4]/label/div/a[1]/text()")[0] house_postion_2=item.xpath("./div[4]/div[2]/div[4]/label/div/a[2]/text()")[0] house_postion_3=item.xpath("./div[4]/div[2]/div[4]/label/div/a[3]/text()")[0] house_subway=item.xpath("./div[4]/div[2]/div[5]/label/text()")[0] else: house_price = None house_area = None house_id = None house_type = None house_floor = None house_postion_1 = None house_postion_2 = None house_postion_3 = None house_subway = None ......
由於程式碼執行過程中中斷了幾次,最終將資料儲存為以下幾個csv檔案中:
資料處理
匯入資料分析包
import pandas as pd import numpy as np from pathlib import Path import re
匯入資料併合並
找到資料夾中的所有csv檔案,遍歷讀取資料,最後用concat方法合併所有資料。
files = Path(r"蛋殼公寓").glob("*.csv") dfs = [pd.read_csv(f) for f in files] df = pd.concat(dfs) df.head()
資料去重
資料爬取過程中有中斷,因此可能存在重複爬取的情況,需要去重處理。
df = df.drop_duplicates()
檢視資料
用df.info()方法檢視整體資料資訊,結合預覽的資料,我們可以很容易發現,價格和麵積欄位不是數字型別,需要轉換處理。樓層欄位可以提取出所在樓層和總樓層。
df.info() <class 'pandas.core.frame.DataFrame'> Int64Index:6026 entries, 0 to 710 Data columns (total 9 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 價格 6025 non-null object 1 面積 6025 non-null object 2 編號 6025 non-null object 3 戶型 6025 non-null object 4 樓層 6025 non-null object 5 位置16025 non-null object 6 位置26025 non-null object 7 小區 6025 non-null object 8 地鐵 6025 non-null object dtypes: object(9) memory usage: 470.8+ KB
資料型別轉換
在欄位型別轉換時報錯,檢查發現是資料存在一行髒資料,因此先刪除髒資料再做轉換即可。資料型別轉換用到astype()方法,提取所在樓層和總樓層時根據字元"/"分列即可,採用split()方法。
#刪除包含髒資料的行 jg = df['價格'] != "價格" df = df.loc[jg,:] #將價格欄位轉為數字型別 df["價格"] = df["價格"].astype("float64") #將面積欄位轉為數字型別 df["面積"] = df["面積"].astype("float64") #提取所在樓層 df = df[df['樓層'].notnull()] df['所在樓層']=df['樓層'].apply(lambda x:x.split('/')[0]) df['所在樓層'] = df['所在樓層'].astype("int32") #提取總樓層 df['總樓層']=df['樓層'].apply(lambda x:x.split('/')[1]) df['總樓層'] = df['總樓層'].str.replace("層","").astype("int32")
地鐵欄位清洗
地鐵欄位可以提取出地鐵數和距離地鐵距離。地鐵數通過統計字元"號線”的數量來計算,而距離地鐵距離通過正則表示式匹配出字元"米"前面的數字即可。為方便理解,這裡直接建構函式進行清洗。
def get_subway_num(row): subway_num=row.count('號線') return subway_num def get_subway_distance(row): distance=re.search(r'\d+(?=米)',row) if distance==None: return-1 else: return distance.group() df['地鐵數']=df['地鐵'].apply(get_subway_num) df['距離地鐵距離']=df['地鐵'].apply(get_subway_distance) df['距離地鐵距離']=df['距離地鐵距離'].astype("int32")
儲存資料
資料清洗完畢後,用df.to_excel()將資料儲存為excel檔案。
df.to_excel(r"蛋殼公寓.xlsx") df.head()
資料視覺化
匯入視覺化相關包
import matplotlib.pyplot as plt import seaborn as sns %matplotlib inline plt.rcParams['font.sans-serif'] = ['SimHei'] # 設定載入的字型名 plt.rcParams['axes.unicode_minus'] = False# 解決儲存影象是負號'-'顯示為方塊的問題 import jieba from pyecharts.charts import * from pyecharts import options as opts from pyecharts.globals import ThemeType import stylecloud from IPython.display import Image
各行政區公寓數量
根據清洗後的資料繪製北京蛋殼公寓分佈地圖,我們可以很清晰的看到蛋殼公寓的佈局,朝陽區和通州區是蛋殼公寓主要分佈區域,延慶、密雲、懷柔、平谷和門頭溝地區蛋殼公寓分佈極少。
從各行政區數量上來看,朝陽區和通州區蛋殼公寓數量均超過1000個,朝陽區遙遙領先其他地區,共計1877個,通州區緊隨其後,為1027個。
df7 = df["位置1"].value_counts()[:10] df7 = df7.sort_values(ascending=True) df7 = df7.tail(10) print(df7.index.to_list()) print(df7.to_list()) c = ( Bar(init_opts=opts.InitOpts(theme=ThemeType.DARK)) .add_xaxis(df7.index.to_list()) .add_yaxis("",df7.to_list()).reversal_axis() #X軸與y軸調換順序 .set_global_opts(title_opts=opts.TitleOpts(title="各行政區公寓數量",subtitle="資料來源:蛋殼公寓 ",pos_left = 'left'), xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(font_size=13)), #更改橫座標字型大小 yaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(font_size=13)), #更改縱座標字型大小 ) .set_series_opts(label_opts=opts.LabelOpts(font_size=16,position='right')) ) c.render_notebook()
小區公寓數量TOP10
從小區數量來看,新建村小區、花香東苑和連心園西區蛋殼公寓數量最多,均超過50個。這也意味著,這些小區的租戶受蛋殼風波的影響相較於其他小區更大。
df7 = df["小區"].value_counts()[:10] df7 = df7.sort_values(ascending=True) df7 = df7.tail(10) print(df7.index.to_list()) print(df7.to_list()) c = ( Bar(init_opts=opts.InitOpts(theme=ThemeType.DARK,width="1100px",height="600px")) .add_xaxis(df7.index.to_list()) .add_yaxis("",df7.to_list()).reversal_axis() #X軸與y軸調換順序 .set_global_opts(title_opts=opts.TitleOpts(title="小區公寓數量TOP10",subtitle="資料來源:蛋殼公寓 ",pos_left = 'left'), xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(font_size=11)), #更改橫座標字型大小 yaxis_opts=opts.AxisOpts(axislabel_opts={"rotate":30}), #更改縱座標字型大小 ) .set_series_opts(label_opts=opts.LabelOpts(font_size=16,position='right')) ) c.render_notebook()
蛋殼公寓租金分佈
對租金進行區間分段,我們發現,北京蛋殼公寓的租金還是相當有吸引力的,超過一半的公寓租金在2000-3000元/月。2000元/月以下的公寓數量佔比也高達26.13%。
#租金分段 df['租金分段'] = pd.cut(df['價格'],[0,1000,2000,3000,4000,1000000],labels=['1000元以下','1000-2000元','2000-3000元','3000-4000元','4000元以上'],right=False) df11 = df["租金分段"].value_counts() df11 = df11.sort_values(ascending=False) df11 = df11.round(2) print(df11) c = ( Pie(init_opts=opts.InitOpts(theme=ThemeType.DARK)) .add( "", [list(z) for z in zip(df11.index.to_list(),df11.to_list())], radius=["20%", "80%"], #圓環的粗細和大小 rosetype='area' ) .set_global_opts(legend_opts = opts.LegendOpts(is_show = False),title_opts=opts.TitleOpts(title="蛋殼公寓租金分佈",subtitle="資料來源:蛋殼公寓",pos_top="0.5%",pos_left = 'left')) .set_series_opts(label_opts=opts.LabelOpts(formatter="{b}:{d}%",font_size=16)) ) c.render_notebook()
各行政區租金分佈
我們繼續將地區因素引入租金分析中,發現,不同行政區內的租金分佈也存在較大差異。以朝陽區為例,2000-3000元/月的公寓佔比最多,而通州區1000-2000元/月的公寓佔比更多。這也很容易理解,畢竟所處的區位和經濟發展狀況差異較大。
h = pd.pivot_table(df,index=['租金分段'],values=['價格'], columns=['位置1'],aggfunc=['count']) k = h.droplevel([0,1],axis=1) #刪除指定的索引/列級別 c = ( Polar(init_opts=opts.InitOpts(theme=ThemeType.DARK)) .add_schema(angleaxis_opts=opts.AngleAxisOpts(data=k.columns.tolist(), type_="category")) .add("1000以下",h.values.tolist()[0], type_="bar", stack="stack0") .add("1000-2000元",h.values.tolist()[1], type_="bar", stack="stack0") .add("2000-3000元", h.values.tolist()[2], type_="bar", stack="stack0") .add("3000-4000元", h.values.tolist()[3], type_="bar", stack="stack0") .add("4000元以上", h.values.tolist()[4], type_="bar", stack="stack0") .set_global_opts(title_opts=opts.TitleOpts(title="各行政區租金情況",subtitle="資料來源:蛋殼公寓")) ) c.render_notebook()
蛋殼公寓樓層分佈
從北京蛋殼公寓的樓層分佈來看,10層以下佔比高達73.92,高層和超高層不是蛋殼公寓的理想選擇。
# 漏斗圖 df['樓層分段'] = pd.cut(df['所在樓層'],[0,10,20,30,40,1000000],labels=['10層以下','10-20層','20-30層','30-40層','40層以上'],right=False) count = df['樓層分段'].value_counts() # pd.Series print(count) job = list(count.index) job_count = count.values.tolist() from pyecharts.charts import Funnel c = ( Funnel(init_opts=opts.InitOpts(theme=ThemeType.DARK)) .add("", [list(i) for i in zip(job,job_count)]) .set_global_opts( title_opts=opts.TitleOpts(title="蛋殼公寓樓層分佈",subtitle="資料來源:蛋殼公寓",pos_top="0.1%",pos_left = 'left'),legend_opts = opts.LegendOpts(is_show = False)) .set_series_opts(label_opts=opts.LabelOpts(formatter="{b}:{d}%",font_size=16)) ) c.render_notebook()
蛋殼公寓戶型分佈
從北京蛋殼公寓的戶型分佈來看,3室1衛為主,共計2783個,其次才是4室1衛。這與深圳蛋殼公寓以4室1衛為主的情況存在較大差異。
df2 = df.groupby('戶型')['價格'].count() df2 = df2.sort_values(ascending=False)[:10] # print(df2) bar = Bar(init_opts=opts.InitOpts(theme=ThemeType.DARK)) bar.add_xaxis(df2.index.to_list()) bar.add_yaxis("",df2.to_list()) #X軸與y軸調換順序 bar.set_global_opts(title_opts=opts.TitleOpts(title="蛋殼公寓戶型分佈",subtitle="資料來源:蛋殼公寓",pos_top="2%",pos_left = 'center'), xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(font_size=16)), #更改橫座標字型大小 yaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(font_size=16)), #更改縱座標字型大小 ) bar.set_series_opts(label_opts=opts.LabelOpts(font_size=16,position='top')) bar.render_notebook()
蛋殼公寓面積分布
從北京蛋殼公寓的面積分布來看,86.77%的公寓面積不足20㎡。北京10㎡以下的蛋殼公寓佔比達到了21.2%,即便如此,這個數字仍不足深圳的一半。
df['面積分段'] = pd.cut(df['面積'],[0,10,20,30,40,1000000],labels=['10㎡以下','10-20㎡','20-30㎡','30-40㎡','40㎡以上'],right=False) df2 = df["面積分段"].astype("str").value_counts() print(df2) df2 = df2.sort_values(ascending=False) regions = df2.index.to_list() values = df2.to_list() c = ( Pie(init_opts=opts.InitOpts(theme=ThemeType.DARK)) .add("", list(zip(regions,values))) .set_global_opts(legend_opts = opts.LegendOpts(is_show = False),title_opts=opts.TitleOpts(title="蛋殼公寓面積分布",subtitle="資料來源:蛋殼公寓",pos_top="0.5%",pos_left = 'left')) .set_series_opts(label_opts=opts.LabelOpts(formatter="{b}:{d}%",font_size=14)) ) c.render_notebook()
蛋殼公寓商圈分佈
通過對北京幾個主要行政區商圈進行詞雲統計(字型越大表示蛋殼公寓數量最多),朝陽區的管莊、望京,通州區的北關,豐臺區的樊羊路、方莊和角門,昌平區的天通苑,海淀區的永豐和西二旗,大興區的黃村和亦莊,是蛋殼公寓主要選擇的商圈。
# 繪製詞雲圖 text1 = get_cut_words(content_series=df1['位置2']) stylecloud.gen_stylecloud(text=' '.join(text1), max_words=100, collocations=False, font_path=r'C:\WINDOWS\FONTS\MSYH.TTC', icon_name='fas fa-home', size=653, palette='cartocolors.diverging.ArmyRose_2', output_name='./1.png') Image(filename='./1.png')
相關性分析
從相關係數表可以看出,北京蛋殼公寓的面積、周邊地鐵數對公寓的價格有較大的的影響,相關係數分別為0.81和0.36。蛋殼公寓在進行房屋定價時,對公寓的面積以及公寓的地鐵配套有較大權重的考慮。由於北京蛋殼公寓距離地鐵都很近,因此,距離的遠近對公寓的價格影響有限。另外,所在樓層也不是北京蛋殼公寓租金高低的重要影響因素。
color_map = sns.light_palette('orange', as_cmap=True) #light_palette調色盤 df.corr().style.background_gradient(color_map)
最後,願所有受蛋殼公寓“暴雷”事件影響的年輕人都能熬過這個寒冬。