Python爬虫实战 [成都短租房项目]

一、项目需求

  1. 目标URL:http://www.youtx.com/chengdu/
  2. 要爬取的数据项:“所属市”,“所属区”,“房屋面积”,“房屋户型-卧室数量”,“房屋户型-卫生间数量”,“宜住人数”,“当日出租价格”,“租房人”,“是否收取押金”,“押金”,“最短入住天数”,“总体评价分数”,共12项。
  3. 能够处理数据中缺失的部分,并进行合理的填充。
  4. 将所有爬取的数据按字段存入一个csv文件。
  5. 数据清洗部分要求使用3种不同的方法。
  6. 能够按照成都市所属区对数据进行分类,算出每个区的房子的平均出租价格、最高出租价格、出租房源总数,并画出对应的柱状图。
  7. 能够按照房屋面积(30平米以下、30-50平米、50-80平米,80平米以上)对数据进行分类,算出每种面积的平均出租价格,最高出租价格,并画出对应的柱状图。
  8. 源程序命名为net_spyder.py,该程序可以用以下方式运行: python net_spyder.py xxx.csv,其中xxx.csv是保存爬取数据的csv文件名。

二、需求分析

  1. 目标URL(http://www.youtx.com/chengdu/)是一个列表页,显然,我们可以通过列表页获取所有详情页的URL。
  2. 我们在获取所有的详情页的URL后,可以很轻松的获得原始报文(异步加载前)。若我们有数据项采用了异步加载,那么我们考虑3种方法:
    [1]在css和js代码中分析是否包含该内容。
    [2]构造并发送ajax虚假请求以欺骗服务器再次发送数据。
    [3]配合selenium模拟浏览器操作获取网页完全加载后的数据。
  3. 对数据缺失部分,采取0值填充,对于"是否收取押金","押金"这两项关联数据,需要判断数据关联合法性。对于其他字段,需要删除多余的字符串。
  4. 数据清洗方法可以使用:
    [1]re+string
    [2]BeautifulSoup
    [3]Xpath
  5. 数据可视化采用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数据文件绝对路径]。如果不想输入绝对路径,也可以将两个文件放在一起,这样就只需要输入文件名了,效果如下:
在这里插入图片描述
在这里插入图片描述

五、转载说明

  1. 本文内容完全原创,代码完成时间2020.12.28。
  2. 若要转载本文,请在转载文章末尾附上本文链接:https://blog.csdn.net/qq_35772105/article/details/112149442
  3. 本文爬虫代码章节唯一MD5:A04F84A1A8687B2DD8F873422E842368。
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

魔菲赫伯特

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值