一、利用python的requests 库爬取链家二手房数据
链家南京二手房url地址:'https://nj.lianjia.com/ershoufang/'
按区查的话,比如鼓楼区,那么url就是'https://nj.lianjia.com/ershoufang/gulou/'
查看发现南京11个区里面,高淳房源太少,所以本次爬虫没有爬取高淳区的。
观察链家网站的页面发现最多显示100页的内容,所以本次爬取数据每个区爬取了100页,3000条数左右进行分析及可视化。
调用url获取到的数据如下:
和页面展示的房源信息一致。
页面上的信息复制粘贴下来会是对人性的压抑,但是我们url既然能调用出结果,那就可以提取出来。
页面展现出来的内容是用前端语言写的,我们用parsel库对网页进行解析,解析出re、xpath、css内容,然后根据css的内容进行匹配。
比如位置信息的元素是positionInfo,那么房子位置就可以根据该元素提取出来。
houseInfo获取到的户型、面积、楼层等等信息是一整块的,为了方便后期数据处理,进行了正则匹配把户型、面积、建成年份等等提取出来。
整体代码如下:
import requests,re
import time,datetime
import pandas as pd
from bs4 import BeautifulSoup
import csv,parsel
import random
url= 'https://nj.lianjia.com/ershoufang/'
citys = ['gulou','jianye','qinhuai','xuanwu','yuhuatai','qixia','jiangning','pukou',
'liuhe','lishui'] # 拼接url用
citys_ch = ['鼓楼','河西','秦淮','玄武','雨花台','栖霞','江宁','浦口','六合','溧水'] #保存各区数据用
items = [
{'http': 'http://171.35.171.247:9999'},
{'http': 'http://114.99.7.122:8752'}
]
for i in range(len(citys)):
city = citys[i]
f = open(str(citys_ch[i])+'二手房信息.csv', mode='a', encoding='utf-8_sig', newline='')
csv_writer = csv.DictWriter(f, fieldnames=['标题', '房子位置', '房子信息', '户型','面积','装修','总共楼层','建成年份','房子结构','发布周期', '售价/万', '单价'])
csv_writer.writeheader()
for page in range(1, 101):
print('===========================正在下载第{}页数据================================'.format(page))
time.sleep(1)
url = 'https://nj.lianjia.com/ershoufang/%s/pg{}/'.format(page) % city
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'
}
proxies = random.choice(items)
response = requests.get(url=url, headers=headers, proxies=proxies)
selector = parsel.Selector(response.content.decode('utf-8'))
lis = selector.css('.sellListContent li')
dit = {}
for li in lis:
title = li.css('.title a::text').get()
dit['标题'] = title
positionInfo = li.css('.positionInfo a::text').getall()
info = '-'.join(positionInfo)
dit['房子位置'] = info
houseInfo = li.css('.houseInfo::text').get()
dit['房子信息'] = houseInfo
partment = re.findall(r'^(\S+室\S+)\s+\|\s+', houseInfo)
if partment ==[]:
dit['户型'] = ""
else:
dit['户型'] = partment[0]
area = re.findall(r'(\d+\S+平米)\s+\|\s+',houseInfo)
if area ==[]:
dit['面积'] = ""
else:
dit['面积'] = area[0]
trim = re.findall(r'\s+(精装|简装|其他|毛坯)\s+\|\s+',houseInfo)
if trim ==[]:
dit['装修'] = ""
else:
dit['装修'] = trim[0]
story = re.findall(r'\s+(\S+层\S+)\s+',houseInfo)
if story ==[]:
dit['总共楼层'] = ""
else:
dit['总共楼层'] = story[0]
particular = re.findall(r'\s+(\d+)年建\s+',houseInfo)
if particular ==[]:
dit['建成年份'] = ""
else:
dit['建成年份'] = particular[0]
struct = re.findall(r'\|\s+(板.*|塔.*)$',houseInfo)
if struct!=[]:
dit['房子结构'] = struct[0]
else:
dit['房子结构'] = ""
followInfo = li.css('.followInfo::text').get()
dit['发布周期'] = followInfo
Price = li.css('.totalPrice span::text').get()
dit['售价/万'] = Price
unitPrice = li.css('.unitPrice span::text').get()
dit['单价'] = unitPrice
csv_writer.writerow(dit)
代码执行后,会把10个区的二手房信息爬取下来,我这里爬取的是3月13号的数据。
这是我爬下来的数据:
二、数据可视化
这次可视化用鼓楼的数据为例,用pandas处理数据,pyecharts进行大图显示。
读取第一步中爬取的数据:
data = pd.read_csv('鼓楼二手房信息.csv',encoding='UTF-8') #GB18030 , errors='ignore' df = pd.DataFrame(data)
我这里做了五张可视化的图,分别是直方图显示单价区间的房源数,饼图显示面积区间内的房源数,饼状图显示总价,直方图显示房龄,直方图显示面积+价格区间内的房源数。
求区间内的房源数的方法是用pd.cut函数:
cuts = pd.cut(prices,range_num,labels=labes)
prices就是区域3000条房源信息的单价,range_num是设定的房价区间范围,我这里按2~2.4万,2.4~2.8万,4千一个区间分的,labes就是要在x轴显示的区间的标签。最终得到如下图:
从图可以看出鼓楼区学区房较多,房价在整个市区中最高,8万/平以上的就占8%。
按照区间统计数据的方法,统计出各个房屋面积区间段内的房源数量,及其占比,统计出5年内、5~10年等范围内房源数量。总价范围内的房源数量等等。不再赘述。
最后是groupby的方法,同时统计房屋面积+总价范围内的房源数量。
level_area = ['0-30平','30-60平','60-90平','90-120平','120-150平','150-200平','200-300平','300-400平','400-800平']
level_sales = ['150万以下','150-200万','200-300万','300-400万''400-500万','500-600万','600-800万','800-1000万','1千-4千万','大于4千万']
group = df.groupby(['面积区间','售价区间'],as_index=False)
groupby在sql语句中用到,pandas中也有该用法,得到的结果如下:
如果在这两个条件下匹配不到,就会返回nan,我们用group.size(),就能把groupby用到的条件和统计出的数量显示出来,剔除掉0的部分,就能得出面积、总价内的房源数量,虽然看起来无用,但是对于宁飘,有买房计划的人来说,多少大洋能买起一套房心中至少会有个底吧。
用该语句grouped[~grouped['size'].isin([0])],剔除结果为0的行。
这是去除没匹配到的结果:
最后是把所有结果放到一张大图上显示:
page = Page(layout=Page.DraggablePageLayout) page.add(bar_unitprice(),area_pie(),saleprice_pie(),hourseage_bar(),clusete_bar())page.render("test.html") 用到的是Page方法,先把可视化的函数写好,然后加到一张页面中,先保存成html文件,然后把各个视图拖动,拖动到你想要的地方,save config后,得到一个json文件,把该json文件放到目录下,注释掉render("test.html")
Page.save_resize_html("test.html", cfg_file = "chart_config.json", dest = "鼓楼二手房信息可视化.html")
就能得到所有可视化结果在一张图上的视图。
代码如下:
# -*- coding: utf-8 -*-
import pandas as pd
import csv
import random,re
from pyecharts.charts import Geo,Map,Bar, Line, Page,Pie, Boxplot, WordCloud
from pyecharts.globals import ChartType, SymbolType,ThemeType
from pyecharts import options as opts
data = pd.read_csv('鼓楼二手房信息.csv',encoding='UTF-8') #GB18030 , errors='ignore'
df = pd.DataFrame(data)
def bar_unitprice():
price_hourses = df['单价'].values.tolist()
prices = []
for price in price_hourses:
price = float(price.replace("元/平","").replace(",",""))
prices.append(price)
labes = []
range_num = []
for i in range(20000,80001,4000):
range_num.append(i)
labes.append(str(i)+"-"+str(i+4000))
range_num.append(200000)
labes.pop(-1)
labes.append("大于80000")
# print(range_num,labes)
cuts = pd.cut(prices,range_num,labels=labes)
couts_range = cuts.value_counts().values.tolist()
bar = Bar(init_opts=opts.InitOpts(width="2000px",height="700px",page_title="二手房价格区间房源数"))
bar.add_xaxis(labes)
bar.add_yaxis("", couts_range)
bar.set_global_opts(
xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=20)),
title_opts=opts.TitleOpts(title="二手房价格区间房源数", subtitle="所"),
datazoom_opts=opts.DataZoomOpts(),
)
return bar
def area_pie():
area=(df['面积'].str.replace('平米','').astype(float))
sum_a=area.sum()
#fanwei=list(range(0,250,30))
bins =[0,30,60,90,120,150,200,300,400,800]
count=pd.cut(area.values,bins=bins,right=False)
area_range=count.value_counts()#series,区间一个数
#print(area_range)
attr = ["[0-30)", "[30-60)", "[60-90)", "[90-120)", "[120-150)", "[150-200)", "[200-300)", "[300-400)",'[400-800)']
area_range=list(area_range)
pie = Pie()
pie.add("", [list(z) for z in zip(attr, area_range)],
radius=["30%", "75%"],
center=["40%", "50%"],
rosetype="radius")
pie.set_global_opts(
title_opts=opts.TitleOpts(title="房屋面积占比"),
legend_opts=opts.LegendOpts(
type_="scroll", pos_left="80%", orient="vertical"
),
).set_series_opts(label_opts=opts.LabelOpts(formatter="{b}:{d}%"))
return pie
def saleprice_pie():
price_sale = df['售价/万'].values.tolist()
labes = []
range_num = []
for i in range(100,1001,50):
range_num.append(i)
labes.append(str(i)+"-"+str(i+50))
range_num.append(3000)
labes.pop(-1)
labes.append("大于1000")
cuts = pd.cut(price_sale,range_num,labels=labes)
price_range = cuts.value_counts().values.tolist()
c = (
Pie(init_opts=opts.InitOpts(theme=ThemeType.LIGHT))
.add(
"",
[
list(z)
for z in zip(
labes ,
price_range ,
)
],
#设置圆心坐标
center=["40%", "57%"],
)
.set_global_opts(
title_opts=opts.TitleOpts(title="总价内的房源数"),
legend_opts=opts.LegendOpts(
type_="scroll", pos_left="80%", orient="vertical",pos_top="15%"
),
)
.set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {c}"))
)
return c
def hourseage_bar():
years = (df['建成年份'])
years = (2022 - years.dropna(axis=0,how="all").astype(int)).values.tolist()
labes = []
range_num = []
for i in range(0,22,3):
range_num.append(i)
labes.append(str(i)+"-"+str(i+3))
range_num.append(70)
labes.pop(-1)
labes.append("大于21")
cuts = pd.cut(years,range_num,labels=labes)
hourse_range = cuts.value_counts().values.tolist()
c = (
Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT)) # 设置主题
.add_xaxis(labes) # x轴为房龄
.add_yaxis("房源数", hourse_range) # y轴为房源数
.set_global_opts(title_opts=opts.TitleOpts(title="各个房龄的房子"))
.set_series_opts(
label_opts=opts.LabelOpts(is_show=False),
# 插入平均值线
markline_opts=opts.MarkLineOpts(data=[opts.MarkLineItem(type_="average", name="平均值"), ]),
# 插入最大值最小值点
markpoint_opts=opts.MarkPointOpts(data=[
opts.MarkPointItem(type_="max", name="最大值"),
opts.MarkPointItem(type_="min", name="最小值"),
])
)
)
return c
def clusete_bar():
bins =[0,30,60,90,120,150,200,300,400,800]
level_area = ['0-30平','30-60平','60-90平','90-120平','120-150平','150-200平','200-300平','300-400平','400-800平']
sales_step = [0,150,200,300,400,500,600,800,1000,4000]
level_sales = ['150万以下','150-200万','200-300万','300-400万''400-500万','500-600万','600-800万','800-1000万','1千-4千万','大于4千万']
years_step = [0,5,10,15,20,25,70]
level_year = ['5年以下','5-10年','10-15年','15-20年','20-25年','20年以上']
area=(df['面积'].str.replace('平米','').astype(float))
price_sale = (df['售价/万']).astype(float)
years = (df['建成年份']) #.fillna(0.inplace=True)
years = years.fillna(1000)
years = (2022 - years.astype(int))
df["面积区间"] = pd.cut(area,bins=bins,labels=level_area)
df["房龄"] = pd.cut(years,bins=years_step,labels=level_year)
df["售价区间"] = pd.cut(price_sale,bins=sales_step,labels=level_sales,right=False)
# df2 = df[['面积区间', '房龄' , '售价区间','标题']]
group = df.groupby(['面积区间','售价区间'],as_index=False)
# test = group.get_group(('60-90平','200-300万','5年以下'))[['售价/万','面积','房龄','标题']]
grouped = group.size()
grouped=grouped[~grouped['size'].isin([0])]
cluster = grouped.values.tolist()
# grouped.drop( index = grouped.size[grouped.size == 0].index )
counts = []
lables = []
for i in range(len(cluster)):
counts.append(cluster[i][2])
lables.append(str(cluster[i][0]+","+cluster[i][1]))
bar = Bar(init_opts=opts.InitOpts(width="2000px",height="700px",page_title="总价+面积聚类",theme=ThemeType.LIGHT))
bar.add_xaxis(lables)
bar.add_yaxis("", counts)
bar.set_global_opts(
xaxis_opts=opts.AxisOpts(axislabel_opts=opts.LabelOpts(rotate=20)),
title_opts=opts.TitleOpts(title="总价+面积聚类", subtitle="所"),
datazoom_opts=opts.DataZoomOpts(),
)
return bar
page = Page(layout=Page.DraggablePageLayout)
page.add(bar_unitprice(),area_pie(),saleprice_pie(),hourseage_bar(),clusete_bar())
# page.render("test.html")
Page.save_resize_html("test.html",
cfg_file = "chart_config.json",
dest = "鼓楼二手房信息可视化.html")
得到的大图如下:
关注页面的小伙伴们可以学习上面大图可视化的方法。
如果只是想关注下数据,那看下面清晰的结果: