网上租房数据的爬取与分析

主要介绍租房书爬取与分析过程中用到的相关技术,爬虫所用开发语言为python,开发环境anaconda,用beautifulsoup解析网页,数据处理numpy,可视化展示matplotlib,所用数据库为mangodb。

一.数据爬取

  1. 构造URL
    baseurl:https://sh.zu.fang.com/
    各地区对应编码
    在这里插入图片描述
    构造URL
urlDir = {
        #"不限": "/house/",
        "浦东": "/house-a025/",
        "闵行": "/house-a018/",
        "徐汇": "/house-a019/",
        "宝山": "/house-a030/",
        "普陀": "/house-a028/",
        "杨浦": "/house-a026/",
        "松江": "/house-a0586/",
        "嘉定": "/house-a029/",
        "虹口": "/house-a023/",
        "闸北": "/house-a027/",
        "静安": "/house-a021/",
        "黄浦": "/house-a024/",
        "卢湾": "/house-a022/",
        "青浦": "/house-a031/",
        "奉贤": "/house-a032/",
        "长宁": "/house-a020/",
        "金山": "/house-a035/",
        "崇明": "/house-a0996/",
        "上海周边": "/house-a01046/",
    }
  1. 网页下载
class HouseSpider:
        def getAreaList(self):
                return [  # "不限",
                        "浦东", "徐汇", "宝山", "普陀", "长宁", "杨浦", "松江",
                        "嘉定", "虹口", "闸北", "黄浦", "卢湾", "青浦", "奉贤",
                        "金山", "崇明", "闵行", "静安", "上海周边"]

        def getOnePageData(self, pageUrl, reginon="浦东"):
            rent = self.getCollection(self.region)
            self.session.headers.update({
                'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.84 Safari/537.36'})
            res = self.session.get(
                pageUrl
            )

        # 设置区域
        def setRegion(self, region):
                self.region = region

        # 设置页数
        def setPage(self, page):
                self.page = page

        def startSpicder(self):
                for url in self.getRegionUrl(self.region, self.page):
                        self.getOnePageData(url, self.region)
                        print("*" * 30 + "one page 分割线" + "*" * 30)
                        time.sleep(3)
spider = HouseSpider()
spider.setPage(100)# 设置爬取页数
for i in range(0,20):
            spider.setRegion(spider.getAreaList()[i]) # 设置爬取区域
            spider.startSpicder()
  1. 网页解析部分
soup = BeautifulSoup(res.text, "html.parser")
# 获取需要爬取得 div
divs = soup.find_all("dd", attrs={"class": "info rel"})

for div in divs:
    ps = div.find_all("p")
    try:  # 捕获异常
        for index, p in enumerate(ps): 
            text = p.text.strip()
            print(text) 
        print("===================================")
        # 爬取并存进 MongoDB 数据库
        roomMsg = ps[1].text.split("|")
        # rentMsg 防止空值
        area = roomMsg[2].strip()[:len(roomMsg[2]) - 2]
        # 标题 房间数 平方数 价格 地址 交通描述 区 房子朝向
        rentMsg = self.getRentMsg(
            ps[0].text.strip(),
            roomMsg[1].strip(),
            int(float(area)),
            int(ps[len(ps) - 1].text.strip()[:len(ps[len(ps) - 1].text.strip()) - 3]),
            ps[2].text.strip(),
            ps[3].text.strip(),
            ps[2].text.strip()[:2],
            roomMsg[3],
        )
        # 插入到数据库中
        rent.insert(rentMsg)
    except:
        continue
  1. 存储功能设计
def __init__(self):
        self.client = MongoClient('mongodb://localhost:27017/')
        self.shzfdata = self.client.shzfdata
# MongoDB 存储数据结构
def getRentMsg(self, title, rooms, area, price, address, traffic, region, direction):
    return {
        "title": title,  # 标题
        "rooms": rooms,  # 房间数
        "area": area,  # 平方数
        "price": price,  # 价格
        "address": address,  # 地址
        "traffic": traffic,  # 交通描述
        "region": region,  # 区、(福田区、南山区)
        "direction": direction,  # 房子朝向(朝南、朝南北)
    }
