一.背景
通过python爬虫获取lian-jia网上广州、深圳、佛山、东莞四个城市月租金在2500以内的房源信息,包括位置、价格、面积...... 将获取到的房源信息进行数据分析,爬取的房源虽然包括公寓,但主要是对非公寓的房源进行数据分析。
二.数据获取
数据爬取主要用到python的requests和靓汤(beautifulsoup)这两个库,靓汤主要是用于对html 或 xml 进行提取数据的。(ps:用re正则表达式 或 xpath 等其他的也行,用自己熟悉的方式提取到数据就行)爬取数据之前,先要了解url的结构,网页上自己所需要的内容,根据自己的需求编写对应的代码进行数据爬取。
1.URL分析
批量的爬取数据,那就应该先分析不同页面url,首先看不同城市的url变化。
从上图可以得知,gz和sz应该是两个城市的缩写,所以后面的爬取url在这个位置设置为变量值。
接下来再看页数变化,url会有什么变化。
要在pg后面的数字应该就是页数,erp2500就是限定的金额。同时在网站上面还能看到页面的数量最多展示到100页,所以我们在爬取的时候也要设定好爬取的页数。
2.房源列表信息爬取
房源列表页面就有很多关键信息,所以我们先从中获取房源标题,区/(区域)、详细地址以及房源详情页的url。对应的请求获取数据的代码如下:
zf_url = [] #用来存放url
zf_apartment = [] #是/否公寓 1/0
zf_area = [] #区
zf_site = [] #详细地址
zf_title = [] #标题
city = ['gz','sz','fs','dg'] #用来存放城市
city_name = ['广州','深圳','佛山','东莞']
for i in range(len(city)):
city_url = f'https://{city[i]}.xxxxxx.com' #爬取的网页url
apartment_city = city_name[i]
for j in range(1,101):
html_url = city_url+ f'/zufang/pg{j}erp2500/'
html_headers = {'User-Agent':'你的User-Agent',
'Connection': 'close'}
response = requests.get(url=html_url,headers=html_headers)
response.encoding='utf-8'
soup = BeautifulSoup(response.text,'html.parser')
#获取每个房源的url
hdurls = soup.select('div.content__list--item a.content__list--item--aside')
url_list = [] #用来存放此次url
for hdurl in hdurls:
url_list.append(city_url+ hdurl.attrs['href'])
zf_url.append(city_url+ hdurl.attrs['href'])
#获取标题
for i in range(len(url_list)):
title_list = soup.select('div.content__list--item--main p.content__list--item--title a')
zf_title.append(title_list[i].text.replace('\n',' '))
#判断是否公寓
ls_list = [] #用来存放临时数据
apartment_list = []
for i in range(len(url_list)):
ls_list.append(url_list[i].split('/')[3])
if ls_list[i] == 'zufang':
zf_apartment.append(0)
apartment_list.append(0)
elif ls_list[i] == 'apartment':
zf_apartment.append(1)
apartment_list.append(1)
#获取每个房源的区、地址(公寓除外)
areas = soup.select('p.content__list--item--des a')
a = 0
for i in range(len(apartment_list)):
ls_num = 0
if apartment_list[i] == 0:
ls_num = a * 3
zf_area.append(areas[ls_num].string) #区
if areas[ls_num+1] is None :
areas[ls_num+1].string = ''
if areas[ls_num+2] is None :
areas[ls_num+2].string = ''
zf_site.append(areas[ls_num+1].string + areas[ls_num+2].string) #地址
a = a + 1
elif apartment_list[i] == 1:
zf_area.append(apartment_city+'公寓')
zf_site.append('/')
zf_apartment 的1表示公寓,0是非公寓,设定这个值的主要原因是公寓详情页跟其他的不同,我能爬取到的内容较少(ps:看不懂提取方式的,去看一下beautifulsoup的语法,或者改用自己熟悉的提取方式)
3.房源详细信息爬取
通过从房源列表爬取到的url对房源的详细信息进行爬取,包括租金、面积、类型、城市、楼层等信息。由于公寓的详情页面不一样,所以要判断是否公寓然后再进行对应的数据爬虫代码,具体代码如下:
for i in range(0,len(zf_url)):
if zf_apartment[i] == 0:
hd_response = requests.get(url=zf_url[i],headers=html_headers)
hd_response.encoding='utf-8'
hd_soup = BeautifulSoup(hd_response.text,'html.parser')
#城市
city = hd_soup.select('meta')[10]['content'].split('city=')[1]
zf_city.append(city)
#房子类型
hd_detail = hd_soup.select('ul.content__aside__list li')
if len(hd_detail) == 0:
zf_type.append('/')
else :
hdtype = hd_detail[1].text.split(' ')[0].split(':')[1]
zf_type.append(hdtype)
hd_details = hd_soup.select('div#info li')
if len(hd_details) == 0:
#面积
zf_proportion.append('0')
#朝向
zf_orientation.append('/')
#楼层
zf_height.append('/')
#是否有电梯
zf_elevator.append('无')
else :
#面积
hdsize = hd_details[1].text.split(":")[1].split('㎡')[0]
zf_proportion.append(hdsize)
#朝向
direction = hd_details[2].text.split(":")[1]
zf_orientation.append(direction)
#楼层
height = hd_details[7].text.split(":")[1]
zf_height.append(height)
#是否有电梯
elevator = hd_details[8].text.split(":")[1]
zf_elevator.append(elevator)
cash = hd_soup.select('div.table_content ul.table_row li')
if len(cash) == 0:
#月租金
zf_price.append(0)
#押金
zf_book_price.append('/')
#服务费
zf_service_price.append('/')
else :
zf_price.append(cash[1].text)
#押金
zf_book_price.append(cash[2].text)
#服务费
zf_service_price.append(cash[3].text)
elif zf_apartment[i] == 1:
hd_response = requests.get(url=zf_url[i],headers=html_headers)
hd_response.encoding='utf-8'
apartment_soup = BeautifulSoup(hd_response.text,'html.parser')
#城市
city = apartment_soup.select('meta')[10]['content'].split('city=')[1]
zf_city.append(city)
#房子类型
zf_type.append('公寓')
#面积
zf_proportion.append('0')
#朝向
zf_orientation.append('/')
#公寓电梯
ap_elevator = apartment_soup.select('div.flat__info--facilities ul li')
if ap_elevator is None :
zf_elevator.append('无')
#print(len(ap_elevator))
elif len(ap_elevator) < 2:
zf_elevator.append('无')
elif ap_elevator[1].text == '电梯':
zf_elevator.append('有')
else :
zf_elevator.append('无')
#楼层
zf_height.append('/')
#公寓地址
ap_site = apartment_soup.select('div#info p')
if len(ap_site) < 2:
zf_site[i] = '/'
else :
zf_site[i] = re.sub(r'\d{11}','',ap_site[1].text.replace('\n',''))
#公寓租金
ap_cash = apartment_soup.select('div#aside p.content__aside--title span')
if len(ap_cash) < 2:
zf_price.append(0)
#押金
zf_book_price.append('/')
#服务费
zf_service_price.append('/')
else:
zf_price.append(ap_cash[1].text.replace('\n','').split('元')[0])
#押金
zf_book_price.append('/')
#服务费
zf_service_price.append('/')
(ps:目前代码还没有进行优化,小伙伴们可以自己加一下多线程之类的提高爬虫效率。后面有时间会重新发一下优化后的代码,但是你看到这就说明我还没有优化☺☺)
三.数据预处理
1.数据准备
zufang_data = pd.DataFrame()
zufang_data['city'] = zf_city
zufang_data['title'] = zf_title
zufang_data['area'] = zf_area
zufang_data['site'] = zf_site
zufang_data['type'] = zf_type
zufang_data['proportion'] = zf_proportion
zufang_data['height'] = zf_height
zufang_data['orientation'] = zf_orientation
zufang_data['elevator'] = zf_elevator
zufang_data['price'] = zf_price
zufang_data['book_price'] = zf_book_price
zufang_data['service_price'] = zf_service_price
zufang_data['url'] = zf_url
zufang_data
网站上限制了100页,四座城市一共爬取到1.2W条房源数据。
2.检查数据
可以看到数据是不存在空值的。
3.处理数据
检查一下爬取数据的准确性,随便查看几个url对应的信息,在浏览器上输入url,上面的信息与网站上的信息进行对比验证准确性。例如:
网站上可能会出现广告信息或者多次发布这种类型的房源,可以使用以下方法检查重复值或删除重复的房源信息:
zufang_data.duplicated() #检验
zufang_data=zufang_data.drop_duplicates() #去除重复值
为了更好的观察一下整体的数据,可以先将数据导出excel,在excel里面看得更加清晰。
zufang_data.to_excel(r'zufang.xlsx',index=False)
height字段表示的是楼层,个人觉得只要显示数字就行,所以对height这列的数据进行处理:
for i in range(len(zufang_data)):
if '楼层/' in zufang_data['height'][i]:
zufang_data['height'][i] = zufang_data['height'][i].split('层/')[1]
if zufang_data['height'][i] != '/':
zufang_data['height'][i] = zufang_data['height'][i].split('层')[0]
zufang_data['height']
检验一下爬取的数据是否有虚假信息,通过面积与价格散点图进行观察,首先使用astype()方法将面积和价格的类型进行转换:
然后绘制散点图:
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
zufang_data.plot.scatter(x='proportion', y='price', alpha=1)
观察上图,有price超出了2500,这个有可能是爬取信息的时候,刚好这个房子租金涨了。再看有的面积竟然达到了2500㎡,太假了,连老实人都欺骗不了。按照实际情况取分析,2500在这四个城市,几乎不可能租到面积大于150㎡(保守的估计☺),所以要提取价格在2500以内,面积在150㎡以内的数据。
zufang_data = zufang_data[zufang_data['price'] <= 2500]
zufang_data = zufang_data[zufang_data['proportion'] < 150]
zufang_data
面积(proportion)处于0的,都是公寓,因为在公寓的详情页面没有它的面积信息。
4.备份数据
常见的是将数据备份到excel或者数据库中,备份到mysql数据库中的具体实现如下:
from datetime import datetime
from sqlalchemy import create_engine,text
import pymysql
# root:mysql用户名
# 123321:我的密码
# pytest:数据库名,要先创建
# name='表名'
engine=create_engine('mysql+pymysql://root:123321@localhost/pytest?charset=utf8mb4')
zufang_data.to_sql(name='zufang_data',con=engine,index=False,if_exists='replace')
四.数据分析
1.词云图
在查看房源时,一般我们设置好价格区间后,大多数都会先留意标题信息,这样我们能快速的了解到房子有什么特点。所以,我们可以提取这些标题的关键字,来制作词云图,从而了解到这些房源的主要特点。
首先是用jieba来提取关键字,我这里设置了提取50个关键字
import jieba
import jieba.analyse as anls
import pickle
title = " ".join(zufang_data['title'])
#topK 用来设置关键词个数
keywords = anls.extract_tags(title, topK=50, withWeight=True) #返回含有两个值的字典
keyword = [] #词
keyweight = [] #权重值
for word, weight in keywords:
keyword.append(word)
keyweight.append(weight)
查看一下前10个词以及对应的权重值
使用wordcloud生成词云图,我这里生成在本地的图片(还有可以直接生成的,可以自行百度)
# 将读取的文件进行切割
word = "".join(jieba.lcut(keyword))
wc = wordcloud.WordCloud(
width=700,
height=700,
background_color="white",
scale=15,
font_path="msyh.ttc"
)
wc.generate(word)
wc.to_file("zufang.png")
整租、独栋表达了房子的独立性、私密性,开间要表达的房子的面积空间大小。多种类型的房子提供给用户,包公寓、合租,单间等等,还会放一些关于朝向、采光、地铁这些租客想要了解的,用来吸引注意。
2.户型分析
户型是租客在查看房源时很关心的点之一,以数量排在 前9的户型 以及 之后的作为其他类型的户型 制作饼图,这里使用了matplotlib库,具体如下:
import matplotlib.pyplot as plt
#分组获取户型数量
type_detail = zufang_data.groupby('type')['title'].count()
type_detail = type_detail.reset_index()
#type_detail
#排序 ,降序
types = type_detail.sort_values('title',ascending=False)
# 取前9个
type_detail = types[:9]
# 剩余的求和作为其他
type_order = types[10:]
type_order['title'] = type_order['title'].astype(numpy.int64)
type_order_count = type_order['title'].sum()
type_detail.loc[len(type_detail)] = ['其他',type_order_count]
# 创建饼图
plt.figure(figsize=(10, 10)) # 设置图像大小
plt.pie(type_detail['title'], labels=type_detail['type'], autopct='%1.1f%%', startangle=90)
plt.title('户型分布图')
# 显示图像
plt.show()
整体前三的是1室1厅1卫占18.6%,公寓17.9%,3室2厅2卫占比16.4%。
再看一下广州、深圳两个一线城市的户型分布(代码跟上面的差不多,只不过换成只有广深两个城市的数据)
广深两城户型前三的分别是公寓31.8%,1室1厅1卫占14.1%,1室0厅1卫占比12%。
结合两图可以看出,在广深两城,2500以内推荐的房源以公寓和单室为主,如果想选择适合一家人居住3室2厅2卫,在佛山和东莞会有更多的选择。
3.面积与区域分析
对四座城市进行面积分析,月租金2500以内在四城一般以多少面积为主,在哪些区域,由于公寓面积没有爬取,都是0,所以就这里排除了对公寓的分析。
interval = 20 # 每个区间的宽度
bins = np.arange(1, 150 + interval, interval) # 创建区间的边界
counts, edges = np.histogram(zufang_data[zufang_data['city'] == '广州']['proportion'], bins=bins)
# 使用matplotlib绘制直方图
plt.hist(zufang_data[zufang_data['city'] == '广州']['proportion'], bins=bins)
# 添加数值标签
for i, v in enumerate(counts):
plt.text(edges[i], v, str(v), color='black')
# 设置x轴和y轴的标签
plt.title('gz房源面积图')
plt.xlabel('面积/㎡')
plt.ylabel('数量')
# 显示图表
plt.show()
每个城市的分布各有不同,我们就去每座城市房源最多的两个区域进行分析,代码以广州的为例
gz_data = zufang_data[zufang_data['city'] == '广州']
gz_proportion1 = gz_data[gz_data['proportion'] > 80]
gz_proportion2 = gz_proportion1[gz_proportion1['proportion'] <= 100].groupby('area')['title'].count().reset_index()
gz_proportion2 = gz_proportion2.rename(columns={'title':'gz-1'})
gz_proportion2.sort_values('gz-1',ascending=False)
(xx-1表示面积区域最多数量,xx-2表示面积区域数量第二)
①.从上图看,在广州80-100㎡之间有523套,但是这些房子基本上距离市中心比较远,集中在增城、南沙、花都。在20-40㎡的有450套,这些房源各区都有,基本上是在市区或者交通很方便的城区。
②.深圳在20-40㎡的有723套,其中龙岗、宝安、罗湖位于前三位;0-20㎡的有603套,南山、福田、龙岗居前三,可以看出2500在0-40㎡在龙岗区的选择会比较多。
③.佛山房源80-100㎡的有823套,20-40㎡的有659套,房源主要集中在顺德、南海两区。
④.东莞房源80-100㎡的有824套,40-60㎡的有626套,东莞的房源分布比较广泛,上面只是展示了区域内前10的地区,总的来说2500在20-100㎡在东莞在全市都有不少的选择。
4.价格分析
各市各区的每平方米的平均价格,代码如下:
#以广州为例,也是排除公寓
#分组获取各区的面积以及价格
gz_data = zufang_data[zufang_data['city'] == '广州']
gz_data = gz_data.groupby('area')['proportion','price'].sum().reset_index()
#求平均价格
gz_data['avg_price'] = 0
for i in range(len(gz_data)):
gz_data['avg_price'][i] = round(gz_data['price'][i]/gz_data['proportion'][i],2)
#根据索引去除公寓
gz_data = gz_data.drop(4)
# 创建横向条形图
plt.barh(gz_data['area'], gz_data['avg_price'])
# 添加数值到每个条形旁边
for i, v in zip(gz_data['area'], gz_data['avg_price']):
plt.text(v, i, v, ha='right', va='center')
plt.xlabel('price/㎡')
plt.ylabel('城区')
plt.title('gz各区1㎡价格')
plt.show()
四个城市租金均价前十区域代码如下:
price_detail = zufang_data.groupby('area')['proportion','price'].sum().reset_index()
#求平均价格
price_detail['avg_price'] = 0
for i in range(len(price_detail)):
price_detail['avg_price'][i] = round(price_detail['price'][i]/price_detail['proportion'][i],2)
#去除公寓
to_remove = price_detail['area'].str.contains('公寓')
price_detail = price_detail[~to_remove]
price_detail = price_detail.sort_values('avg_price',ascending=False)[:10]
# 创建横向条形图
plt.barh(price_detail['area'], price_detail['avg_price'],color = 'lightblue')
# 添加数值到每个条形旁边
for i, v in zip(price_detail['area'], price_detail['avg_price']):
plt.text(v, i, v, ha='right', va='center')
plt.xlabel('price/㎡')
plt.ylabel('城区')
plt.title('四城租金均价最高前十区')
plt.show()
四个城市全区域平均租金代码如下:
#去除公寓
to_remove = zufang_data['area'].str.contains('公寓')
price_detail = zufang_data[~to_remove]
price_detail = price_detail.groupby('city')['proportion','price'].sum().reset_index()
#求平均价格
price_detail['avg_price'] = 0
for i in range(len(price_detail)):
price_detail['avg_price'][i] = round(price_detail['price'][i]/price_detail['proportion'][i],2)
price_detail = price_detail.sort_values('avg_price',ascending=False)
# 创建横向条形图
plt.barh(price_detail['city'], price_detail['avg_price'],color = 'lightgreen')
# 添加数值到每个条形旁边
for i, v in zip(price_detail['city'], price_detail['avg_price']):
plt.text(v, i, v, ha='right', va='center')
plt.xlabel('price/㎡')
plt.ylabel('城区')
plt.title('四市全区域平均租金均价')
plt.show()
2500以内的房源信息,四城整体都呈现出租金从市区到郊区逐渐下降,跟市场的整体趋势。广州的市中心区与郊区的租金差距较大,平均租金与同为一线城市的深圳比就显得租金便宜很多,与佛山、东莞相差不大;深圳的租金是均价最高的,四城均价前10里面有7个是深圳的;佛山时均价最低的城市,中心城区的价格也是四市最低的;东莞呈现出广泛,价格在各区域大体来看相对均匀。
房源信息中包含有服务费这一项费用,这也是租客们关注点之一。下面 排除不需要服务费以及需要联系咨询服务费的,只看标明服务费用的房源在各区的占比情况。
#排除 需要联系的
to_remove = zufang_data['service_price'].str.contains('/')
service_price_detail = zufang_data[~to_remove]
# 排除 不需要服务费的
to_remove = service_price_detail['service_price'].str.contains("0")
service_price_detail = service_price_detail[~to_remove]
#根据区域 分组获取服务费的房源数量
service_count = service_price_detail.groupby('area')['service_price'].count().reset_index()
service_count = service_count.rename(columns={'service_price':'spd_total'})
#获取各区域的房源数量
count_data = zufang_data.groupby('area')['title'].count().reset_index()
count_data = count_data.rename(columns={'title':'total'})
#需要服务费区域的所有房源
service_count['total'] = 0
for i in range(len(service_count)):
for j in range(len(count_data)):
if service_count['area'][i] == count_data['area'][j] :
service_count['total'][i] = count_data['total'][j]
#某区域需要服务费的占比
service_count['need_service_fee'] = 0
for i in range(len(service_count)):
service_count['need_service_fee'][i] = round((service_count['spd_total'][i]/service_count['total'][i]),2)
service_count = service_count.sort_values('need_service_fee',ascending=False)
#转化为百分比
for i in range(len(service_count)):
service_count['need_service_fee'][i] = str(service_count['need_service_fee'][i]) + '%'
service_count
在标明需要服务费的房源信息来看,收取服务费的城市主要广深两城,并且推断出越是中心城区收取服务费的几率越大。
五.分析总结
通过对租房信息的几方面分析,各市周边配套好的、交通方便的区域自然租金会比其他区域高,广州所呈现出来的有点像由外到内(郊区到市区)租金逐渐增长,中心城市与郊区的租金差距大;而深圳所呈现的就是整体的租金都比较高,这也是正常的,毕竟深圳发展迅速,机遇多、年轻人多,较高的工资也能承受较高的房租;佛山的平均租金是四个城市最低的,即便是靠近广州区域平均租金也不高,如果在广州非郊区区域工作,想要租金较低,周边配套好,交通方便,那么去佛山的中心区域租房未尝不是一个选择;东莞租房的房源分布就较为广泛,除了临近深圳的区域以及小部分主城区,整体的租金差距不是很大,选择会有很多。