Python数据分析-二手车数据用于机器学习二手车价格预测

更新

时隔1年半,终于战胜了懒惰,更新了二手车价格预测的数据处理部分,后续的模型训练部分可能也在秘密基地更新,懂得都懂。
在这里插入图片描述

前言

    浑浑噩噩的研一过去了,不知道自己都学了些什么东西,所以痛定思痛,想要做一些东西,正好看到阿里天池有一个二手车价格预测的问题,想自己搞一个数据集,再写个机器学习的模型预测一下二手车价格,因此有了这篇blog。

一、配置数据库

    我采用的数据库是MongoDB非关系型数据库,MongoDB将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB文档类似于JSON 对象。字段值可以包含其他文档,数组及文档数组。

    采用MongoDB的好处在哪呢,它将数据存储为Json格式,与Pandas中的Dataframe数据类似,在之后的数据处理和探索性数据分析时,会有很大的方便。
    下载安装好数据库后,配置就很简单了,在项目列表里新建一个名为config.py的python文件,配置MongoDB_URL和要存入的数据库即可。

config.py

MONGO_URL = 'localhost'
MONGO_DB = 'used_car'

二、目标分析

    我们要获取的是二手车的数据,首先去到官网:某车官网
人人车官网页面
    我们应该找到买车页面,因为要进行预测二手车的价格,所以需要买车的一些数据和它的售价。点进买车的页面,我们可以看到有城市列表,下面有每辆车的图片和简介,点击图片即可查看车辆的详细信息,在页面的最底部有翻页按钮。
    那么,我们大致可以明确需要做些什么了。

  1. 首先,我们需要获取一个城市列表,对每座城市二手车的数据都进行一个爬取。
  2. 其次,我们需要获取每个城市的二手车页面有多少页的信息,有些城市较少,最多为50页。
  3. 最后,我们需要将页面中的每一辆二手车的链接获取到。

    这样,我们就算完成了第一步,获取到了二手车网站中,所有城市的所有二手车页面的链接,并将它们存入数据库中。

在这里插入图片描述
在这里插入图片描述

三、页面解析-获取城市链接

    在分析完我们的目标后,我们开始对页面进行分析。
    鼠标右键检查元素或F12,点击绿色框的按钮,到页面上选中“北京”,我们可以看到,被橙色框框中的地方,变成了选中状态,这说明我们要找的城市信息在这个区域。
在这里插入图片描述
    进一步寻找,我在 div标签中的 class="area-city-letter"的这个标签下找到了城市列表。
在这里插入图片描述
    找到城市列表所在的标签,我们开始写代码。首先要获取整个页面的文本信息。调用requests库,获取传过来的url的页面信息。

import requests
def get_page(self, url):
    try:
        res = requests.get(url)
        if res.status_code == 200:
            # print(res.text)
            return res.text
    except:
        print("url出错了!")

    接下来,我们写一下获取城市列表的方法。通过分析页面,我们可以知道,城市的名称和对应的链接都在class为"area-city-letter"的div标签中,这里我们直接用BeautifulSoup进行页面解析,BeautifulSoup可以直接从html的text中获取对应的标签,找到name=“div”, class="area-city-letter"所有的div标签,再从这个div标签中找到所有的 a 链接,a标签中的href直接调用方法a.get(“href”)就可以获取,城市的名字直接采用字典读取数据的方式即可a[“rrc-event-expand-tag_value”]。

from bs4 import BeautifulSoup
def get_city_url(self):
	# 用于存放每个城市对应的url和城市名称
    url_list = []
    # 用于存放每个城市页面的url、城市名称、每个城市页面的页数,其中是字典格式
    url_dict = []
    # page_text获取页面内容,调用上面写好的get_page()方法
    page_text = self.get_page(self.url)
    # 解析页面
    soup = BeautifulSoup(page_text, 'lxml')
    # 查找页面中div的class="area-city-letter"的所有标签
    div = soup.find_all(name="div", attrs={"class": "area-city-letter"})
	# 再将查找到的div标签内容重新解析,这里要将上面的div进行一个字符串转换,因为BeautifulSoup方法里传参为str格式
    soup2 = BeautifulSoup(str(div), 'lxml')
    # 查找div标签中的所有超链接标签
    urls = soup2.find_all(name="a")
    # 将城市链接和城市名称存入list
    for u in urls:
        url_list.append((u.get('href'), u["rrc-event-expand-tag_value"]))
    print("共抓取到"+str(len(url_list))+"条url链接!")
    # 用于存放具体城市买车的链接和城市名称
    city_url_list = []
    for u in url_list:
        city_url_list.append(["https://www.renrenche.com" + u[0] + "ershouche/", u[1]])
    # 将最终的链接、城市名称、城市页数 存入结果集
    for u in city_url_list:
    	# 这里是另一个方法,用来获取每个城市页面中一共有多少页买车页面,达到翻页的效果
        pagecount = self.get_pagecount(u[0])
        url_dict.append({"city_url": u[0], "city_name": u[1], "page_count": pagecount})
        u.append(pagecount)
    # 返回列表和字典
    return city_url_list, url_dict

    这里加上获取每个城市页面的页数代码。页码的解析和找城市列表类似的,我们会找到页码都存在一个ul标签下,它的class为"pagination js-pagination",在其中的li标签里。

def get_pagecount(self, url: str):
    try:
    	# 先解析页面
        page_text = self.get_page(url)
        soup = BeautifulSoup(page_text, 'lxml')
        ul = soup.find_all(name="ul", attrs={"class": "pagination js-pagination"})
        soup2 = BeautifulSoup(str(ul), 'lxml')
        pagecount = soup2.find_all(name="li")
        # 这里解释一下为什么还要再解析pagecount[-2],我发现最大页码是在倒数第二个li标签下的,页码数字在其a标签下,因此有了进一步的解析
        page = BeautifulSoup(str(pagecount[-2]), 'lxml')
        a_content = page.find_all(name='a')[0].string
        # print(a_content)
        # for item in page.find_all("a"):
        #     print(item.string)
        return int(a_content)
    except Exception as res:
        return 0

    至此,我们已经可以获取到城市链接、城市名称、城市页面的页数,我们把这些数据存到数据库中,方便以后的调用。

from spider_renren.get_city_data.config import *
import pymongo

# 配置数据库
client = pymongo.MongoClient(MONGO_URL)
db = client[MONGO_DB]

def save_city_url(self, table, city_url_dict):
    print("save to db[city_url]")
    data_len = len(city_url_dict)
    for item in city_url_dict:
        db[table].save(item)
    print("共存入"+str(data_len)+"条数据到"+table+"数据库!")

    这里,我为了重复使用一些方法,我做了一个小小的封装,将存入数据库写成了一个类,将获取城市列表和页码写成了一个类,最后在另一个文件中,进行调用。以下是这几部分的代码汇总。
config.py

MONGO_URL = 'localhost'
MONGO_DB = 'used_car'

sava_to_db.py

from spider_renren.get_city_data.config import *
import pymongo

client = pymongo.MongoClient(MONGO_URL)
db = client[MONGO_DB]

class SaveData:
    def save_city_url(self, table, city_url_dict):
        print("save to db[city_url]")
        data_len = len(city_url_dict)
        for item in city_url_dict:
            db[table].save(item)
        print("共存入"+str(data_len)+"条数据到"+table+"数据库!")

spider_city_url.py

import requests
from bs4 import BeautifulSoup


