資料爬蟲 + 資料清洗 + 資料視覺化,完整的專案教程!
一:資料探勘
我選用了鏈家網做資料爬取場所(不得不嘮叨一句,這個網站真是為了爬蟲而生的,對爬蟲特別友好哈哈哈,反扒措施比較少)
比如我們爬取貴陽市烏當區的所有房子的房價及其他資訊:
比如我們爬取第一個房子的價格:115萬:
接下來我們可以使用複製CSS選擇器或者XPath等等來實現獲取:
下面我們使用複製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函式,就會開始爬取了
然後我們看一下結果:
然後爬蟲階段就結束了,當然也可以寫入資料庫中,我們儲存在文字檔案中是為了更方便。我們儲存在了左邊的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中檔案各自儲存的資料(‘白雲區’, ‘觀山湖區’, ‘花溪區’, ‘南明區’, ‘烏當區’, '雲巖區’區的資料):
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(’,’, ‘.’)是把逗號改成小數點,這樣就是我們想要的結果了。
下面執行看一下結果:
資料篩選結束~
由於篇幅過長,貼不出來 需要完整的教程或者原始碼的加下群:1136192749