美文网首页
Scrapy 抓取链家租房(深圳)信息&高德地图Map Lab

Scrapy 抓取链家租房(深圳)信息&高德地图Map Lab

作者: 马本不想再等了 | 来源:发表于2019-03-15 15:19 被阅读0次

    一、项目介绍

    项目目标

    1.获取链家网上的深圳市租房数据
    2.将获取的数据可视化
    文章略长,为节约部分读者时间,提前展示可视化效果



    工具

    python3.6、pycharm2018.1、高德地图Map Lab

    技术

    数据抓取:Scarpy
    数据展示:高德地图API(Map Lab)

    整体思路

    1. 分析链家租房模块url(地区、翻页变化),找出请求url的规则
    2. 分析租房条目的类别(大致分为两类,青年公寓和普通租房)
    3. 分析房间详情页html(此处一般要注意是否是ajax加载)
    4. 编写项目进行数据抓取(注意存储数据的形式,方便对接高德地图)
    5. 使用高德地图开发者模式,导入数据,选择合适的图表类型,展示数据

    二、项目搭建:

    打开cmd,进入project目录(我自己的项目目录),执行scrpay startproject LianJia,创建scrapy项目;
    执行cd LianJia进入项目;
    执行scrapy genspider LJ lianjia.com,创建通用爬虫

    三、基本设置

    settings设置
    这里的UA使用fake_useragent库中的UserAgent,fake_useragent是一个在git上开源的项目,维护了几百个目前比较常用的UA,导入后直接调用random就可以随机生成UA,使用方便,推荐。代码如下:

    from fake_useragent import UserAgent
    
    # 设置延迟为0.2
    DOWNLOAD_DELAY = 0.2
    # 关闭robots协议
    ROBOTSTXT_OBEY = False
    # headers设置
    ua = UserAgent()
    DEFAULT_REQUEST_HEADERS = {
      'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
      'Accept-Language': 'en',
      'User-Agent': 'ua.random'
    }
    

    启动文件 - 同样创建一个start.py来负责开启爬虫

    from scrapy import cmdline
    
    # 这里使用 -o 文件名.csv -s FEED_EXPORT_ENCODING=UTF-8 将数据直接保存为csv文件,简单方便。
    cmdline.execute("scrapy crawl LJ -o sz-lianjia.csv -s FEED_EXPORT_ENCODING=UTF-8".split())
    # cmdline.execute("scrapy crawl LJ".split())
    

    四、页面分析

    4.1 链家的租房页面可以查看100页,每页30条数据

    但是仔细观察可以发现其中很多条目是相同,这样也不难发现在深圳链家的线上房源,其实并没有页面上写的21447套 21447套房间?

    在租房列表页面,可以看到两种不同的房屋类型


    两张中不同的租房类型
    对应的详情页面也不同,对于这两种不用页面要分类爬取
    青年公寓型
    正常整租型
    链家的反爬其实一般,只要使用随机请求头基本都可以很顺畅的爬下来

    4.2注意:在详情页面中很多信息比较繁杂,爬取时要细心分析

    比如基本信息中会有12项可视数据,但是源码中有17个li,可以使用循环来剔除掉无用的li


    基本信息
    基本信息

    经纬度信息(高德地图需要用到)放在一个script标签中,这里推荐使用正则进行提取


    经纬度

    五、代码展示

    5.1 spider

    这里没什么好说的都是一些基本套路,当然也有一些地方经过多次调试才拿到数据
    推荐大家咋终端使用scarpy shell 来进行测试提取结果
    有问题的读者,可以在评论区留言,有问必答哦

    # -*- coding: utf-8 -*-
    import scrapy
    from urllib import parse
    from LianJia.items import LjApartmentItem, LjZufangItem
    import re
    
    
    class LjSpiderSpider(scrapy.Spider):
        name = 'LJ'
        allowed_domains = ['lianjia.com']
        page = 1
        start_urls = ['https://sz.lianjia.com/zufang/pg1/']
    
        def parse(self, response):
            """
            获取每一个租房详情页的链接
            :param response:
            :return:
            """
            links = response.xpath("//div[@class='content__list']/div/a/@href").extract()
            for link in links:
                # 补全详情页链接
                url = parse.urljoin(response.url, link)
                if url.find('apartment') != -1:
                    yield scrapy.Request(url=url, callback=self.apartment_parse)
                else:
                    yield scrapy.Request(url, callback=self.zufang_parse)
            # 翻页
            self.page += 1
            page_urls = 'https://sz.lianjia.com/zufang/pg{}/'.format(self.page)
            # 爬取100页数据
            if self.page < 101:
                yield scrapy.Request(url=page_urls, callback=self.parse)
            else:
                print('爬取结束')
    
        def apartment_parse(self, response):
            """
            爬取公寓房间信息
            :param response:
            :return:
            """
            title = response.xpath("//p[contains(@class,'flat__info--title')]/text()").extract()[0].strip('\n').strip()
            price = int("".join(response.xpath("//p[@class='content__aside--title']/span[last()]/text()").extract()).strip())
            # 将response.text中的特殊符号去掉,方便正则匹配
            text = re.sub(r"[{}\s':,;]", "", response.text)
            address = re.match(r".*g_conf.name=(.*)g_conf.houseCode.*", text).group(1)
            longitude = re.match(r".*longitude?(.*)latitude.*", text).group(1)
            latitude = re.match(r".*latitude?(.*)g_conf.name.*", text).group(1)
            # 将经纬度格式化,为之后数据可视化做准备
            location = longitude + "," +latitude
            room_url = response.url
            apartment_desc = response.xpath("//p[@data-el='descInfo']/@data-desc").extract()[0]
            introduction = apartment_desc.replace(r"<br />", "").replace("\n", "")
            li_list = response.xpath("//ul[@data-el='layoutList']/li")
            room_number = len(li_list)
            room = []
            for li in li_list:
                rooms = {}
                _type = li.xpath(".//p[@class='flat__layout--title']/text()").extract()[0]
                room_type = _type.replace("\n", "").strip(" ")
                room_img = li.xpath(".//img/@data-src").extract()[0]
                li_price = li.xpath(".//p[@class='flat__layout--title']/span/text()").extract()[0]
                room_price = li_price.replace("\n", "").strip(" ")
                area = li.xpath(".//p[@class='flat__layout--subtitle']/text()").extract()[0]
                room_area_str = area.replace("\n", "").replace(" ", "")
                room_area = re.match(r".*?(\d+).*", room_area_str)
                if room_area is None:
                    room_area = "未知"
                    room_price = "已满房"
                else:
                    room_area = room_area.group(1)
                room_left = li.xpath(".//p[@class='flat__layout--subtitle']/span/text()").extract()[0]
                rooms['图片'] = room_img
                rooms['类型'] = room_type
                rooms['价格'] = room_price
                rooms['面积'] = room_area
                rooms['余房'] = room_left
                room.append(rooms)
    
            item = LjApartmentItem()
            item['title'] = title
            item['price'] = price
            item['address'] = address
            item['location'] = location
            item['introduction'] = introduction
            item['room_number'] = room_number
            item['room_infos'] = room
            item['room_url'] = room_url
            yield item
    
        def zufang_parse(self, response):
            """
            爬取业主出租房间信息
            :param response:
            :return:
            """
            title = response.xpath("//p[@class='content__title']/text()").extract()[0]
            price = int(response.xpath("//p[@class='content__aside--title']/span/text()").extract()[0])/3
            publish_time = "".join(response.xpath("//div[@class='content__subtitle']/text()").extract()).strip().split(" ")[-1]
            # 将response.text中的特殊符号去掉,方便正则匹配
            text = re.sub(r"[{}\s':,;]", "", response.text)
            address = re.match(r".*g_conf.name=(.*)g_conf.houseCode.*", text).group(1)
            longitude = re.match(r".*longitude?(.*)latitude.*", text).group(1)
            latitude = re.match(r".*latitude?(.*)g_conf.subway.*", text).group(1)
            # 将经纬度格式化,为之后数据可视化做准备
            location = longitude + "," + latitude
            room_url = response.url
            room_img = "".join(response.xpath("//div[@class='content__article__slide__item']/img/@data-src").extract())
            # conditions中有4项内容(租赁方式、布局、面积、朝向)
            conditions = response.xpath("//p[@class='content__article__table']/span/text()").extract()
            room_layout = conditions[1]
            room_area = conditions[2]
            room_orientation = conditions[3]
            room_infos = response.xpath("//div[@class='content__article__info']/ul/li/text()").extract()
            for index, li in enumerate(room_infos):
                if li.find("\xa0") != -1:
                    del room_infos[index]
            surrounding = "".join(response.xpath("//p[@data-el='houseComment']/@data-desc").extract())
            surrounding_desc = surrounding.replace("<br />", "").replace("\n", "")
            item = LjZufangItem()
            item['title'] = title
            item['price'] = price
            item['publish_time'] = publish_time
            item['address'] = address
            item['location'] = location
            item['room_img'] = room_img
            item['room_layout'] = room_layout
            item['room_area'] = room_area
            item['room_orientation'] = room_orientation
            item['room_infos'] = room_infos
            item['surrounding_desc'] = surrounding_desc
            item['room_url'] = room_url
            yield item
    

    5.2 item

    在写item时一开始,按照自己的想法来,想提取什么写什么(当然前提是有些东西能你可以提取得到..),在写爬虫时,可以进行适当调整(对部分item进行取舍)

    # -*- coding: utf-8 -*-
    import scrapy
    
    
    class LjApartmentItem(scrapy.Item):
        # 公寓名称
        title = scrapy.Field()
        # 公寓最低单间价
        price = scrapy.Field()
        # 公寓地址
        address = scrapy.Field()
        # 公寓坐标(绘制地图备用)
        location = scrapy.Field()
        # 公寓介绍
        introduction = scrapy.Field()
        # 单间个数
        room_number = scrapy.Field()
        # 单间信息
        room_infos = scrapy.Field()
        # 房间链接
        room_url = scrapy.Field()
    
    
    class LjZufangItem(scrapy.Item):
        # 房间名称
        title = scrapy.Field()
        # 房间价格
        price = scrapy.Field()
        # 发布日期
        publish_time = scrapy.Field()
        # 房间地址
        address = scrapy.Field()
        # 房间坐标(绘制地图备用)
        location = scrapy.Field()
        # 房间图片
        room_img = scrapy.Field()
        # 房间布局
        room_layout = scrapy.Field()
        # 房间面积
        room_area = scrapy.Field()
        # 房间朝向
        room_orientation = scrapy.Field()
        # 房间基本信息
        room_infos = scrapy.Field()
        # 周围环境描述
        surrounding_desc = scrapy.Field()
        # 房间链接
        room_url = scrapy.Field()
    

    六、爬取结果

    两种不同的房间信息我们都拿到了

    zufang(建议查看原图)
    apartment(建议查看原图)
    前面提到过
    我们使用scrapy crawl LJ -o sz-lianjia.csv -s FEED_EXPORT_ENCODING=UTF-8命令,直接将文件保存为csv文件。爬取完成后,项目录下会生成该文件,使用execl打开文件查看结果如下:
    1600多条房源信息

    七、高德地图Map Lab 可视化

    7.1 进入高德地图开发者平台

    https://lbs.amap.com/

    高德地图Map Lap
    创建可视化项目
    导入csv文件

    导入数据前要查看开发者文档,导入的数据格式一定要正确

    格式要求:https://lbs.amap.com/faq/mapdata/platform/upload

    数据格式
    成功导入后,我们可以删除room_infos,room_number,introdcuction等字段,主要保留price和location就可以
    数据预览

    7.2 选择合适的呈现图

    选择呈现效果,不同的图像对数据的要求也不同,可以尝试查看说明来进行选择


    2D效果
    3D效果

    地图数据依赖默认选择location字段,成像数据依赖要选择price 就ok了


    数据依赖选择价格

    7.3可视化效果展示

    这里分别选择了2D热力图和3D直方图来进行渲染,效果如下:


    链家租房(深圳)价格直方图
    链家租房(深圳)价格热力图

    从上图可以简单分析出,目前房源大多都沿地铁线分布,租房价格最高的在南山区,福田区其次,也可以看到坂田、保安、罗湖也都有不少房源。

    八、项目反思

    该项目主要爬取链家网租房模块中的信息,但是爬取过程中发现整租类的大面积住房价格会很高,而青年公寓价格偏低,形成两个价格集中区域,容易出现断崖式数据分布。目前想到的解决方法是,将数据进一步处理,采用价格/面积的方式来作为成像的数据依赖,这样效果应该会更好一些,有兴趣的读者可以在此基础上加以改进。

    笔者能力有限,若发现文中出现错误,请即时留言加以纠正,以免误导其他读者。

    相关文章

      网友评论

          本文标题:Scrapy 抓取链家租房(深圳)信息&高德地图Map Lab

          本文链接:https://www.haomeiwen.com/subject/rtalmqtx.html