class Get_City_Url:
    def __init__(self, url):
        self.url = url
    """
        :param url:str, 用来抓取页面文本的url
        :return res.text:str, 返回页面文本
    """
    def get_page(self, url):
        try:
            res = requests.get(url)
            if res.status_code == 200:
                # print(res.text)
                return res.text
        except:
            print("url出错了!")

    def get_city_url(self):
        url_list = []
        url_dict = []
        page_text = self.get_page(self.url)
        soup = BeautifulSoup(page_text, 'lxml')
        div = soup.find_all(name="div", attrs={"class": "area-city-letter"})
        soup2 = BeautifulSoup(str(div), 'lxml')
        urls = soup2.find_all(name="a")
        for u in urls:
            url_list.append((u.get('href'), u["rrc-event-expand-tag_value"]))
        print("共抓取到"+str(len(url_list))+"条url链接!")
        city_url_list = []
        for u in url_list:
            city_url_list.append(["https://www.renrenche.com" + u[0] + "ershouche/", u[1]])
        for u in city_url_list:
            pagecount = self.get_pagecount(u[0])
            url_dict.append({"city_url": u[0], "city_name": u[1], "page_count": pagecount})
            u.append(pagecount)
        return city_url_list, url_dict

    def get_pagecount(self, url: str):
        try:
            page_text = self.get_page(url)
            soup = BeautifulSoup(page_text, 'lxml')
            ul = soup.find_all(name="ul", attrs={"class": "pagination js-pagination"})
            soup2 = BeautifulSoup(str(ul), 'lxml')
            pagecount = soup2.find_all(name="li")
            page = BeautifulSoup(str(pagecount[-2]), 'lxml')
            a_content = page.find_all(name='a')[0].string
            # print(a_content)
            # for item in page.find_all("a"):
            #     print(item.string)
            return int(a_content)
        except Exception as res:
        	# 有些城市没有二手车数据,会抛出异常,我们做返回0的操作,方便以后的区分
            return 0

start_spider.py

from spider_renren.get_city_data.save_to_db import *
from spider_renren.get_city_data.spider_city_url import Get_City_Url

if __name__ == '__main__':

    gcu = Get_City_Url("https://www.renrenche.com/bj/ershouche/?&plog_id=bb97445b3ce898dec2c3c0f4f4f85ea3")
    city_url_list, city_url_dict = gcu.get_city_url()

    print(city_url_list, city_url_dict)
    print(len(city_url_list), len(city_url_dict))

    save_city_url = SaveData()
    save_city_url.save_city_url("city_url", city_url_dict)

    最后,我们看一下获取到的数据的样子~ 可以看到,在数据库中,数据形式为字典,每一条数据对应key-value对,其中_id是数据库自行生成的,city_url、city_name、page_count是我们通过获取存入的,接下来我们要将city_url和page_count进行一个拼接,这样就可以得到所有城市所有页码的链接了。
在这里插入图片描述

四、拼接城市链接和页码(获取所有城市所有页码的链接)

    在上一章节我们成功的获取了城市链接、城市名称和城市页面的页数,要想获取二手车网站内的所有二手车数据,首先要将每个城市的页面都获取到,一个城市的页面中可能会有几十页数据,因此,而不同页码对应了不同的链接,因此我们将城市链接和页码进行一个拼接,这样就可以成功得到所有城市的所有二手车页面了。
    以北京市为例,我们选择北京市的二手车页面,它的url是:https://www.renrenche.com/bj/ershouche/p1/ ,其中结尾的p1代表了这是北京市二手车页面的第一页,我试着把p1改成了p10,结果就跳转到了北京市二手车页面的第十页。因此,想要获得一个城市二手车的所有页面,只需要在城市链接后加上对应的页码就可以了。
    拼接链接的代码十分简单,从数据库中调取数据,直接拼接好再存入数据库中就可以了,以下是我全部代码。
get_page_url.py

import pandas as pd
import pymongo
from spider_renren.get_city_data.config import *
from spider_renren.get_city_data.save_to_db import *


# 连接数据库
client = pymongo.MongoClient(MONGO_URL)
db = client[MONGO_DB]
# 读取数据库中的“city_url”表
table = db['city_url']


def get_page_url():
	# 将数据表中的数据转化成pandas中DataFrame格式,方便数据处理
    data = pd.DataFrame(list(table.find()))
    # 不读取“_id”列
    data = data[["city_url", "city_name", "page_count"]]
    # 筛选出页码大于0得到数据
    data = data[data["page_count"] > 0]
    # 存放每个页码对应页面的url和城市名称
    page_dict = []
    for i in data.index:
        for p in range(1, data.loc[i][2] + 1):
        	# 进行拼接和存放 
            page_dict.append({"page_url": data.loc[i][0] + 'p' + str(p), "city_name": data.loc[i][1]})
    return page_dict


