此项目爬取饿了么外卖网站的披萨商品信息,包括店铺坐标、店铺评星、店铺订单量、单品描述、单品销量、单品评星等信息,通过对商品描述进行分词,统计词频提取披萨商品的特征,运用神经网络和XGBoost两种机器学习算法,构建披萨商品预测模型。
- 分析饿了么官网地址关键字搜索:
以在南京市搜索“新街口”为例,抓包查看到地址关键字搜索的访问地址为https://www.ele.me/restapi/v2/pois?extras%5B%5D=count&geohash=wtsw9yrc72y&keyword=%E6%96%B0%E8%A1%97%E5%8F%A3&limit=20&type=nearby
分析url我们可以发现,关键字内容存放在参数keyword中,这样构造请求地址后就可以爬取到地址信息了,我们这里默认选择地址信息中店铺数量最多的地址。
Tips: 需要注意的是如果爬取其他城市的话,需要更换geohash后的内容。 - 分析店铺搜索结果页面
选择某一地址后,进入店铺页面,选择“美食-汉堡披萨”类,抓包查看此类店铺搜索的访问地址为https://www.ele.me/restapi/shopping/restaurants?geohash=wtsqqbuz1u87&latitude=32.041479&limit=24&longitude=118.779792&offset=0&restaurant_category_ids%5B%5D=3&terminal=web
分析url,geohash为地理编码信息,与上一个地址关键字搜索url相同;latitude和longitude为经纬度信息,可以通过地址搜索结果获取;offset为翻页信息,查看爬取数据结果可以发现每页有24条数据,因此可以迭代offset值获取所有的店铺信息。
构造了请求地址后就可以爬取店铺信息了,需要注意的是,本项目只需要爬取披萨门店就可以了,因此可以在获取店铺信息前判断该门店是否是披萨门店,即判断店铺id中是否含有211,如果没有211就直接跳过爬取下一个店铺。for i in range(0, 10): # search_result为爬取地址信息的具体内容 shop_url = 'https://www.ele.me/restapi/shopping/restaurants?geohash=' + str(search_result['geohash']) + '&latitude=' + str(search_result['latitude']) + '&limit=24&longitude=' + str(search_result['longitude']) + '&offset=' + str(i*24) + '&restaurant_category_ids%5B%5D=3&terminal=web' shop_ = requests.get(shop_url, headers=head, cookies=cookie) time.sleep(2 + numpy.random.randint(0, 3)) shop_json = json.loads(shop_.text) if len(shop_json) == 0: break for shop in shop_json: # 首先判断是否为披萨店 json_id = [] for flv in shop['flavors']: json_id.append(flv['id']) if 211 not in json_id: continue # 店铺信息 shop_id = shop['id'] shop_address = shop['address'] shop_name = shop['name'] shop_delivery_fee = shop['float_delivery_fee'] shop_latitude = shop['latitude'] shop_longitude = shop['longitude'] shop_opening_hours = shop['opening_hours'] shop_recent_order_num = shop['recent_order_num'] shop_rating = shop['rating']
- 分析店铺页面爬取披萨品类信息
查看某个店铺的页面,可以看到页面中对所有商品做了一个分类,分类内容会有沙拉、披萨、饮料等等,因此我们可以先判断该分类是否为披萨品类,然后再爬取改分类下的披萨商品,判断披萨品类方法如下。找到披萨分类后遍历所有披萨商品,爬取披萨商品的信息包括商品描述、月销量、名称、评星、评星数等,最终结果保存在csv文件中。def is_pizza(str): if '披萨' in str: return True if '比萨' in str: return True if 'Pizza' in str: return True if 'pizza' in str: return True return False
pizza_ = requests.get(pizza_url) time.sleep(2 + numpy.random.randint(0, 3)) pizza_json = json.loads(pizza_.text) for menu_json in pizza_json: if is_pizza(menu_json['name']): # 目录信息 menu_name = menu_json['name'] food_json = menu_json['foods'] for foods in food_json: # 单品信息 foods_id = foods['item_id'] foods_description = foods['description'] foods_month_sales = foods['month_sales'] foods_name = foods['name'] foods_rating = foods['rating'] foods_rating_count = foods['rating_count'] foods_satisfy_count = foods['satisfy_count'] foods_satisfy_rate = foods['satisfy_rate']
-
数据清洗
因为不同的地理位置覆盖的范围可能重叠,因此我们爬到的店铺可能存在重复,所以我们需要将重复的数据去除,可以根据item_id去除重复数据data = data.drop_duplicates(['item_id'])
同时,评星为0的数据对我们来说意义也不大,这些大多是销量很低还没有人评星的商品,可以视为离群数据将其取出
data = data[data['item_rating'] > 0]
-
自然语言处理
为了提取商品原料的信息内容,将爬取到的商品描述信息进行分词处理,使用的是jieba库def jieba_cut(word): s = jieba.cut(word) s = [word.encode('utf-8') for word in list(s)] stoplist = {}.fromkeys([line.strip() for line in open("./stopwords.txt")]) segs = [word for word in list(s) if word not in stoplist] return segs
将分词的结果进行词频统计,使用pyecharts库将词频统计结果通过词云图可视化
def draw_word_cloud(word_data): from pyecharts import WordCloud word = [] value = [] for (k, v) in word_data.items(): if v < 50: del word_data[k] else: word.append(k) value.append(v) wordcloud = WordCloud(width=1300, height=620) wordcloud.add("", word, value, word_size_range=[5, 1000], shape='diamond') wordcloud.render("./wordcloud.html")
可以看到,词频较高的词有牛肉、鸡肉、培根、奶酪等,这些都是我们希望获得的披萨原料和风味,但是也有一些词,比如纸巾、风味、超级等没有多大意义的词,这里展示一下词频排在前30的有哪些词
词 词频 芝士 2224 披萨 1292 寸 1018 菠萝 826 青椒 823 牛肉 755 酱 748 火腿 737 洋葱 718 原料 666 培根 641 比萨 639 蘑菇 603 里拉 557 马苏 515 搭配 443 12 413 面团 409 榴莲 399 美味 395 肠 389 玉米 381 鸡肉 374 腊肉 329 提供 325 纸巾 325 奶酪 308 蔬菜 307 香 288 香肠 284 海鲜 277 可以发现,芝士的词频遥遥领先,果然芝士就是腻酿!!
其中发现了12这个有趣的高频词,其实是很多披萨商品是12寸的,因此12的词频就很高。可以看到分词结果还是比较混乱的,因此我将所有词频高于5且对商品描述有意义的词进行逐一分类,共分为风味、口味、烹制方法、蔬菜类、荤菜类、水果类、海鲜水产类、辅料佐料类。大类 子类 词 风味 热带风味 夏威夷 热带 意式风味 意式 意大利 那不勒斯 博洛尼亚 西西里 贝贝罗尼 罗马 东南亚风味 泰国 泰式 马来西亚 墨西哥风味 墨西哥 奥尔良风味 奥尔良 新奥尔良 澳洲风味 澳洲 新西兰 法式风味 法国 美式风味 美国 纽约 荷兰风味 荷兰 德式风味 德式 德国 **风味 ** 韩式风味 韩国 韩式 日式风味 日式 中式风味 京味 韩式 海陆风味 海陆 全素风味 全素 口味 脆 香脆 脆爽 松脆 清脆 薄脆 酥脆 鲜 鲜美 香鲜 酸 酸甜 甜 酸甜 香甜 甜辣 辣 微辣 香辣 麻辣 辣爽 甜辣 醇 香醇 醇香 醇厚 清 清甜 清新 清脆 柔 柔韧 柔嫩 香 果香 芝香 五香 香甜 香鲜 浓香 烹制方法 薄底 薄底 薄饼 厚底 厚底 铁盘 铁盘 烤制 烘烤 烤 BBQ 烤制 烧烤 现烤 烤肉 岩烧 岩烧 炭烧 炭烧 腌制 腌制 焗烧 焗 蜜汁 蜜汁 蔬菜类 菜椒 彩椒 青椒 红椒 红彩椒 黄彩椒 灯笼椒 甜椒 辣椒 脆椒 辣椒 尖椒 橄榄 橄榄 洋葱 洋葱 onion 京葱 京葱 青葱 青葱 玉米 玉米 玉米片 玉米粒 荞麦 荞麦 荞麦面 黄瓜 黄瓜 酸黄瓜 番茄 西红柿 番茄 蕃茄 菌菇 蘑菇 松露菌 褐菇 青豆 青豆 芦笋 芦笋 莴笋 莴笋 西葫芦 西葫芦 南瓜 南瓜 香菜 香菜 西兰花 西兰花 藕 藕 藕片 茄子 茄子 土豆 土豆 土豆片 薯角 薯条 脆薯 红薯 红薯 荤菜类 培根 培根 牛肉 牛肉 牛排 鸡肉 鸡肉 烤鸡 鸡腿肉 鸡胸 鸡胸肉 鸡丁 猪肉 猪肉 里脊 鸭肉 鸭肉 烤鸭 北京烤鸭 烤鸭 羊肉 羊肉 腌肉 腊肉 午餐肉 叉烧 肉肠 香肠 腊肠 烤肠 肉肠 红肠 热狗 火腿 火腿 熟火腿 肉松 肉松 脆骨 脆骨 小龙虾 小龙虾 鸡蛋 鸡蛋 水果类 菠萝 菠萝 凤梨 凤梨 榴莲 榴莲 榴梿 榴莲果 樱桃 樱桃 黄桃 黄桃 柠檬 柠檬 香蕉 香蕉 芒果 芒果 椰子 椰果 椰蓉 清椰 火龙果 火龙果 苹果 苹果 蔓越莓 蔓越莓 海鲜水产类 鱿鱼 鱿鱼 章鱼 章鱼 墨鱼 墨鱼 乌贼 虾类 虾 虾仁 大虾 鲜虾 虾球 虾肉 金枪鱼 金枪鱼 三文鱼 三文鱼 吞拿鱼 吞拿鱼 鳗鱼 鳗鱼 银鱼 银鱼 扇贝 扇贝 蟹类 蟹肉 海苔 海苔 辅料佐料 奶油 奶油 白汁 奶酪 奶酪 乳酪 乳酪 起士 起士 小米 小米 番茄酱 番茄酱 蛋黄酱 蛋黄酱 奶盖酱 奶盖酱 千岛酱 千岛 千岛酱 沙拉酱 沙拉酱 果酱 果酱 辣酱 辣酱 蜂蜜 蜂蜜 芥末 芥末 黑胡椒 黑椒 黑胡椒 花椒 花椒 迷迭香 迷迭香 茴香 茴香 芝麻 芝麻 香草 香草 大蒜 大蒜 蒜蓉 咖喱 咖喱 薄荷 薄荷 慕斯 慕斯 酸奶 酸奶 土豆泥 土豆泥 桃仁 桃仁 板栗 板栗 焦糖 焦糖 松露 松露 三文治 三文治 橄榄油 橄榄油 黄油 黄油 -
特征与标签提取
根据上述表格的分类,可以得到130个子类,这些就是我们需要提取的特征了。参考词袋模型表达文本信息,我们也给每个披萨商品构建一个1*130的向量,每个特征占据一个维度,如果该商品包含该特征,则该维度的值为1,否则该维度值为0,这样得到了每个披萨商品的特征向量
我们想要预测披萨商品畅销与否,有了特征之后还需要提取披萨商品的标签。观察数据发现,我们可以通过判断商品评星是否大于店铺口味评星来判断,如果某件商品评星高于店铺口味评星,则说明该商品畅销,标记为1,否则标记为0def rate_to_marker(food_score, item_rating): if item_rating >= food_score: return np.array([[1]]) else: return np.array([[0]])
-
神经网络模型
本项目实现的神经网络共有4层,第一层共有64个神经元,且包含0.8的droupout,第二层共有16个神经元,第三层共有8层神经元,最后一层是输出层,所有层的激活函数都是sigmoid函数,本项目使用kears库实现模型model = Sequential() model.add(Dense(64, activation='sigmoid', input_dim=X.shape[1])) model.add(Dropout(0.8)) model.add(Dense(16, activation='sigmoid')) model.add(Dense(8, activation='sigmoid')) model.add(Dense(1, activation='sigmoid'))
模型的一些参数是包括:
参数 值 优化函数 梯度下降SGD 损失函数 均方误差mse 学习率 0.001 batch 32 epoch 100 交叉验证比例 0.2 将模型训练中的loss和accuracy绘制成plot展示,可以看到最后训练集和验证集的accuracy都达到了80%以上,且最终趋于稳定,没有产生过拟合现象
最后使用测试集测试模型的泛化能力,发现准确率可以达到0.811,与训练样本的准确率0.815相近,证明整个模型的能力还是不错的 -
XGBoost 本项目还实用XGBoost实现了预测模型,具体参数如下
参数 值 booster gbtree max_depth 18 scale_pos_weight 0.8 eta 0.5 epoch 100 objective binary:logistic eval_metric ['error', 'auc'] 迭代次数 500 交叉验证比例 0.2 由于XGBoost的评价函数中没有precision,本项目结合sklearn自定义了评价函数,应用于每个round中
def precision_and_recall(preds, dtrain): lab = dtrain.get_label() preds = [int(i >= 0.5) for i in preds] conf = confusion_matrix(lab, preds) precision = float(conf[0][0]) / float(conf[1][0]+conf[0][0]) recall = float(conf[0][0]) / float(conf[0][1]+conf[0][0]) return 'precision', precision
模型的训练过程可视化的结果如下图
可以看到训练集验证集的精度最终都可以达到0.8以上,但验证集error下降的不是很明显,但这个是效果较好的一组参数了,后期还需要对模型参数多摸索!
最后用测试集数据对模型进行验证,测试集精度达到0.826,可以看出整个模型的效果还是不错的
XGBoost通过训练最后可以得到一个特征的重要性评分,我们最后看到重要性排名前20的特征是排名 特征 1 洋葱 2 菜椒 3 菌菇 4 菠萝 5 培根 6 牛肉 7 火腿 8 鸡肉 9 肉肠 10 玉米 11 番茄 12 榴莲 13 意式风味 14 奶酪 15 奥尔良风味 16 烤制 17 热带风味 18 土豆 19 薄底 20 虾类 分析可以看出,蔬菜类、水果类和荤菜类还是对销量有很大的影响,烹制方法和配料的影响并不是很大。 随机森林也可以输出特征重要性指标
rf = RandomForestClassifier() rf.fit(X, y) names = ["X%s" % x for x in range(0, 130)] print "Features sorted by their score:" print sorted(zip(map(lambda x: round(x, 4), rf.feature_importances_), names), reverse=True)
随机森林输出的重要性排名前20的特征是
排名 特征 1 鸡肉 2 洋葱 3 菜椒 4 菌菇 5 培根 6 牛肉 7 玉米 8 肉肠 9 奶酪 10 烤制 11 火腿 12 意式风味 13 菠萝 14 番茄 15 番茄酱 16 香 17 薄底 18 奥尔良风味 19 橄榄 20 热带风味 对比XGBoost输出的特征重要性结果,两份结果在前20的特征有17个是相同的,洋葱、菜椒、菌菇、鸡肉、培根等特征在两个模型中重要性都非常高,这便是我们想要得到的结果了,这些特征对于销量起到了关键性的作用。同时,可以看到意式风味、奥尔良风味和热带风味是最受欢迎的3个口味,鸡肉牛肉也是比较畅销的品类......
- 本项目披萨品类的特征仅仅只包含商品描述的语义信息,未来可以探索一些其它重要特征来描述披萨商品,使数据表达更加完整精确
- XGBoost验证集进度提升较为困难,调参是一门“玄学”,考虑通过网格搜索再优化一下参数的配置,争取达到更好的效果
E-mail: [email protected]
GitHub主页: https://github.com/JohnsonKlose
博客园: http://www.cnblogs.com/KloseJiao/
喜欢的朋友们可以加个star,也欢迎留言和邮件与我交流!