Python爬取并分析二手房数据

admin / 博文 / ... / Reads: 2389

1 问题描述

在大数据时代,网络爬虫成为获取数据的重要方式之一。这里采用Python网络爬虫技术爬取二手房网站上的房屋信息,并分别采用传统统计分析视角机器学习视角对二手房价格的影响因素进行探讨。

统计分析的核心是用样本来推断总体,且推断主要是建立在假设检验上面,所以当估计出统计模型后,需要对模型参数进行各种检验,进行模型选择!而机器学习的思想是直接分析总体数据来认识总体特征,因而需要海量的样本数据,估计出机器学习模型后,主要通过预测值的精度来进行模型选择,一般不需要对参数进行假设检验!

2 爬取二手房数据

在二手房交易平台中,房屋信息一般以列表的形式展示,每个网页展示多条房屋数据,并且有多页数据,如下图所示。爬取的思路是先提取并整理一个网页上的数据,再采用循环语句按照相同步骤提取多个页面上的数据并进行汇总。

r-100

Python爬取房屋信息的步骤

导入所需模块

#-*- coding:utf-8 -*-
from urllib.request import Request,urlopen
from bs4 import BeautifulSoup
import importlib,sys
importlib.reload(sys)
import numpy as np
import statsmodels.api as sm
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression

定义网页前面相同部分的网址

由于我们需要提取多个页面上的数据,所以这里的网址是所有网址前面相同的部分(不包含页码),后面需要和页码进行组合。

urlbase='https://bj.ke.com/ershoufang/pg'

定义一个空的数据框变量houseDatas,用于存放爬取的数据。

houseDatas=pd.DataFrame(columns = ['house_floor','house_age','house_type','house_area','house_orient','total_price','avg_price'])

采用循环语句提取房屋数据

这里我们只提取前20页的数据,所以设range(1,20)url = urlbase+str(i)是前面定义的网址和页码的组合;Request()提取网页信息需、BeautifulSoup()对网页进行解析。

该部分内容的难点是定位所需信息的css节点。可以使用chrome等浏览器的"检查元素"功能。然后点击下图中红色区域图标!

r-101

之后鼠标移到需要提取信息的网页位置,该例子中需要定位两个节点,一个是价格信息的div(节点是div.priceInfo),一个是房屋面积等信息的div(节点是div.houseInfo),如下面两幅图所示。

Snipaste_2022-11-03_17-23-21

Snipaste_2022-11-03_17-27-29

另外,需要注意的是。提取到文本数据后,需要进行拆分、去除汉字单位等操作:item.get_text().strip().replace("\n","").replace(" ","").split('万')

由于houseInfo中有些信息不完整,无法进行后文的统计分析,需要将该条数据删除,但是一条完整的数据样本是由houseInfo中的内容和priceInfo中的内容组合而成,所以houseInfo中有些信息不完整时先用空格替代该条数据info.append([" "," "," "," "," "]),保证和priceInfo中的样本一一对应,等匹配好数据后再统一删除。

这部分的代码如下:

for i in range(1,20):
    url = urlbase+str(i)

    req = Request(url)
    html = urlopen(req).read().decode('utf-8')
    # 解析保存的网页内容
    soup = BeautifulSoup(html, 'lxml')

    price = []
    priceInfo=soup.find_all('div','priceInfo')
    for item in priceInfo:
        info=item.get_text().strip().replace("\n","").replace(" ","").split('万')
        info[1]=info[1].replace(",","").replace("元/平","")
        price.append(info)

    priceData = pd.DataFrame(price,columns=['total_price','avg_price'])

    info = []
    houseInfo=soup.find_all('div','houseInfo')
    for item in houseInfo:
        data = item.get_text().strip().replace("\n","").replace(" ","").split('|')
        if len(data)<5:
            info.append([" "," "," "," "," "])
            continue
        data[0]=data[0].split('(')[1].split(')')[0].replace("共","").replace("层","")
        data[1]=data[1][0:4]
        data[3]=data[3].replace("平米","")
        info.append(data)

    infoData=pd.DataFrame(info,columns=['house_floor','house_age','house_type','house_area','house_orient'])

    houseData=pd.concat([infoData,priceData],axis=1)

    houseDatas=pd.concat([houseDatas,houseData])

删除缺失值样本

首先将数据框中的空格替换成None值,然后再采用dropna()函数删除存在None值得样本。

houseDatas = houseDatas.applymap(lambda x: np.where(x!=' ', x,None))
houseDatas = houseDatas.dropna()

爬取并清洗完数据后可以用print(houseDatas.head(5))命令查看前5条数据。

Snipaste_2022-11-03_19-43-44

3 传统统计视角的分析

Python中传统统计分析需要使用statsmodels模块。

3.1 初步回归

