• python 二手车数据分析以及价格预测


    引言

    本文着眼于车辆信息,结合当下较为火热的二手车交易市场数据,对最近二手车的交易价格进行分析以及预测。
    经过前期调研,最终决定通过爬取一些网站的二手车数据和一些公开的数据集,分析交易数据的特征,根据交易特征对二手车交易价格进行分析预测。
    本文主要核心内容:数据爬取数据分析交易价格预测

    一、数据爬取

    1.1 解析数据

    1. 选择二手车交易网站-瓜子二手车。点开官网,点击我要买车。以QQ浏览器为例,点击开发者工具(菜单-工具中),选择Network-DOC过滤。获取头部cookie等相关信息,如下图所示。
      在这里插入图片描述
    2. 通过搜索框(点击开发者工具页面右上角 ‘竖着的三个点’ - Search),输入页面商品名称和属性名称,获取想要数据的位置,方便后续爬取解析,如下图所示。
      在这里插入图片描述
    3. 根据以上获取的信息构造程序的headers以及编写具体数据的爬取代码

    1.2 编写代码爬

    1.2.1 获取详细信息

    # 获取详细数据的 url
    def get_detail_url(url, headers):
        rq = requests.get(url, headers=headers)
        soup = BeautifulSoup(rq.text, 'lxml')
        content = soup.find(class_='carlist clearfix js-top')
        links = content.find_all('a')
        detail_url_list = []
        for link in links:
            detail_url_list.append(f"https://www.guazi.com{link['href']}")
        return detail_url_list
    
    # 获取详情数据
    def get_detail(url, headers):
        rq = requests.get(url, headers=headers)
        soup = BeautifulSoup(rq.text, 'lxml')
        # name
        content = soup.find(class_='product-textbox')
        title = content.find('h1').text
    
        # info (上牌时间为图片)  里程数 排量 变速箱
        info = content.find(class_='assort clearfix')
        span = info.find_all('span')
        if len(span) >= 4:
            info_dic = {'name': title.strip(), 'km': span[1].text, 'displacement': span[2].text, 'gearbox': span[3].text}
        else:  # 某些网页 span数据较少
            info_dic = {'name': title.strip(), 'km': span[1].text, 'displacement': '无', 'gearbox': '无'}
    
        # price 价格单位是万
        price = soup.find(class_='price-num')  # price-num  pricebox js-disprice
        price = price.text
    
        # 销售方
        seller = soup.find(class_='ten')
        seller = seller.find(class_='typebox')
        seller = seller.text
    
        # 燃油类型
        fuel = soup.find_all(class_='td2')  # 很多信息  第十五个是燃油类型 或者 没有信息
        horsepower = "无"
        if len(fuel)>=13:
            horsepower = fuel[12].text
        if len(fuel)>=15:
            fuel = fuel[14].text
        else:
            fuel = "无"
        # fuel
        return info_dic, price, seller, fuel,horsepower
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    1.2.2 数据处理

    1. 数据集补充
      网站上的数据爬取难度较高,且数据量较少,一直爬取容易被封IP,所以这里使用公开赛的一个数据集对数据进行补充------阿里天池的二手车交易价格预测数据集,其数据具体内容如下图所示。
      在这里插入图片描述

    2. 统一数据格式
      网站上爬取的数据与公开赛数据集的数据是不一致的,相对来说公开赛数据集的标签比较多,信息比较详细,且数据类型单一,方便后续处理。所以这里需要对两个数据集进行统一,爬取的数据向公开赛数据靠拢,并将其转换为同一单位(比如fuelType网站上的数据是汉字,而公开赛数据集里的数据为数字,为了较少存储内存以及方便数据的后续处理,在这里将其统一为数字),同时删除无法在网站上爬取的公开赛数据中的标签

    3. 代码

    name = []
    fuelType = []
    gearbox = []
    power = []
    kilometer = []
    seller = []
    price = []
    # 爬取 保存数据
    for page in range(1,3):
        print("************第{}页正在保存**********".format(page)) # 每一页 数量不定
        base_url = 'https://www.guazi.com/sjz/buy/o{}/#bread'.format(page) #
        detail_url = get_detail_url(base_url, headers)  # 所有车辆详细信息链接
        print(len(detail_url))
        for d_url in detail_url:
            print(d_url)
            info_dic, pric, sell, fuel, horsepower = get_detail(d_url, headers)
            name.append(info_dic['name'])  # 名字
            if fuel.find("汽油"):
                fuel = 0
            elif fuel.find("柴油"):
              fuel = 1
            elif fuel.find("液化石油气"):
                fuel = 2
            elif fuel.find("天然气"):
                fuel = 3
            elif fuel.find("混合动力"):
                fuel = 4
            else:
                fuel = 6
            fuelType.append(fuel)           # 燃油类型
            tmp = info_dic['gearbox']
            if tmp.find("手动"):
                tmp = 0
            else:                           # 电动汽车 大多是自动挡
                tmp = 1
            gearbox.append(tmp)             # 变速箱 自动 or 手动
            tmp = re.findall("\d+", horsepower)
            if len(tmp) == 0:
                tmp = 0
            else:
                tmp = int(tmp[0]) * 0.735
            power.append(tmp)                # 功率
            km = re.findall(r"\d+\.?\d*", info_dic['km'])
            if len(km) == 0:
                km = 0
            else:
                km = float(km[0])
            kilometer.append(km)             # 里程数
            if sell.find("私户"):
                sell = 0
            else:
                sell = 1
            seller.append(sell)              # 销售方、
            tmp = re.findall(r"\d+\.?\d*", pric)
            if len(tmp) == 0:
                tmp = 0
            else:
                tmp = float(tmp[0]) * 10000
            price.append(tmp)                # 价格
    
    df = pd.DataFrame({'name'    : name,
                       'fuelType': fuelType,
                       'gearbox' : gearbox ,
                       'power'   : power,
                       'kilometer': kilometer,
                       'seller'  : seller,
                       'price'   : price})
    df.to_csv('guazi.csv')  # 最终保存为csv文件
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    二、数据分析

    2.1 统计分析

    1. 公开数据集为例,这里只显示了其部分标签列(共30列),一共有15万条交易数据。可以看到每一个数据的SaleID都是不同的,从0递增到149999,name标签最大为19万,但是其一共只有15万条数据,想必name的分布也是相对均匀的。model是车型编码,最大为247,数据中车辆种类一共不超过248种。之后标签的max都很小,其分布相对密集。
      在这里插入图片描述
    2. 查看数据类型、缺失和异常值
      在这里插入图片描述
    3. 代码
    #!/usr/bin/env python
    # coding: utf-8
    ## 导入所需包
    import numpy as np
    import pandas as pd
    import warnings
    import matplotlib
    import matplotlib.pyplot as plt
    import seaborn as sns
    from scipy.special import jn
    from IPython.display import display, clear_output
    import time
    import pickle
    warnings.filterwarnings('ignore')
    ## 在Jupyter noteboo显示图像
    get_ipython().run_line_magic('matplotlib', 'inline')
    from sklearn import linear_model  ## 模型预测的
    from sklearn import preprocessing
    from sklearn.svm import SVR
    from sklearn.ensemble import RandomForestRegressor,GradientBoostingRegressor
    from sklearn.decomposition import PCA,FastICA,FactorAnalysis,SparsePCA ## 数据降维处理的
    import lightgbm as lgb
    import xgboost as xgb
    ## 参数搜索和评价的
    from sklearn.model_selection import GridSearchCV,cross_val_score,StratifiedKFold,train_test_split
    from sklearn.metrics import mean_squared_error, mean_absolute_error
    
    #Train_data = pd.read_csv('used_car_train_20200313.csv', sep=' ') # shape: (150000, 40)
    Train_data = pd.read_csv('guazi.csv', sep=',') # shape: (150000, 40)
    
    # In[10]:
    pd.set_option('display.max_rows', 500)
    pd.set_option('display.max_columns', 500)
    pd.set_option('display.width', 1000)
    print(Train_data.describe())
    
    # In[11]:
    Train_data.head()
    
    # In[12]:
    Train_data.info()
    
    # In[13]:
    Train_data.isnull().sum()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    2.2 可视化分析

    在这里插入图片描述

    #!/usr/bin/env python
    # coding: utf-8
    ## 导入所需包
    import numpy as np
    import pandas as pd
    import warnings
    import matplotlib
    import matplotlib.pyplot as plt
    import seaborn as sns
    from scipy.special import jn
    from IPython.display import display, clear_output
    import time
    import pickle
    warnings.filterwarnings('ignore')
    ## 在Jupyter noteboo显示图像
    get_ipython().run_line_magic('matplotlib', 'inline')
    from sklearn import linear_model  ## 模型预测的
    from sklearn import preprocessing
    from sklearn.svm import SVR
    from sklearn.ensemble import RandomForestRegressor,GradientBoostingRegressor
    from sklearn.decomposition import PCA,FastICA,FactorAnalysis,SparsePCA ## 数据降维处理的
    import lightgbm as lgb
    import xgboost as xgb
    ## 参数搜索和评价的
    from sklearn.model_selection import GridSearchCV,cross_val_score,StratifiedKFold,train_test_split
    from sklearn.metrics import mean_squared_error, mean_absolute_error
    
    #Train_data = pd.read_csv('used_car_train_20200313.csv', sep=' ') # shape: (150000, 40)
    Train_data = pd.read_csv('guazi.csv', sep=',') 
    # In[3]:
    plt.hist(Train_data['price'])
    plt.show()
    # In[4]:
    features = Train_data.columns
    #print(features)
    features = [col for col in features if col not in ['price','notRepairedDamage']]
    f = pd.melt(Train_data, value_vars=features)
    g = sns.FacetGrid(f, col="variable",  col_wrap=2, sharex=False, sharey=False)
    g = g.map(sns.distplot, "value")
    # In[ ]:
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    三、价格预测

    爬取的车辆数据集特征数量较少,能否准确预测交易价格?

    3.1 价格趋势分析(特征分析)

    1. 按照爬取数据集的特征选取公开赛数据集的部分特征,然后将其分为训练集和验证集,训练集用于训练,验证集用于验证训练的模型的好坏(模型采用xgboost),然后对爬取数据进行测试,查看预测价格与实际价格的分布是否大致相同。
      以下是结果图,黄色表示为实际价格,蓝色为预测价格*6,由于公开赛数据集的二手车交易价格普遍较低,所以预测出来的价格也相对实际价格较低,这里乘以6是为了方便观察其价格分布。
      在这里插入图片描述
    2. 可以看到实际价格和预测价格的趋势大致相同,再计算以下他们的相关性,如下图。
      在这里插入图片描述
    3. 代码
    ## 导入所需包
    import pandas as pd
    import matplotlib.pyplot as plt
    import lightgbm as lgb
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import mean_absolute_error
    Train_data = pd.read_csv('used_car_train_20200313.csv', sep=' ') # shape: (150000, 40)
    ## 查看列
    #print(Train_data.columns)
    cols = Train_data.columns
    ## 选择特征列
    feature_cols =['fuelType','gearbox','power','kilometer','seller']
    ## 构造训练样本和测试样本
    X_data = Train_data[feature_cols]
    Y_data = Train_data['price']
    def build_model_lgb(x_train,y_train):
        model = lgb.LGBMRegressor(objective='regression',
                                  max_depth = 10,
                                  num_leaves = 1000,
                                  learning_rate=0.1, n_estimators=100,
                                  metric='rmse',  #bagging_fraction = 0.8, feature_fraction = 0.8
                                  )
        model.fit(x_train, y_train)
        return model
    x_train,x_val,y_train,y_val = train_test_split(X_data,Y_data,test_size=0.2)
    # 训练模型
    print('Predict lgb...')
    model_lgb = build_model_lgb(x_train,y_train)
    print('ok!')
    # 测试模型
    val_lgb = model_lgb.predict(x_val)
    val_lgb[val_lgb<0] = 0
    MAE_lgb = mean_absolute_error(y_val,val_lgb)
    print('MAE of val with lgb:',MAE_lgb)
    MAE = mean_absolute_error(y_val,val_lgb*0.5)
    print('MAE:',MAE)
    
    Test = pd.read_csv('guazi.csv', sep=',') # shape
    test_price = model_lgb.predict(Test[feature_cols])
    #mean_absolute_error(Test['price'],test_price)
    plt.plot(test_price*6)
    plt.plot(Test['price'])
    plt.show()
    data = pd.DataFrame({'predict':test_price,'gt':Test['price']})
    t2=data.corr()
    print(t2)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    3.2 价格预测

    1. 通过对价格趋势的分析,有理由相信爬取的有限的这几个特征:['fuelType','gearbox','power','kilometer','seller'],在一定程度上影响着车辆的价值。同时由于公开赛数据集的价格与爬取数据集的价格相差很大,所以没有办法使用公开赛数据集对爬取数据集做定量分析。只能爬取跟多的二手车数据集对二手车交易价格做定量的预测。

    2. 相对于价格趋势分析,这里同样使用的是lgb模型,但是为了价格预测的准确性,将学习率降低为0.01,迭代次数增加为十万次。其测试误差如下(MAE-平均绝对误差7755):
      在这里插入图片描述

    3. 查看实际价格与预测价格的折线图:
      在这里插入图片描述

    4. 代码

    #!/usr/bin/env python
    # coding: utf-8
    ## 导入所需包
    import numpy as np
    import pandas as pd
    import warnings
    import matplotlib
    import matplotlib.pyplot as plt
    import time
    import pickle
    warnings.filterwarnings('ignore')
    ## 在Jupyter noteboo显示图像
    get_ipython().run_line_magic('matplotlib', 'inline')
    from sklearn import preprocessing
    import lightgbm as lgb
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import mean_squared_error, mean_absolute_error
    
    Train_data = pd.read_csv('guazi_more.csv', sep=',') # shape: (150000, 40)
    #TestA_data = pd.read_csv('used_car_testB_20200421.csv', sep=' ') # shape: (50000, 39)
    Train_data.describe()
    
    # In[35]:
    ## 查看列
    #print(Train_data.columns) 
    cols = Train_data.columns
    ## 选择特征列
    feature_cols =['fuelType','gearbox','power','kilometer','seller']
    ## 构造训练样本和测试样本
    X_data = Train_data[feature_cols]
    Y_data = Train_data['price']
    x_train,x_val,y_train,y_val = train_test_split(X_data,Y_data,test_size=0.2)
    
    # In[62]:
    def build_model_lgb(x_train,y_train):
        model = lgb.LGBMRegressor(objective='regression',
                                  max_depth = 10,
                                  num_leaves = 1000,
                                  learning_rate=0.01, n_estimators=100000, # 60000 7千   # 
                                  metric='rmse',  #bagging_fraction = 0.8, feature_fraction = 0.8
                                  )
        model.fit(x_train, y_train)
        return model
    
    # In[63]:
    # 训练模型
    print('Predict lgb...')
    model_lgb = build_model_lgb(x_train,y_train)
    print('ok!')
    
    # In[64]:
    # 测试模型
    val_lgb = model_lgb.predict(x_val)
    #val_lgb[val_lgb<0] = 0
    MAE_lgb = mean_absolute_error(y_val,val_lgb)
    print('MAE of val with lgb:',MAE_lgb) 
    
    # In[68]:
    x = np.arange(0,len(y_val),1)
    plt.plot(x,val_lgb) # scatter
    plt.plot(x,y_val)
    plt.figure(figsize=(30,10),dpi=36)
    plt.show()
    
    # In[69]:
    data = pd.DataFrame({'predict':val_lgb,'gt':y_val})
    t2=data.corr()
    print(t2)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
  • 相关阅读:
    造个Python轮子,实现根据Excel生成Model和数据导入脚本
    ES6 入门教程 19 Generator 函数的语法 19.7 yield星号表达式
    用代码玩转迷你图:手把手教你用编程语言打造简洁易读的数据图表!
    剑指Offer05-替换空格
    根据WebService接口地址获取接口定义文件(wsdl文件)
    大数据信用报告查询应该选什么样的平台?
    Java中的正则表达式是什么该怎么用?
    Javascript的HTML DOM (文档对象模型)
    MathType2024苹果版数学公式编辑器
    庆阳市宁县副县长李晓刚一行调研珈和科技
  • 原文地址:https://blog.csdn.net/qq_38204686/article/details/132839248