【項目實戰】數據挖掘 + 數據清洗 + 數據可視化

自己親手全手打了一套系統的代碼,幫助朋友完成設計,做了貴陽市幾個區的房屋價格爬取以及數據清洗和可視化操作,代碼細細道來:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-CKhdmiWg-1589163973193)(D:\CSDN\pic\爬蟲畢設_買房\8dd4fd69b590a25bd4427de2020a559.png)]

上圖鎮樓,接下來細說。


一:數據挖掘

我選用了鏈家網做數據爬取場所(不得不嘮叨一句,這個網站真是爲了爬蟲而生的,對爬蟲特別友好哈哈哈,反扒措施比較少)

比如我們爬取貴陽市烏當區的所有房子的房價及其他信息:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-55GwXGeD-1589163973196)(D:\CSDN\pic\爬蟲畢設_買房\1589118368542.png)]

比如我們爬取第一個房子的價格:115萬:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-xyKYNu5z-1589163973199)(D:\CSDN\pic\爬蟲畢設_買房\1589119934247.png)]

接下來我們可以使用複製CSS選擇器或者XPath等等來實現獲取:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-iNZbnpRE-1589163973204)(D:\CSDN\pic\爬蟲畢設_買房\1589120121369.png)]

下面我們使用複製XPath的方式,修改路徑即可(需要一定前端知識)

分別實現詳解:

1:導入必備庫

import requests
from lxml import etree
import xlwt
from xlutils.copy import copy
import xlrd
import csv
import pandas as pd
import time

細說一下:
Requests 是用Python語言編寫,基於 urllib,採用 Apache2 Licensed 開源協議的 HTTP 庫,爬蟲必備技能之一。它比 urllib 更加方便,可以節約我們大量的工作,完全滿足 HTTP 測試需求。Requests 的哲學是以 PEP 20 的習語爲中心開發的,所以它比 urllib 更加 Pythoner。更重要的一點是它支持 Python3 哦!

Pandas是python第三方庫,提供高性能易用數據類型和分析工具 , pandas是一個強大的分析結構化數據的工具集;它的使用基礎是Numpy(提供高性能的矩陣運算);用於數據挖掘和數據分析,同時也提供數據清洗功能。

2:定義爬取URL地址和設置請求頭(其實還可以更完善,不過鏈家網比較友善,這點夠用了)

        self.url = 'https://gy.lianjia.com/ershoufang/wudangqu/pg{}/'
        self.headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36"}

url是要獲取信息的地址:我們選用貴陽市(gy)烏當區(wudangqu)爲目標,然後pg{}是頁碼的意思:pg100就是爬第一百頁,這裏我們使用{}做一下佔位,方便後續從第一頁迭代到最後。

headers是我們的請求頭,就是模擬人正常登錄的意思,而不是通過python,讓網頁知道你是爬蟲,知道了就有可能封掉你的IP等。 通常HTTP消息包括客戶機向服務器的請求消息和服務器向客戶機的響應消息。這兩種類型的消息由一個起始行,一個或者多個頭域,一個只是頭域結束的空行和可 選的消息體組成。HTTP的頭域包括通用頭,請求頭,響應頭和實體頭四個部分。每個頭域由一個域名,冒號(:)和域值三部分組成。域名是大小寫無關的,域 值前可以添加任何數量的空格符,頭域可以被擴展爲多行,在每行開始處,使用至少一個空格或製表符。 User-Agent頭域的內容包含發出請求的用戶信息。

3:使用Requests獲取數據

    def get_response_spider(self, url_str):  # 發送請求
        get_response = requests.get(self.url, headers=self.headers)
        time.sleep(4)
        response = get_response.content.decode()
        html = etree.HTML(response)
        return html

4:使用Xpath篩選數據源,過程見上圖,需要一定的前端知識,不過,也有一些技巧:

    def get_content_html(self, html):  # 使xpath獲取數據
        self.houseInfo = html.xpath('//div[@class="houseInfo"]/text()')
        self.title = html.xpath('//div[@class="title"]/a/text()')
        self.positionInfo = html.xpath('//div[@class="positionInfo"]/a/text()')
        self.totalPrice = html.xpath('//div[@class="totalPrice"]/span/text()')
        self.unitPrice = html.xpath('//div[@class="unitPrice"]/span/text()')
        self.followInfo = html.xpath('//div[@class="followInfo"]/text()')
        self.tag = html.xpath('//div[@class="tag"]/span/text()')