if __name__ == '__main__':
    # print(get_page_url())
    # 存入数据库中
    save_page_url = SaveData()
    save_page_url.save_page_url("page_url", get_page_url())

五、获取每辆二手车页面url

    在上一章节,我们成功获取到了每个城市所有页码的url,接下来就是爬取每个页面中,每辆二手车对应的链接。
在这里插入图片描述
    检查页面源码,发现每辆车的链接是再一个ul标签中的li标签里。
在这里插入图片描述
    具体的代码就直接放了,和上面爬链接的方法是一样的。

import pandas as pd
import pymongo
import requests
import json
from bs4 import BeautifulSoup
from spider_renren.get_city_data.config import *
from spider_renren.get_city_data.save_to_db import *


# 连接数据库
client = pymongo.MongoClient(MONGO_URL)
db = client[MONGO_DB]
table = db['page_url']


def get_page_url():
	# 读取上一节爬取到的每个城市对应的所有页面url
    data = pd.DataFrame(list(table.find()))
    data = data[["page_url", "city_name"]]
    page_list = []
    for i in data.index:
        page_list.append([data.loc[i][1], data.loc[i][0]])
    return page_list

# 解析网页
def get_page(url):
    try:
        res = requests.get(url)
        if res.status_code == 200:
            # print(res.text)
            return res.text
    except:
        print("url解析错误,url:"+url)