# 获取数据库 collection
def getCollection(self, name):
    shzfdata = self.shzfdata
    if name == "浦东":
        return shzfdata.pudong
    if name == "闵行":
        return shzfdata.minhang
    if name == "徐汇":
        return shzfdata.xihui
    if name == "宝山":                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
        return shzfdata.baoshan
    。。。。。

到此为止,数据爬取部分完成,爬取数据1.5W条。

二. 数据分析

  1. 地址转换经纬度
    结构化地址转化为经纬度需要借助百度地图API,百度地图Web服务API为开发者提供http/https接口,即开发者通过http/https形式发起检索请求,获取返回json或xml格式的检索数据。用户可以基于此开发JavaScript、C#、C++、Java等语言的地图应用。本文将使用百度地图API的正地理编码服务提供将结构化地址数据(如:北京市海淀区上地十街十号)转换为对应坐标点(经纬度)功能。
    正地理编码的接口为:http://api.map.baidu.com/geocoder/v2/?address=北京市海淀区上地十街10号&output=json&ak=您的ak&callback=showLocation ,其中ak,address为必须参数
    转换代码:
from urllib.request import urlopen, quote
from pymongo import MongoClient
import json
import codecs
import sys
import os

path = sys.path[0] + os.sep

def getlnglat(address):
    """根据传入地名参数获取经纬度"""
    url = 'http://api.map.baidu.com/geocoder/v2/'
    output = 'json'
    ak = '***********************************' # 浏览器端密钥
    address = quote(address)
    uri = url + '?' + 'address=' + address  + '&output=' + output + '&ak=' + ak
    req = urlopen(uri)
    res = req.read().decode()
    temp = json.loads(res)
    lat = temp['result']['location']['lat']
    lng = temp['result']['location']['lng']
    return lat, lng

def jsondump(outfilename, dic):
    """传入保存路径和字典参数,保存数据到对应的文件中"""
    with codecs.open(path + outfilename + '.json', 'a', 'utf-8') as outfile:
        json.dump(dic, outfile, ensure_ascii=False)
        outfile.write('\n')

def convertfile(filename):
    file = codecs.open(path + filename, 'r', encoding='utf-8')
    outfilename = 'loc' + filename
    for line in file:
        dic = json.loads(line.strip())
        address = dic['地址']
        dic['lat'], dic['lng'] = getlnglat(address)
        jsondump(outfilename, dic)

def convertmongodb(host, dbname, collname):
    '''连接mongodb, 并根据其位置字段得到其坐标信息,进而更新数据库'''
    client = MongoClient('mongodb://localhost:27017/')
    db = client[dbname]
    collection = db[collname]
    for dic in collection.find():
        dic['lat'], dic['lng'] = getlnglat(dic['address'])
        collection.save(dic) # 更新数据,并覆盖相同_id的记录
    print (dic)

if __name__ == '__main__':
    filename = 'E:\Code\Python\test1\test.json'
    # convertfile(filename)
    host = '*********************' # 需要连接的数据库所在ip
    dbname = 'databackup'
    collname = 'allinfo' #allinfo是合并所有数据表所得的所有数据
    convertmongodb(host, dbname, collname)
  1. 数据展示
    (1)数据读入dataframe
import pandas as pd
import numpy as np
import pymongo
client = pymongo.MongoClient("mongodb://localhost:27017/",connect=False)
db = client["databackup"]
table = db["allinfo"]
df = pd.DataFrame(list(table.find()))

(2)上海租房平均价格

import matplotlib.pyplot as plt
from pylab import mpl
mpl.rcParams['font.sans-serif'] = ['FangSong']
mpl.rcParams['axes.unicode_minus'] = False

price = df['price']
max_price = price.max()
min_price = price.min()
mean_price = price.mean()
median_price = price.median()

print("上海市租房最高价格:%.2f元/平方米" % max_price)
print("上海市租房最低价格:%.2f元/平方米" % min_price)
print("上海市租房平均价格:%.2f元/平方米" % mean_price)
print("上海市租房中位数价格:%.2f元/平方米" % median_price)

% matplotlib
inline

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

mean_price_per_region = df.groupby(df.region)

fig = plt.figure(figsize=(12, 7))
ax = fig.add_subplot(111)

ax.set_title('上海租房平均价格')
mean_price_per_region.price.mean().plot.bar()