由于house_orient和house_type分类变量的类型较多,处理起来较为麻烦,所以这里只考虑数值型变量。将avg_price设置因变量y,将house_floor,house_age,house_area变量设为自变量X,并加上常数项。

numData=houseDatas[['house_floor','house_age','house_area','total_price','avg_price']]

y=numData.loc[:, 'avg_price']

X=numData.loc[:, 'house_floor' : 'house_area']

X=sm.add_constant(X)

ols_model = sm.OLS(y.astype(float),X.astype(float))
ols_results = ols_model.fit()
print(ols_results.summary())

回归结果如下图所示,其中,house_floor house_age变量的系数显著,house_area变量的系数不显著。注意,这里的房价是均价,所以房屋面积对均价的影响不显著。house_age变量是房屋建造的年份,建造年份大表示房龄越小,其系数为负数表示房子越新均价越低,可能的原因是北京的老房子位于城市的核心位置而新房离核心位置较远。

Snipaste_2022-11-03_19-58-08

3.2 逐步回归

多元线性回归中多重共线性是比较严重的问题,可能会导致参数估计检验失效。一般采用逐步回归法来处理多重共线问题。

Python中暂时没有直接进行逐步回归的函数,所以需要自己手动编写,基本思路是每次将上一步回归结果中系数p值大于0.1的变量去掉重新进行回归,重复该循环直到所有变量系数P值都小于0.1为止。

逐步回归代码如下:

X_step = X
pv_var = pd.DataFrame()
pv_var['index'] = X_step.columns

pv_var['pv'] = pd.Index(ols_results.pvalues)

while (pv_var['pv'] > 0.1).any():
    remove = pv_var.sort_values(by='pv',ascending=False)['index'][:1].values[0]
    X_step.drop(remove,axis=1,inplace=True)
    ols_model_step = sm.OLS(y.astype(float), X_step.astype(float))
    ols_results_step = ols_model_step.fit()
    pv_var = pd.DataFrame()
    pv_var['index'] = X_step.columns
    pv_var['pv'] = pd.Index(ols_results_step.pvalues)

if 'const' not in X_step.columns:
    X_step = sm.add_constant(X_step)
ols_model_step = sm.OLS(y.astype(float), X_step.astype(float))
ols_results_step = ols_model_step.fit()
print(ols_results_step.summary())

结果只有house_floor和house_age变量保留再模型中。

Snipaste_2022-11-03_20-19-55

4 机器学习视角的分析

Python中机器学习需要使用sklearn模块。

该部分仍然只考虑数值型变量。

设置因变量和自变量,并且将70%数据划分为训练样本,30%数据划分为测试样本

X=numData.loc[:, 'house_floor' : 'house_area'].astype(float)

y=numData.loc[:, 'avg_price'].astype(float)

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0, test_size=0.7)

训练模型

lr = LinearRegression().fit(X_train, y_train)

输出训练模型的系数

feature_names = X.columns.values

coefs = pd.DataFrame(
    lr.coef_,
    columns=["系数"],
    index=feature_names,
)
print("模型的系数:")
print(coefs)
print("模型的截距项为: \n", lr.intercept_)

可以看出,变量系数和统计分析中的值基本一致。

Snipaste_2022-11-03_20-27-11

给出变量的重要性图

机器学习模型中不需要对变量的显著性进行检验,但是可以给出变量的重要性试图。

coefs_std = pd.DataFrame(
    lr.coef_ * X_train.std(axis=0),
    columns=["变量重要性"],
    index=feature_names,
)
coefs_std.plot(kind="barh", figsize=(9, 7))
plt.rcParams['font.sans-serif'] = ['SimHei']        # 解决中文显示乱码问题!
plt.rc('axes', unicode_minus=False)                 # 解决坐标轴负号显示乱码问题!
plt.xlabel("标准化系数")
plt.title("普通线性回归")
plt.axvline(x=0, color=".5")
plt.subplots_adjust(left=0.3)
plt.show()

可以看出,house_area变量不太重要,house_age和house_floor变量重要性相差不大。

Snipaste_2022-11-03_20-32-31

获得模型预测值,计算误差与可决系数

y_train_pred = lr.predict(X_train)
print("训练样本集均方误差: %.2f" % mean_squared_error(y_train, y_train_pred))
print("训练样本集可决系数: %.2f" % lr.score(X_train, y_train))

y_test_pred = lr.predict(X_test)
print("测试样本集均方误差: %.2f" % mean_squared_error(y_test, y_test_pred))
print("测试样本集可决系数: %.2f" % lr.score(X_test, y_test))

可以看出,无论训练样本还是测试样本的可决系数都不太高,说明仅仅用目前三个自变量对房价的预测效果一般。未来需要加入更多的自变量来预测房价。

Snipaste_2022-11-03_20-34-03



获取案例源代码,请关注微信公众号并回复:bw_dt4

Comments

Make a comment

Author: admin

Publish at: ...

关注公众号: