Python爬虫实战[成都短租房项目]
一、项目需求
- 目标URL:http://www.youtx.com/chengdu/;
- 要爬取的数据项:“所属市”,“所属区”,“房屋面积”,“房屋户型-卧室数量”,“房屋户型-卫生间数量”,“宜住人数”,“当日出租价格”,“租房人”,“是否收取押金”,“押金”,“最短入住天数”,“总体评价分数”,共12项。
- 能够处理数据中缺失的部分,并进行合理的填充。
- 将所有爬取的数据按字段存入一个csv文件。
- 数据清洗部分要求使用3种不同的方法。
- 能够按照成都市所属区对数据进行分类,算出每个区的房子的平均出租价格、最高出租价格、出租房源总数,并画出对应的柱状图。
- 能够按照房屋面积(30平米以下、30-50平米、50-80平米,80平米以上)对数据进行分类,算出每种面积的平均出租价格,最高出租价格,并画出对应的柱状图。
- 源程序命名为net_spyder.py,该程序可以用以下方式运行: python net_spyder.py xxx.csv,其中xxx.csv是保存爬取数据的csv文件名。
二、需求分析
- 目标URL(http://www.youtx.com/chengdu/)是一个列表页,显然,我们可以通过列表页获取所有详情页的URL。
- 我们在获取所有的详情页的URL后,可以很轻松的获得原始报文(异步加载前)。若我们有数据项采用了异步加载,那么我们考虑3种方法:
[1]在css和js代码中分析是否包含该内容。
[2]构造并发送ajax虚假请求以欺骗服务器再次发送数据。
[3]配合selenium模拟浏览器操作获取网页完全加载后的数据。 - 对数据缺失部分,采取0值填充,对于"是否收取押金","押金"这两项关联数据,需要判断数据关联合法性。对于其他字段,需要删除多余的字符串。
- 数据清洗方法可以使用:
[1]re+string
[2]BeautifulSoup
[3]Xpath - 数据可视化采用matplotlib,至于csv文件和命令行下运行,都可以很轻松的实现。
三、爬虫部分
3.1 获取原始报文
按照爬虫步长为3秒计算,完全爬取需要至少50分钟,这种效率对数据清洗阶段的调试来讲是很不理想的,所以首先我们保存所有报文到本地。
文件保存和读取代码均在测试部分,包括“写入网页数据”和“读取网页数据”。在我们需要获取数据时,注释读取部分;在调试数据清洗部分代码时,注释写入部分。主要代码如下:
class chengDuHouse():
def __init__(self):
self.url_times=32#页面数量
self.step=3#爬取时间步长
self.base_url1="http://www.youtx.com/chengdu/page"
self.base_url2="/"
self.urls=[]#各房源详情页URL
self.text=[]#各房源详情页原始报文
self.get_web_urls()
self.get_url_detail()
'''测试'''
# 写入网页数据
if not os.path.exists("urldata"):
os.mkdir("urldata") # 创建文件夹
for index in range(len(self.text)):
img_file = "urldata/" + str(index) + ".txt" # 路径
with open(img_file, "w+",encoding='utf-8') as f:
f.write(self.text[index])
# 读取网页数据
for index in range(952):
img_file = "urldata/" + str(index) + ".txt" # 路径
with open(img_file, "r",encoding='utf-8') as f:
data=f.read()
self.text.append(data)
def get_web_urls(self):
for i in range(self.url_times):
url=self.base_url1+str(i+1)+self.base_url2
http = urllib3.PoolManager()
res = http.request("GET", url)
soup = BeautifulSoup(res.data.decode("utf-8"), 'lxml')
for html_data in soup.findAll(name="li", attrs={"class": "clearfix"}):
data = etree.HTML(str(html_data))
link = data.xpath("string(//div[@class='houseInfo clearfix']/div[@class='house-tit clearfix']/h3/a/@href)")
if (len(link) > 5):
self.urls.append(link)
time.sleep(self.step)
def get_url_detail(self):
for i in self.urls:
http = urllib3.PoolManager()
res = http.request("GET", i)
data=res.data.decode("utf-8")
self.text.append(data)
time.sleep(self.step)
这样,我们便在urldata文件夹下获得了953个页面的原始报文,文件的命名格式为[0-952].txt。
3.2 数据清洗(re+string方法)
在原始网页中,有些数据项难以在HTML中精确的获取,但在js中我们往往可以轻松的匹配到。通过分析报文,我们可以发现,“所属市”,“所属区”,"当日出租价格"这3个数据项可以在js中用一行代码获得。我们也发现,使用re可以从js中轻松获取数据。代码如下:
# 根据原始报文获取12个标签值,re+string方法
def progress_detailToLabels_Re(self):
for data in self.text:
# 所属市
city= re.findall(".*%s(.*)%s.*"%("var housepercity = '","';"),data)[0]
# 所属区
district=re.findall(".*%s(.*)%s.*"%("var housedistrict = '","';"),data)[0]
# 房屋面积
house_information_index=data.index('<li class="clearfix housemessage">')
house_information=data[house_information_index:data.index('</li>',house_information_index)]
area_index=house_information.index("出租类型:<span>")
area=house_information[area_index:house_information.index("</span></p>",area_index)].strip()
area = str(re.findall(r"(.*?)", area))[3:-4]
# 房屋户型 - 卧室数量
bedrooms_number_index = house_information.index("卧室数量:<span>")
bedrooms_number = house_information[bedrooms_number_index+len("卧室数量:<span>"):house_information.index("</span></p>", bedrooms_number_index)]
if ("+" in bedrooms_number):
bedrooms_number = bedrooms_number.replace("+", "")
if not (bedrooms_number.isdigit()):
bedrooms_number = 0
# 房屋户型 - 卫生间数量
toilets_number_index = house_information.index("卫生间数量:<span>")
toilets_number = house_information[toilets_number_index:house_information.index("</span>", toilets_number_index)]
toilets_number = str(re.findall(r"\d", toilets_number))[2:-2]
if ("', '" in toilets_number):
toilets_number = toilets_number.replace("', '", "")
if not (toilets_number.isdigit()):
toilets_number = 0
# 宜住人数
suit_living_number_index = house_information.index("标准可住人数:<span>")
suit_living_number = house_information[suit_living_number_index+len("标准可住人数:<span>"):house_information.index("</span></p>", suit_living_number_index)]
suit_living_number=str(suit_living_number).replace("+","")
if ("+" in suit_living_number):
suit_living_number = suit_living_number.replace("+", "")
# 当日出租价格
rental_price_now=re.findall(".*%s(.*)%s.*" % ("var dayprice = '", "';"), data)[0]
# 租房人
tenants_index=data.index('<div class="se-one clearfix">')
tenants=data[tenants_index+len('<div class="se-one clearfix">'):data.index("</a></span>",tenants_index)]
tenants=tenants[tenants.index(">",tenants.index("<span><")+len('<span><'))+1:]
# 押金
try:
deposit_index=data.index("<li>押金:<span>¥")
deposit=data[deposit_index+len("<li>押金:<span>¥"):data.index("<img",deposit_index)]
except Exception as e:
print(e,"不存在押金相关的HTML!")
deposit=0
# 是否收取押金
collect_deposit = False
if(int(deposit)>0):
collect_deposit=True
# 最短入住天数
minimum_stay_days_index=data.index("<li>最短入住天数:<span>")
minimum_stay_days=data[minimum_stay_days_index+len("<li>最短入住天数:<span>"):data.index("天</span></li>",minimum_stay_days_index)]
# 总体评价分数
try:
score_index = data.index('<div class="leftstars">')
score = data[score_index + len('<div class="leftstars">'):data.index("</span></p>",score_index)]
score=score[score.index("/")+1:]
except Exception as e:
print(e,"不存在总体评价分数相关的HTML!")
score=0
li_temp=[]
li_temp.append(city)
li_temp.append(district)
li_temp.append(area)
li_temp.append(bedrooms_number)
li_temp.append(toilets_number)
li_temp.append(suit_living_number)
li_temp.append(rental_price_now)
li_temp.append(tenants)
li_temp.append(collect_deposit)
li_temp.append(deposit)
li_temp.append(minimum_stay_days)
li_temp.append(score)
self.labels.append(li_temp)
3.3 数据清洗(BeautifulSoup方法)
使用BeautifulSoup方法时,需要先将字符串类型的报文转换为soup对象,再使用函数(一般使用soup.findAll())或按照标签名称和class选择器类型提取数据。但从正则匹配转换为对HTML进行数据清洗时,我们就需要考虑某些页面数据项不完整或标签顺序不同的问题。
某些页面不包含“总体评价分数”数据项
在对前30个页面进行的测试中,发现第6/19/21号页面的详情页中,无法按照现有规则获取数据项。对于这种情况,我们可以考虑加入异常捕获语句,若发现异常则将该值置为0。(在pycharm搜索模式下,文件右侧应该有30条均匀分布的绿线,按照序号数,很显然,缺失了3条线)
在调试“押金”数据项时,发现部分页面标签顺序不同,有的是第5个li标签,有的是第6个li标签。对于这种情况,我们需要用if语句判断当前字符串是否包含"押金"子串,若不包含,我们便筛选下一个标签,两个li标签,不是第一个就一定是第二个。
通过HTML获取地址依旧麻烦,不是所有地址都是按照“XX市XX区”格式填写的。对于这种情况,可以先通过BeautifulSoup方法缩小字串范围,再用string方法判断提取。
除此之外的,在详细检查的过程中再一次发现了一个矛盾,即部分房源在前台是没有显示当日房价的,按道理无法爬取,但我们的正则又工作的很好。这时候,我们就需要获取XHR数据,来分析这个当日房价不显示的原因,如果是已出租变灰了,那么我们可以认为当日房价的属性依旧存在。若是因为房东没有发布房价,那么我们将该项数据置为0.
在对比分析不同页面的XHR报文后,发现有一则报文包含Result信息。Result=OK时,基于soup方法的HTML数据获取规则可以很好的工作,当Result=check时,表示当日房子已经出租,当日价格不在前台显示,这时,只有正则(re方法)可以正常工作。
很明显,前台按钮变灰的原因是房屋已出租,但当日价格的属性依旧存在,我们依然得获取到这个值。我们可以获取上面的报文,基本思路是,若Result=OK,我们就通过HTML获取值;若Result=check,报文中将没有对应的HTML,我们便将其置为0。
可以看出,这个值是异步加载的,前面已经提到了3种对付异步加载的方法。但现在,我们的正则并没有失效,此处便不使用其他两种方法来获取。需要说明的是,通过HTML获取当日价格很麻烦,所以Xpath方法和BeautifulSoup方法在获取该项数据时使用的都是正则,BeautifulSoup方法代码如下:
# 根据原始报文获取12个标签值,BeautifulSoup方法
def progress_detailToLabels_BeautifulSoup(self):
for data in self.text:
print(self.text.index(data))
# 所属市
soup = BeautifulSoup(data, 'html5lib')
city=soup.findAll(name="div", attrs={"class": "position"})
city_soup = BeautifulSoup(str(city), 'html5lib')
city = city_soup.i["title"]
city=city[:str(city).index("市")+1]
# 所属区
district = city_soup.i["title"][:10]
if("区" in district):
district = district[str(district).index("市") + 1:str(district).index("区") + 1]
elif("县" in district):
district = district[str(district).index("市") + 1:str(district).index("县") + 1]
else:
district_index=str(district).index("市")
district = district[district_index+ 1:str(district).index("市",district_index+1) + 1]
# 房屋面积
house_information = soup.findAll(name="li", attrs={"class": "clearfix housemessage"})
house_information_soup = BeautifulSoup(str(house_information), 'html5lib')
house_information_details=house_information_soup.findAll("span")
area =str(house_information_details[2])
area=area[area.index("(")+1:area.index("㎡")]
# 房屋户型 - 卧室数量
bedrooms_number = str(house_information_details[-2])
bedrooms_number=bedrooms_number[bedrooms_number.index(">")+1:bedrooms_number.index("</")]
if ("+" in bedrooms_number):
bedrooms_number = bedrooms_number.replace("+", "")
if not (bedrooms_number.isdigit()):
bedrooms_number = 0
# 房屋户型 - 卫生间数量
toilets_number = str(house_information_details[1])
toilets_number = toilets_number[toilets_number.index(">") + 1:toilets_number.index("</")]
if ("', '" in toilets_number):
toilets_number = toilets_number.replace("', '", "")
if not (toilets_number.isdigit()):
toilets_number = 0
# 宜住人数
suit_living_number = str(house_information_details[-3])
suit_living_number = suit_living_number[suit_living_number.index(">") + 1:suit_living_number.index("</")]
suit_living_number = str(suit_living_number).replace("+","")
if("(" in suit_living_number):
suit_living_number=suit_living_number[:suit_living_number.index("(")]
if("张" in suit_living_number):
suit_living_number=suit_living_number.replace("张","")
# 当日出租价格
#网页使用异步加载,step=5s,HTML中无精确匹配值,此处无法使用BeautifulSoup方法获取精确值。
#可选择更换为scrapy框架配合selenium模拟浏览器操作获取或使用ajax发送虚假请求
rental_price_now = re.findall(".*%s(.*)%s.*" % ("var dayprice = '", "';"), data)[0]
# 租房人
tenants=soup.findAll(name="div", attrs={"class": "se-one clearfix"})
tenants_soup = BeautifulSoup(str(tenants), 'html5lib')
tenants = tenants_soup.span.a.string
# 押金
try:
deposit = soup.findAll(name="div", attrs={"class": "checkin"})
deposit_soup = BeautifulSoup(str(deposit), 'lxml')
total_informtion=deposit_soup.findAll("li")
if("押金" in str(total_informtion[5])):
deposit=str(total_informtion[5])
else:
deposit = str(total_informtion[4])
deposit_soup = BeautifulSoup(str(deposit), 'lxml')
deposit=deposit_soup.span.text[1:]
except Exception as e:
print(e,"不存在押金相关的HTML!")
deposit=0
# 是否收取押金
collect_deposit = False
if (str(deposit).isdigit()):
if(int(deposit) > 0):
collect_deposit = True
if(collect_deposit==False):
deposit=0
# 最短入住天数
minimum_stay_days =str(total_informtion[2])
minimum_stay_days_soup = BeautifulSoup(str(minimum_stay_days), 'lxml')
minimum_stay_days=minimum_stay_days_soup.span.text[:-1]
# 总体评价分数
try:
score= soup.findAll(name="div", attrs={"class": "leftstars"})
score_soup = BeautifulSoup(str(score)[1:-1], 'lxml')
score=score_soup.p.span.text[1:]
except Exception as e:
print(e,"不存在评价相关的HTML!")
score=0
li_temp = []
li_temp.append(city)
li_temp.append(district)
li_temp.append(area)
li_temp.append(bedrooms_number)
li_temp.append(toilets_number)
li_temp.append(suit_living_number)
li_temp.append(rental_price_now)
li_temp.append(tenants)
li_temp.append(collect_deposit)
li_temp.append(deposit)
li_temp.append(minimum_stay_days)
li_temp.append(score)
self.labels.append(li_temp)
3.4 数据清洗(Xpath方法)
Xpath方法就像是一棵树,你只需要按照标签的分支提取就行,代码和BeautifulSoup没有太大的区别。当然,在得到数据后要检查一下结果,一定要保证合法性,比如下面的例子。
这种问题很普遍,使用数据拼接就好了,剩下的大同小异,直接上代码:
# 根据原始报文获取12个标签值,Xpath方法
def progress_detailToLabels_Xpath(self):
for data in self.text:
html = etree.HTML(data)
# 所属市
city_district = str(html.xpath("string(//div[@class='sec-2 clearfix']/div[@class='position']/i[1]/@title)"))
city=city_district[:city_district.index("市")+1]
# 所属区
district = city_district[:10]
if ("区" in district):
district = district[str(district).index("市") + 1:str(district).index("区") + 1]
elif ("县" in district):
district = district[str(district).index("市") + 1:str(district).index("县") + 1]
else:
district_index = str(district).index("市")
district = district[district_index + 1:str(district).index("市", district_index + 1) + 1]
# 房屋面积
area = str(html.xpath("string(//li[@class='clearfix housemessage']/div[@class='lists clearfix']/p[3]/span)"))
area=area[area.index("(")+1:area.index("㎡")]
# 房屋户型 - 卧室数量
bedrooms_number =str(html.xpath("string(//li[@class='clearfix housemessage']/div[@class='lists clearfix']/p[5]/span)"))
if ("+" in bedrooms_number):
bedrooms_number = bedrooms_number.replace("+", "")
if not (bedrooms_number.isdigit()):
bedrooms_number = 0
# 房屋户型 - 卫生间数量
toilets_number = str(html.xpath("string(//li[@class='clearfix housemessage']/div[@class='lists clearfix']/p[2]/span)"))
if("个" in toilets_number):
toilets_number=toilets_number[:toilets_number.index("个")]
if("', '" in toilets_number):
toilets_number=toilets_number.replace("', '","")
if not (toilets_number.isdigit()):
toilets_number=0
# 宜住人数
suit_living_number =str(html.xpath("string(//li[@class='clearfix housemessage']/div[@class='lists clearfix']/p[4]/span)"))
if("+" in suit_living_number):
suit_living_number = suit_living_number.replace("+", "")
# 当日出租价格
# 网页使用异步加载,step=5s,HTML中无精确匹配值,此处无法使用xpath方法获取精确值。
# 可选择更换为scrapy框架配合selenium模拟浏览器操作获取或使用ajax发送虚假请求
rental_price_now = re.findall(".*%s(.*)%s.*" % ("var dayprice = '", "';"), data)[0]
# 租房人
tenants = str(html.xpath("string(//div[@class='se-one clearfix']/span/a)"))
# 押金
try:
deposit1 =str(html.xpath("string(//div[@class='dsection-4']/div[@class='checkin']/ul[@class='clearfix']/li[6]/span)"))[1:]
deposit2 =str(html.xpath("string(//div[@class='dsection-4']/div[@class='checkin']/ul[@class='clearfix']/li[5]/span)"))[1:]
if(deposit1.isdigit()):
deposit=int(deposit1)
else:
deposit=int(deposit2)
except Exception as e:
print(e,"不存在押金相关的HTML!")
deposit=0
# 是否收取押金
collect_deposit = False
if (int(deposit) > 0):
collect_deposit = True
# 最短入住天数
minimum_stay_days =str(html.xpath("string(//div[@class='dsection-4']/div[@class='checkin']/ul[@class='clearfix']/li[3]/span)"))[:-1]
# 总体评价分数
try:
score = str(html.xpath("string(//div[@class='leftstars']/p)"))
score=score[score.index("/")+1:]
except Exception as e:
print(e,"不存在评价相关的HTML!")
score=0
li_temp = []
li_temp.append(city)
li_temp.append(district)
li_temp.append(area)
li_temp.append(bedrooms_number)
li_temp.append(toilets_number)
li_temp.append(suit_living_number)
li_temp.append(rental_price_now)
li_temp.append(tenants)
li_temp.append(collect_deposit)
li_temp.append(deposit)
li_temp.append(minimum_stay_days)
li_temp.append(score)
self.labels.append(li_temp)
3.5 爬虫代码说明
参数说明
- self.url_times:页面数量;
- self.step:爬取时间步长;
- self.base_url1:目标URL开头;
- self.base_url2:目标URL结尾;
- self.item_list:要爬取的数据项;
- self.urls:各房源详情页URL;
- self.text:各房源详情页原始报文;
- self.labels:各房源详情页数据各标签值;
函数说明
- get_web_urls(self, response):目录页面爬取,获得详情页URL;
- get_url_detail(self,response):详情页报文(未加载)爬取;
- progress_detailToLabels_Re(self, response):数据清洗(re+string方法);
- progress_detailToLabels_BeautifulSoup(self,response):数据清洗(BeautifulSoup方法);
- progress_detailToLabels_Xpath(self, response):数据清洗(Xpath方法);
- outport_txt(self,response):导出TXT文件;
- outport_excel(self, response):导出EXCEL文件;
- outport_csv(self,response):导出CSV文件;
- outport_json(self, response):导出JSON文件;
3.6 数据
csv文件
json文件
3.7 爬虫代码
以下代码不包含之前的3种数据清洗方法,每一个数据清洗方法的运行结果都是一样的,所以只需要从3个里面选一个放进chengDuHouse()类里。相应的,你需要在__init__(self)方法中修改你所使用的数据清洗方法的名字,这里用了re方法,剩余2个被注释了。
import urllib3
from bs4 import BeautifulSoup
from lxml import etree
import numpy as np
import pandas as pd
import datetime
import json
import time
import re
import os
class chengDuHouse():
def __init__(self):
self.url_times=32#页面数量
self.step=3#爬取时间步长
self.base_url1="http://www.youtx.com/chengdu/page"
self.base_url2="/"
self.item_list=["所属市","所属区","房屋面积","房屋户型-卧室数量","房屋户型-卫生间数量",
"宜住人数","当日出租价格","租房人","是否收取押金","押金"
,"最短入住天数","总体评价分数"]#项目列表
self.urls=[]#各房源详情页URL
self.text=[]#各房源详情页原始报文
self.labels=[]#各房源详情页数据各标签值
self.get_web_urls()
self.get_url_detail()
'''测试'''
# 写入网页数据
if not os.path.exists("urldata"):
os.mkdir("urldata") # 创建文件夹
for index in range(len(self.text)):
img_file = "urldata/" + str(index) + ".txt" # 路径
with open(img_file, "w+",encoding='utf-8') as f:
f.write(self.text[index])
# 读取网页数据
for index in range(952):
img_file = "urldata/" + str(index) + ".txt" # 路径
with open(img_file, "r",encoding='utf-8') as f:
data=f.read()
self.text.append(data)
self.progress_detailToLabels_Re()
# self.progress_detailToLabels_BeautifulSoup()
# self.progress_detailToLabels_Xpath()
self.outport_txt()
self.outport_excel()
self.outport_csv()
self.outport_json()
#urllib3获取原始网页详情页URL,共32页,31*30+22=952条URL
def get_web_urls(self):
for i in range(self.url_times):
print("step1 progress=[%.4f]" % ((i+1) / self.url_times))
url=self.base_url1+str(i+1)+self.base_url2
http = urllib3.PoolManager()
res = http.request("GET", url)
soup = BeautifulSoup(res.data.decode("utf-8"), 'lxml')
for html_data in soup.findAll(name="li", attrs={"class": "clearfix"}):
data = etree.HTML(str(html_data))
link = data.xpath("string(//div[@class='houseInfo clearfix']/div[@class='house-tit clearfix']/h3/a/@href)")
if (len(link) > 5):
self.urls.append(link)
time.sleep(self.step)
# 获取详情页数据,不包含数据处理,保存为字符串数据
def get_url_detail(self):
for i in self.urls:
print("step2 progress=[%.4f]"%((self.urls.index(i)+1)/len(self.urls)))
http = urllib3.PoolManager()
res = http.request("GET", i)
data=res.data.decode("utf-8")
self.text.append(data)
time.sleep(self.step)
# 根据原始报文获取12个标签值,re+string方法(略)
# 根据原始报文获取12个标签值,BeautifulSoup方法(略)
# 根据原始报文获取12个标签值,Xpath方法(略)
# 导出为txt文件
def outport_txt(self):
with open("成都短租房.txt","w+",encoding='utf-8') as f:
f.seek(0)
for i in self.labels:
f.write(str(i)+"\n")
#导出excel
def outport_excel(self):
data_df = pd.DataFrame(self.labels)
data_df.columns = self.item_list
data_df.index = np.arange(1,len(self.labels)+1)
i = datetime.datetime.now()
writer = pd.ExcelWriter('成都短租房 ' + str(i.year) + "-" + str(i.month) + "-" + str(i.day) + " " + str(i.hour)
+ "-" + str(i.minute) + "-" + str(i.second) + "-" + str(i.microsecond) + '.xlsx')
data_df.to_excel(writer, float_format='%.5f')
writer.save()
# 导出csv
def outport_csv(self):
data_df = pd.DataFrame(self.labels)
data_df.columns = self.item_list
data_df.index = np.arange(1, len(self.labels) + 1)
i = datetime.datetime.now()
data_df.to_csv('成都短租房 ' + str(i.year) + "-" + str(i.month) + "-" + str(i.day) + " " + str(i.hour)
+ "-" + str(i.minute) + "-" + str(i.second) + "-" + str(i.microsecond) + '.csv',
encoding="utf-8-sig")
# 导出json文件
def outport_json(self):
data_df = pd.DataFrame(self.labels,index=np.arange(1,len(self.labels)+1),
columns=self.item_list)
jsonFile=data_df.to_dict(orient='records')
i = datetime.datetime.now()
with open('成都短租房 ' + str(i.year) + "-" + str(i.month) + "-" + str(i.day) + " " + str(i.hour)
+ "-" + str(i.minute) + "-" + str(i.second) + "-" + str(i.microsecond) + '.json',
'w', encoding='UTF-8') as fp:
fp.write(json.dumps(jsonFile, indent=4, ensure_ascii=False))
if __name__=='__main__':
chengDuHouse()
四、数据可视化
我们需要先获取命令行的第一个参数(按照需求,我们只有一个参数),使用sys.argv[1]即可。
需要注意的是,在读取csv文件时,解码方式要和导出文件时的一致,若路径有问题,这里建议先使用open()函数,可以解决编码和路径两个问题,再用panda库读取csv文件,代码如下:
import sys
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
def progress_data(file):
li_temp=[]
li_temp.append(file['所属市'])
li_temp.append(file['所属区'])
li_temp.append(file['房屋面积'])
li_temp.append(file['房屋户型-卧室数量'])
li_temp.append(file['房屋户型-卫生间数量'])
li_temp.append(file['宜住人数'])
li_temp.append(file['当日出租价格'])
li_temp.append(file['租房人'])
li_temp.append(file['是否收取押金'])
li_temp.append(file['押金'])
li_temp.append(file['最短入住天数'])
li_temp.append(file['总体评价分数'])#11
return li_temp
'''多数据柱状图'''
'''
4、能够按照成都市所属区对数据进行分类,算出每个区的房子的平均出租价格、最高出租价格、出租房源总数,并画出对应的柱状图
'''
def shou_new1(data):
# 中文转码
plt.rcParams['font.family'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 数据
li_data = data[1]
name = list(set(li_data)) # 区域列表
b = list(np.zeros(len(name), dtype=np.int)) # 出租房源总数
for i in li_data:
b[name.index(i)] += 1
# 各个区域平均出租价格
ya_money = list(np.zeros(len(name), dtype=np.int))#平均出租价格
max_house_money=list(np.zeros(len(name), dtype=np.int))#最高出租价格
li_score = data[6]
for i in range(len(li_data)):
ya_money[name.index(li_data[i])] += li_score[i]
if(max_house_money[name.index(li_data[i])]<li_score[i]):
max_house_money[name.index(li_data[i])]=li_score[i]
for i in range(len(ya_money)):
ya_money[i] = ya_money[i] / b[i]
y1_data = ya_money
y2_data = max_house_money
y3_data = b
x_data = np.linspace(0, len(y1_data), len(y1_data), dtype=np.int)
# 画布大小
plt.figure(figsize=(12, 8))
# 设置间隔
bar_width = 0.3
# 颜色 绿-g,蓝-b,红-r
plt.bar(x_data, y1_data, bar_width, label="平均出租价格", color='g', alpha=0.5)
plt.bar(x_data + bar_width, y2_data, bar_width, label="最高出租价格", color='orange', alpha=0.5)
plt.bar(x_data + 2 * bar_width, y3_data, bar_width, label="出租房源总数", color='b', alpha=0.5)
# 添加图例
plt.legend()
# 添加X,Y轴标识
plt.xlabel('区域')
# 添加标题
plt.title('成都市所属区平均出租价格、最高出租价格、出租房源总数柱状图')
# 定位x坐标轴图例
plt.xticks(x_data + bar_width, x_data)
# 显示
plt.show()
'''多数据柱状图'''
'''
5、能够按照房屋面积(30平米以下、30-50平米、50-80平米,80平米以上)对数据进行分类,算出每种面积的平均出租价格,最高出租价格,并画出对应的柱状图
'''
def shou_new2(data):
# 中文转码
plt.rcParams['font.family'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 数据
li_data = data[2]
money=data[6]
average=list(np.zeros(4))
average_number=list(np.zeros(4))
max_price=list(np.zeros(4))
for area in range(len(li_data)):
if(li_data[area]<30):
average[0]+=money[area]
average_number[0]+=1
if(max_price[0]<money[area]):
max_price[0]=money[area]
elif(li_data[area]>=30 and li_data[area]<50):
average[1] += money[area]
average_number[1]+=1
if (max_price[1] < money[area]):
max_price[1] = money[area]
elif(li_data[area]>=50 and li_data[area]<80):
average[2] += money[area]
average_number[2]+=1
if (max_price[2] < money[area]):
max_price[2] = money[area]
else:
average[3] += money[area]
average_number[3]+=1
if (max_price[3] < money[area]):
max_price[3] = money[area]
for i in range(4):
average[i]=average[i]/average_number[i]
y1_data = average
y2_data = max_price
x_data = np.linspace(0, 3, 4, dtype=np.int)
# 画布大小
plt.figure(figsize=(12, 8))
# 设置间隔
bar_width = 0.3
# 颜色 绿-g,蓝-b,红-r
plt.bar(x_data, y1_data, bar_width, label="平均出租价格", color='g', alpha=0.5)
plt.bar(x_data + bar_width, y2_data, bar_width, label="最高出租价格", color='orange', alpha=0.5)
# 添加图例
plt.legend()
# 添加X,Y轴标识
plt.xlabel('面积')
# 添加标题
plt.title('房屋面积30平米以下、30-50平米、50-80平米,80平米以上的平均出租价格,最高出租价格柱状图')
# 定位x坐标轴图例
plt.xticks(x_data + bar_width, ["30平米以下","30-50平米","50-80平米","80平米以上"])
# 显示
plt.show()
# 命令:python net_spyder.py 成都短租房.csv
if __name__=='__main__':
file_path=sys.argv[1]
# filename = open('成都短租房 2020-12-27 13-2-28-198491.csv',encoding="utf-8-sig")
filename = open(file_path,encoding="utf-8-sig")
file_data = pd.read_csv(filename)
#参数处理
data = progress_data(file_data)
shou_new1(data)
shou_new2(data)
这里的progress_data()方法时用来提取文件数据的,它将把csv文件数据转换为多为列表,当需要使用某一列数据时用切片的方式提取即可。
若需要在命令行下运行该文件,只需要进入命令提示符输入命令即可,命令格式为:python [py文件绝对路径] [csv数据文件绝对路径]。如果不想输入绝对路径,也可以将两个文件放在一起,这样就只需要输入文件名了,效果如下:
五、转载说明
- 本文内容完全原创,代码完成时间2020.12.28。
- 若要转载本文,请在转载文章末尾附上本文链接:https://blog.csdn.net/qq_35772105/article/details/112149442
- 本文爬虫代码章节唯一MD5:A04F84A1A8687B2DD8F873422E842368。