畢業(yè)設(shè)計(jì) 大數(shù)據(jù)上海租房數(shù)據(jù)分析與可視化
?? 這兩年開始畢業(yè)設(shè)計(jì)和畢業(yè)答辯的要求和難度不斷提升,傳統(tǒng)的畢設(shè)題目缺少創(chuàng)新和亮點(diǎn),往往達(dá)不到畢業(yè)答辯的要求,這兩年不斷有學(xué)弟學(xué)妹告訴學(xué)長(zhǎng)自己做的項(xiàng)目系統(tǒng)達(dá)不到老師的要求。
為了大家能夠順利以及最少的精力通過畢設(shè),學(xué)長(zhǎng)分享優(yōu)質(zhì)畢業(yè)設(shè)計(jì)項(xiàng)目,今天要分享的是
?? ?
??學(xué)長(zhǎng)這里給一個(gè)題目綜合評(píng)分(每項(xiàng)滿分5分)
難度系數(shù):3分
工作量:3分
創(chuàng)新點(diǎn):5分
畢設(shè)幫助,選題指導(dǎo),技術(shù)解答,歡迎打擾,見B站個(gè)人主頁
https://space.bilibili.com/33886978
1 課題背景
基于Python的上海自如租房大數(shù)據(jù)聚類分析與可視化,爬取自如所有上海房源,進(jìn)行k-means聚類分析,將房源劃分為不同等級(jí)。并對(duì)數(shù)據(jù)進(jìn)行可視化分析。
2 實(shí)現(xiàn)效果
聚類后的dataframe結(jié)果

堆疊柱狀圖

餅圖

3D柱狀圖


3 獲取房源數(shù)據(jù)
此次側(cè)重XPath的使用和反爬蟲小技巧。XPath是用路徑表達(dá)式在XML文檔中選取節(jié)點(diǎn),這里也同樣適用于HTML文檔的搜索。
3.1 確定URL
打開上海鏈家網(wǎng)的租房頁面,選擇篩選條件,示例如下。確認(rèn)后地址欄的URL會(huì)根據(jù)篩選條件而發(fā)生變化。(當(dāng)然如果沒有想好想要住的區(qū)域,地鐵線,租金,面積,朝向,戶型也沒有關(guān)系,可以直接爬取全部的上海房源數(shù)據(jù)。)

共計(jì)28頁房源信息,點(diǎn)擊切換下一頁,觀察URL會(huì)發(fā)現(xiàn)鏈家網(wǎng)是靜態(tài)的網(wǎng)頁,頁面切換通過在URL中加入pg{i}參數(shù)實(shí)現(xiàn)。因此我們只要能爬取一頁的信息,就可以通過參數(shù)循環(huán)來爬取所有頁面。

3.2 解析頁面
按F12打開開發(fā)者工具,在頁面中選擇一個(gè)元素以進(jìn)行檢查。可以看到右側(cè)的房源列表模塊和左邊的房源信息是一一對(duì)應(yīng)的。左側(cè)的每一條房源信息都等價(jià)于右側(cè)的class屬性為content__list --item的一個(gè)div圖層。因此我們只需要觀察了解第一個(gè)房源信息即可。

繼續(xù)展開這個(gè)div圖層,會(huì)發(fā)現(xiàn)我們需要的信息基本都被包含在屬性為content__list--item-- main的子div圖層中,尤其是其中幾個(gè)class為title,description,price的元素里。

以屬性為content__list--item-- title的paragraph為例,其中包含了租賃方式,街區(qū),房屋朝向等信息。我們可以用XPath來匹配和定位到這個(gè)段落,取出里面的文本。常用的匹配規(guī)則為 / 代表選取直接子節(jié)點(diǎn),// 代表選擇所有子孫節(jié)點(diǎn),. 代表選取當(dāng)前節(jié)點(diǎn),.. 代表選取當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn),@ 則是加了屬性的限定,選取匹配屬性的特定節(jié)點(diǎn)。下面是租賃方式(整租/合租)的匹配方式。