5:使用生成器,通過for循環和yield生成器迭代生成數據項:

    def xpath_title(self):
        for i in range(len(self.title)):
            yield self.title[i]

    def xpath_positionInfo(self):
        for i in range(len(self.positionInfo)):
            yield self.positionInfo[i]

    def xpath_totalPrice(self):
        for i in range(len(self.totalPrice)):
            yield self.totalPrice[i]
    def xpath_unitPrice(self):
        for i in range(len(self.unitPrice)):
            yield self.unitPrice[i]
    def xpath_followInfo(self):
        for i in range(len(self.followInfo)):
            yield self.followInfo[i]
    def xpath_tag(self):
        for i in range(len(self.tag)):
            yield self.tag[i]

6:通過調用這些函數進行預獲得:

self.xpath_houseInfo()
        self.xpath_title()
        self.xpath_positionInfo()
        self.xpath_totalPrice()
        self.xpath_unitPrice()
        self.xpath_followInfo()
        self.xpath_tag()
        get_houseInfo = self.xpath_houseInfo()
        get_title = self.xpath_title()
        get_positionInfo=self.xpath_positionInfo()
        get_totalPrice = self.xpath_totalPrice()
        get_unitPrice = self.xpath_unitPrice()
        get_followInfo=self.xpath_followInfo()
        get_tag=self.xpath_tag()

這裏的函數就是調用上面的生成器的函數:
生成器yield 理解的關鍵在於:下次迭代時,代碼從yield的下一跳語句開始執行。

7:數據篩選,寫入文本中:

        while True:
            data_houseInfo= next(get_houseInfo)
            data_title=next(get_title)
            data_positionInfo=next(get_positionInfo)
            data_totalPrice=next(get_totalPrice)
            data_unitPrice=next(get_unitPrice)
            data_followInfo=next(get_followInfo)
            data_tag=next(get_tag)

            with open("lianjia1.csv", "a", newline="", encoding="utf-8-sig") as f:
                fieldnames = ['houseInfo', 'title', 'positionInfo', 'totalPrice/萬元', 'unitPrice', 'followInfo', 'tag']
                writer = csv.DictWriter(f, fieldnames=fieldnames)  # 寫入表頭
                writer.writeheader()
                list_1 = ['houseInfo', 'title', 'positionInfo', 'totalPrice/萬元', 'unitPrice', 'followInfo', 'tag']
                list_2 = [data_houseInfo,data_title,data_positionInfo,data_totalPrice,data_unitPrice,data_followInfo,data_tag]
                list_3 = dict(zip(list_1, list_2))
                writer.writerow(list_3)
                print("寫入第"+str(i)+"行數據")
            i += 1
            if i > len(self.houseInfo):
                break

8:這裏用過Next方法對生成器中內容不斷提取:

fieldnames = ['houseInfo', 'title', 'positionInfo', 'totalPrice/萬元', 'unitPrice', 'followInfo', 'tag']
writer = csv.DictWriter(f, fieldnames=fieldnames)  # 寫入表頭
writer.writeheader()

9:將其加在表頭中。然後每一行寫入一次數據

10:最後構造run函數:

    def run(self):
        i = 1
        while True:
            url_str = self.url.format(i)  # 構造請求url
            html = self.get_response_spider(url_str)
            self.get_content_html(html)
            self.qingxi_data_houseInfo()

            i += 1
            if i == 57:  
                break

11:循環迭代一下,將上述的page頁碼從一到最後

12:main函數中啓動一下,先new一下這個類,再啓動run函數,就會開始爬取了

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-YpDU0UZV-1589163973208)(D:\CSDN\pic\爬蟲畢設_買房\1589157338385.png)]

然後我們看一下結果:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-MdzDg69f-1589163973209)(D:\CSDN\pic\爬蟲畢設_買房\1589157875463.png)]

然後爬蟲階段就結束了,當然也可以寫入數據庫中,我們保存在文本文件中是爲了更方便。我們保存在了左邊的csv文件中,是不是很簡單~,源碼這個網上應該也有,我就暫時不放了,等朋友畢業再發


二:數據清洗與提取

1:首先導入一下需要的庫

"""
數據分析及可視化
"""
import pandas as pd
from pyecharts.charts import Line, Bar
import numpy as np
from pyecharts.globals import ThemeType
from pyecharts.charts import Pie
from pyecharts import options as opts

2:數據全局定義:

places = ['lianjia_BaiYunQu', 'lianjia_GuanShanHuQu', 'lianjia_HuaXiQu', 'lianjia_NanMingQu', 'lianjia_WuDangQu', 'lianjia_YunYanQu']
place = ['白雲區', '觀山湖區', '花溪區', '南明區', '烏當區', '雲巖區']
avgs = []  # 房價均值
median = []  # 房價中位數
favourate_avg = []  # 房價收藏人數均值
favourate_median = []  # 房價收藏人數中位數
houseidfo = ['2室1廳', '3室1廳', '2室2廳', '3室2廳', '其他']  # 房型定義
houseidfos = ['2.1', '3.1', '2.2', '3.2']  
sum_house = [0,  0, 0, 0, 0]  # 各房型數量
price = []  # 房價
fav = []  # 收藏人數
type = []  
area = []  # 房間面積

註釋寫的很清楚了,我的places是爲了方便讀取這幾個csv中文件各自保存的數據(‘白雲區’, ‘觀山湖區’, ‘花溪區’, ‘南明區’, ‘烏當區’, '雲巖區’區的數據):
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-PSolJZcb-1589163973212)(D:\CSDN\pic\爬蟲畢設_買房\1589158268089.png)]

3:文件操作,打開文件:

def avg(name):
	df = pd.read_csv(str(name)+'.csv', encoding='utf-8')
    pattern = '\d+'
    df['totalPrice/萬元'] = df['totalPrice/萬元'].str.findall(pattern) # 轉換成字符串,並且查找只含數字的項
    df['followInfo'] = df['followInfo'].str.findall(pattern)
    df['houseInfo'] = df['houseInfo'].str.findall(pattern)

使用padas的read_csv方式讀取csv文件 name以傳參形式迭代傳入,也就是一個區一個區的傳入主要是爲了減少代碼量,增加審美。就不必每一次都寫幾十行代碼了

然後是一些匹配,轉換成字符串,並且查找只含數字的項。

    for i in range(len(df)):
        if (i + 1) % 2 == 0:
            continue
        else:
            if len(df['totalPrice/萬元'][i]) == 2:
                avg_work_year.append(','.join(df['totalPrice/萬元'][i]).replace(',', '.'))
                medians.append(float(','.join(df['totalPrice/萬元'][i]).replace(',', '.')))
                price.append(','.join(df['totalPrice/萬元'][i]).replace(',', '.'))
            if len(df['followInfo'][i]) ==2:
                favourates.append(int(','.join(df['followInfo'][i][:1])))
                fav.append(int(','.join(df['followInfo'][i][:1])))
            if float(','.join(df['houseInfo'][i][:2]).replace(',', '.')) == 2.1:
                k +=1
                sum_houses[0] =k
                type.append(2.1)
            if float(','.join(df['houseInfo'][i][:2]).replace(',', '.')) == 3.1:
                k1 +=1
                sum_houses[1] =k1
                type.append(3.1)
            if float(','.join(df['houseInfo'][i][:2]).replace(',', '.')) == 2.2:
                k3 +=1
                sum_houses[2] =k3
                type.append(2.2)
            if float(','.join(df['houseInfo'][i][:2]).replace(',', '.')) == 3.2:
                k4 +=1
                sum_houses[3] =k4
                type.append(3.2)
            else:
                k4 +=1
                sum_houses[4] = k4
                type.append('other')
            area.append(float(','.join(df['houseInfo'][i][2:4]).replace(',', '.')))
    sum_house[0] =sum_houses[0]
    sum_house[1] = sum_houses[1]
    sum_house[2] = sum_houses[2]
    sum_house[3] = sum_houses[3]
    sum_house[4] = sum_houses[4]

    favourates.sort()
    favourate_median.append(int(np.median(favourates)))
    medians.sort()
    median.append(np.median(medians))
    # price = avg_work_year
    b = len(avg_work_year)
    b1= len(favourates)

    sum = 0
    sum1 = 0
    for i in avg_work_year:
        sum = sum+float(i)
    avgs.append(round(sum/b, 2))
    for i in favourates:
        sum1 = sum1+float(i)
    favourate_avg.append(round(int(sum1/b1), 2))

4:這裏是數據篩選的核心部分,我們細說一下:

		if len(df['totalPrice/萬元'][i]) == 2:
                avg_work_year.append(','.join(df['totalPrice/萬元'][i]).replace(',', '.'))
                medians.append(float(','.join(df['totalPrice/萬元'][i]).replace(',', '.')))
                price.append(','.join(df['totalPrice/萬元'][i]).replace(',', '.'))

5:這裏是獲取總價格,並且清洗好,放入前面定義好的數組中,保存好,

if len(df['followInfo'][i]) ==2:
                favourates.append(int(','.join(df['followInfo'][i][:1])))
                fav.append(int(','.join(df['followInfo'][i][:1])))

6:這裏是獲取總收藏人數,並且清洗好,放入前面定義好的數組中,保存好,