输出结果
上海市租房最高价格:1800000.00
上海市租房最低价格:100.00
上海市租房平均价格:8977.88
上海市租房中位数价格:5500.00
在这里插入图片描述
(3)上海各行政区内房屋面积对房价的影响(散点图)

def plot_scatter():
    plt.figure(figsize=(10, 8), dpi=256)
    colors = ['red', 'red', 'red', 'red',
              'blue', 'blue', 'blue', 'blue',
              'green', 'green', 'green', 'green',
              'gray', 'gray', 'gray', 'gray',
              'yellow', 'yellow', 'yellow', 'yellow']
    region = ['黄浦', '徐汇', '静安', '长宁',
              '浦东', '虹口', '杨浦', '普陀',
              '闵行', '宝山', '青浦', '松江',
              '嘉定', '奉贤', '金山', '崇明',
              '闸北', '卢湾', '上海']
    markers = ['o', 's', 'v', 'x',
               'o', 's', 'v', 'x',
               'o', 's', 'v', 'x',
               'o', 's', 'v', 'x',
               'o', 's', 'v']
    print(region)
    for i in range(19):
        x = df.loc[df['region'] == region[i]]['area']
        y = df.loc[df['region'] == region[i]]['price']
        plt.scatter(x, y, c=colors[i], s=5, label=region[i], marker=markers[i])

    plt.legend(loc=1, bbox_to_anchor=(1.138, 1.0), fontsize=12)
    plt.xlim(0, 800)
    plt.ylim(0, 100000)
    plt.title('上海各行政区内房屋面积对房价的影响(散点图)', fontsize=20)
    plt.xlabel('房屋面积(平方米)', fontsize=16)
    plt.ylabel('房屋价格', fontsize=16)
    plt.show()
plot_scatter()
['黄浦', '徐汇', '静安', '长宁', '浦东', '虹口', '杨浦', '普陀', '闵行', '宝山', '青浦', '松江', '嘉定', '奉贤', '金山', '崇明', '闸北', '卢湾', '上海']

在这里插入图片描述
(4)上海各行政区内房屋面积对房价的影响(线性拟合)

from scipy import optimize
# 直线函数方程
def linearfitting(x, A, B):
    return A * x + B
def plot_line():
    plt.figure(figsize=(10, 8), dpi=256)
    colors = ['red', 'red', 'red', 'red',
              'blue', 'blue', 'blue', 'blue',
              'green', 'green', 'green', 'green',
              'gray', 'gray', 'gray', 'gray',
              'yellow', 'yellow', 'yellow', 'yellow']
    region = ['黄浦', '徐汇', '静安', '长宁',
              '浦东', '虹口', '杨浦', '普陀',
              '闵行', '宝山', '青浦', '松江',
              '嘉定', '奉贤', '金山', '崇明',
              '闸北', '卢湾', '上海']
    markers = ['o', 's', 'v', 'x',
               'o', 's', 'v', 'x',
               'o', 's', 'v', 'x',
               'o', 's', 'v', 'x',
               'o', 's', 'v']
    for i in range(19):
        x = df.loc[df['region'] == region[i]]['area']
        y = df.loc[df['region'] == region[i]]['price']
        A, B = optimize.curve_fit(linearfitting, x, y)[0]
        xx = np.arange(0, 2000, 100)
        yy = A * xx + B
        plt.plot(xx, yy, c=colors[i], marker=markers[i], label=region[i], linewidth=2)
    plt.legend(loc=1, bbox_to_anchor=(1.138, 1.0), fontsize=12)
    plt.xlim(0, 800)
    plt.ylim(0, 100000)
    plt.title('上海各行政区内房屋面积对房价的影响(线性拟合)', fontsize=20)
    plt.xlabel('房屋面积(平方米)', fontsize=16)
    plt.ylabel('房屋价格', fontsize=16)
    plt.show()
plot_line()

在这里插入图片描述
(5)各区房屋面积大小

df.area = df.area.astype(np.float)
fig = plt.figure(figsize=(12, 7))
ax = fig.add_subplot(111)
df.groupby('region').area.mean().plot.bar()
plt.title("各区房屋面积大小")

在这里插入图片描述
(6)房屋整体结构占比情况