LeaseMethod = li.xpath('.//div[@class="content__list--item--main"]/p[@class="content__list--item--title"]/a/text()')[0].strip().split(' ')[0].split('·')[0]
以此類推,我們可以輕易的取出街區(qū),租賃方式,朝向,每月租金,行政區(qū),板塊,房屋面積,格局和發(fā)布時(shí)長(zhǎng)等信息。
3.3 反爬蟲解決措施
網(wǎng)站的反爬措施有很多,比如檢測(cè)訪問請(qǐng)求頭。且如果一個(gè)header短期頻繁發(fā)送請(qǐng)求,也很容易被識(shí)別。這種情況下可以通過添加多個(gè)請(qǐng)求頭,每次隨機(jī)選取一個(gè)header,偽裝成瀏覽器訪問;且設(shè)置time sleep,每次發(fā)送請(qǐng)求隨機(jī)間隔一段時(shí)間來防止出現(xiàn)error403/404。還有一些方法如添加Referer,host,代理IP等,這里不做過多闡述,感興趣的小伙伴歡迎自行探索。
完整代碼和最終結(jié)果如下??梢钥吹降谝徊皆阪溂抑黜撍阉鲿r(shí)顯示的828條記錄已經(jīng)全部獲取到數(shù)據(jù)框中??梢赃M(jìn)行下一步的分析啦!
import requests
from lxml import etree
import random
import time
import pandas as pd
? ?
#偽裝請(qǐng)求頭
user_agents = [
? ?'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 OPR/26.0.1656.60',
? ?'Opera/8.0 (Windows NT 5.1; U; en)',
? ?'Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/2.0.0 Opera 9.50',
? ?'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.50',
? ?'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:34.0) Gecko/20100101 Firefox/34.0',
? ?'Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10',
? ?'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.57.2 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2',
? ?'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36',
? ?'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11',
? ?'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.133 ',
? ?'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36',
? ?"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36",
? ?"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36",
? ?"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0",
? ?"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/537.75.14",
? ?"Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0)",
? ?'Mozilla/5.0 (Windows; U; Windows NT 5.1; it; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11',
? ?'Opera/9.25 (Windows NT 5.1; U; en)',
? ?'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)',
? ?'Mozilla/5.0 (compatible; Konqueror/3.5; Linux) KHTML/3.5.5 (like Gecko) (Kubuntu)',
? ?'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.0.12) Gecko/20070731 Ubuntu/dapper-security Firefox/1.5.0.12',
? ?'Lynx/2.8.5rel.1 libwww-FM/2.14 SSL-MM/1.4.1 GNUTLS/1.2.9',
? ?"Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.7 (KHTML, like Gecko) Ubuntu/11.04 Chromium/16.0.912.77 Chrome/16.0.912.77 Safari/535.7",
? ?"Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:10.0) Gecko/20100101 Firefox/10.0 "
]
def getHeaders():
? ?user_agent = user_agents[random.randint(0, len(user_agents)-1)]
? ?headers = {
? ? ? ?'User-Agent': user_agent
? ?}
? ?return headers
? ? ?
#對(duì)一個(gè)URL發(fā)送請(qǐng)求,解析結(jié)果,獲取所需數(shù)據(jù)
def get_data(url):
? ?#反爬蟲策略1:隨機(jī)取headers
? ?response = requests.get(url, headers=getHeaders(), stream=True)
? ?tree = etree.HTML(response.text)
? ?# 定位到content__list
? ?li_list = tree.xpath('//div[@class="content w1150"]/div[@class="content__article"]/div[@class="content__list"]/div')
? ?# all_house_list = []
? ?for li in li_list:
? ? ? ?#下面是兩種定位方式,都可
? ? ? ?# Nbhood = li.xpath('.//div[@class="content__list--item--main"]/p[@class="content__list--item--title"]/a[@class="twoline"]/text()')[0].strip().split(' ')[0].split('·')[1]
? ? ? ?Nbhood = li.xpath('.//div[@class="content__list--item--main"]/p[@class="content__list--item--title"]/a/text()')[0].strip().split(' ')[0].split('·')[1]
? ? ? ?LeaseMethod = li.xpath('.//div[@class="content__list--item--main"]/p[@class="content__list--item--title"]/a/text()')[0].strip().split(' ')[0].split('·')[0]
? ? ? ?HouseOrientation = li.xpath('.//div[@class="content__list--item--main"]/p[@class="content__list--item--title"]/a/text()')[0].strip().split(' ')[2]
? ? ? ?Rent = li.xpath('.//div[@class="content__list--item--main"]/span[@class="content__list--item-price"]/em/text()')[0]
? ? ? ?District = li.xpath('.//div[@class="content__list--item--main"]/p[@class="content__list--item--des"]/a/text()')[0]
? ? ? ?Location = li.xpath('.//div[@class="content__list--item--main"]/p[@class="content__list--item--des"]/a/text()')[1]
? ? ? ?Size = li.xpath('.//div[@class="content__list--item--main"]/p[@class="content__list--item--des"]/text()')[4].strip()
? ? ? ?HouseType = li.xpath('.//div[@class="content__list--item--main"]/p[@class="content__list--item--des"]/text()')[6].strip()
? ? ? ?releaseTime = li.xpath('.//div[@class="content__list--item--main"]/p[@class="content__list--item--brand oneline"]/span[@class="content__list--item--time oneline"]/text()')[0]
? ? ? ?Link = li.xpath('.//div[@class="content__list--item--main"]/p[@class="content__list--item--title"]/a[@class="twoline"]//@href')[0]
? ? ? ?all_house_list.append((Nbhood,LeaseMethod,HouseOrientation,Rent,District,Location,Size,HouseType,releaseTime))
? ? ? ?
? ?return all_house_list
#循環(huán)爬取所需租房信息
pages = ['https://sh.lianjia.com/ditiezufang/li143685063/pg{}rt200600000001l1l0ra1ra2ra0rp5rp6/'.format(x) for x in range(1,29)]
all_house_list = []
count = 0
for page in pages:
? ?a = get_data(page)
? ?#反爬蟲策略2:每次爬取隨機(jī)間隔3-10s
? ?time.sleep(random.randint(3,10))
? ?count=count+1
? ?print ('the '+str(count)+' page is sucessful')
name = ["街區(qū)", "租賃方式", "朝向", "每月租金", "行政區(qū)","板塊","房屋面積","格局","發(fā)布時(shí)長(zhǎng)"]
page_data = pd.DataFrame( columns= name,data=all_house_list)

