老猫我在南五环租了一个80平两居室,租房合同马上到期,房东打电话问续租的事,想要加房租;我想现在国家正在在抑制房价,房子价格没怎么涨,房租应该也不会涨,于是霸气拒绝了,以下是聊天记录:
房东要涨800房租,我用Python抓取帝都几万套房源信息,主动涨了1000。
确认不续租之后,两三年没找过房的我上网搜索租房,没想到出来一坨自如,蛋壳,贝壳等中介网站;进去看看,各种房照非常漂亮,但是一看房租,想送给自己一首《凉凉》;附近房租居然比我当前房租高1000多RMB;自我安慰下,这些网站房源价格不是真实的,于是切换到我爱我家,链家等大中介平台,结果发现房租价格都差不多;心想这才几年,如果两年前对自己狠点买了房,房租都够还房贷了。为了生存,不甘心的我,决定把这些大网站的房源信息抓取下来进行分析,帝都这么大总有一套适合自己。

说干就干,先找到市场上主要的中介平台,我主要关注我爱我家、链家、贝壳,还有其他类的长租公寓例如自如,蛋壳等;经过查资料还发现一个问题,好像链家、贝壳、自如是一个大老板,链家房源和自如的都是混到一起的,整的我一个头大;而贝壳类似于京东,不仅自己自营,还允许其他中介商来入住这个平台,这老板脑瓜子真是好使,折腾半天把数据都掌握在自己手里,总感觉这些年房价与房租上涨和这个货有种说不清的关系。

最后决定使用Python抓取链家,我爱我家,与贝壳网租房信息。

老猫租住小区门口就有一链家,每天西装革履的经纪人在小区里晃悠;先从链家下手,抓取与分析过程如下:

1:查看链家房源信息,决定抓取数据;
2:数据请求行为分析;
3:分析实现这个功能所使用知识点,
4:抓取代码实现与调试;
5:对出租房源信息进行分析;

1.使用浏览器查看要抓取信息:

借助浏览器打开链家主页,找到租房菜单,查看要抓取信息;
地址:https://bj.lianjia.com/zufang/ ;
页面信息如下图:
房东要涨800房租,我用Python抓取帝都几万套房源信息,主动涨了1000。
从当前信息中可以看到,链家有近19000套在线房源(某些为单间,一些为北三县);
根据页面信息确认抓取信息,主要包括:所属小区,户型,面积,价格,所属区域,具体地址,房龄,房屋编号及详情页地址;确认抓取信息后,分析请求行为,进行抓取。

2.请求行为分析:

使用浏览器打开页面,查看请求过程,主要分几步进行抓?。?/p>

1> 请求首页:https://bj.lianjia.com/zufang/ ;
2>解析北京下属区域对应url,注意:这里不包括燕郊与香河;
3>按照行区域逐页抓取并提取页面房源信息,直到抓取结束;

整个请求过程如下:
房东要涨800房租,我用Python抓取帝都几万套房源信息,主动涨了1000。
确认抓取过程后,分析抓取过程使用的知识点。

3.知识点分析:

整个抓取过程分为请求数据,提取信息,数据保存,老猫这边使用Python实现,主要知识点如下:

1>请求数据:requests???;
2>提取信息:BeautifulSoup???;
3>数据保存:csv格式存储,csv???;

请求行为与知识点分析完成后,老猫在Jupyter下完成了单页数据抓取与信息提取,具体过程这里不详细讲解,直接上最终代码。

4.链家出租房源信息抓取实现:

老猫使用类实现抓取与信息提取,快速描述实现过程:

1>整理思路,确认要做的事情,分步实现;
2>根据请求与提取行为定义类与相关方法;
3>将Jupyter下实现填充到对应方法中;
4>存储类实现;
5>代码调试与运行;

最终代码如下:

#coding=utf-8
#Author: qimao

import requests
from bs4 import BeautifulSoup
import json
import csv
import time

