概述
- 前言
- 統(tǒng)計(jì)結(jié)果
- 爬蟲技術(shù)分析
- 爬蟲代碼實(shí)現(xiàn)
- 爬蟲分析實(shí)現(xiàn)
- 后記
前言
最近各大一二線城市的房租都有上漲,究竟整體上漲到什么程度呢汽纠?我們也不得而知,于是乎 zone 為了一探究竟淑倾,便用 Python 爬取了房某下的深圳的租房數(shù)據(jù)河狐,以下是本次的樣本數(shù)據(jù):
除去【不限】的數(shù)據(jù)(因?yàn)榭赡軙?huì)與后面重疊),總數(shù)據(jù)量為 16971 垢村,其中后半部分地區(qū)數(shù)據(jù)量偏少割疾,是由于該區(qū)房源確實(shí)不足。因此嘉栓,此次調(diào)查也并非非常準(zhǔn)確宏榕,權(quán)且當(dāng)個(gè)娛樂(lè)項(xiàng)目,供大家觀賞侵佃。
統(tǒng)計(jì)結(jié)果
我們且先看統(tǒng)計(jì)結(jié)果麻昼,然后再看技術(shù)分析。
深圳房源分布:(按區(qū)劃分)
其中福田與南山的房源分布是最多的趣钱。但這兩塊地的房租可是不菲啊涌献。
房租單價(jià):(每月每平方米單價(jià) -- 平均數(shù))
即是 1 平方米 1 個(gè)月的價(jià)格。方塊越大首有,代表價(jià)格越高燕垃。
可以看出福田與南山是獨(dú)占鰲頭,分別是 114.874 與 113.483 井联,是其他地區(qū)的幾倍卜壕。如果租個(gè)福田 20 平方的房間:
114.874 x 20 = 2297.48
再來(lái)個(gè)兩百的水電、物業(yè):
2297.48 + 200 = 2497.48
我們節(jié)儉一點(diǎn)來(lái)算的話烙常,每天早餐 10 塊轴捎,中午 25 塊鹤盒,晚飯 25 塊:
2497.48 + 50 x 30 = 3997.48
是的,僅僅是活下來(lái)就需要 3997.48 塊侦副。
隔斷時(shí)間下個(gè)館子侦锯,每個(gè)月買些衣服,交通費(fèi)秦驯,談個(gè)女朋友尺碰,與女朋友出去逛街,妥妥滴加個(gè) 3500
3997.48 + 3500 = 7497.48
給爸媽一人一千:
7497.48 + 2000 = 9497.48
月薪一萬(wàn)妥妥滴译隘,變成了月光族亲桥。
房租單價(jià):(每日每平方米單價(jià) -- 平均數(shù))
即是 1 平方米 1 天的價(jià)格。
以前在鄉(xiāng)下沒(méi)有寸土寸金的感覺(jué)固耘,那么可以到北上廣深體驗(yàn)一下题篷,福田區(qū)每平方米每天需要 3.829 元。[捂臉]
戶型
戶型主要以 3 室 2 廳與 2 室 2 廳為主厅目。與小伙伴抱團(tuán)租房是最好的選擇了番枚,不然與不認(rèn)識(shí)的人一起合租,可能會(huì)發(fā)生一系列讓你不舒服的事情璧瞬。字體越大户辫,代表戶型數(shù)量越多。
租房面積統(tǒng)計(jì)
其中 30 - 90 平方米的租房占大多數(shù)嗤锉,如今之計(jì)渔欢,也只能是幾個(gè)小伙伴一起租房,抱團(tuán)取暖了瘟忱。
租房描述詞云
這是爬取的租房描述奥额,其中字體越大,標(biāo)識(shí)出現(xiàn)的次數(shù)越多访诱。其中【精裝修】占據(jù)了很大的部分垫挨,說(shuō)明長(zhǎng)租公寓也占領(lǐng)了很大一部分市場(chǎng)。
爬蟲思路
先爬取房某下深圳各個(gè)板塊的數(shù)據(jù)触菜,然后存進(jìn) MongoDB 數(shù)據(jù)庫(kù)九榔,最后再進(jìn)行數(shù)據(jù)分析。
數(shù)據(jù)庫(kù)部分?jǐn)?shù)據(jù):
/* 1 */
{
"_id" : ObjectId("5b827d5e8a4c184e63fb1325"),
"traffic" : "距沙井電子城公交站約567米涡相。",//交通描述
"address" : "寶安-沙井-名豪麗城",//地址
"price" : 3100,//價(jià)格
"area" : 110,//面積
"direction" : "朝南\r\n ",//朝向
"title" : "沙井 名豪麗城精裝三房 家私齊拎包住 高層朝南隨時(shí)看房",//標(biāo)題
"rooms" : "3室2廳",//戶型
"region" : "寶安"http://地區(qū)
}
爬蟲技術(shù)分析
- 請(qǐng)求庫(kù):requests
- HTML 解析:BeautifulSoup
- 詞云:wordcloud
- 數(shù)據(jù)可視化:pyecharts
- 數(shù)據(jù)庫(kù):MongoDB
- 數(shù)據(jù)庫(kù)連接:pymongo
爬蟲代碼實(shí)現(xiàn)
首先右鍵網(wǎng)頁(yè)哲泊,查看頁(yè)面源碼,找出我們要爬取得部分催蝗。
代碼實(shí)現(xiàn)切威,由于篇幅原因只展示主要代碼:(獲取一個(gè)頁(yè)面的數(shù)據(jù))
def getOnePageData(self, pageUrl, reginon="不限"):
rent = self.getCollection(self.region)
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.84 Safari/537.36'})
res = self.session.get(
pageUrl
)
soup = BeautifulSoup(res.text, "html.parser")
divs = soup.find_all("dd", attrs={"class": "info rel"}) # 獲取需要爬取得 div
for div in divs:
ps = div.find_all("p")
try: # 捕獲異常,因?yàn)轫?yè)面中有些數(shù)據(jù)沒(méi)有被填寫完整丙号,或者被插入了一條廣告先朦,則會(huì)沒(méi)有相應(yīng)的標(biāo)簽缰冤,所以會(huì)報(bào)錯(cuò)
for index, p in enumerate(ps): # 從源碼中可以看出,每一條 p 標(biāo)簽都有我們想要的信息喳魏,故在此遍歷 p 標(biāo)簽棉浸,
text = p.text.strip()
print(text) # 輸出看看是否為我們想要的信息
print("===================================")
# 爬取并存進(jìn) MongoDB 數(shù)據(jù)庫(kù)
roomMsg = ps[1].text.split("|")
# rentMsg 這樣處理是因?yàn)橛行┬畔⑽刺顚懲暾瑢?dǎo)致對(duì)象報(bào)空
area = roomMsg[2].strip()[:len(roomMsg[2]) - 2]
rentMsg = self.getRentMsg(
ps[0].text.strip(),
roomMsg[1].strip(),
int(float(area)),
int(ps[len(ps) - 1].text.strip()[:len(ps[len(ps) - 1].text.strip()) - 3]),
ps[2].text.strip(),
ps[3].text.strip(),
ps[2].text.strip()[:2],
roomMsg[3],
)
rent.insert(rentMsg)
except:
continue
數(shù)據(jù)分析實(shí)現(xiàn)
數(shù)據(jù)分析:
# 求一個(gè)區(qū)的房租單價(jià)(平方米/元)
def getAvgPrice(self, region):
areaPinYin = self.getPinyin(region=region)
collection = self.zfdb[areaPinYin]
totalPrice = collection.aggregate([{'$group': {'_id': '$region', 'total_price': {'$sum': '$price'}}}])
totalArea = collection.aggregate([{'$group': {'_id': '$region', 'total_area': {'$sum': '$area'}}}])
totalPrice2 = list(totalPrice)[0]["total_price"]
totalArea2 = list(totalArea)[0]["total_area"]
return totalPrice2 / totalArea2
# 獲取各個(gè)區(qū) 每個(gè)月一平方米需要多少錢
def getTotalAvgPrice(self):
totalAvgPriceList = []
totalAvgPriceDirList = []
for index, region in enumerate(self.getAreaList()):
avgPrice = self.getAvgPrice(region)
totalAvgPriceList.append(round(avgPrice, 3))
totalAvgPriceDirList.append({"value": round(avgPrice, 3), "name": region + " " + str(round(avgPrice, 3))})
return totalAvgPriceDirList
# 獲取各個(gè)區(qū) 每一天一平方米需要多少錢
def getTotalAvgPricePerDay(self):
totalAvgPriceList = []
for index, region in enumerate(self.getAreaList()):
avgPrice = self.getAvgPrice(region)
totalAvgPriceList.append(round(avgPrice / 30, 3))
return (self.getAreaList(), totalAvgPriceList)
# 獲取各區(qū)統(tǒng)計(jì)樣本數(shù)量
def getAnalycisNum(self):
analycisList = []
for index, region in enumerate(self.getAreaList()):
collection = self.zfdb[self.pinyinDir[region]]
print(region)
totalNum = collection.aggregate([{'$group': {'_id': '', 'total_num': {'$sum': 1}}}])
totalNum2 = list(totalNum)[0]["total_num"]
analycisList.append(totalNum2)
return (self.getAreaList(), analycisList)
# 獲取各個(gè)區(qū)的房源比重
def getAreaWeight(self):
result = self.zfdb.rent.aggregate([{'$group': {'_id': '$region', 'weight': {'$sum': 1}}}])
areaName = []
areaWeight = []
for item in result:
if item["_id"] in self.getAreaList():
areaWeight.append(item["weight"])
areaName.append(item["_id"])
print(item["_id"])
print(item["weight"])
# print(type(item))
return (areaName, areaWeight)
# 獲取 title 數(shù)據(jù)截酷,用于構(gòu)建詞云
def getTitle(self):
collection = self.zfdb["rent"]
queryArgs = {}
projectionFields = {'_id': False, 'title': True} # 用字典指定需要的字段
searchRes = collection.find(queryArgs, projection=projectionFields).limit(1000)
content = ''
for result in searchRes:
print(result["title"])
content += result["title"]
return content
# 獲取戶型數(shù)據(jù)(例如:3 室 2 廳)
def getRooms(self):
results = self.zfdb.rent.aggregate([{'$group': {'_id': '$rooms', 'weight': {'$sum': 1}}}])
roomList = []
weightList = []
for result in results:
roomList.append(result["_id"])
weightList.append(result["weight"])
# print(list(result))
return (roomList, weightList)
# 獲取租房面積
def getAcreage(self):
results0_30 = self.zfdb.rent.aggregate([
{'$match': {'area': {'$gt': 0, '$lte': 30}}},
{'$group': {'_id': '', 'count': {'$sum': 1}}}
])
results30_60 = self.zfdb.rent.aggregate([
{'$match': {'area': {'$gt': 30, '$lte': 60}}},
{'$group': {'_id': '', 'count': {'$sum': 1}}}
])
results60_90 = self.zfdb.rent.aggregate([
{'$match': {'area': {'$gt': 60, '$lte': 90}}},
{'$group': {'_id': '', 'count': {'$sum': 1}}}
])
results90_120 = self.zfdb.rent.aggregate([
{'$match': {'area': {'$gt': 90, '$lte': 120}}},
{'$group': {'_id': '', 'count': {'$sum': 1}}}
])
results120_200 = self.zfdb.rent.aggregate([
{'$match': {'area': {'$gt': 120, '$lte': 200}}},
{'$group': {'_id': '', 'count': {'$sum': 1}}}
])
results200_300 = self.zfdb.rent.aggregate([
{'$match': {'area': {'$gt': 200, '$lte': 300}}},
{'$group': {'_id': '', 'count': {'$sum': 1}}}
])
results300_400 = self.zfdb.rent.aggregate([
{'$match': {'area': {'$gt': 300, '$lte': 400}}},
{'$group': {'_id': '', 'count': {'$sum': 1}}}
])
results400_10000 = self.zfdb.rent.aggregate([
{'$match': {'area': {'$gt': 300, '$lte': 10000}}},
{'$group': {'_id': '', 'count': {'$sum': 1}}}
])
results0_30_ = list(results0_30)[0]["count"]
results30_60_ = list(results30_60)[0]["count"]
results60_90_ = list(results60_90)[0]["count"]
results90_120_ = list(results90_120)[0]["count"]
results120_200_ = list(results120_200)[0]["count"]
results200_300_ = list(results200_300)[0]["count"]
results300_400_ = list(results300_400)[0]["count"]
results400_10000_ = list(results400_10000)[0]["count"]
attr = ["0-30平方米", "30-60平方米", "60-90平方米", "90-120平方米", "120-200平方米", "200-300平方米", "300-400平方米", "400+平方米"]
value = [
results0_30_, results30_60_, results60_90_, results90_120_, results120_200_, results200_300_, results300_400_, results400_10000_
]
return (attr, value)
數(shù)據(jù)展示:
# 展示餅圖
def showPie(self, title, attr, value):
from pyecharts import Pie
pie = Pie(title)
pie.add("aa", attr, value, is_label_show=True)
pie.render()
# 展示矩形樹圖
def showTreeMap(self, title, data):
from pyecharts import TreeMap
data = data
treemap = TreeMap(title, width=1200, height=600)
treemap.add("深圳", data, is_label_show=True, label_pos='inside', label_text_size=19)
treemap.render()
# 展示條形圖
def showLine(self, title, attr, value):
from pyecharts import Bar
bar = Bar(title)
bar.add("深圳", attr, value, is_convert=False, is_label_show=True, label_text_size=18, is_random=True,
# xaxis_interval=0, xaxis_label_textsize=9,
legend_text_size=18, label_text_color=["#000"])
bar.render()
# 展示詞云
def showWorkCloud(self, content, image_filename, font_filename, out_filename):
d = path.dirname(__name__)
# content = open(path.join(d, filename), 'rb').read()
# 基于TF-IDF算法的關(guān)鍵字抽取, topK返回頻率最高的幾項(xiàng), 默認(rèn)值為20, withWeight
# 為是否返回關(guān)鍵字的權(quán)重
tags = jieba.analyse.extract_tags(content, topK=100, withWeight=False)
text = " ".join(tags)
# 需要顯示的背景圖片
img = imread(path.join(d, image_filename))
# 指定中文字體, 不然會(huì)亂碼的
wc = WordCloud(font_path=font_filename,
background_color='black',
# 詞云形狀涮拗,
mask=img,
# 允許最大詞匯
max_words=400,
# 最大號(hào)字體乾戏,如果不指定則為圖像高度
max_font_size=100,
# 畫布寬度和高度迂苛,如果設(shè)置了msak則不會(huì)生效
# width=600,
# height=400,
margin=2,
# 詞語(yǔ)水平擺放的頻率,默認(rèn)為0.9.即豎直擺放的頻率為0.1
prefer_horizontal=0.9
)
wc.generate(text)
img_color = ImageColorGenerator(img)
plt.imshow(wc.recolor(color_func=img_color))
plt.axis("off")
plt.show()
wc.to_file(path.join(d, out_filename))
# 展示 pyecharts 的詞云
def showPyechartsWordCloud(self, attr, value):
from pyecharts import WordCloud
wordcloud = WordCloud(width=1300, height=620)
wordcloud.add("", attr, value, word_size_range=[20, 100])
wordcloud.render()
后記
最近還真是挺多事情發(fā)生的鼓择,房租的暴漲三幻,其實(shí)是資本力量進(jìn)駐了租房市場(chǎng)。自如呐能、蛋殼這些長(zhǎng)租公寓念搬,相互太高房租價(jià)格,而且讓客戶簽第三方貸款協(xié)議摆出,前期發(fā)展可能需要一點(diǎn)錢朗徊,但是到后期壟斷市場(chǎng)之后,只要住房剛需在偎漫,就不會(huì)賺不回錢爷恳。最后,應(yīng)對(duì)外界條件的變動(dòng)象踊,我們還是應(yīng)該提升自己的硬實(shí)力温亲,這樣才能提升自己的生存能力。
本篇文章首發(fā)于公眾號(hào)「zone7」杯矩,關(guān)注公眾號(hào)獲取最新推文栈虚,后臺(tái)回復(fù)【深圳租房】獲取源碼。