4 K-means聚類算法
基本原理
k-Means算法是一種使用最普遍的聚類算法,它是一種無監(jiān)督學(xué)習(xí)算法,目的是將相似的對(duì)象歸到同一個(gè)簇中。簇內(nèi)的對(duì)象越相似,聚類的效果就越好。該算法不適合處理離散型屬性,但對(duì)于連續(xù)型屬性具有較好的聚類效果。
聚類效果判定標(biāo)準(zhǔn)
使各個(gè)樣本點(diǎn)與所在簇的質(zhì)心的誤差平方和達(dá)到最小,這是評(píng)價(jià)k-means算法最后聚類效果的評(píng)價(jià)標(biāo)準(zhǔn)。

算法實(shí)現(xiàn)步驟
1)選定k值
2)創(chuàng)建k個(gè)點(diǎn)作為k個(gè)簇的起始質(zhì)心。
3)分別計(jì)算剩下的元素到k個(gè)簇的質(zhì)心的距離,將這些元素分別劃歸到距離最小的簇。
4)根據(jù)聚類結(jié)果,重新計(jì)算k個(gè)簇各自的新的質(zhì)心,即取簇中全部元素各自維度下的算術(shù)平均值。
5)將全部元素按照新的質(zhì)心重新聚類。
6)重復(fù)第5步,直到聚類結(jié)果不再變化。
7)最后,輸出聚類結(jié)果。
算法缺點(diǎn)
雖然K-Means算法原理簡(jiǎn)單,但是有自身的缺陷:
1)聚類的簇?cái)?shù)k值需在聚類前給出,但在很多時(shí)候中k值的選定是十分難以估計(jì)的,很多情況我們聚類前并不清楚給出的數(shù)據(jù)集應(yīng)當(dāng)分成多少類才最恰當(dāng)。
2)k-means需要人為地確定初始質(zhì)心,不一樣的初始質(zhì)心可能會(huì)得出差別很大的聚類結(jié)果,無法保證k-means算法收斂于全局最優(yōu)解。
3)對(duì)離群點(diǎn)敏感。
4)結(jié)果不穩(wěn)定(受輸入順序影響)。
5)時(shí)間復(fù)雜度高O(nkt),其中n是對(duì)象總數(shù),k是簇?cái)?shù),t是迭代次數(shù)。
算法實(shí)現(xiàn)關(guān)鍵問題說明
K值的選定說明
根據(jù)聚類原則:組內(nèi)差距要小,組間差距要大。我們先算出不同k值下各個(gè)SSE(Sum of squared errors)值,然后繪制出折線圖來比較,從中選定最優(yōu)解。從圖中,我們可以看出k值到達(dá)5以后,SSE變化趨于平緩,所以我們選定5作為k值。