class CsvSaveModule(object):
    #存储csv类
    def __init__(self, fname = "info.csv"):
        self.f = open(fname, 'w', encoding='utf-8')
        self.fcsv = csv.writer(self.f)
        self.head = True

    def write(self, keys, data):
        #写入字段
        if self.head:
            self.head = False
            self.fcsv.writerow(keys)
        #获取并写入数据
        values = [data.get(key, "") for key in keys]
        self.fcsv.writerow(values)

    def close(self):
        self.f.close()

class SpiderMoujia(object):
    def __init__(self, *args):
        'args:保存数据对象'
        self.urlhead = 'https://bj.lianjia.com/'
        self.hds = args

    def reqPage(self, url):
        num = 0
        #一个页面最多尝试请求三次
        while num < 3:
            #请求页面信息,添加请求异常处理,防止某个页面请求失败导致整个抓取结束,
            try:
                if url:
                    req = requests.get(url)
                    if req.status_code == 200:
                        #返回BeautifulSoup对象
                        return BeautifulSoup(req.text, 'html5lib')
            except:
                pass
            time.sleep(3)
            num += 1

    def startCrawler(self, url):
        #抓取对外接口,Url为种子url
        self.startRequest(url)
        self.close()

    def startRequest(self, url):
        #开始抓取
        obj = self.reqPage(url)
        areas = obj.find('div', class_="option-list")
        #提取区域信息:海淀,朝阳,....
        listarea = areas.select('a[href^="/zufang/"]')
        for area in listarea[1:]:
            aurl = self.urlhead + area.get('href')
            district = area.text.strip()
            #按照区域进行抓取
            self.crawlerArea(aurl,district)

    def crawlerArea(self, url, district):
        #print('crawlerArea', url)
        page = self.reqPage(url)
        if not page:
            return
        #解析页面信息
        self.parsePage(page,district)
        #获取下一页地址
        nextpage = self.getNextPage(page)
        if nextpage:
            urlhead = url.rsplit('/',1)[0]
            nexturl = '/'.join([urlhead, nextpage])
            #继续请求下一页
            self.crawlerArea(nexturl,district)

    def extractInfo(self, node, css):
        snode = node.select_one(css)
        val = ''
        if snode and snode.text:
            val = snode.text.strip()
        return val

    def parsePage(self, page,district):
        #主要功能,提取页面信息
        listhouse = page.select('#house-lst > li')
        #定义出提取字段
        keys = ['信息', '小区', '户型', '价格', '面积', '区域',
                '位置', '楼层', '年份', '附加信息', '房屋ID', '详情页地址']
        for house in listhouse:
            hinfo = []
            #提取title, 小区,户型,价格,面积
            cssexpress = ['h2 a', '.where a', '.zone', '.num','.meters']
            info = house.select_one(".info-panel")
            if not info:
                continue
            for express in cssexpress:
                hinfo.append(self.extractInfo(info, express))
            #添加区域
            hinfo.append(district)
            #区域,楼层,年代
            hinfo.extend(info.select_one('.con').text.split('/'))
            #房子其他信息,例如:地铁,阳台,供暖等
            spans = info.select_one('.view-label')
            hpoint = ','.join((spans.stripped_strings))
            hinfo.append(hpoint)
            #房子ID号,唯一标识
            hinfo.append(house.get('data-id'))
            #房租详情页url
            hinfo.append(info.select_one('h2 a').get('href'))

            #转换成字典
            housedata = dict(zip(keys, hinfo))
            print (housedata)
            self.saveData(housedata)

    def getNextPage(self, page):
        try:
            #获取下一页
            pagenode = page.select_one('.page-box')
            pageinfo = pagenode.get('page-data')
            jdata = json.loads(pageinfo)
            curpage = jdata.get('curPage')
            total = jdata.get('totalPage')
            if (curpage < total):
                return 'pg%d'%(int(curpage+1))
        except:
            print('error')
            pass

    def saveData(self, data):
        #按照顺序保存数据
        keys = ['信息', '小区', '户型', '价格', '面积', '区域',
                '位置', '楼层', '年份', '附加信息', '房屋ID', '详情页地址']
        [hd.write(keys, data) for hd in self.hds]

    def close(self):
        #列表解析关闭存储???        [hd.close() for hd in self.hds]

