一、項目介紹
項目目標(biāo)
1.獲取鏈家網(wǎng)上的深圳市租房數(shù)據(jù)
2.將獲取的數(shù)據(jù)可視化
文章略長,為節(jié)約部分讀者時間撩荣,提前展示可視化效果
工具
python3.6、pycharm2018.1饶深、高德地圖Map Lab
技術(shù)
數(shù)據(jù)抓炔筒堋:Scarpy
數(shù)據(jù)展示:高德地圖API(Map Lab)
整體思路
- 分析鏈家租房模塊url(地區(qū)、翻頁變化)敌厘,找出請求url的規(guī)則
- 分析租房條目的類別(大致分為兩類台猴,青年公寓和普通租房)
- 分析房間詳情頁html(此處一般要注意是否是ajax加載)
- 編寫項目進行數(shù)據(jù)抓取(注意存儲數(shù)據(jù)的形式俱两,方便對接高德地圖)
- 使用高德地圖開發(fā)者模式卿吐,導(dǎo)入數(shù)據(jù),選擇合適的圖表類型锋华,展示數(shù)據(jù)
二、項目搭建:
打開cmd箭窜,進入project目錄(我自己的項目目錄)毯焕,執(zhí)行scrpay startproject LianJia
,創(chuàng)建scrapy項目磺樱;
執(zhí)行cd LianJia
進入項目纳猫;
執(zhí)行scrapy genspider LJ lianjia.com
,創(chuàng)建通用爬蟲
三竹捉、基本設(shè)置
settings設(shè)置
這里的UA使用fake_useragent庫中的UserAgent芜辕,fake_useragent是一個在git上開源的項目,維護了幾百個目前比較常用的UA块差,導(dǎo)入后直接調(diào)用random就可以隨機生成UA侵续,使用方便倔丈,推薦。代碼如下:
from fake_useragent import UserAgent
# 設(shè)置延遲為0.2
DOWNLOAD_DELAY = 0.2
# 關(guān)閉robots協(xié)議
ROBOTSTXT_OBEY = False
# headers設(shè)置
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'
}
啟動文件 - 同樣創(chuàng)建一個start.py來負責(zé)開啟爬蟲
from scrapy import cmdline
# 這里使用 -o 文件名.csv -s FEED_EXPORT_ENCODING=UTF-8 將數(shù)據(jù)直接保存為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條數(shù)據(jù)
但是仔細觀察可以發(fā)現(xiàn)其中很多條目是相同宏邮,這樣也不難發(fā)現(xiàn)在深圳鏈家的線上房源,其實并沒有頁面上寫的21447套在租房列表頁面蜜氨,可以看到兩種不同的房屋類型
對應(yīng)的詳情頁面也不同,對于這兩種不用頁面要分類爬取
鏈家的反爬其實一般捎泻,只要使用隨機請求頭基本都可以很順暢的爬下來
4.2注意:在詳情頁面中很多信息比較繁雜飒炎,爬取時要細心分析
比如基本信息中會有12項可視數(shù)據(jù),但是源碼中有17個li族扰,可以使用循環(huán)來剔除掉無用的li
經(jīng)緯度信息(高德地圖需要用到)放在一個script標(biāo)簽中厌丑,這里推薦使用正則進行提取
五、代碼展示
5.1 spider
這里沒什么好說的都是一些基本套路渔呵,當(dāng)然也有一些地方經(jīng)過多次調(diào)試才拿到數(shù)據(jù)
推薦大家咋終端使用scarpy shell 來進行測試提取結(jié)果
有問題的讀者怒竿,可以在評論區(qū)留言,有問必答哦
# -*- 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("http://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頁數(shù)據(jù)
if self.page < 101:
yield scrapy.Request(url=page_urls, callback=self.parse)
else:
print('爬取結(jié)束')
def apartment_parse(self, response):
"""
爬取公寓房間信息
:param response:
:return:
"""
title = response.xpath("http://p[contains(@class,'flat__info--title')]/text()").extract()[0].strip('\n').strip()
price = int("".join(response.xpath("http://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)
# 將經(jīng)緯度格式化耕驰,為之后數(shù)據(jù)可視化做準(zhǔn)備
location = longitude + "," +latitude
room_url = response.url
apartment_desc = response.xpath("http://p[@data-el='descInfo']/@data-desc").extract()[0]
introduction = apartment_desc.replace(r"<br />", "").replace("\n", "")
li_list = response.xpath("http://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):
"""
爬取業(yè)主出租房間信息
:param response:
:return:
"""
title = response.xpath("http://p[@class='content__title']/text()").extract()[0]
price = int(response.xpath("http://p[@class='content__aside--title']/span/text()").extract()[0])/3
publish_time = "".join(response.xpath("http://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)
# 將經(jīng)緯度格式化录豺,為之后數(shù)據(jù)可視化做準(zhǔn)備
location = longitude + "," + latitude
room_url = response.url
room_img = "".join(response.xpath("http://div[@class='content__article__slide__item']/img/@data-src").extract())
# conditions中有4項內(nèi)容(租賃方式朦肘、布局、面積双饥、朝向)
conditions = response.xpath("http://p[@class='content__article__table']/span/text()").extract()
room_layout = conditions[1]
room_area = conditions[2]
room_orientation = conditions[3]
room_infos = response.xpath("http://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("http://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時一開始媒抠,按照自己的想法來,想提取什么寫什么(當(dāng)然前提是有些東西能你可以提取得到..)咏花,在寫爬蟲時趴生,可以進行適當(dāng)調(diào)整(對部分item進行取舍)
# -*- coding: utf-8 -*-
import scrapy
class LjApartmentItem(scrapy.Item):
# 公寓名稱
title = scrapy.Field()
# 公寓最低單間價
price = scrapy.Field()
# 公寓地址
address = scrapy.Field()
# 公寓坐標(biāo)(繪制地圖備用)
location = scrapy.Field()
# 公寓介紹
introduction = scrapy.Field()
# 單間個數(shù)
room_number = scrapy.Field()
# 單間信息
room_infos = scrapy.Field()
# 房間鏈接
room_url = scrapy.Field()
class LjZufangItem(scrapy.Item):
# 房間名稱
title = scrapy.Field()
# 房間價格
price = scrapy.Field()
# 發(fā)布日期
publish_time = scrapy.Field()
# 房間地址
address = scrapy.Field()
# 房間坐標(biāo)(繪制地圖備用)
location = scrapy.Field()
# 房間圖片
room_img = scrapy.Field()
# 房間布局
room_layout = scrapy.Field()
# 房間面積
room_area = scrapy.Field()
# 房間朝向
room_orientation = scrapy.Field()
# 房間基本信息
room_infos = scrapy.Field()
# 周圍環(huán)境描述
surrounding_desc = scrapy.Field()
# 房間鏈接
room_url = scrapy.Field()
六、爬取結(jié)果
兩種不同的房間信息我們都拿到了
前面提到過
我們使用
scrapy crawl LJ -o sz-lianjia.csv -s FEED_EXPORT_ENCODING=UTF-8
命令昏翰,直接將文件保存為csv文件苍匆。爬取完成后,項目錄下會生成該文件棚菊,使用execl打開文件查看結(jié)果如下:七浸踩、高德地圖Map Lab 可視化
7.1 進入高德地圖開發(fā)者平臺
導(dǎo)入數(shù)據(jù)前要查看開發(fā)者文檔,導(dǎo)入的數(shù)據(jù)格式一定要正確
格式要求:https://lbs.amap.com/faq/mapdata/platform/upload
成功導(dǎo)入后统求,我們可以刪除room_infos检碗,room_number据块,introdcuction等字段,主要保留price和location就可以
7.2 選擇合適的呈現(xiàn)圖
選擇呈現(xiàn)效果后裸,不同的圖像對數(shù)據(jù)的要求也不同瑰钮,可以嘗試查看說明來進行選擇
地圖數(shù)據(jù)依賴默認選擇location字段,成像數(shù)據(jù)依賴要選擇price 就ok了
7.3可視化效果展示
這里分別選擇了2D熱力圖和3D直方圖來進行渲染微驶,效果如下:
從上圖可以簡單分析出浪谴,目前房源大多都沿地鐵線分布,租房價格最高的在南山區(qū)因苹,福田區(qū)其次苟耻,也可以看到坂田、保安扶檐、羅湖也都有不少房源凶杖。
八、項目反思
該項目主要爬取鏈家網(wǎng)租房模塊中的信息款筑,但是爬取過程中發(fā)現(xiàn)整租類的大面積住房價格會很高智蝠,而青年公寓價格偏低,形成兩個價格集中區(qū)域奈梳,容易出現(xiàn)斷崖式數(shù)據(jù)分布杈湾。目前想到的解決方法是,將數(shù)據(jù)進一步處理攘须,采用價格/面積的方式來作為成像的數(shù)據(jù)依賴漆撞,這樣效果應(yīng)該會更好一些,有興趣的讀者可以在此基礎(chǔ)上加以改進于宙。