作者 | AJ Gordon
责编 | 李雪敬
出品 | CSDN(ID:CSDNnews)
当你手头有一件闲置的物品时,最好的办法不是放在家里积灰,而是拿到二手网站上进行拍卖,例如淘宝的闲鱼,京东的拍拍等等。有这方面经验的小伙伴都知道,这两个平台都会自动给出一个最佳的售价。假设我现在有一堆旧手机想二手转让,但又不知道定价多少合适时,最好的办法是上网查查相同配置的二手手机价格后,再进行定价。
京东平台上有专门的二手手机分类,因此选择它作为定价参考。为了方便进行分析,最省时的方法就是直接把数据都抓取到本地,再进行分析。下面一起看看如何用程序的方式获取我们想要的:
由于京东的防爬措施,直接用requests去读取链接是不行的,我的抓取方式是这样的:
首先,获取详情页链接前均是采用selenium进行无界面访问。先获取各手机品牌的ID,再用手机品牌的ID构建二级链接,获取各手机品牌的总页码数Page。再用ID+Page构建三级链接,获取二手手机的详情页链接。最后用requests读取详情页链接,获取具体数据。
# 获取手机型号id
def get_mobile_model_id():
# 浏览器设置
option = webdriver.ChromeOptions()
# 防拦截
option.add_experimental_option('excludeSwitches', ['enable-automation'])
# 不加载图片
option.add_experimental_option("prefs", {"profile.managed_default_content_settings.images": 2})
# 无界面
option.add_argument('--headless')
option.add_argument('--disable-gpu')
#
browser = webdriver.Chrome(options=option)
browser.get('https://list.jd.com/list.html?cat=13765%2C13767')
#获取浏览器当前打开页面的页面源码数据
page_text = browser.page_source
browser.quit()
# 获取手机型号ID
soup = BeautifulSoup(page_text,'lxml')
model_type = soup.find_all('ul',{'class':'J_valueList clearfix'})[1].find_all('li')
for i in model_type:
# 手机型号名称
# type = i.find('a').get_text()
# 手机型号id
type_id = i.find('a')['href'].split('ev=')[-1].split('&cid2=')[0]
redis_db.sadd('jd_mobile_model_id', type_id)
截止至2020年6月22日,总共抓取了2.2万件二手手机商品,27.7万条评论数据。总体上分为三个部分:商品基本信息,店铺评论信息和商品评论信息。
商品基本信息:商品ID,店铺ID,新旧程度,品牌,机型,颜色,内存和单价等等。
店铺评论信息:店铺ID,好评率,总评论数,默认好评数和好评数等等。
商品评论信息:商品ID,用户ID,打分,评论内容,评论时间和下单时间。
接下来,对清洗后的数据进行描述性统计。
根据历史买家下单时间,从图1可以看出每年的峰值都是出现在618,双11,双12这些电商节日,并且每年销售量同比增长了300%。
根据历史买家下单时间,从图2可以看出每天的销量趋势,早上4:00 – 12:00一路飙升,下午12:00 – 16:00保持平缓,傍晚16:00 – 19:00有所下降,晚上19:00 – 22:00回到下午水平,22:00 – 4:00逐渐下降到最低点。
根据图3可知,在售的二手手机颜色主要是金色,黑色和银色,而销售出去的颜色主要是金色,黑色和玫瑰金。虽然在售的红色手机也挺多的,但是销量却很低。
根据图4可知,在售的二手手机价格主要是2000元以下,5000元以上的二手手机也是有不少的。而销量最高的1000元以下的二手手机,价格越高,销量越低。
从图5可以看出来,京东上的手机品牌有19个。苹果的产品数量和销售量远远超过了其它品牌的总和。国产品牌中主要卖的是华为、小米、OPPO和Vivo。其它牌子的机型产品数量少,销售量就也很少。
目前二手手机市场上的品牌主要有苹果,OPPO,Vivo,小米,华为和三星,所以可以了解一下每个品牌销量最好的前三名机型分别是什么。苹果主要是iphone 6s、iphone x和iphone 7。华为主要是mate20、p20和p20pro。
图7是将评论信息中打分分数1和2的归为差评,然后利用jieba分词将文本内容截成若干个词,再用词云图展示。从这个差评词云图可以看出大多数用户对二手手机的不满主要是客服、屏幕和电池这三个原因。首先商家对买家的态度就是“买前是上帝,买后置之不理”,购买前会很热情地接待,但售后又变成另一副嘴脸。其次,原装屏幕和组装屏幕的利润差的是一副二手手机的价格,所以很多二手手机用的都是组装屏幕,效果自然没有新机好。最后,手机用久了就会出现电池老化的问题,耗电量特别快,这个也是部分人更换新手机的原因。
经过前面的数据获取和数据描述之后,对二手手机已经大致了解。现在可以开始对这些二手手机数据进行建模,因为现在是需要进行定价,属于回归模型。
首先,导入需要用到的库和数据。
import pandas as pd
import numpy as np
from scipy.special import boxcox1p,inv_boxcox1p
from sklearn.preprocessing import MinMaxScaler,StandardScaler,RobustScaler
from sklearn.model_selection import GridSearchCV,RandomizedSearchCV
from sklearn.decomposition import PCA
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error,r2_score
from sklearn.metrics import make_scorer
import seaborn as sns
import matplotlib.pyplot as plt
pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', None)
plt.rcParams['font.sans-serif']=['SimHei']
plt.rcParams['axes.unicode_minus'] = False #负号显示
def load_data():
data = pd.read_csv('result.csv',dtype={'skuId':str})
data = data[['skuId','old_new_degree', 'brand', 'model', 'color','version', 'Double_card_machine_type', 'Front_card_machine_type','Rear_camera_pixel','Battery_capacity', 'Running_memory','screen_size', 'price']].drop_duplicates('skuId')
return data
def clean_data(data):
# 缺失值填充
data['model'].fillna('Missing', inplace=True)
data['color'].fillna('Missing', inplace=True)
# 修改字段
data['old_new_degree'] = data.apply(lambda x: str(x['old_new_degree']),axis=1)
data['version'] = data.apply(lambda x:'0' if x['version']=='Missing' else str(x['version']),axis=1)
data['Front_card_machine_type'] = data.apply(lambda x:'0' if x['Front_card_machine_type']=='Missing' else str(x['Front_card_machine_type'][:4].replace('万','')),axis=1)
data['Rear_camera_pixel'] = data.apply(lambda x:'0' if x['Rear_camera_pixel']=='Missing' else str(x['Rear_camera_pixel'][:4].replace('万','')),axis=1)
data['Battery_capacity'] = data.apply(lambda x:'0' if x['Battery_capacity']=='Missing' else str(x['Battery_capacity']),axis=1)
data['Running_memory'] = data.apply(lambda x:'0' if x['Running_memory']=='Missing' else str(x['Running_memory'].replace('GB','')),axis=1)
data['screen_size'] = data.apply(lambda x:'0' if x['screen_size']=='Missing' else str(x['screen_size'].replace('英寸','')),axis=1)
return data
def get_dummy(df):
cols = ['version', 'Front_card_machine_type', 'Rear_camera_pixel', 'Battery_capacity', 'Running_memory','screen_size','old_new_degree', 'brand', 'model', 'color','Double_card_machine_type']
dummy_cols = df[cols].copy()
df = df.drop(cols,axis=1)
dummy_cols = pd.get_dummies(dummy_cols,prefix=cols)
df = pd.concat([df,dummy_cols],axis=1)
return df
def cut_data(df):
# 拆分数据
all_rows = df.shape[0]
## 训练集
X_train = df[:all_rows-100]
y_train = X_train['price'].copy()
y_train = boxcox1p(y_train, 0)
X_train = X_train.drop(['skuId','price'],axis=1)
## 测试集
X_test = df[all_rows-100:]
y_test = X_test[['skuId','price']].copy()
X_test = X_test.drop(['skuId','price'],axis=1)
return X_train,y_train,X_test,y_test
def value_pca(X_train,X_test):
pca = PCA(n_components=0.9)
X_train = pca.fit_transform(X_train)
X_test = pca.transform(X_test)
#variance = pd.DataFrame(pca.explained_variance_ratio_)
#np.cumsum(pca.explained_variance_ratio_)
return X_train,X_test
6) 数据建模
def model(X_train,y_train,X_test,y_test):
# 设置自定义评分函数
def my_custom_loss_func(y_true, y_pred):
return np.sqrt(mean_squared_error(y_true, y_pred))
rmse = make_scorer(my_custom_loss_func, greater_is_better=False) # 以_error结尾的函数,返回一个最小值,越小越好;如果使用make_scorer来创建scorer时,将greater_is_better设为False
# 设置自定义参数
rfr_param_test = {
'n_estimators': [10,20,30,40,50,60],
'max_depth': [5,6,7,8,9,10]}
# 进行网格搜索
grid_search = GridSearchCV(estimator=RandomForestRegressor(), param_grid=rfr_param_test, cv=5, scoring=rmse)
grid_search.fit(X_train,y_train)
print(grid_search.best_params_)
# 预测结果
rft_model = grid_search.best_estimator_
rft_model.fit(X_train, y_train)
y_pred = rft_model.predict(X_test)
y_pred = inv_boxcox1p(y_pred, 0)
# 输出R2值
R2 = r2_score(y_test['price'], y_pred)
print('R2:{}'.format(R2))
# 输出结果
result = pd.DataFrame({
'skuID':y_test['skuID'],
'price_old':y_test['price'],
'price_pred':y_pred})
result.to_csv('Regress_result.csv',index=False,encoding='utf_8_sig')
return result
更多阅读推荐
下一代 IDE:Eclipse Che 究竟有什么奥秘?
窃隐私、放高利贷,输入法的骚操作真不少!
进入编译器后,一个函数经历了什么?
程序员离职后收到原公司 2400 元,被告违反竞业协议赔 18 万