if len(df['followInfo'][i]) ==2:
                favourates.append(int(','.join(df['followInfo'][i][:1])))
                fav.append(int(','.join(df['followInfo'][i][:1])))
            if float(','.join(df['houseInfo'][i][:2]).replace(',', '.')) == 2.1:
                k +=1
                sum_houses[0] =k
                type.append(2.1)
            if float(','.join(df['houseInfo'][i][:2]).replace(',', '.')) == 3.1:
                k1 +=1
                sum_houses[1] =k1
                type.append(3.1)
            if float(','.join(df['houseInfo'][i][:2]).replace(',', '.')) == 2.2:
                k3 +=1
                sum_houses[2] =k3
                type.append(2.2)
            if float(','.join(df['houseInfo'][i][:2]).replace(',', '.')) == 3.2:
                k4 +=1
                sum_houses[3] =k4
                type.append(3.2)
            else:
                k4 +=1
                sum_houses[4] = k4
                type.append('other')
            area.append(float(','.join(df['houseInfo'][i][2:4]).replace(',', '.')))

7:這裏是獲取房型和麪積,清洗好,放入數組中

    favourates.sort()
    favourate_median.append(int(np.median(favourates)))
    medians.sort()
    median.append(np.median(medians))
    # price = avg_work_year
    b = len(avg_work_year)
    b1= len(favourates)

    sum = 0
    sum1 = 0
    for i in avg_work_year:
        sum = sum+float(i)
    avgs.append(round(sum/b, 2))
    for i in favourates:
        sum1 = sum1+float(i)
    favourate_avg.append(round(int(sum1/b1), 2))

8:這裏是把上面的信息加工,生成平均數,中位數等。

另外說一下,清洗過程:
’,’.join()是爲了篩選出的信息不含中括號和逗號

df[‘houseInfo’][i][2:4]是爲了取出相應的數據,使用了python的切片操作

.replace(’,’, ‘.’)是把逗號改成小數點,這樣就是我們想要的結果了。

下面執行看一下結果:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-IgXgGFDW-1589163973214)(D:\CSDN\pic\爬蟲畢設_買房\1589159955086.png)]

數據篩選結束~


三、數據可視化

python的數據可視化操作很完善,還很多樣,我們先來看看幾種常用的:

  • Matplotlib:基於Python的繪圖庫,提供完全的 2D 支持和部分 3D 圖像支持。在跨平臺和互動式環境中生成高質量數據時,matplotlib 會很有幫助。也可以用作製作動畫。
  • Seaborn:該 Python 庫能夠創建富含信息量和美觀的統計圖形。Seaborn 基於 matplotlib,具有多種特性,比如內置主題、調色板、可以可視化單變量數據、雙變量數據,線性迴歸數據和數據矩陣以及統計型時序數據等,能讓我們創建複雜的可視化圖形。

我們今天提供一個更美觀,更方便的數據可視化庫:Pyecahrts

1:首先繪製一下地區與房價關係折線圖:

line = Line()
line.add_xaxis(place)
line.add_yaxis("貴陽各地房價平均值(萬元)", avgs)
line.add_yaxis("貴陽各地房價中位數值(萬元)", median)
line.render("predict_line.html")

x軸放入我們的place地區,y軸放入房價平均值和中位數值,就這幾行代碼,有木有很舒服。這時,執行一下,就會在當前目錄生成predict_line.html,我們點開看一下

​	[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-HSdtoFU7-1589163973216)(D:\CSDN\pic\爬蟲畢設_買房\1589160609873.png)]

這是html網頁代碼,會支持在本地局域網打開我們點擊一下右上角的瀏覽器,就可以看到:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-sQlQWykj-1589163973218)(D:\CSDN\pic\爬蟲畢設_買房\1589160666559.png)]

分析一下:
折線圖直觀明瞭地告訴了我們以下信息:
在這六個區中烏當區的平均房價和中位數遠低於其他幾個區,其中花溪區和雲巖區以及南明區不相上平均房價爲79萬左右,說明這兩地的經濟水平,購房水平比較相似。烏當區經濟水平和居民購房能力較弱。

2:繪製上述的柱狀圖

def bar() -> Bar:
    c = (
        Bar({"theme": ThemeType.MACARONS})
            .add_xaxis(place)
            .add_yaxis("平均值", avgs)
            .add_yaxis("中位數", median)
            .set_global_opts(
            title_opts={"text": "貴陽各地房價(萬元)"}
        )
    )
    return c
bar().render("predict_bar.html")

同樣的方式查看一下:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-O37wVxAL-1589163973219)(D:\CSDN\pic\爬蟲畢設_買房\1589162000990.png)]

3:繪製各區房型與數量的關係柱狀圖:

def bar() -> Bar:
    c = (
        Bar({"theme": ThemeType.MACARONS})
            .add_xaxis(houseidfo)
            .add_yaxis(place[0], [280, 56, 504, 1676, 1680])
            .add_yaxis(place[1], [392, 112, 448, 1679, 1680])
            .add_yaxis(place[2], [224, 0, 616, 3359, 3360])
            .add_yaxis(place[3], [448, 112, 280, 1679, 1680])
            .add_yaxis(place[4], [504, 0, 336, 1680, 1679])
            .add_yaxis(place[-1], sum_house)
            # .add_yaxis("中位數", favourate_median)
            .set_global_opts(
            title_opts={"text": "貴陽各地房型\n數量"}
        )
    )
    return c
bar().render("house_bar.html")

查看一下:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-xFyeOBGS-1589163973222)(D:\CSDN\pic\爬蟲畢設_買房\1589162145051.png)]

可以看到各區普遍是三室兩廳和其他房型較多,其中花溪區的三室兩廳遠超平均水平,其他區比較持平。

4:繪製各區平均房間收藏人數和中位數餅狀圖

list_num = favourate_avg
attr = place
# print(zip(attr, list_num))
s = [list(z) for z in zip(attr, list_num)]
c = (Pie().add("", s).set_global_opts(title_opts=opts.TitleOpts(title="貴陽市各區樓房\n平均收藏人數"))
     .set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}"))
     )
c.render("pie_avg.html")

list_num = favourate_median
attr = place
# print(zip(attr, list_num))
s = [list(z) for z in zip(attr, list_num)]
c = (Pie().add("", s).set_global_opts(title_opts=opts.TitleOpts(title="貴陽市各區樓房\n收藏人數中位數"))
     .set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}"))
     )
c.render("pie_median.html")

看效果:
![[外鏈圖片轉存失敗,源站可能有防10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ2MDk4NTc0,size_16,color_FFFFFF,t_70)

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-h5s1DJMJ-1589163973226)(D:\CSDN\pic\爬蟲畢設_買房\1589163186035.png)]

5:繪製貴陽各地房子平均面積\n(平米)折線圖

line = Line()
line.add_xaxis(place)
line.add_yaxis("貴陽各地房子平均面積\n(平米)", area)
line.render("Area_line.html")

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-g9lDNV3S-1589163973227)(D:\CSDN\pic\爬蟲畢設_買房\1589162739501.png)]

可以看到南明區的房間的平均面積最大,觀山湖區最小,右上可以看出,南明區的房價又高,面積又大不過收藏人數缺平均最低,可見可能由於郊區等原因,購房羣體較少

6:繪製房型和用戶收藏人數和房間價格三維立體散點圖

from pyecharts import options as  opts
from pyecharts.charts import Scatter3D
from pyecharts.faker import Faker


price=[float(i)/1 for i in price]
# print(price)
# types=list(map(mapfunc,df.house_type.values))
# type = [224, 56, 168, 1680, 1670]
data = []
# print(fav,type)
# for j in range(len(type)):
#     for k in range(len(fav)):
for j in range(100):
    for k in range(100):
        for i in range(500):
            try:
                data.append([type[j], favourate_avg[k],price[i]])
            except:
                continue
# print(data)
scatter = (
     Scatter3D(init_opts=opts.InitOpts(width='900px', height='600px'))  # 初始化
          .add("", data,
               grid3d_opts=opts.Grid3DOpts(
                    width=300, depth=300, rotate_speed=300, is_rotate=True,
               ),)

          # 設置全局配置項
          .set_global_opts(
          title_opts=opts.TitleOpts(title="房型——關注度——價格\n三維關係圖"),  # 添加標題
          visualmap_opts=opts.VisualMapOpts(
               max_=100,  # 最大值
               pos_top=200,  # visualMap 組件離容器上側的距離
               range_color=Faker.visual_color  # 顏色映射
          )
     )
          # .render("3D散點圖.html")
)
print('數據分析和可視化結束,左邊點開~')

可看到:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-zPkmwOSd-1589163973230)(D:\CSDN\pic\爬蟲畢設_買房\1589163435450.png)]

說明房型爲三室兩廳的情況下,用戶的收藏喜愛程度和價格都可關,人們普遍喜歡80萬左右的三室兩廳房子。


最後總結一下:

我們通過爬取網站中貴陽市的6個區的房價,做了數據分析,並可視化呈現出來,爲了更直觀的感受到,各個維度之間的影響(見上)。完善的實驗,可以成爲有利的論據,今天的分享到此結束。感謝~

最後請務必關注一下博主,碼字不易~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章