初始的K個(gè)質(zhì)心選定說明
初始的k個(gè)質(zhì)心選定是采用的隨機(jī)法。從各列數(shù)值最大值和最小值中間按正太分布隨機(jī)選取k個(gè)質(zhì)心。
關(guān)于離群點(diǎn)
離群點(diǎn)就是遠(yuǎn)離整體的,非常異常、非常特殊的數(shù)據(jù)點(diǎn)。因?yàn)閗-means算法對(duì)離群點(diǎn)十分敏感,所以在聚類之前應(yīng)該將這些“極大”、“極小”之類的離群數(shù)據(jù)都去掉,否則會(huì)對(duì)于聚類的結(jié)果有影響。離群點(diǎn)的判定標(biāo)準(zhǔn)是根據(jù)前面數(shù)據(jù)可視化分析過程的散點(diǎn)圖和箱線圖進(jìn)行判定。根據(jù)散點(diǎn)圖和箱線圖,需要去除離散值的范圍如下:
1)單價(jià):基本都在100000以內(nèi),沒有特別的異常值。
2)總價(jià):基本都集中在3000以內(nèi),這里我們需要去除3000外的異常值。
3)建筑面積:基本都集中在500以內(nèi),這里我們需要去除500外的異常值。
數(shù)據(jù)的標(biāo)準(zhǔn)化
因?yàn)榭們r(jià)的單位為萬元,單價(jià)的單位為元/平米,建筑面積的單位為平米,所以數(shù)據(jù)點(diǎn)計(jì)算出歐幾里德距離的單位是沒有意義的。同時(shí),總價(jià)都是3000以內(nèi)的數(shù),建筑面積都是500以內(nèi)的數(shù),但單價(jià)基本都是20000以上的數(shù),在計(jì)算距離時(shí)單價(jià)起到的作用就比總價(jià)大,總價(jià)和單價(jià)的作用都遠(yuǎn)大于建筑面積,這樣聚類出來的結(jié)果是有問題的。這樣的情況下,我們需要將數(shù)據(jù)標(biāo)準(zhǔn)化,即將數(shù)據(jù)按比例縮放,使之都落入一個(gè)特定區(qū)間內(nèi)。去除數(shù)據(jù)的單位限制,將其轉(zhuǎn)化為無量綱的純數(shù)值,便于不同單位或量級(jí)的指標(biāo)能夠進(jìn)行計(jì)算和比較。
我們將單價(jià)、總價(jià)和面積都映射到500,因?yàn)槊娣e本身就都在500以內(nèi),不要特別處理。單價(jià)在計(jì)算距離時(shí),需要先乘以映射比例0.005,總價(jià)需要乘以映射比例0.16。進(jìn)行數(shù)據(jù)標(biāo)準(zhǔn)化前和進(jìn)行數(shù)據(jù)標(biāo)準(zhǔn)化后的聚類效果對(duì)比如下:圖32、圖33是沒有數(shù)據(jù)標(biāo)準(zhǔn)化前的聚類效果散點(diǎn)圖;圖34、圖35是數(shù)據(jù)標(biāo)準(zhǔn)化后的聚類效果散點(diǎn)圖。
數(shù)據(jù)標(biāo)準(zhǔn)化前的單價(jià)與建筑面積聚類效果散點(diǎn)圖:

聚類結(jié)果分析
聚類結(jié)果如下

5 部分核心代碼
from sklearn.cluster import KMeans # 導(dǎo)入Kmeans
k=5 # 聚類類別數(shù)
kmodel = KMeans(n_clusters=k, n_jobs=4)
kmodel.fit(housing_prepared)
kmodel.cluster_centers_[0:5,:3]
ss.inverse_transform(kmodel.cluster_centers_[0:5,:3],copy=True)
housing_data = housing.copy()
housing_data.head(1)
c = pd.DataFrame(kmodel.cluster_centers_).sort_values(2)
c['房屋等級(jí)']=['交通不便普通','交通一般普通','交通便利普通','高端房源','豪宅']
#c
labels = c
# 高價(jià)值
expensive = labels[labels['房屋等級(jí)']=='豪宅'].index[0]
high = labels[labels['房屋等級(jí)']=='高端房源'].index[0]
mid = labels[labels['房屋等級(jí)']=='交通便利普通'].index[0]
normal = labels[labels['房屋等級(jí)']=='交通一般普通'].index[0]
low = labels[labels['房屋等級(jí)']=='交通不便普通'].index[0]
#print(expensive,high,mid,low)
kmeans_type_shift = {
? ?expensive:'豪宅',
? ?high:'高端房源',
? ?mid:'交通便利普通',
? ?normal:'交通一般普通',
? ?low:'交通不便普通'
}
housing_data['聚類類別'] = housing_data['聚類類別'].map(kmeans_type_shift)
housing_data.head()
housing_data.shape
#housing_data.info()
housing_data.to_csv('housing_with_type.csv')
畢設(shè)幫助,選題指導(dǎo),技術(shù)解答,歡迎打擾,見B站個(gè)人主頁
https://space.bilibili.com/33886978