# 开始获取每辆车对应的url
def get_url(url):
    try:
        url_list = []
        page_text = get_page(url)
        soup = BeautifulSoup(page_text, 'lxml')
        ul = soup.find_all(name="ul", attrs={"class": "row-fluid list-row js-car-list"})
        soup2 = BeautifulSoup(str(ul), 'lxml')
        urls = soup2.find_all(name="a")
        # 抓取单个车的url list
        for u in urls:
            url_list.append("https://www.renrenche.com"+u.get('href'))
        # 这里对链接进行了以下排序,因为在爬取过程中发现,有一些广告是穿插在车辆信息里的,所以多了一些无效链接,这些链接有明显的特征,链接长度小于正常车辆链接。
        url_list.sort()
        # 取排序后链接的中间链接长度作为正确连接的长度进行判断
        url_len = len(url_list[len(url_list)//2])
        new_url_list = []
        # for u in url_list:
        #     print(u)
        # 获取正确链接
        for i in range(len(url_list)):
            if len(url_list[i]) == url_len:
                new_url_list.append(url_list[i])
        url_dict = []
        # print(len(url_list))
        for u in new_url_list:
            url_dict.append({"every_car_url": u})
        save_every_car_url = SaveData()
        save_every_car_url.save_every_car_url("every_car_url", url_dict)
    except:
        print("error")


if __name__ == '__main__':
    page_list = get_page_url()
    # 开始爬每个页面的每个车辆连接
    # 这里博主遇到了问题,在爬取过程中,频繁访问网站,再加上网络有时连接不好,所以经常会跳出错误,导致程序中断,因此我在爬的时候用索引作为迭代依据,输出每次的索引和链接,在出错的时候,将range的起始值做一次手动更新,效率较低,好在出错数据不多。
    for i in range(0, len(page_list)):
        print("index:", i)
        print("开始"+page_list[i][0]+"爬取,链接:"+page_list[i][1])
        get_url(page_list[i][1])

六、获取每辆车的详细信息

    终于到了数据获取的最后一个阶段,点开一辆车的链接,进到页面我们发现有好多关于车辆的信息,在这里,我选取了我认为对二手车价格预测有用的一些信息。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
    以上三张图片中的内容就是我选取的信息,对其一一解析。
    我将解析过程中需要注意的事情放在代码的注释中。
get_detail_from_car.py

import pandas as pd
import pymongo
import time
from multiprocessing import Pool
import requests
from bs4 import BeautifulSoup
from spider_renren.get_city_data.config import *
from spider_renren.get_city_data.save_to_db import *

# 连接数据库
client = pymongo.MongoClient(MONGO_URL)
db = client[MONGO_DB]
table = db['every_car_url']


def get_every_car_url():
    data = pd.DataFrame(list(table.find()))
    data = data["every_car_url"]
    every_car_url_list = []
    for i in data.index:
        every_car_url_list.append(data.loc[i])
    return every_car_url_list


def get_page(url):
    try:
        # time.sleep(1.5)
        res = requests.get(url)
        if res.status_code == 200:
            # print(res.text)
            return res.text
    except:
        time.sleep(10)


def get_cars_detail_info(url):
    table_dict = {}
    table_dict["url"] = url
    try:
        page_text = get_page(url)
        # 解析页面
        soup = BeautifulSoup(page_text, 'lxml')
        # 这里爬取车辆售价,寻找售价在页面的哪个标签中的方法与之前一样
        right_container = soup.find_all(name="div", attrs={"class": "right-container"})
        middle_content = BeautifulSoup(str(right_container), 'lxml').find_all(name="div", attrs={"class": "middle-content"})
        price = BeautifulSoup(str(middle_content), 'lxml').find(name="p", attrs={"class": "price detail-title-right-tagP"})
        # 这里做一个判断,有些时候网页加载或者链接错误,导致爬出来的数据为None
        if price is not None:
            price = str(price.contents[0]).replace('\n', '').replace(' ', '')
            table_dict["售价"] = price
            
        # 这里爬取车辆的新车售价
        new_car_price_span = BeautifulSoup(str(middle_content), 'lxml').find(name="div", attrs={
            "class": "new-car-price detail-title-right-tagP"})
        new_car_price = BeautifulSoup(str(new_car_price_span), 'lxml').find(name="span")
        if new_car_price is not None:
            new_car_price = str(new_car_price.get_text()).replace('\n', '').replace(' ', '')
            table_dict["新车售价"] = new_car_price[:-1]
		
		# 这里爬取车辆行驶里程
        mileage_li = soup.find_all(name="li", attrs={"class": "kilometre"})
        mileage = BeautifulSoup(str(mileage_li), 'lxml').find(name="strong")
        if mileage is not None:
            mileage = str(mileage.get_text()).replace('\n', '').replace(' ', '')
            # print(mileage[:-3])
            table_dict["行驶里程"] = mileage[:-3]
		
		# 这里爬取车牌所在地
        city_div = soup.find_all(name="div", attrs={"class": "licensed-city"})
        city = BeautifulSoup(str(city_div), 'lxml').find(name="strong")
        if city is not None:
            city = str(city.get_text()).replace('\n', '').replace(' ', '')
            table_dict["车牌所在地"] = city
		
		# 这里爬取图2车辆基础信息的内容
        info_ul = soup.find_all(name="ul", attrs={"class": "module-base-content clearfixnew"})
        ul_soup = BeautifulSoup(str(info_ul), 'lxml')
        for index, li in enumerate(ul_soup.find_all('li')):
            li_soup = BeautifulSoup(str(li), 'lxml')
            base_key = li_soup.find(name="span", attrs={"class": "module-base-content-key"})
            base_value = li_soup.find(name="span", attrs={"class": "module-base-content-value"})
            if base_key is not None and base_value is not None:
                base_key = str(base_key.contents[0]).replace('\n', '').replace(' ', '')
                base_value = str(base_value.contents[0]).replace('\n', '').replace(' ', '')
                table_dict[base_key] = base_value
		
		# 这里爬取最后一部分,车辆配置参数
        js_parms_table = soup.find_all(name="div", attrs={"id": "js-parms-table"})
        soup2 = BeautifulSoup(str(js_parms_table), 'lxml')
		# 这些参数都在页面的table标签中,对table进行行和单元格的迭代,依次找出想要的信息
        for index, table in enumerate(soup2.find_all('table')):
            soup3 = BeautifulSoup(str(table), 'lxml')
            for index2, tr in enumerate(soup3.find_all("tr")):
                if index2 != 0:
                    tds = tr.find_all('td')
                    for index3, td in enumerate(tds):
                        soup_key = BeautifulSoup(str(td), 'lxml').find(name="div", attrs={"class": "item-name"})
                        soup_value = BeautifulSoup(str(td), 'lxml').find(name="div", attrs={"class": "item-value"})
                        if soup_key is not None and soup_value is not None:
                            dict_key = str(soup_key.contents[0]).replace('\n', '').replace(' ', '')
                            dict_value = str(soup_value.contents[0]).replace('\n', '').replace(' ', '')
                            table_dict[dict_key] = dict_value
        save_car_detail = SaveData()
        save_car_detail.save_car_detail("cars_info", table_dict)
    except:
        print("咱也不知道啥问题!我擦!")


def main(i):
    try:
        get_cars_detail_info(every_car_url_list[i])
    except:
        print("问题url:"+str(every_car_url_list[i]))
        print("main里出问题了!我擦!")


every_car_url_list = get_every_car_url()
failed_url = []
if __name__ == '__main__':
	# 每辆车的链接大概有不到10万条数据,因此在爬虫的过程中,会耗费大量的时间,这里做了一个简单的多线程,可以加快爬虫速度,Pool()里穿的是线程数量,一般笔记本跑就不要太大,太大会烧机器,卡死。。
    pool = Pool(4)
    try:
        pool.map(main, [i for i in range(0, len(every_car_url_list))])
    except:
        print("多线程出问题了!我擦!")
    pool.close()
    pool.join()

七、将MongoDB里的数据导出成excel文件

    放在数据库里的数据看起来不那么美观和清晰,因此,可以将数据库中的文件,导出成excel文件。

import pandas as pd
import pymongo
from spider_renren.get_city_data.config import *
from spider_renren.get_city_data.save_to_db import *

# 连接数据库
client = pymongo.MongoClient(MONGO_URL)
db = client[MONGO_DB]
table = db['cars_info']

# def get_table():
data = pd.DataFrame(list(table.find()))
data.to_excel("cars_info.xlsx", index=False)

【注意】如果想将数据全部导出成excel文件,可能会打不开,因为这是一个10万*200维的数据表,数据量还是很大的,在做数据处理的时候,读成DataFrame格式的数据也会占据不小的内存。所以可以进行分步读取,每次存一部分,先用小部分数据做数据预处理和探索性数据分析,最后流程熟悉后,再进行处理。

八、常见问题总结

  1. 在页面分析时,有些同学找不到想要的数据在哪里,我一般都是先在Elements下,用选取按钮找到大致范围,随后在其中寻找数据。在这里插入图片描述

  2. Elements中找不到怎么办?有些数据是动态存储的,开发者将这些数据用JavaScript进行渲染,所以在Elements页面中找不到这部分数据,但是它们都在Network中,以json的数据格式存储,我们可以切换到Network下,清楚当前的信息,进行页面刷新,这时会在Name栏出现很多条链接数据,点击某行,在右侧的Preview中会显示这个链接的预览情况,找到你想要的数据连接,点击Headers,你可以得到Request URL,通过这个连接就可以得到Elements中找不到的数据了。
    在这里插入图片描述
    在这里插入图片描述

  3. 代码的规范性。
         博主在写代码的时候就不太注意规范性,有些变量和方法的命名随心所欲,导致经常会忘记,降低了编程效率。其次,有些方法可以进行重复调用的时候,最好写一个类将它封装起来,这样减少了许多工作量。

  4. 抛出异常!
        这是我最想说的一点,在写代码的时候,一定要加上try,except,不加会导致什么后果呢?比如解析网页的时候遇到网络连接断开,啪!你的程序爆红了,终止运行了,当你爬取很大的数据时,你都不知道在哪里断开的,这个时候就得重新新建一个数据表,重新跑代码,费时费力。

九、结语

    spider的知识还需要进一步学习,有许多更加便捷的方法我还不知道,在一步一步的学习中,你会发现,有些困扰你很久的东西,突然看到哪篇博客就能有一个更好的方法去解决。
接下来想用这些数据做一个二手车价格预测的模型,在建模之前肯定还会有一个数据预处理和探索性数据分析,应该还会再出两篇博客,不过我实在是太懒了,一次性敲这么多字也好费精力鸭,不知道什么时候会在更新。
    最后,如果我的分析和代码出现什么问题,或者有更好的解决方法,欢迎各位同学进行指正,相互学习。

未完待续…

评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值