用Python告訴你深圳房租有多高
概述
- 前言
- 統(tǒng)計(jì)結(jié)果
- 爬蟲技術(shù)分析
- 爬蟲代碼實(shí)現(xiàn)
- 爬蟲分析實(shí)現(xiàn)
- 后記
前言
最近各大一二線城市的房租都有上漲,究竟整體上漲到什么程度呢?我們也不得而知,于是乎 zone 為了一探究竟,便用 Python 爬取了房某下的深圳的租房數(shù)據(jù),以下是本次的樣本數(shù)據(jù):
除去【不限】的數(shù)據(jù)(因?yàn)榭赡軙c后面重疊),總數(shù)據(jù)量為 16971 ,其中后半部分地區(qū)數(shù)據(jù)量偏少,是由于該區(qū)房源確實(shí)不足。因此,此次調(diào)查也并非非常準(zhǔn)確,權(quán)且當(dāng)個娛樂項(xiàng)目,供大家觀賞。
統(tǒng)計(jì)結(jié)果
我們且先看統(tǒng)計(jì)結(jié)果,然后再看技術(shù)分析。
深圳房源分布:(按區(qū)劃分)
其中福田與南山的房源分布是最多的。但這兩塊地的房租可是不菲啊。
房租單價(jià):(每月每平方米單價(jià) -- 平均數(shù))
即是 1 平方米 1 個月的價(jià)格。方塊越大,代表價(jià)格越高。
可以看出福田與南山是獨(dú)占鰲頭,分別是 114.874 與 113.483 ,是其他地區(qū)的幾倍。如果租個福田 20 平方的房間:
- 114.874 x 20 = 2297.48
再來個兩百的水電、物業(yè):
- 2297.48 + 200 = 2497.48
我們節(jié)儉一點(diǎn)來算的話,每天早餐 10 塊,中午 25 塊,晚飯 25 塊:
- 2497.48 + 50 x 30 = 3997.48
是的,僅僅是活下來就需要 3997.48 塊。
隔斷時間下個館子,每個月買些衣服,交通費(fèi),談個女朋友,與女朋友出去逛街,妥妥滴加個 3500
- 3997.48 + 3500 = 7497.48
給爸媽一人一千:
- 7497.48 + 2000 = 9497.48
月薪一萬妥妥滴,變成了月光族。
房租單價(jià):(每日每平方米單價(jià) -- 平均數(shù))
- 即是 1 平方米 1 天的價(jià)格。
以前在鄉(xiāng)下沒有***的感覺,那么可以到北上廣深體驗(yàn)一下,福田區(qū)每平方米每天需要 3.829 元。[捂臉]
戶型
戶型主要以 3 室 2 廳與 2 室 2 廳為主。與小伙伴抱團(tuán)租房是***的選擇了,不然與不認(rèn)識的人一起合租,可能會發(fā)生一系列讓你不舒服的事情。字體越大,代表戶型數(shù)量越多。
租房面積統(tǒng)計(jì)
其中 30 - 90 平方米的租房占大多數(shù),如今之計(jì),也只能是幾個小伙伴一起租房,抱團(tuán)取暖了。
租房描述詞云
這是爬取的租房描述,其中字體越大,標(biāo)識出現(xiàn)的次數(shù)越多。其中【精裝修】占據(jù)了很大的部分,說明長租公寓也占領(lǐng)了很大一部分市場。
爬蟲思路
先爬取房某下深圳各個板塊的數(shù)據(jù),然后存進(jìn) MongoDB 數(shù)據(jù)庫,***再進(jìn)行數(shù)據(jù)分析。
數(shù)據(jù)庫部分?jǐn)?shù)據(jù):
- /* 1 */
- {
- "_id" : ObjectId("5b827d5e8a4c184e63fb1325"),
- "traffic" : "距沙井電子城公交站約567米。",//交通描述
- "address" : "寶安-沙井-名豪麗城",//地址
- "price" : 3100,//價(jià)格
- "area" : 110,//面積
- "direction" : "朝南\r\n ",//朝向
- "title" : "沙井 名豪麗城精裝三房 家私齊拎包住 高層朝南隨時看房",//標(biāo)題
- "rooms" : "3室2廳",//戶型
- "region" : "寶安"//地區(qū)
- }
爬蟲技術(shù)分析
- 請求庫:requests
- HTML 解析:BeautifulSoup
- 詞云:wordcloud
- 數(shù)據(jù)可視化:pyecharts
- 數(shù)據(jù)庫:MongoDB
- 數(shù)據(jù)庫連接:pymongo
爬蟲代碼實(shí)現(xiàn)
首先右鍵網(wǎng)頁,查看頁面源碼,找出我們要爬取得部分。
源碼
代碼實(shí)現(xiàn),由于篇幅原因只展示主要代碼:(獲取一個頁面的數(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)轫撁嬷杏行?shù)據(jù)沒有被填寫完整,或者被插入了一條廣告,則會沒有相應(yīng)的標(biāo)簽,所以會報(bào)錯
- for index, p in enumerate(ps): # 從源碼中可以看出,每一條 p 標(biāo)簽都有我們想要的信息,故在此遍歷 p 標(biāo)簽,
- text = p.text.strip()
- print(text) # 輸出看看是否為我們想要的信息
- print("===================================")
- # 爬取并存進(jìn) MongoDB 數(shù)據(jù)庫
- roomMsg = ps[1].text.split("|")
- # rentMsg 這樣處理是因?yàn)橛行┬畔⑽刺顚懲暾瑢?dǎo)致對象報(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ù)分析:
- # 求一個區(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
- # 獲取各個區(qū) 每個月一平方米需要多少錢
- 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
- # 獲取各個區(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)
- # 獲取各個區(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))
- # 指定中文字體, 不然會亂碼的
- wc = WordCloud(font_path=font_filename,
- background_color='black',
- # 詞云形狀,
- mask=img,
- # 允許***詞匯
- max_words=400,
- # ***號字體,如果不指定則為圖像高度
- max_font_size=100,
- # 畫布寬度和高度,如果設(shè)置了msak則不會生效
- # width=600,
- # height=400,
- margin=2,
- # 詞語水平擺放的頻率,默認(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)駐了租房市場。自如、蛋殼這些長租公寓,相互抬高房租價(jià)格,而且讓客戶簽第三方貸款協(xié)議,前期發(fā)展可能需要一點(diǎn)錢,但是到后期壟斷市場之后,只要住房剛需在,就不會賺不回錢。***,應(yīng)對外界條件的變動,我們還是應(yīng)該提升自己的硬實(shí)力,這樣才能提升自己的生存能力。