本次涉及到的知识:
- scrapy模块的使用
- pyecharts模块的使用
- pymongo模块的使用
- jieba模块的使用
- dateuntil模块的使用
- fake_useragent模块的使用
目录
1.安装scrapy、pyecharts、dateutil、pymongo、jieba、fake_useragent模块
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
爬取步骤
-
确定待爬取的数据
-
分析待爬取数据的xpth路径
-
创建scrapy项目
-
爬取数据
-
使用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强多了,可以画柱状图、折线图、地图、热力图、球图等多种图,并且图还都是动态的,十分高大上。
本次分析要得到的结果有以下四个:
- 成都各区县二手房房价均价柱状图
- 成都各区县2018年3月-2019年2月房价变化折线图
- 标题词云
- 关注度排名前十小区
关于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