fig = plt.figure(figsize=(8, 8))
ax = fig.add_subplot(111)
df['rooms'].value_counts()[:6].plot.pie(cmap=plt.cm.rainbow)
plt.title('房屋整体结构占比情况')

在这里插入图片描述
(7)房源朝向分布情况

fig = plt.figure(figsize=(12,7))
ax = fig.add_subplot(111)
df.direction.value_counts()[:10].plot.bar()
plt.title('房源朝向分布情况')

在这里插入图片描述
(8)热力图

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
    <script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=4imlHLWLDIka6Av3zIY4bdyLQ7X3rvcH"></script>
    <script type="text/javascript" src="http://api.map.baidu.com/library/Heatmap/2.0/src/Heatmap_min.js"></script>
    <title>热力图功能示例</title>
    <style type="text/css">
		ul,li{list-style: none;margin:0;padding:0;float:left;}
		html{height:100%}
		body{height:100%;margin:0px;padding:0px;font-family:"微软雅黑";}
		#container{height:500px;width:100%;}
		#r-result{width:100%;}
    </style>	
</head>
<body>
	<div id="container"></div>
	<div id="r-result">
		<input type="button"  onclick="openHeatmap();" value="显示热力图"/><input type="button"  onclick="closeHeatmap();" value="关闭热力图"/>
	</div>
</body>
</html>
<script type="text/javascript">
    var map = new BMap.Map("container");          // 创建地图实例

    var point = new BMap.Point(121.480248, 31.236276);
    map.centerAndZoom(point, 15);             // 初始化地图,设置中心点坐标和地图级别
    map.enableScrollWheelZoom(); // 允许滚轮缩放
  
    var points =[
{"lat":31.32940203,"lng":121.4524398,"count":3000},
{"lat":30.74623653,"lng":120.7754725,"count":11500},
{"lat":30.73972623,"lng":120.8048763,"count":50000},
    ];
   
    if(!isSupportCanvas()){
    	alert('热力图目前只支持有canvas支持的浏览器,您所使用的浏览器不能使用热力图功能~')
    }
	//详细的参数,可以查看heatmap.js的文档 https://github.com/pa7/heatmap.js/blob/master/README.md
	//参数说明如下:
	/* visible 热力图是否显示,默认为true
     * opacity 热力的透明度,1-100
     * radius 势力图的每个点的半径大小   
     * gradient  {JSON} 热力图的渐变区间 . gradient如下所示
     *	{
			.2:'rgb(0, 255, 255)',
			.5:'rgb(0, 110, 255)',
			.8:'rgb(100, 0, 255)'
		}
		其中 key 表示插值的位置, 0~1. 
		    value 为颜色值. 
     */
	heatmapOverlay = new BMapLib.HeatmapOverlay({"radius":20});
	map.addOverlay(heatmapOverlay);
	heatmapOverlay.setDataSet({data:points,max:100});
	//是否显示热力图
    function openHeatmap(){
        heatmapOverlay.show();
    }
	function closeHeatmap(){
        heatmapOverlay.hide();
    }
	closeHeatmap();
    function setGradient(){
     	/*格式如下所示:
		{
	  		0:'rgb(102, 255, 0)',
	 	 	.5:'rgb(255, 170, 0)',
		  	1:'rgb(255, 0, 0)'
		}*/
     	var gradient = {};
     	var colors = document.querySelectorAll("input[type='color']");
     	colors = [].slice.call(colors,0);
     	colors.forEach(function(ele){
			gradient[ele.getAttribute("data-key")] = ele.value; 
     	});
        heatmapOverlay.setOptions({"gradient":gradient});
    }
	//判断浏览区是否支持canvas
    function isSupportCanvas(){
        var elem = document.createElement('canvas');
        return !!(elem.getContext && elem.getContext('2d'));
    }
</script>

var points =[
{“lat”:31.32940203,“lng”:121.4524398,“count”:3000},
{“lat”:30.74623653,“lng”:120.7754725,“count”:11500},
{“lat”:30.73972623,“lng”:120.8048763,“count”:50000},
];
var points 中导入所有数据1.5W条,其中lat,lng是地址转换的经纬度,count权重取价格。

百度热力图

在这里插入图片描述

  • 6
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值