if __name__ == '__main__':
    #开始抓取
    url = 'https://bj.lianjia.com/zufang/'
    csvhd = CsvSaveModule('ljinfo.csv')
    spider = SpiderMoujia(csvhd)
    spider.startCrawler(url)

运行过程如下图:
房东要涨800房租,我用Python抓取帝都几万套房源信息,主动涨了1000。
最终获取了1W多套出租房信息,到这里我完成链家出租房源信息抓取。

5.我爱我家与贝壳抓取分析。

我爱我家是一个老牌的房地产中介,老猫来帝都10年了,毕业当年大部分同学干了当年认为比较正统的工作,一个另类同学去了我爱我家做中介,早些年他经常群里发消息,给我们介绍房源,大家认为他是推销,很少有人理他;最近同学聚会见面我问他:你当年为什么不在使劲逼着我买房?同学抬起头,吸了一口烟吐了一圈,说了一句话,让我感觉夏天顿时凉爽了许多:'逼你你也买不起,逼你你也买不起,逼你你也买不起,重要事情说三遍',感觉友谊的小船就要翻了。
下面我们来看我爱我家出租房源页面信息,如下图:
房东要涨800房租,我用Python抓取帝都几万套房源信息,主动涨了1000。
从页面中我们可以看到,我爱我家有3.2万套出租房源,房源信息比较直观;通过分析发现抓取我爱我家请求行为与抓取链家类似,不同之处在于提取数据与提取过程;我们可以在基于链家爬虫进行修改实现;具体实现过程在这里不详细讲解,直接上抓取部分结果:

区域,地址,小区,价格,面积,楼层,装修,地铁附近,户型,朝向,信息,附加信息,详情
朝阳,望京,宝星国际,8000,64  平米,低楼层/25层,精装,距离地铁望京东450米,1  室  1  厅,南,宝星国际紧邻望京SOHO 阿里巴巴  精装朝南大开间,"近地铁,随时看,拎包入住,押金减免",/zufang/41330436.html
朝阳,望京,炫彩嘉轩,6500,60  平米,中楼层/21层,精装,距离地铁望京东760米,1  室  0  厅,南,炫彩一居室老业主委托 我有钥匙 邻望京SOHO,"近地铁,可短租,拎包入住,集中供暖",/zufang/41348206.html
朝阳,望京,宝星国际,8500,64  平米,高楼层/25层,精装,距离地铁望京东450米,1  室  1  厅,南北,望京SOHO旁宝星国际,邻阿里巴巴,绿地,南向大开间,"近地铁,随时看,拎包入住,押金减免",/zufang/41331126.html
朝阳,CBD,阳光100国际公寓,9500,48  平米,低楼层/33层,精装,距离地铁大望路391米,1  室  1  厅,北,阳光100国际公寓精装开 二次装修有钥匙看房方便,"近地铁,随时看,免佣,拎包入住",/zufang/41330729.html
朝阳,望京,朝庭公寓,13000,112  平米,高楼层/27层,精装,距离地铁望京南476米,2  室  1  厅,南北,望京南朝庭公寓,邻悠乐汇和方恒国际,看房有钥匙,随时看房,"近地铁,随时看,免佣,拎包入住",/zufang/41337320.html
朝阳,石佛营,日月东华,6700,66.3  平米,顶层/21层,精装,距离地铁十里堡1072米,1  室  1  厅,南,石佛营新上婚房一居拎包入住 看房预约,"近地铁,拎包入住,集中供暖",/zufang/41348814.html
朝阳,惠新西街,胜古北里,2800,58  平米,底层/5层,精装,距离地铁惠新西街南口232米,3  室  1  厅,北,随时看 随时住三家合住正规中卧室精装修,"近地铁,随时看,拎包入住,集中供暖",/zufang/41338276.html
朝阳,潘家园,弘善家园,5300,61  平米,中楼层/13层,精装,距离地铁十里河691米,1  室  1  厅,南,十里河 潘家园10号线 弘善家园精装正规一居 拎包入住,"近地铁,拎包入住",/zufang/41334177.html
朝阳,东坝,朝阳新城一区,6500,114  平米,中楼层/6层,精装,,2  室  1  厅,南北,朝阳新城一区两室一厅,随时看,/zufang/41351101.html
朝阳,十里堡,十里堡,6000,60  平米,底层/4层,中装,距离地铁十里堡781米,2  室  1  厅,南北,6号线 十里堡 南北两居带小院子 居家必备,"近地铁,随时看,集中供暖",/zufang/41345370.html
朝阳,十里堡,十里堡北里,5000,50  平米,中楼层/13层,中装,距离地铁十里堡371米,1  室  1  厅,南北,十里堡 八里庄北里 晨光家园  随时看,"近地铁,免佣",/zufang/41342158.html
朝阳,南沙滩,南沙滩小区,7000,65  平米,中楼层/6层,精装,距离地铁北沙滩775米,2  室  1  厅,南北,南沙滩 中间层两居室 临地铁北辰中科院 有钥匙,"近地铁,随时看,拎包入住",/zufang/41341943.html
朝阳,管庄,管庄周井大院,6000,72  平米,中楼层/5层,精装,距离地铁双桥604米,3  室  1  厅,南北,管庄周井大院精装三居室出租 随时入住,"近地铁,随时看,拎包入住,集中供暖",/zufang/41339579.html
朝阳,百子湾,金泰先锋,10000,92  平米,底层/11层,精装,距离地铁百子湾437米,2  室  1  厅,南北,金泰先锋 南北两室一厅一厨一卫,"近地铁,拎包入住,集中供暖",/zufang/41328442.html
朝阳,北苑,蕴实园,5800,66.37  平米,中楼层/14层,精装,距离地铁北苑571米,1  室  1  厅,南,钥匙房源,看房随时。,"近地铁,随时看,拎包入住",/zufang/41349514.html
朝阳,芍药居,芍药居,13000,118.38  平米,中楼层/14层,简装,距离地铁芍药居116米,3  室  1  厅,南,芍药居南里精装修三室一厅,"近地铁,随时看,拎包入住,集中供暖",/zufang/41348855.html
朝阳,双桥,东一时区,4500,65  平米,中楼层/26层,精装,距离地铁管庄1214米,1  室  1  厅,西北,管庄 东一时区 精装一居室 家电齐全 随时入住看房,"近地铁,拎包入住,集中供暖",/zufang/41346286.html
...

最后我们来看贝壳抓取,贝壳是近些年起来的中介平台,有点类京东,不仅有链家,自如房源,还有其他家;除了贝壳,还有其他很多短租长租平台,例如小猪短租,蛋壳等;
有时候我常思考一个问题:

负能量:这些网站都是IT开发人员搞得,我们都是一个行业,为什么要自己坑自己。如果没有这么多中介平台去抢房源,炒房价,价格可能落下来;
正能量:中介商根据市场需求搭建中介平台,使广大租客可以从正规渠道租房,远离黑中介,给我们省去很多租房过程中遇到难题与烦恼,我们应该感谢这些中介商。

下面我们看下贝壳网页面信息:
房东要涨800房租,我用Python抓取帝都几万套房源信息,主动涨了1000。
贝壳房源信息中除了基本信息,还包括房源属于哪个中介商,例如:自如,链家等;贝壳抓取过程和链家也是类似的,具体实现在这里不做讲解,直接看我们抓取的数据:

中介商,价格,面积,信息,区域,位置,户型,楼层,附加信息,详情页地址
链家,14000,84㎡,整租 · 海运仓小区 3室1厅 14000元,东城,东直门,3室1厅1卫,低楼层(7层),"近地铁,精装,集中供暖",/zufang/BJ2058598225715675136.html
链家,9200,89㎡,整租 · 安定门 安德路 精装修大两居 可拎包入住,东城,安定门,2室1厅1卫,高楼层(19层),"近地铁,精装",/zufang/BJ2047499309347512320.html
链家,6000,61㎡,整租 · 安化南里 1室1厅 6000元,东城,广渠门,1室1厅1卫,低楼层(16层),"近地铁,新上,随时看房",/zufang/BJ2062732570394894336.html
链家,3500,14㎡,整租 · 南剪子巷 1室1厅 3500元,东城,东四,1室1厅0卫,低楼层(1层),"近地铁,新上,随时看房",/zufang/BJ2064894449783685120.html
链家,25000,158㎡,整租 · 东城逸墅南北通透大三居 婚房出租 可看房 家具齐全,东城,工体,3室1厅2卫,中楼层(7层),"近地铁,精装,集中供暖,双卫生间,随时看房",/zufang/BJ2034641117466591232.html
城家精选公寓,11300,43㎡,整租 · 元嘉国际公寓 1室0厅,东城,东直门,1室0厅1卫,未知(10层),"公寓,租房节,七天换租,近地铁,精装,集中供暖,免中介费,押一付一,新上",/zufang/BJ2066558104174067712.html
链家,28000,207㎡,盛德大厦 4室2厅 28000元,东城,和平里,4室2厅2卫,中楼层(20层),"近地铁,集中供暖,随时看房",/zufang/BJ2049715273455910912.html
龙源易家,3500,26㎡,合租 · 海运仓小区 3室1厅,东城,东直门,3室1厅1卫,未知(6层),"限女生,限男生,独立卫生间,租房节,近地铁,精装,独立阳台,集中供暖,免中介费,新上",/zufang/BJ2066335711790891008.html
...

房源信息抓取完成后保存到csv文件中,然后对数据进行分析,找到适合自己的那一套。

5.房源价格分析:

老猫算了下,三个平台数据加起来差不多7W套左右(链家数据和贝壳有重复,同一个房源可同时挂在不同中介商下),所有应该到不了7W套。
老猫使用Python中的matplotlib, pandas, seaborn, pyecharts对数据进行分析。

5.1 链家各区房源数量,最高价,最低价,均值统计:
#pandas导入文件
pathlj = ljinfo.csv'
ljdata = pd.read_csv(pathlj)
#按区域分组
r = ljdata.groupby('区域')
#根据价格统计最大、最小、均值、数量
r.价格.agg(['max', 'min', 'mean', 'count'])

结果如下:
房东要涨800房租,我用Python抓取帝都几万套房源信息,主动涨了1000。

使用柱状图描述各区房源数量与均价:
房东要涨800房租,我用Python抓取帝都几万套房源信息,主动涨了1000。
房东要涨800房租,我用Python抓取帝都几万套房源信息,主动涨了1000。

在来分析下链家,贝壳与我爱我家各区域房源统计结果:
房东要涨800房租,我用Python抓取帝都几万套房源信息,主动涨了1000。
通过统计结果可以看到:我爱我家房源比链家,贝壳要多,假设房源都是真是的,如果要在朝阳,海淀租房可以选择我爱我家。

5.2 分析北京各辖区出租房源的价格分布:

当前数据中,出租房源参差不齐,面积价格都不一样,不能统一统计,通过箱状图看房租价格统计,具体实现如下:

#文件路径
pathlj = 'ljinfo.csv'
#pandas导入
ljdata = pd.read_csv(pathlj)
#过滤掉房租价格大于1W的,老猫认为这辈子也不会租这样的房
data = ljdata[ljdata.价格 < 10000]
plt.figure(figsize=(10,4))
#箱状图查看房租价格整体趋势
sns.boxplot(x='区域',y ='价格', data=data)

输出结果:
房东要涨800房租,我用Python抓取帝都几万套房源信息,主动涨了1000。

简单介绍下箱状图:箱状图使用四分位数表述数据分布情况,中间矩形表示25%到75%之间数据分布,超过上限和下限认为是异常值;
再来看我爱我家与贝壳统计结果,如下图:
房东要涨800房租,我用Python抓取帝都几万套房源信息,主动涨了1000。房东要涨800房租,我用Python抓取帝都几万套房源信息,主动涨了1000。

从图中可以看到帝都核心区域房租明显比郊区高很多,东城最低房源价格都得两千多,亦庄虽然在五环外,但是因为是开发区,有很多大公司入住,所有房价较高;房源选择上,我爱我家价格感觉比其他两个平台要高。

现在我对北京租房价格有了全新认识,我主要想找70到85平左右小两居,需要找到各区这个面积范围内房源及其对应价格,我对链家数据进行下面操作:

import re
#数据整理,将字符串转成数字
def func(size):
    s = re.search(r'\d+', size)
    if s:
        return int(s.group())
    return pd.np
#pandas导入数据
pathlj = 'jinfo.csv'
ljdata = pd.read_csv(pathlj)
data = ljdata.面积.to_frame()
#整理面积数据
r = data.applymap(func)
#添加新的列
ljdata['hsize'] = r.面积
#清洗无面积房源
rdata = ljdata.dropna(subset=['hsize'])
#整理70~85平米之间房源
rdata = rdata[(rdata.hsize>70) & (rdata.hsize < 85)]
#过滤掉价格大于1W的房源
rdata = rdata[rdata.价格<10000]
#统计最小值,最大值,均值
rdata.价格.agg(['min', 'max', 'mean'])

输出结果如下:

min     2500.0
max     9890.0
mean    5997.4

经过分析,老猫感觉3000左右可以租到想要的房子,然后统计各个辖区内房源面积在70~85平之间的价格分布,结果如下:
房东要涨800房租,我用Python抓取帝都几万套房源信息,主动涨了1000。
核心区域价格确实高,老猫想找大兴和亦庄开发区附近房子,价格好像超出老猫预算,我租住房屋面积75,房租3100,房东加800也才不到4000,根据分析结果看,找4000左右的两居室感觉也没那么容易;再找一下大兴和亦庄开发区的房子,具体实现如下:

tdata = rdata[rdata.区域.isin(['大兴','亦庄开发区'])]
#大兴与亦庄房子数据
print(len(tdata))
#房源均价,最低,最高值
print(tdata.价格.agg(['mean', 'min', 'max']))
#房源价格小于4000
print(len(tdata[tdata.价格<4000]))

输出结果如下:

109
mean    4907.889908
min     3100.000000
max     9000.000000
21

找到21套价格小于4000房源,看下房源信息,多是老旧楼,或者高楼层没电梯,好容易有合适的,打电话咨询,已经租出去了;链家都已经这样了,估计我爱我家,贝壳也类似。
思前想后,感觉还是现在房东最好,赶紧给她联系续租,以下聊天记录。
房东要涨800房租,我用Python抓取帝都几万套房源信息,主动涨了1000。
这波涨租潮,对老猫来说就这样过去了,看着满屏幕房源信息,我不知道明年房租是否还会疯长;现在老猫的租房竞争对手不仅是众多北漂,还有实力更为雄厚的自如、蛋壳等各种中介商,为了生存,今年必须要更加努力了。


最后打个小广告,有对爬虫和数据分析感兴趣同学可以购买奇猫的专栏《Python爬虫与数据分析》

另外,如果感觉还行可以关注奇猫的视频课程或者微职位。