目录
1.1背景
北京市是中华人民共和国首都、省级行政区、直辖市、国家中心城市、超大城市,国务院批复确定的中国政治中心、文化中心、国际交往中心、科技创新中心。
截至2018年,全市下辖16个区,总面积16410.54平方千米,2019年末,常住人口2153.6万人,城镇人口1865万人,城镇化率86.6%,常住外来人口达794.3万人。
2019年,全年实现地区生产总值35371.3亿元,按可比价格计算,比上年增长6.1%。其中,第一产业增加值113.7亿元,下降2.5%;第二产业增加值5715.1亿元,增长4.5%;第三产业增加值29542.5亿元,增长6.4%。三次产业构成由上年的0.4:16.5:83.1,变化为0.3:16.2:83.5。按常住人口计算,全市人均地区生产总值为16.4万元。
2019年,全市完成一般公共预算收入5817.1亿元,比上年增长0.5%。其中,增值税1820.9亿元,增长1.6%;企业所得税和个人所得税分别为1228.5亿元和544.2亿元,分别下降4.6%和25.3%。
2017年,北京市连续三年位居中国百强城市排行榜榜首。截至2016年底,北京政府性债务余额4052.80亿元(人民币)。
所以北京是人口极多的城市,房源极其紧张的城市,所以对于二手房的房源分析对于北京市的居住人口来说,是极其重要的。
二手房是已经在房地产交易中心备过案、完成初始登记和总登记的、再次上市进行交易的房产。它是相对开发商手里的商品房而言,是房地产产权交易三级市场的俗称。包括商品房、允许上市交易的二手公房(房改房)、解困房、拆迁房、自建房、经济适用房、限价房。
众所周知,发展二手房市场对于稳定住房价格,引导梯次消费,实现住房市场的健康发展具有重要的现实意义。但不可否认,二手房市场有效房源依旧供不应求,整体供求比例仅维持在1:4左右。
1.2项目简介
“贝壳找房”平台包含二手房、新房、租赁、家装等内容,定位于技术驱动的品质居住服务平台,开放优质资源和线上能力,聚合和赋能全行业的服务者,打造品质居住服务生态,为消费者提供包括二手房、新房、租赁等居住服务。
贝壳的底层价值观决定了外在的业务规则和核心能力。全面进行组织内部的使命、愿景、价值观建设,也是今年贝壳主要聚焦的重要事件之一,贝壳希望更加净化价值观选择。
贝壳是目前中国比较有名的找房网站,里面房源信息丰富可靠,所以本项目采取对贝壳网的房源信息进行爬取,然后利用python进行可视化分析,方便用户查看和获取房源信息。房源信息具体信息包括:
总建筑面积:指建设用地范围内所有建筑物地上及地下各层建筑面积总和;
建筑基底总面积:指建设用地范围内所有建筑物底层勒脚以上外围水平投影面积之和;
建筑用地总面积:指建筑或建筑群实际占用的土地面积,包括室外工程(如绿化、道路、停车场等)的面积,其形状和大小由建筑红线加以控制;
房屋地址、房产证号、房地产权利人;
房主或者联系人电话及看房时间、具体的户型朝向、房屋价格及可议程度;
2.1?客户需求分析
买房是人生中必不可少的一个阶段也是广大普通青年最为困扰的阶段,事业刚刚起步,没有家里的资金支持,那么二手房是一个很不错的选择。但是怎么从众多二手房中选取价格最低,优惠组合最棒的二手房呢。客户需要我们做的正是这一点,需要我们从众多的网站中爬取二手房的价格以及地理位置等众多重要信息,最后进行可视化分析,来告诉客户哪个网站以及哪个地方的二手房性价比最高,最值得买,将最直观、最清晰的数据摆在客户面前。
2.2?可行性分析
我们对多个二手房网站进行了信息分析,以及反爬虫措施分析,我们最终选定了贝壳二手房网进行用户需求的数据爬取以及分析,贝壳二手房网站的数据是最多、最全并且反爬虫措施并没有很严密。可以方便的进行数据的爬取并且其中的数据包括方面很全面,可以给客户提供非常全面的分析,例如:学区房、地理位置好、性价比高、优惠组合好、礼品多等一系列分析。
2.2?性能需求分析
在性能需求方面,在万级数据面前,我们的可视化可以很轻松的非常便捷以及迅速地分析出爬虫所爬取的数据。为用户提供最为便捷、清晰的图表分析。并且可以为用户定制需求,可以按照客户的需求来自定义图标,非常的方便便捷。在爬虫方面,我们的程序预留了接口,可以进行并行爬取数据,预计节省时间80%以上。
3.1 总体设计
本项目是对贝壳北京二手房网站进行爬取分析,项目采用requests、bs4、xml爬取了'区/县','区域','小区','总价','单价','房屋户型','楼层','总面积','朝向','建筑结构','装修情况','交易权属','形式','是否满五','产权形式','是否有房本','小区均价','小区建成','总栋数'总共十种属性存储到csv格式的文件中进行存储。在数据分析以及可视化阶段采用pandas进行数据读入的处理,采用pyplot以及pyecharts进行数据分析以及可视化的实现。
3.2 爬虫具体实现
爬虫采用requests进行网页的访问操作,采用bs4进行网页内容获取,以及xml通过关键路径获取需要的内容信息。通过csv库将内容有序的保存到csv格式的文件当中供分析以及可视化使用。
具体实现如下图所示:
图3.1 requests初始化定义代码
图3.2 获取一级页面当中的二级页面链接代码
图3.3 通过关键路径获取二级页面的元素代码
图3.4 将获取到的信息保存到csv格式的表格中代码
图3.5 类的定义以及循环执行代码
3.3项目分析以及可视化实现
项目分析
为实现实现可视化,本项目使用pandas、matplotlib以及pyecharts模块。
pandas 是基于NumPy的一种工具,该工具是为了解决数据分析任务而创建的。Pandas纳入了大量库和一些标准的数据模型,提供了高效地操作大型数据集所需的工具。pandas提供了大量能使我们快速便捷地处理数据的函数和方法。你很快就会发现,它是使Python成为强大而高效的数据分析环境的重要因素之一。
Matplotlib 是一个 Python 的 2D绘图库,它以各种硬拷贝格式和跨平台的交互式环境生成出版质量级别的图形。
pyecharts是一个由百度开源的数据可视化,凭借着良好的交互性,精巧的图表设计,得到了众多开发者的认可。而 Python 是一门富有表达力的语言,很适合用于数据处理。
可视化实现
第一步:为了方便后面的数据处理和分析,首先第一步对数据进行预处理;
第二步:北京二手房各区、县房源分布信息;
第三步:北京二手房各区、县房屋均价分布信息;
第四步:北京二手房房屋户型情况;
第五步:使用pyecharts绘制楼层和房屋数量的饼图;
本项目运行良好,能够完整的爬取网页信息以及完成分析。
项目测试结果如下图所示:
先对北京整个区、县的房源数量信息进行分析,如下图所示:
图4.1 房源数量信息
对各区房源均价进行分析:
图4.2 房源数量信息房源均价
分析房屋户型情况,结果如下:
图4.3 分析房屋户型情况
分析房屋总价和总面积的关系图,使用散点图进行展示,结果如下:
图4.4 分析房屋总价和总面积的关系图
楼层和房源数量的饼图:
图4.5 楼层和房源数量的饼图
通过设计对北京二手房信息数据分析及可视化展示,我较为全面的掌握了python的基本知识和编程技巧,并在开发过程中我的python开发能力得到了进一步的提高。
在开发过程中我学到了一些经验:系统分析的好坏将决定着的系统开发成功与否,一份好分析设计将是成功开发主要因素。我们在着手开发之前不要急于编程,先应有较长的时间去把分析做好,做好数据库设计工作,写出相关的开发文档等。然后再开始编写程序代码,这样做到每段代码心底都有数,有条不紊。
此外,我还觉得,我个人在这次设计中走了很多弯路。主要是因为平时很少接触软件开发工作,在应用方面缺乏经验,以后还需要更多的努力。
爬虫代码:
import requests as req
from bs4 import BeautifulSoup as bs
import lxml
import pandas as pd
class House_Spider():
??? def __init__(self):
??????? self.url = 'https://bj.ke.com/ershoufang/pg'
??????? self.head={
??????????? 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36'
??????????? }
??????? self.proxies = {
??????????? 'http://':'222.73.144.63:80',
??????????? 'https://':'222.73.144.63:80'
??????? }
??????? self.response = ''
??????? self.url_list = []
??????? self.data_list = []
??? def MainPage(self):
??????? self.response = req.get(url = self.url, headers = self.head, proxies = self.proxies)
??????? print("code-", self.response.status_code)
??????? soup = bs(self.response.text, "html.parser")
??????? # title = soup.title
??????? # print(title)
??????? home_list = soup.find(name="ul",attrs={"class":"sellListContent"}).find_all(name="li",attrs={"class":"clear"})
??????? for i in range(len(home_list)):
??????????? self.url_list.append(home_list[i].a.attrs.get('href'))
??? def SecondPage(self, x):
??????? url = self.url_list[x]
??????? self.response = req.get(url=url, headers=self.head, proxies=self.proxies)
??????? print("code-", self.response.status_code)
??????? ####?? 这里如果用BeautifulSoup写我的手就废了
??????? html = lxml.etree.HTML(self.response.text)
??????? data_list = []
??????? # 地址
??????? home_location = html.xpath('//div[@data-component="overviewIntro"]//div[@class="content"]//div[@class="areaName"]/span[@class="info"]/a/text()')
??????? data_list.append(home_location)
??????? # 小区
??????? local_name = html.xpath('//div[@data-component="overviewIntro"]//div[@class="content"]//div[@class="communityName"]/a/text()')
??????? data_list.append(local_name[0])
??????? # 总价格
??????? total_price = html.xpath('//div[@data-component="overviewIntro"]//div[@class="content"]//div[@class="price "]/span[@class="total"]/text()')[0]
??????? data_list.append(total_price)
??????? # 单价
??????? unit_price = html.xpath('//div[@data-component="overviewIntro"]//div[@class="content"]//div[@class="price "]//div[@class="unitPrice"]/span/text()')[0]
??????? data_list.append(unit_price)
??????? # 房屋基本信息
??????? home_style = html.xpath('//div[@class="introContent"]//div[@class="base"]//div[@class="content"]/ul/li/text()')
??????? data_list.append(home_style)
??????? # 房屋交易属性信息
??????? transaction_info = html.xpath('//div[@class="introContent"]//div[@class="transaction"]//div[@class="content"]/ul/li/text()')
??????? for i in range(len(transaction_info)):
??????????? transaction_info[i] = transaction_info[i].replace(' ','').replace('\n','')
??????? data_list.append(transaction_info)
??????? # 小区均价
??????? xiaoqu_price = html.xpath('//div[@class="xiaoquCard"]//div[@class="xiaoqu_main fl"]//span[@class="xiaoqu_main_info price_red"]/text()')
?? ?????data_list.append(xiaoqu_price[0].replace(' ','').replace('\n',''))
??????? # 小区建造时间
??????? xiaoqu_built_time = html.xpath('//div[@class="xiaoquCard"]//div[@class="xiaoqu_main fl"]//span[@class="xiaoqu_main_info"]/text()')
??????? data_list.append(xiaoqu_built_time[0].replace(' ','').replace('\n',''))
??????? # 小区建筑类型
??????? xiaoqu_built_style = html.xpath('//div[@class="xiaoquCard"]//div[@class="xiaoqu_main fl"]//span[@class="xiaoqu_main_info"]/text()')
??????? data_list.append(xiaoqu_built_style[1].replace(' ','').replace('\n',''))
??????? # 小区楼层总数
??????? xiaoqu_total_ceng = html.xpath('//div[@class="xiaoquCard"]//div[@class="xiaoqu_main fl"]//span[@class="xiaoqu_main_info"]/text()')
??????? data_list.append(xiaoqu_total_ceng[2].replace(' ','').replace('\n',''))
??????? self.data_list.append(data_list)
??? def save_data(self, data):
??????? data_frame = pd.DataFrame(data, columns=['小区位置','小区名称','房屋总价','房屋单价','房屋基本信息','房屋交易信息','小区均价','小区建造时间','小区房屋类型','小区层数'])
??????? data_frame.to_csv('House_price_pk.csv', header=False, index=False, mode='a', encoding='utf_8_sig')
??????? print(data_frame)
if __name__ == "__main__":
??? House = House_Spider()
??? for i in range(0, 100):
??????? House.url = 'https://bj.ke.com/ershoufang/pg' + str(i) + '/'
??????? House.MainPage()
??? print(len(House.url_list))
??? for i in range(len(House.url_list)):
??????? House.SecondPage(i)
??? House.save_data(House.data_list)
可视化分析代码:
import pandas as pd
from matplotlib import pyplot as plt
from pyecharts.charts import Pie
from pyecharts import options as opts
columns = ['区/县','区域','小区','总价','单价','房屋户型','楼层','总面积','户型结构','套内面积','建筑类型','朝向',
?????????? '建筑结构','装修情况','梯户比例','供暖方式','配备电梯','产权年限','s','交易权属','u','形式','是否满五','产权形式',
?????????? '是否有房本','小区均价','小区建成','style','总栋数']
data = pd.read_excel(r"data.xlsx", names = columns)
data['装修情况'] = data.apply(lambda x:x['建筑类型'] if ('南北' in str(x['户型结构'])) else x['装修情况'],axis=1)
data['建筑结构'] = data.apply(lambda x:x['套内面积'] if ('南北' in str(x['户型结构'])) else x['建筑结构'],axis=1)
data['朝向'] = data.apply(lambda x:x['户型结构'] if ('南北' in str(x['户型结构'])) else x['朝向'],axis=1)
data['套内面积'] = data.apply(lambda x:'㎡' if ('南北' in str(x['户型结构'])) else x['套内面积'],axis=1)
data['装修情况'] = data.apply(lambda x:x['朝向'] if ('㎡' in str(x['户型结构'])) else x['装修情况'],axis=1)
data['建筑结构'] = data.apply(lambda x:x['建筑类型'] if ('㎡' in str(x['户型结构'])) else x['建筑结构'],axis=1)
data['朝向'] = data.apply(lambda x:x['套内面积'] if ('㎡' in str(x['户型结构'])) else x['朝向'],axis=1)
data['套内面积'] = data.apply(lambda x:'㎡' if ('㎡' in str(x['户型结构'])) else x['套内面积'],axis=1)
data['套内面积'] = data.apply(lambda x:'㎡' if ('暂无数据' in str(x['套内面积'])) else x['套内面积'],axis=1)
data['装修情况'] = data.apply(lambda x:x['装修情况'] if ('㎡' in str(x['套内面积'])) else x['建筑结构'],axis=1)
data['建筑结构'] = data.apply(lambda x:x['建筑结构'] if ('㎡' in str(x['套内面积'])) else x['朝向'],axis=1)
data['朝向'] = data.apply(lambda x:x['朝向'] if ('㎡' in str(x['套内面积'])) else x['建筑类型'],axis=1)
data['建筑类型'] = data.apply(lambda x:x['建筑类型'] if ('㎡' in str(x['套内面积'])) else x['套内面积'],axis=1)
data['套内面积'] = data.apply(lambda x:x['套内面积'] if ('㎡' in str(x['套内面积'])) else '无信息',axis=1)
data['装修情况'] = data.apply(lambda x:x['建筑结构'] if (('户') in str(x['装修情况'])) else x['装修情况'],axis=1)
data['建筑结构'] = data.apply(lambda x:x['朝向'] if (('户') in str(x['装修情况'])) else x['建筑结构'],axis=1)
data['朝向'] = data.apply(lambda x:x['建筑类型'] if (('户') in str(x['装修情况'])) else x['朝向'],axis=1)
data['建筑结构'] = data.apply(lambda x:x['朝向'] if ('结构' in str(x['朝向'])) else x['建筑结构'],axis=1)
data['朝向'] = data.apply(lambda x:x['建筑类型'] if ('结构' in str(x['朝向'])) else x['朝向'],axis=1)
data['总楼层'] = data.apply(lambda x:str(x[6])[3:].strip('(共').strip('层)'),axis=1)
data['楼层'] = data.apply(lambda x:str(x[6])[:3],axis=1)
data['总面积'] = data.apply(lambda x:str(x[7]).strip('㎡'),axis=1)
data['小区均价'] = data.apply(lambda x:str(x[-5]).strip('元/㎡\n').strip('\n'),axis=1)
data['小区建成'] = data.apply(lambda x:str(x[-4])[:4],axis=1)
data['总栋数'] = data.apply(lambda x:str(x[-2])[:-1],axis=1)
data.to_csv('after_deal_data.csv',encoding='utf_8_sig')
need_data = data[['区/县','区域','小区','总价','单价','房屋户型','楼层','总面积','朝向','建筑结构','装修情况','交易权属','形式','是否满五','产权形式','是否有房本','小区均价','小区建成','总栋数']]
need_data.head()
# print(data.head(10))
# print(need_data.head(10))
#? 图表中文显示
plt.rcParams['font.sans-serif'] = ['SimHei'] # 步骤一(替换sans-serif字体)
plt.rcParams['axes.unicode_minus'] = False?? # 步骤二(解决坐标轴负数的负号显示问题)
fig, ax=plt.subplots()
# print(need_data.info())
# print(need_data.describe())
'''
各区县房源分布情况
北京二手房各区、县房源分布信息
'''
need_data['区/县'].value_counts().plot(kind='bar',color=['green','red','blue','grey','pink'],alpha=0.5)
x = need_data['区/县'].value_counts()
plt.title('北京二手房各区、县房源分布信息',fontsize=15)
plt.xlabel('区、县名称',fontsize=15)
plt.ylabel('房源数量',fontsize=15)
plt.grid(linestyle=":", color="r")
plt.xticks(rotation=60)
plt.legend()
plt.show()
'''
各区县房源均价分布情况
北京二手房各区、县房屋均价分布信息
'''
need_data.groupby('区/县').mean()['单价'].sort_values(ascending=True).plot(kind='barh',color=['r','g','y','b'],alpha=0.5)
plt.title('北京二手房各区、县房屋均价分布信息',fontsize=15)
plt.xlabel('房屋均价',fontsize=15)
plt.ylabel('区、县名称',fontsize=15)
plt.grid(linestyle=":", color="r")
plt.legend()
plt.show()
'''
各区县房源分布情况
北京二手房房屋户型情况
'''
need_data['房屋户型'].value_counts().plot(kind='bar',color=['green','red','blue','grey','pink'],alpha=0.5)
plt.title('北京二手房房屋户型情况',fontsize=15)
plt.xlabel('房屋户型',fontsize=15)
plt.ylabel('房源数量',fontsize=15)
plt.grid(linestyle=":", color="r")
plt.xticks(rotation=60)
plt.legend()
plt.show()
# print(need_data[need_data.房屋户型 == '5室2厅4卫'])
# 北京二手房总价最大、最小值及其房源信息
total_price_min = need_data['总价'].min()
total_price_min_room_info = need_data[need_data.总价==total_price_min]
# print('二手房总价最低价位为:\n{}'.format(total_price_min))
# print('二手房总价最低的房源信息为:\n{}'.format(total_price_min_room_info))
total_price_max = need_data['总价'].max()
total_price_max_room_info = need_data[need_data.总价==total_price_max]
# print('二手房总价最高价位为:\n{}'.format(total_price_max))
# print('二手房总价最低的房源信息为:\n{}'.format(total_price_max_room_info))
#? 绘制总面积和总价的散点关系图
home_area = need_data['总面积'].apply(lambda x:float(x))
# print(home_area.head())
total_price = need_data['总价']
# print(total_price.head())
plt.scatter(home_area,total_price,s=3)
plt.title('北京二手房房屋户型情况',fontsize=15)
plt.xlabel('房屋面积',fontsize=15)
plt.ylabel('房源总价',fontsize=15)
plt.grid(linestyle=":", color="r")
plt.show()
#? 分析面积大但是价格较低的房源
area_max = home_area.max()
area_max_room_info = need_data[home_area==area_max]
# print('二手房面积最大的房源信息为:\n{}'.format(area_max_room_info))
# 使用pyecharts绘制楼层和房屋数量的饼图
x = need_data['楼层'].value_counts()
y = ['高楼层', '低楼层', '中楼层', '地下室', '未知']
# print("x =", x)
# print("y =", y)
c = (
??? Pie()
??? .add("", [list(z) for z in zip(y, x)])
??? .set_colors(["blue", "green", "yellow", "red", "pink", "orange", "purple"])
??? .set_global_opts(title_opts=opts.TitleOpts(title="房源楼层分布图"))
??? .set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}"))
??? .render("房源楼层分布图.html")
)