背景:因营销需求,对家具市场近期活动有明确的了解,分析出近期对手上新的产品&活动力度。
数据:天猫林氏木业旗舰店各类家具商品的详细信息,包括但不限于产品详细信息,价格,销量,评论量,图片。
目标:编写爬虫框架,定期获取目标数据,导入数据库。分析出热销产品的关键因素,活动折扣力度。为图片打上标签,以便后期对比划分,调整节日期间活动。
【一】思路
1、爬虫:观察目标网址检查后发现数据信息通过js隐藏,需要导出其实际跳转网址进行爬取。
2、分析:清洗数据&特征工程后,分析出热销产品的关键点。结合活动力度找出提升销量的关键因素。
3、分类:直接使用百度AI。
【二】项目流程
【三】爬虫代码
1.获取商品的主要分类链接
分类子分类
直接获取分类的链接,生成表格
主链接表格2.根据主链接爬取异步加载的链接
# 提取主分类链接的ID
def ID (X):
import pandas as pd
import re
patter = 'category-(.*).htm?'
A = []
for i in range(len(X['子分类链接'])):
A.append(re.findall(patter, X['子分类链接'][i]))
A=pd.DataFrame(pd.Series(A),columns=['ID'])
X = pd.concat([X,A],axis = 1)
return(X)
# 提取网址中的编码
def Encode (X):
import pandas as pd
A = []
for i in range(len(X['子分类'])):
a = X['子分类'][i].encode('gb2312')
a = str(a)
a = a.replace('\\x','%')
a = a.strip("b'")
A.append(a)
A = pd.DataFrame(pd.Series(A), columns=['url编码'])
X = pd.concat([X, A], axis=1)
return (X)
再将ID与编码拼接成异步链接网址
# 生成对应分类的异步加载链接
def URL (X):
import pandas as pd
A = []
for i in range(len(X['ID'])):
a = 'https://lshmy.tmall.com/i/asynSearch.htm?_ksTS=1537406700485_954&\
callback=jsonp955&mid=w-14434271715-0&wid=14434271715&path=/category-[ID]\
.htm&search=y&keyword=[cat]&scene=taobao_shop&catId=[ID]&scid=[ID]'
b = a.replace('[ID]',X['ID'][i][0])
b = b.replace('[cat]',X['url编码'][i])
A.append(b)
A = pd.DataFrame(pd.Series(A), columns=['异步加载'])
X = pd.concat([X, A], axis=1)
return (X)
异步链接
3.获取所有子分类链接
翻页考虑到翻页情况,在需要翻页的网址后面写上page。
# 生成子分类的链接, X = 异步链接
def URL_classify (X) :
import pandas as pd
import requests
from lxml import etree
url_classify = []
for i in X['异步加载']:
url_classify.append(i)
r = requests.get(i)
A = etree.HTML(r.text)
B = A.xpath('/html/body/div/div[3]/div/a/text()')
if B != []:
B.remove('1')
if '上一页' in B:
B.remove('上一页')
if '下一页' in B:
B.remove('下一页')
if B != []:
for j in B:
C = i + '&pageNo=' +j
url_classify.append(C)
print('--' * 20)
url_classify = pd.DataFrame(pd.Series(url_classify), columns=['分类链接'])
return (url_classify )
4.获取产品的链接&ID
根据所有子类的链接,获取所有产品的详细地址。
#根据子分类的链接,爬取货物的详细链接,X = 分类链接
def URL_detailed (X):
import requests
from lxml import etree
import pandas as pd
import re
url_2 = []
for i in X['分类链接']:
r = requests.get(i)
A = etree.HTML(r.text)
B = A.xpath('/html/body/div/div[3]/div/dl/dd/a/@href')
while '\\"javascript:;\\"' in B:
B.remove('\\"javascript:;\\"')
url_2.append(B)
print ('-'*40)
url_detailed = []
for i in url_2 :
for j in i:
url_detailed.append(j)
url_detailed_2 = []
for i in url_detailed:
i = i.strip('\\"')
i = 'https:' + i +'&tdsourcetag=s_pctim_aiomsg&sku_properties=21433:50753410;10142888:302694851'
url_detailed_2.append (i)
url_detailed_2 = pd.DataFrame(pd.Series(url_detailed_2), columns=['货物链接'])
return (url_detailed_2)
# 根据货物的链接,爬取货物的ID,X = 货物链接
def ID_detailed (X):
import pandas as pd
import re
patter = 'id=(.*)&rn'
A = []
for i in X['货物链接']:
A.append(re.findall(patter, i))
A = pd.DataFrame(pd.Series(A), columns=['货物ID'])
return(A)
产品链接
5.获取产品的详细属性
查看需要获取的属性,将其整合成列表。在爬取过程中生成对应的键值对。
产品属性
#根据货物的详细链接,爬去货物的属性信息,X = 货物详细链接表
def Label (X):
import pandas as pd
import re
import requests
from lxml import etree
requests.adapters.DEFAULT_RETRIES = 5
d1 =['包装体积:','型号:','是否预售:','款式定位:','颜色分类:','尺寸:','是否带储物空间:','填充物:','是否带软靠:',\
'是否可定制:','材质:','风格:','家具结构:']
label = []
number = 0
headers = {
'Connection':'close',
'user-agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)Chrome/54.0.2840.99 Safari/537.36',
'cookie':'cna=P+QpFIBjZRECAd3tmK78L147; _m_h5_tk=fb7756c8ab31726f5bf669b649827a44_1537527405908; _m_h5_tk_enc=69ada9216bd726de6e5e7b6bd87bc923; t=eba31fabfb5d9e662f3140dda391b1c2; _tb_token_=ed137f8e33b3a;cookie2=147853597cf22040f4cc675132589da6; pnm_cku822=098%23E1hvMpvUvbpvUvCkvvvvvjiPPsS96jtbR2MpQjthPmP9tj1hP2dOtjnjPFdyzjtbRphvCvvvphvEvpCW99K0r30gRbutnV3Xibm0HskTWlK91Ep7%2BulAbMo6eCOtHjnNAbmAdBeK4Z7xfBeK5dXKjdzCHEp7%2B3%2BuaNpArqVTbZkt640AdByapbyCvm9vvhCvvvvvvvvvBJZvvUhCvvCHtpvv9ZUvvhcDvvmCb9vvBJZvvUhKkphvC9hvpyPZ68yCvv9vvhh5RPb9YQhCvvOv9hCvvvvPvpvhvv2MMTwCvvpvvhHh; cq=ccp%3D1; isg=BKqqBC3Mhcz5IwmrkTMwD7HR-xCMsy_cRtO92TRjHP2CZ0ohHKgmhLmV89Nel6YN'
}
for i in X ['货物链接'] :
r = requests.session()
r.keep_alive = False
r = requests.get(i,timeout = 10,headers = headers)
A = etree.HTML(r.text)
B = A.xpath('//*[@id="J_AttrUL"]/li/text()')
C = ''
for j in B:
j = '-' + j + '-'
C = C + j
C = C.replace('\xa0','')
_属性={}
for k in d1:
A = re.findall('-%s(.*?)-'%k,C)
if A == []:
A = 'NAN'
else :
A = A[0]
_属性.update({k:A})
label.append(_属性)
number += 1
print ('---'*20 + str(number))
label = pd.DataFrame(label)
return (label)
产品属性列表
5.获取产品的评论量
累计评论量观察页面的信息,发现累计评价是通过加载json文件进行传输的。页面中找到对应的网址,观察发现是由于链接最后的ID变动导致的。
json的链接 json文件其中设置断连判断,如果链接断开的话,就进行睡眠。
def Comment (X):
requests.adapters.DEFAULT_RETRIES = 5
r = requests.session()
r.keep_alive = False
url = 'https://dsr-rate.tmall.com/list_dsr_info.htm?itemId='
comment = []
number = 0
for i,j in zip(X['货物ID'],X['货物链接']):
i = i.strip("[]'")
A = url + i
B = []
headers = {
"Referer":j,
'cookie':'cna=P+QpFIBjZRECAd3tmK78L147; _m_h5_tk=fb7756c8ab31726f5bf669b649827a44_1537527405908; _m_h5_tk_enc=69ada9216bd726de6e5e7b6bd87bc923; t=425bdd5592c604f0d691f808f0a844b1; _tb_token_=73137373e63e8; cookie2=10d98d0bd350def7bc50f0880033ce61; isg=BGNjUSgJPOPwC_DccBAZECAC8qfN8PYzV6AkDpXDFUIy1ID2HSkK6nBFysT_9E-S',
'user-agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36'
}
try:
r = requests.get(A,timeout = 10,headers = headers)
B = re.findall('"rateTotal":(.*?)}',r.text)
comment.append(B)
number += 1
print ('---'*20 + str(number))
except:
# print("Connection refused by the server..")
# print("Let me sleep for 5 seconds")
print("ZZzzzz...")
time.sleep(5)
print("Was a nice sleep, now let me continue...")
comment = pd.DataFrame(pd.Series(comment), columns=['评论数量'])
return (comment)
6.获取产品的价格
同理根据隐藏拼接即可获取相应的价格。
7.下载图片
将先前获取的图片链接逐一清洗出来,再进行下载。
#清洗出图片地址,X = 产品包含的图片地址
def JPG_url (X):
jpg_url = []
for i in X['图片']:
if ',' not in i:
url = 'http:' + i.strip(r"[]'")
jpg_url.append(url)
else :
i = i.replace("'",'')
i = i.strip(r"[]'")
i = i.split(',')
for j in i:
j = j.strip()
url = 'http:' + j
jpg_url.append(url)
#jpg_url = pd.DataFrame(pd.Series(jpg_url), columns=['图片下载地址'])
return (jpg_url)
#通过图片地址,将图片下载到本地,X = 每张图片的下载地址
def JPG (X):
import os
number = 1
for i in X['图片下载地址']:
url = i
#图片存储的地址
root = "G://python//"
path = root + url.split("/")[-1]
try:
if not os.path.exists(root):
os.mkdir(root)
if not os.path.exists(path):
r = requests.get(url,timeout = 10)
r.raise_for_status()
#使用with语句可以不用自己手动关闭已经打开的文件流
with open(path,"wb") as f: #开始写文件,wb代表写二进制文件
f.write(r.content)
print("爬取完成" + '---'*20 +str(number))
number += 1
else:
print("文件已存在")
except Exception as e:
print("爬取失败:"+str(e))
else :
time.sleep(1)
return ('爬取完成')
8.导入数据库
python导入DB2数据库时,需要调整ibm_db包,点我查看
【三】建模分析
1.数据探索
获取数据后观察现有的数据:
1.、这是家具类产品的数据,是消费者能获取的最直观的信息。
2、有什么类型的feature:产品名称、图片、产品属性('包装体积:','型号:','是否预售:','款式定位:','颜色分类:','尺寸:','是否带储物空间:','填充物:','是否带软靠:','是否可定制:','材质:','风格:','家具结构: 等)、价格(市场价格,本站价格)、评论(评论量,最近评论时间)、销量、活动(折扣,活动名称)。这些都 目前可以获取的信息 。
再问几个问题:
1.相同的产品,不同的名称会不会导致在train和test中重复?
2.数据在时间上的分布是否会有影响?
3.有多少价格区间,价格区间如何划分,产品的风格种类有哪些?不同类型的产品是否会随时间的变化?
4.活动对销量的影响如何判定?
5.近期什么样的产品是最受欢迎的?
以上都是需要与业务部门同事交流探讨后确定的重要维度。
2.数据清洗
剔除缺失值明显的数据,数据归一化,文字类型数据转化成数字类型数据。
def Sigmoid (X):
return (1.0 / (1 + np.exp(-float(X)));
def Replace (X,columns):
a = X.groupby([columns],as_index=False)[columns].agg({'cnt':'count'})
for i in a[columns]:
X[columns] = X[columns].replace(i,a[(a[columns]== i )].index.tolist()[0])
return (X)
3.特征工程
个人认为特征工程是建模分析的重中之重,模型大同小异,基本是都是调用几种开源框架。所以,模型都差不多,特征就是关键了。
利用模型自带的feature_importance_,写出一个特征选择函数:
#alg:模型,columns:特征集合
def select_important_features(alg,columns):
importances=zip(map(lambda x: round(x, 4), alg.feature_importances_), columns)
importance=[]
sum=0
for i in importances:
if i[0]>0.008:
importance.append(i[1])
sum=sum+i[0]
print("Num : %f | Sum: %3f" % (len(importance), sum))
return importance
importance = select_important_features(rfr,x_train.columns)
每个模型都有独特的业务背景,想要发现甚至是自己创造出重要的特征,往往需要深厚的业务逻辑&行业经验。
比如这个项目要预测沙发产品,沙发大小&储物空间大小的比列,就需要通过原始数据进行创造。
当然一切还需要参考业务部门的逻辑&意见。
一般进行特此选择可以参考常用特征的选择方式
4.模型选择&调参
本次测试中选用的是LightGBM模型,模型的性能与准确率都较好。当然为了提高效果的话,应当多个模型比较。
LightGBM:
- num_leaves: 叶子节点的个数
- max_depth:最大深度,控制分裂的深度
- learning_rate: 学习率
- objective: 损失函数(mse, huber loss, fair loss等)
- min_data_in_leaf: 叶子节点必须包含的最少样本数
- feature_fraction: 训练时使用feature的比例
- bagging_fraction: 训练时使用样本的比例
调整参数还是需要一些玄学,想要找到最佳的参数很多时候需要一些经验和运气。
5.模型融合
-
Averaging
Averaging融合方式就是加权平均。格局模型的多样性,平均后长补短,更准确第提高模的泛化能力。 -
Stacking
Stacking个人认为就是将第一层模型的结果作为第二层模型的特征,然后训练第二层模型得到最终结果。
详细解释
6.分析呈现
测试后,根据相应的得分结果选择模型。(分类:混合矩阵、f1_score 。回归:rmse、rmae、r2_score )
直接使用帆软,将导入的结果以BI报表的形式呈现。
【四】分类打标
1.图像识别分类
目前根据业务部门需求,需要将竞品按照风格,类型等划分入不同的标签。
经过部门沟通后,选择百度 EasyDL进行图像划分。方便业务部门进行操作。
根据自有图片进行训练,多次校准。可以将竞品的图片划分入现有的风格分类。
训练的模型
多次训练后,选择适合的版本可以有效的划分相应的家具产品分类。
沙发产品划分结果
对模型进行多次修正,直到效果满足预期,然后接入API接口,自动返回比对结果。
API的接入参考----百度easyDL API接入
【五】总结分析
本次项目中主要侧重的是数据的获取与处理方面。
建模分析是基于业务部门的支持,产品数据的调整很大一部分都来自于业务逻辑的经验之谈。
因为部分原因,不能展示数据结果,分析部分只能说明思路。
仅用于记录个人工作。
网友评论