Scrapy+PyEcharts+MongoDB可视化分析成都市二手房市场

本次涉及到的知识:

  1. scrapy模块的使用
  2. pyecharts模块的使用
  3. pymongo模块的使用
  4. jieba模块的使用
  5. dateuntil模块的使用
  6. fake_useragent模块的使用

目录

python版本

爬取站点

准备工作

1.安装scrapy、pyecharts、dateutil、pymongo、jieba、fake_useragent模块

2.安装MongoDB

3.安装phantomjs

爬取步骤

1.确定待爬取的数据,打开贝壳网

2.确定XPath

3.创建crapy项目

4.爬取数据

1.items.py

2. pipelines.py

3.settings.py

 4.main.py

5.beike_spider.py

6.pipelines.py

6.使用pyecharts分析数据

最终结果

1.成都各区县二手房房价均价柱状图 

2.成都各区县2018年3月-2019年2月房价变化折线图 

3.标题词云

4.关注度排名前十小区


python版本

python3.6

爬取站点

贝壳网成都二手房:https://cd.ke.com/ershoufang/

准备工作

1.安装scrapy、pyecharts、dateutil、pymongo、jieba、fake_useragent模块

pip3 install scrapy
pip3 install pyecharts
pip3 install dateutil
pip3 install pymongo
pip3 install jieba
pip3 install fake_useragent

2.安装MongoDB

自行查找安装教程,下载地址:https://www.mongodb.com/download-center

3.安装phantomjs

自行查找安装教程 ,下载地址:http://phantomjs.org/download.html

爬取步骤

  1. 确定待爬取的数据

  2. 分析待爬取数据的xpth路径

  3. 创建scrapy项目

  4. 爬取数据

  5. 使用pyecharts分析数据

1.确定待爬取的数据,打开贝壳网

上面红框里的东西都爬。

2.确定XPath

  • 编号1://*[@class='info clear']
  • 编号2:./div[1]/a
  • 编号3:./div[2]/div[1]
  • 编号4:./div[2]/div[2]/div[1]
  • 编号5:./div[2]/div[3]
  • 编号6:./div[2]/div[5]/div[1]/span
  • 编号7:./div[2]/div[5]/div[2]/span

3.创建crapy项目

在待创建的路径下进入cmd,输入:

scrapy startproject AnalyseHousePrice

创建后的目录:

AnalyseHousePrice/
    scrapy.cfg
    AnalyseHousePrice/
        __init__.py
        items.py
        pipelines.py
        settings.py
        spiders/
            __init__.py
            ...

之后还需创建一些文件,创建后的目录如:

AnalyseHousePrice/
    scrapy.cfg
    AnalyseHousePrice/
        __init__.py
        items.py
        pipelines.py
        settings.py
        main.py
        spiders/
            __init__.py
            beike_spider.py

4.爬取数据

这里需要编辑的文件有:

  • items.py
  • middlewares.py
  • settings.py
  • main.py
  • beike_spider.py
  • pipelines.py

1.items.py

items.py用于定义待爬取的数据。

import scrapy


class AnalysehousepriceItem(scrapy.Item):
    # 房产id
    _id = scrapy.Field()
    # 房产标题
    name = scrapy.Field()
    # 房产所在城市
    city = scrapy.Field()
    # 房产所在区县
    area = scrapy.Field()
    # 房产地址
    address = scrapy.Field()
    # 房产样式
    style = scrapy.Field()
    # 房产面积
    size = scrapy.Field()
    # 房产朝向
    direction = scrapy.Field()
    # 房产楼层
    floor = scrapy.Field()
    # 房产关注度
    follow = scrapy.Field()
    # 房产发布时间
    time = scrapy.Field()
    # 房产总价
    total_price = scrapy.Field()
    # 房产单价
    per_price = scrapy.Field()
    # 房产链接
    link = scrapy.Field()

2. middlewares.py

pipelines.py文件用于设置爬取请求头,这里使用了fake_useragent模块随机生成请求头,向该文件头部添加如下内容:

from fake_useragent import UserAgent
USER_AGENT = UserAgent()

修改AnalysehousepriceDownloaderMiddleware类中的process_request方法为:

    def process_request(self, request, spider):
        #随机生成请求头
        request.headers.setdefault('User-Agent', USER_AGENT.random)
        return None

3.settings.py

settings.py用于定义爬取时的配置,向该文件添加如下内容:

#爬取的城市代码以及名称
CITYS = {'cd': '成都'}

#加速爬取
DOWNLOAD_DELAY = 0
CONCURRENT_REQUESTS = 100
CONCURRENT_REQUESTS_PER_DOMAIN = 100
CONCURRENT_REQUESTS_PER_IP = 100
COOKIES_ENABLED = False

 将该文件中下列内容的注释去掉:

#下载中间件,用于设置爬取请求头
DOWNLOADER_MIDDLEWARES = {
    'AnalyseHousePrice.middlewares.AnalysehousepriceDownloaderMiddleware': 543,
}
#启用pipelines,用于保存爬取的数据
ITEM_PIPELINES = {
    'AnalyseHousePrice.pipelines.AnalysehousepricePipeline': 300,
}

设置默认不遵守robots.txt规则:

#原为True,改为False则可正常爬取
ROBOTSTXT_OBEY = False

 4.main.py

main.py用于启动scrapy。

import os

if __name__ == "__main__":
    #启动scrapy
    os.system('scrapy crawl beike ')

5.beike_spider.py

beike_spider.py用于定义爬取规则,是核心文件。

import scrapy
from AnalyseHousePrice import settings
import re
import time
import datetime
from dateutil.relativedelta import relativedelta
from AnalyseHousePrice.items import AnalysehousepriceItem


class BeikeSpider(scrapy.Spider):
    name = "beike"
    allowed_domains = ["ke.com"]
    start_urls = []
    # 构造起始url,可在settings.py中添加多个城市的信息,一次爬取多个城市
    for id, city in settings.CITYS.items():
        url = 'https://%s.ke.com/ershoufang/' % id
        start_urls.append(url)

    # 对爬取起始url后返回的内容进行操作
    def parse(self, response):
        # 获取该城市二手房界面的首页链接
        index = response.xpath(
            "//*[@id='beike']/div[1]/div[1]/div/ul/li[2]/a/@href").extract()[0].replace('/ershoufang/', '')
        # 获取该城市各区县二手房界面的首页链接
        hrefs = response.xpath("//*[@class=' CLICKDATA']/@href").extract()
        for href in hrefs:
            # 分页爬取
            for page in range(1, 100):
                # 构造该城市各区县二手房信息列表的链接
                url = '%s%spg%s/' % (
                    index, href, page)
                # 再次爬取,将返回的内容发送到parse_info方法,爬取内容为各区县的二手房信息
                yield scrapy.Request(url, callback=self.parse_info, dont_filter=True)

  
    # 爬取二手房信息
    def parse_info(self, response):
        # 遍历二手房列表
        for info in response.xpath("//*[@class='info clear']"):
            item = AnalysehousepriceItem()
            # 获取二手房id
            item['_id'] = info.xpath(
                "./div[1]/a/@href").extract()[0].split('/')[-1].split('.')[0]
            # 获取二手房标题
            item['name'] = info.xpath(
                "./div[1]/a/text()").extract()[0]
            # 获取二手房所在城市
            item['city'] = settings.CITYS.get(response.xpath(
                "//*[@id='beike']/div[1]/div[1]/div/ul/li[2]/a/@href").extract()[0].split('//')[1].split('.')[0], None)
            # 获取二手房所在区县
            item['area'] = info.xpath(
                "//*[@class='selected CLICKDATA']/text()").extract()[0]
            # 获取二手房基本信息,即编号3中的信息
            houseInfo = info.xpath(
                "./div[2]/div[1]/text()").extract()[1].replace('\n', '').replace(' ', '').split('|')
            item['address'] = houseInfo[0]
            item['style'] = houseInfo[1]
            item['size'] = houseInfo[2]
            item['direction'] = houseInfo[3]
            # 获取二手房楼层信息,即编号4中的信息
            floor = info.xpath(
                "./div[2]/div[2]/div[1]/text()").extract()[1].replace('\n', '').replace(' ', '')
            item['floor'] = floor
            # 获取二手房关注及发布信息,即编号5中的信息
            followInfo = info.xpath(
                "./div[2]/div[3]/text()").extract()[1].split('/')
            # 提取关注度数字
            item['follow'] = int(re.findall(
                r'-?\d+\.?\d*e?-?\d*?', followInfo[0])[0])
            datetime_now = datetime.datetime.now()
            # 判断二手房发布距今的月份
            if '今天发布' == followInfo[1] or '天' in followInfo[1]:
                item['time'] = time.strftime("%Y-%m")
            elif '月' in followInfo[1]:
                item['time'] = (
                    datetime_now - relativedelta(months=int(followInfo[1].replace('月前发布', '')))).strftime("%Y-%m")
            else:
                continue
            # 获取二手房的总价
            item['total_price'] = float(info.xpath(
                "./div[2]/div[5]/div[1]/span/text()").extract()[0])
            # 获取二手房的单价
            item['per_price'] = float(re.findall(r'-?\d+\.?\d*e?-?\d*?', info.xpath(
                "./div[2]/div[5]/div[2]/span/text()").extract()[0])[0])
            # 获取二手房的链接
            item['link'] = info.xpath(
                "./div[1]/a/@href").extract()[0]
            # 将item交与pipelines.py处理
            yield item

6.pipelines.py

本次爬取将爬取的数据保存到MongoDB数据库,使用的模块为pymongo模块,保存数据的文件为pipelines.py文件。

import logging
import pymongo
logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)


class AnalysehousepricePipeline(object):

    def __init__(self):
        # 构造MongoDB client
        self.client = pymongo.MongoClient("mongodb://localhost:27017/test:123")
        # 连接house_price数据库
        self.db = self.client['house_price']

    def process_item(self, item, spider):
        # 将beike_spider.py中BeikeSpider类的parse_info方法返回的item交与save_info方法处理保存到MongoDB
        self.save_info(item)
        # 这里必须要返回item
        return item

    # 保存爬取的信息
    def save_info(self, info):
        try:
            # 连接beike集合
            mycol = self.db['beike']
            # 插入一条数据到MongoDB
            mycol.insert_one(info)
            # 打印日志
            logger.info('save success')
        except Exception as e:
            logger.warning('save fail' + str(e))

 之后执行main.py文件启动scrapy

python3 main.py

 爬取之后使用MongoDB Compass查看爬取的数据

6.使用pyecharts分析数据

不得不说pyecharts是一个可视化数据很好的模块,比matplotlib强多了,可以画柱状图、折线图、地图、热力图、球图等多种图,并且图还都是动态的,十分高大上。

本次分析要得到的结果有以下四个:

  1. 成都各区县二手房房价均价柱状图
  2. 成都各区县2018年3月-2019年2月房价变化折线图
  3. 标题词云 
  4. 关注度排名前十小区

关于pyecharts怎样画上面的图请参考文档:http://pyecharts.org/#/zh-cn/

创建analyse目录和对应的文件后的结果为:

AnalyseHousePrice/
    scrapy.cfg
    AnalyseHousePrice/
        __init__.py
        items.py
        pipelines.py
        settings.py
        main.py
        analyse/
            html/
            img/
            beike_analyse.py
            哈工大停用词.txt
        spiders/
            __init__.py
            beike_spider.py

其中html目录用于存储生成的html文件,img用于生成的图片文件,beike_analyse.py文件用于分析爬取的数据。哈工大停用词.txt为停用词,用于生成标题词云时去掉标题中的停用词,这里需要向里面添加两行:

好
的

beike_analyse.py文件中的代码为:

import pymongo
import jieba
from dateutil.relativedelta import relativedelta
import datetime
import collections
from pyecharts import Bar, Line, WordCloud
# 构造MongoDB client
client = pymongo.MongoClient("mongodb://localhost:27017/test:123")
# 连接house_price数据库
db = client['house_price']
# 连接beike集合
mycol = db['beike']


class Analyse(object):

    def __init__(self, id, city):
        super(Analyse, self).__init__()
        # 城市id
        self.id = id
        # 城市名称
        self.city = city
        # 停用词文件
        self.break_words_file = '哈工大停用词.txt'
        # 停用词
        self.break_words = []

    # 生成各区县二手房房价均价柱状图
    def avg_prices(self):
        # 区县
        areas = []
        # 各区县对应的均价
        avg_prices = []
        # 根据区县分组,求各组的均值
        for item in list(mycol.aggregate([{'$match': {'city': self.city}}, {'$group': {'_id': "$area", 'avg_price': {'$avg': "$per_price"}}}])):
            areas.append(item['_id'])
            avg_prices.append(round(item['avg_price'], 2))
        bar = Bar('%s各区县二手房房价均价(单位:元/平方米)' % self.city,
                  page_title='%s各区县二手房房价均价(单位:元/平方米)' % self.city)
        # 设置统计图样式
        bar.use_theme("vintage")
        bar.add('均价',
                areas,
                avg_prices,
                is_more_utils=True,
                is_xaxislabel_align=True,
                xaxis_type='category',
                # 旋转x轴的标签,防止标签显示不全
                xaxis_rotate=-45
                )
        # 生成html
        bar.render('./html/%s_avg_prices.html' % self.id)
        # 生成图片
        bar.render(path='./img/%s_avg_prices.png' % self.id)

    # 生成各区县2018年3月-2019年2月房价变化折线图
    def tendency(self):
        # 区县
        areas = list(mycol.distinct('area', {'city': self.city}))
        line = Line(page_title='%s各区县二手房房价变化(单位:元/平方米)' %
                    self.city, title_top=100)
        datetime_now = datetime.datetime.now()
        months = [i for i in range(1, 13)]
        # 将当前月份减去发布距今的月份得到发布月份
        times = [(datetime_now -
                  relativedelta(months=i)).strftime("%Y-%m") for i in months][::-1]
        for area in areas:
            avg_prices = []
            for time in times:
                item = list(mycol.aggregate(
                    [{'$match': {'time': time, 'area': area}}, {'$group': {'_id': "$area",  'avg_price': {'$avg': "$per_price"}}}]))
                if item:
                    avg_prices.append(round(item[0]['avg_price'], 2))
                else:
                    # 如果该月没有数据,则以均值填充
                    avg_prices.append(round(list(mycol.aggregate(
                        [{'$match': {'area': area}}, {'$group': {'_id': "$area",  'avg_price': {'$avg': "$per_price"}}}]))[0]['avg_price'], 2))
            print('now:', area)
            line.add(area, times, avg_prices,
                     mark_point=['max', 'min'], is_more_utils=True,
                     is_xaxislabel_align=True, xaxis_rotate=-45, mark_point_textcolor='#000000', xaxis_force_interval=1, yaxis_force_interval=int(max(avg_prices) / 5))
        line.render('./html/%s_prices_tendency.html' % self.id)
        line.render(path='./img/%s_prices_tendency.png' % self.id)

    # 判断词语是否是中文
    def is_chinese(self, words):
        for word in words:
            if not(u'\u4e00' <= word <= u'\u9fa5'):
                return False
        return True

    # 获取停用词
    def get_break_stopWords(self):
        with open(self.break_words_file, 'r', encoding='UTF-8-sig') as f:
            for line in f:
                self.break_words.append(line.replace('\n', ''))
        f.close()

    # 删除停用词以及非中文
    def delete_break_words(self, word):
        if word not in self.break_words:
            if self.is_chinese(word):
                return word

    # 生成标题词云图 
    def word_cloud(self):
        # 词语
        words = []
        # 对应词语出现的次数
        count = []
        # 获取停用词
        self.get_break_stopWords()
        for item in mycol.find({}, {'name'}):
            # 使用jieba模块分词
            for word in jieba.lcut(item['name']):
                word = self.delete_break_words(word)
                if word:
                    words.append(word)
        # 使用collections模块统计词频
        result = collections.Counter(words)
        words = list(result.keys())
        count = list(result.values())
        wordcloud = WordCloud(width=1300, height=620)
        wordcloud.add("", words, count, shape='triangle')
        wordcloud.render('./html/%s_word_cloud.html' % self.id)
        wordcloud.render(path='./img/%s_word_cloud.png' % self.id)

    # 生成关注度排名前十小区图
    def hot_estate(self):
        # 查询所有结果,将查询结果按关注度排序,取前十个数据
        data = list(mycol.find({'city': self.city}, {'address': 1, "follow": 1}).sort(
            'follow', pymongo.DESCENDING))[:10]
        # 小区名
        addresses = []
        # 小区关注度
        value = []
        bar = Bar('%s各区县二手房关注度前十小区' % self.city,
                  page_title='%s各区县二手房关注度前十小区' % self.city)
        bar.use_theme("vintage")
        for item in data:
            value.append(item['follow'])
            addresses.append(item['address'])
        bar.add('',
                addresses,
                value,
                is_more_utils=True,
                is_xaxislabel_align=True,
                xaxis_type='category',
                xaxis_rotate=-45
                )
        bar.render('./html/%s_hot_estate.html' % self.id)
        bar.render(path='./img/%s_hot_estate.png' % self.id)

if __name__ == "__main__":
    analyse = Analyse('cd', '成都')
    analyse.avg_prices()
    analyse.tendency()
    analyse.word_cloud()
    analyse.hot_estate()

 

最终结果

1.成都各区县二手房房价均价柱状图 

从上图可以看出成都市二手房房价最高的为锦江区和高新区,这可能使因为成都市有钱人以及上班族大多都集中在这两个区吧。 

2.成都各区县2018年3月-2019年2月房价变化折线图 

从上面可以 看出锦江区和高新区的二手房房价高居榜位,都江堰区的二手房房价波动最大 ,可能跟季节有关。天府新区南区有上涨的趋势,这可能跟成都的开发政策相关,毕竟是新建的区。

3.标题词云

 

从上可以看出发布房产的人提到最多的就是房型、楼型、采光、 装修等词。

4.关注度排名前十小区

 

从上面可以看出,成都市二手房关注度最高的小区为润和原筑,搜索查看:

看着还不错哦,难怪关注度这么高。

最后,附上本次项目的github地址:https://github.com/RickyHal/AnalyseHousePrice

  • 6
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值