链家网租房信息数据分析——从爬虫到房租预测
前言
只身一人来到异地工作,首当其冲考虑的是住宿问题。如果公司有提供住宿当然最好,但大多时候还是需要我们自己去租房子。可是作为人生地不熟的异乡人,往往对当地租房市场难以有清晰的洞见,也担心自己被房东或中介当了猪宰。现在你可以稍安勿躁了,本文将运用python爬虫,sklearn包,以及tableau可视化工具,从数据获取到最终的结果呈现,对当地的租房价格做深入浅出的分析和预测,助你找到称心如意的房子。同时也是我自己数据分析学习的一个小结。
数据爬取
思路
由于链家网的反爬手段比较温和,只要不太过分地爬取,一般不会被ban(本文写作阶段博主还没被其ban过),我们选择链家网进行租房信息的爬取。
由于博主现住上海,就以上海的信息为例。这是上海的网址链接:
https://sh.lianjia.com/zufang/
如果我们切换成其他城市,比如北京,html会变成:
https://bj.lianjia.com/zufang/
可以发现html只有很小的改变,由sh变成了bj,也就是城市的拼音缩写
继续观察,不出意外,我们应该位于链家租房页面的第一页,而拉到底后会发现,一共有100页,那么我们随便点一页,看看网址的变化。
没错,网址变成了:
https://sh.lianjia.com/zufang/pg{你点击的页数}/#contentList
我们将末尾的#contentList去掉后,网址依然可以使用
同理,https://sh.lianjia.com/zufang/pg1/ 也能进入我们最开始的首页
那么我们可以得知,链家租房网页的html结构基本就是这样:
https://{city}.lianjia.com/zufang/pg{number}/
但是还有一个很严重的问题,相信细心的同学已经发现了,链家租房信息一共只有100页,而每页的信息只有30条,一共也就是3000条。而上海的租房信息首页上却显示一共有20000+条。那么怎么把这20000多条全部爬下来呢。这时候我们看看首页的上面还有什么,没错,区域!。对区域加以限制后可以发现,各个区域的租房信息依然最多能有100页信息,也就是3000条。而只有浦东区是超过3000条的,竟然达到了6890条!看来近几年浦东的发展势头很好呀。
结合起来,包含区域的网址结构则为:
https://{city}.lianjia.com/zufang/{region}/pg{number}/
所以我们可以依不同区域,将各区域的100页网址信息全部爬取下来,再对每一页网址信息进行处理,将每一页网址30条信息(if reaching)的网址爬下来。
对于超过3000条的浦东区,可以再进一步,同按区域爬取的思路一样,按租金进行爬取。这样不同租金的网址结构就变成了:
https://{city}.lianjia.com/zufang/{region}/rp{number}/pg{number}/
获取到所有网址信息后,在进入每一个网址,获取该住房的详细信息。好了,废话不多说,show u my code.
爬虫代码
这里只用了requests和BeautifulSoup库,而且是简单的单线程爬取,跟博主类似的新手应该也能看懂。
# -*- coding: UTF-8 -*-
# 导入需要的各种库
import re
import time
import requests
from random import uniform
from bs4 import BeautifulSoup
import pandas as pd
from sqlalchemy import create_engine
import pymysql
# 设置一个请求头,不然无法通过链家的验证
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 Safari/537.36'}
def get_parent_url(city):
"""
获取选定城市的所有父母网址
:param city:
:return:
"""
url = 'http://{city}.lianjia.com/zufang'.format(city=city)
html = requests.get(url, headers=headers) # 获取网址html
Soup = BeautifulSoup(html.text, 'lxml') # 解析html
Selector = Soup.select('ul[data-target="area"] > li.filter__item--level2') # 找出html中的区域文本
Selector = Selector[1:] # 排除第一个区域“不限”
url_parent_all = [] # 初始化最终的父母网址列表
for i in Selector: # 对每一个区域进行loop
url_region = "https://sh.lianjia.com" + i.select('a')[0]['href'] # 找出区域网址
html_region = requests.get(url_region, headers=headers) # 获取区域网址html
Soup_region = BeautifulSoup(html_region.text, 'lxml') # 解析html
number_data = int(Soup_region.select('span.content__title--hl')[0].text) # 获取该区域信息条数
if number_data <= 3000: # 信息条数少于3000直接开始爬取
index = Soup_region.select('div.content__pg')[0] # 找出页数文本
index = str(index) # 将bs4对象转换为str,否则无法进行正则提取
re_set = re.compile(r'data-totalpage="(.*?)"')
index = re.findall(re_set, index)[0] # 正则表达式提取出页数
for j in range(1, int(index)+1): # 对每一页网址进行循环
url_parent = url_region + "pg{}".format(j)
url_parent_all.append(url_parent) # 得到该区域每一页的网址,并添加至父母网址列表中
print(url_parent)
t = uniform(0, 1)
time.sleep(t) # 每爬一个区域,稍作等待,避免被ban
else: # 信息条数大于3000按租金分层
for i in range(1, 8):
url_region_rp = url_region + "rp{}/".format(i)
html_region_rp = requests.get(url_region_rp, headers=headers)
Soup_region_rp = BeautifulSoup(html_region_rp.text, 'lxml')
index = Soup_region_rp.select('div.content__pg')[0] # 操作同上
index = str(index)
re_set = re.compile(r'data-totalpage="(.*?)"')
index = re.findall(re_set, index)[0]
for j in range(1, int(index) + 1):
url_parent = url_region + "rp{}/".format(i) + "pg{}".format(j)
url_parent_all.append(url_parent)
print(url_parent)
t = uniform(0, 1)
time.sleep(t)
return url_parent_all
def get_detail_url(url_parent_all):
"""
对每一个父母网址进行操作,获取最终详尽的子网址列表
:param city:
:return:
"""
url_detail_all = [] # 创建最终的子网址列表
for url in url_parent_all: # 对每一个父母网址进行for loop
html = requests.get(url, headers=headers)
Soup = BeautifulSoup(html.text, 'lxml')
Selector = Soup.select('div a.content__list--item--aside') # 解析并找出子网址bs4对象
for i in Selector:
i = i['href']
i = 'http://{city}.lianjia.com'.format(city=city) + i # 对每一个bs4子网址对象循环,构建最终的子网址
url_detail_all.append(i) # 添加到子网址列表
print(i)
t = uniform(0, 0.01)
time.sleep(t) # 每处理一条父母网址暂停t秒
return url_detail_all
def get_data(url_detail_all):
"""
从子网址列表中网址获取数据
:param url_detail_all:
:return:
"""
data = [] # 初始化一个爬虫数据列表
num_error = 0 # 记录错误数
for i in url_detail_all: # for loop对每一个网址进行爬取操作
try: # 使用try...except...方法防止循环中途出错退出
info = {
}
url = i
html = requests.get(url, headers=headers)
Soup = BeautifulSoup(html.text, 'lxml')
info['房源编号'] = Soup.select('i.house_code')[0].text
info['链接'] = i
info['标题'] = Soup.select('p.content__title')[0].text
info['价格'] = Soup.select('p.content__aside--title')[0].text
Selector1 = Soup.select('p.content__article__table')[0].text.split('\n')
info['租赁方式'] = Selector1[1]
info['户型'] = Selector1[2]
info['面积'] = Selector1[3]
info['朝向'] = Selector1[4]
info['发布时间'] = Soup.select('li[class^="fl oneline"]')[1].text[3:]
info['入住'] = Soup.select('li[class^="fl oneline"]')[2].text[3:]
info['租期'] = Soup.select('li[class^="fl oneline"]')[4