点评门店推荐搜索初级
整理总结一下 ElasticSearch7+Spark 构建高相关性搜索服务&千人千面推荐系统的笔记
完成一个点评搜索推荐系统,初步实现关键词搜索与多条件筛选,结合LBS地理围栏通过距离控制搜索排序,同时优先将距离与评价好的商户门店推荐给用户。
门店搜索推荐简介
关键词搜索与多条件筛选,可检索出满意的商家门户
门店推荐-猜你喜欢是通过计算用户到门店的距离和门店的评分综合给门店打分后排序输出给用户的。
推荐效果:
搜索效果:
数据库里的门店数据
搜索推荐基础
搜索推荐基础
距离计算:
1、需要接入百度地图sdk通过api获取用户当前经纬度传给服务端
http://lbs.baidu.com/index.php?title=webapi/guide/webservice-geocoding
调用百度地图的接口获取当前地址的经纬度
http://lbs.baidu.com/apiconsole/key#/home
eg:
GET请求 address=你的地址,ak你的申请注册的key
2、利用球体平面计算公式,在sql语句层面计算出用户和门店之间的距离
经纬度计算
3、对应距离数值可集成到门店模型内用于展示或后续的排序
应用程序集成
4、通过spring mvc集成推荐的controller,service和dao
5、完成前端页面闭环
门店搜索推荐实现
创建门店表
CREATE TABLE `dianpingdb`.`shop` (
`id` int(0) NOT NULL AUTO_INCREMENT,
`created_at` datetime(0) NOT NULL DEFAULT '0000-00-00 00:00:00',
`updated_at` datetime(0) NOT NULL DEFAULT '0000-00-00 00:00:00',
`name` varchar(80) NOT NULL DEFAULT '',
`remark_score` decimal(2, 1) NOT NULL DEFAULT 0,
`price_per_man` int(0) NOT NULL DEFAULT 0,
`latitude` decimal(10, 6) NOT NULL DEFAULT 0,
`longitude` decimal(10, 6) NOT NULL DEFAULT 0,
`category_id` int(0) NOT NULL DEFAULT 0,
`tags` varchar(2000) NOT NULL DEFAULT '',
`start_time` varchar(200) NOT NULL DEFAULT '',
`end_time` varchar(200) NOT NULL DEFAULT '',
`address` varchar(200) NOT NULL DEFAULT '',
`seller_id` int(0) NOT NULL DEFAULT 0,
`icon_url` varchar(100) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
);
id门店的编号,created_at创建时间 updated_at更新时间
name店名 remark_score门店的评价 price_per_man平均每个人消费钱
latitude、longitude店的位置经纬度,category_id门店属于那种分类,
seller_id属于那个商家公司的,address门店地址,icon_url商家图片地址。
在项目的src/main/java目录下,创建一个com.awen.dianping.service包,并在包中创建接口ShopService,接口中定义一系列方法,代码如下所示。
interface ShopService.java
public interface ShopService {
ShopModel create(ShopModel shopModel) throws BusinessException;
ShopModel get(Integer id);
List<ShopModel> selectAll();
List<ShopModel> recommend(BigDecimal longitude,BigDecimal latitude);
List<Map<String,Object>> searchGroupByTags(String keyword,Integer categoryId,String tags);
Integer countAllShop();
List<ShopModel> search(BigDecimal longitude,BigDecimal latitude,
String keyword,Integer orderby,Integer categoryId,String tags);
}
在com.awen.dianping.service.impl包下,创建ShopService接口的实现类ShopServiceImpl,该类需要实现接口中的所有方法,代码如下所示。
@Service
public class ShopServiceImpl implements ShopService{
@Autowired
private ShopModelMapper shopModelMapper;
@Autowired
private CategoryService categoryService;
@Autowired
private SellerService sellerService;
@Override
@Transactional
public ShopModel create(ShopModel shopModel) throws BusinessException {
shopModel.setCreatedAt(new Date());
shopModel.setUpdatedAt(new Date());
//校验商家是否存在正确
SellerModel sellerModel = sellerService.get(shopModel.getSellerId());
if(sellerModel == null){
throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"商户不存在");
}
if(sellerModel.getDisabledFlag().intValue() == 1){
throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"商户已禁用");
}
//校验类目
CategoryModel categoryModel = categoryService.get(shopModel.getCategoryId());
if(categoryModel == null){
throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR,"类目不存在");
}
shopModelMapper.insertSelective(shopModel);
return get(shopModel.getId());
}
@Override
public ShopModel get(Integer id) {
ShopModel shopModel = shopModelMapper.selectByPrimaryKey(id);
if(shopModel == null){
return null;
}
shopModel.setSellerModel(sellerService.get(shopModel.getSellerId()));
shopModel.setCategoryModel(categoryService.get(shopModel.getCategoryId()));
return shopModel;
}
@Override
public List<ShopModel> selectAll() {
List<ShopModel> shopModelList = shopModelMapper.selectAll();
shopModelList.forEach(shopModel -> {
shopModel.setSellerModel(sellerService.get(shopModel.getSellerId()));
shopModel.setCategoryModel(categoryService.get(shopModel.getCategoryId()));
});
return shopModelList;
}
@Override
public List<ShopModel> recommend(BigDecimal longitude, BigDecimal latitude) {
List<ShopModel> shopModelList = shopModelMapper.recommend(longitude, latitude);
shopModelList.forEach(shopModel -> {
shopModel.setSellerModel(sellerService.get(shopModel.getSellerId()));
shopModel.setCategoryModel(categoryService.get(shopModel.getCategoryId()));
});
return shopModelList;
}
@Override
public List<Map<String, Object>> searchGroupByTags(String keyword, Integer categoryId, String tags) {
return shopModelMapper.searchGroupByTags(keyword,categoryId,tags);
}
@Override
public Integer countAllShop() {
return shopModelMapper.countAllShop();
}
@Override
public List<ShopModel> search(BigDecimal longitude,
BigDecimal latitude, String keyword,Integer orderby,
Integer categoryId,String tags) {
List<ShopModel> shopModelList = shopModelMapper.search(longitude,latitude,keyword,orderby,categoryId,tags);
shopModelList.forEach(shopModel -> {
shopModel.setSellerModel(sellerService.get(shopModel.getSellerId()));
shopModel.setCategoryModel(categoryService.get(shopModel.getCategoryId()));
});
return shopModelList;
}
}
interface ShopModelMapper.java 定义方法
public interface ShopModelMapper {
int deleteByPrimaryKey(Integer id);
Integer countAllShop();
int insert(ShopModel record);
int insertSelective(ShopModel record);
ShopModel selectByPrimaryKey(Integer id);
List<ShopModel> selectAll();
int updateByPrimaryKeySelective(ShopModel record);
int updateByPrimaryKey(ShopModel record);
List<ShopModel> recommend(@Param("longitude") BigDecimal longitude, @Param("latitude") BigDecimal latitude);
List<ShopModel> search(@Param("longitude") BigDecimal longitude,
@Param("latitude") BigDecimal latitude,
@Param("keyword")String keyword,
@Param("orderby")Integer orderby,
@Param("categoryId")Integer categoryId,
@Param("tags")String tags);
List<Map<String,Object>> searchGroupByTags(@Param("keyword")String keyword,
@Param("categoryId")Integer categoryId,
@Param("tags")String tags);
}
在src/main/resources/mapping,创建ShopModelMapper.xml
写一系列SQL。
在com.awen.dianping.controller包下,创建ShopController,代码如下所示。
ShopController.java
@Controller("/shop")
@RequestMapping("/shop")
public class ShopController {
@Autowired
private ShopService shopService;
@Autowired
private CategoryService categoryService;
//推荐服务V1.0
@RequestMapping("/recommend")
@ResponseBody
public CommonRes recommend(@RequestParam(name="longitude")BigDecimal longitude,
@RequestParam(name="latitude")BigDecimal latitude) throws BusinessException {
if(longitude == null || latitude == null){
throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR);
}
List<ShopModel> shopModelList = shopService.recommend(longitude,latitude);
return CommonRes.create(shopModelList);
}
//搜索服务V1.0
@RequestMapping("/search")
@ResponseBody
public CommonRes search(@RequestParam(name="longitude")BigDecimal longitude,
@RequestParam(name="latitude")BigDecimal latitude,
@RequestParam(name="keyword")String keyword,
@RequestParam(name="orderby",required = false)Integer orderby,
@RequestParam(name="categoryId",required = false)Integer categoryId,
@RequestParam(name="tags",required = false)String tags) throws BusinessException {
if(StringUtils.isEmpty(keyword) || longitude == null || latitude == null){
throw new BusinessException(EmBusinessError.PARAMETER_VALIDATION_ERROR);
}
List<ShopModel> shopModelList = shopService.search(longitude,latitude,keyword,orderby,categoryId,tags);
List<CategoryModel> categoryModelList = categoryService.selectAll();
List<Map<String,Object>> tagsAggregation = shopService.searchGroupByTags(keyword,categoryId,tags);
Map<String,Object> resMap = new HashMap<>();
resMap.put("shop",shopModelList);
resMap.put("category",categoryModelList);
resMap.put("tags",tagsAggregation);
return CommonRes.create(resMap);
}
}
等等 代码 Github:https://github.com/liuawen/dianping
运行执行
小结
门店推荐
通过数据库的线性计算公式给门店打分后排序输出给用户,线性计算公式,人的理解和实际计算操作过程有误差,线性计算公式没有办法很好的满足实际的需求。
这个推荐只是简单计算了距离和门店评分,没有考虑人的特点喜欢什么经常浏览什么,没有办法做到个性化的推荐。
门店搜索
通过数据库的关键词模糊匹配的方式结合线性计算公式给门店打分后排序输出给用户
关键词模糊匹配,最基础的文本匹配方式,没办法考虑分词,中文特性,用户深层次需求等,连最基本的分词模糊匹配都无法完成。
和推荐一样的线性排序计算公式,人的理解和实际计算操作过程有误差,线性计算公式没有办法很好的满足实际的需求
数据库做搜索性能很差,性能非常非常差的。
最终要实现
搜索推荐
- 快速根据用户关键词搜索给出满意的结果
- 结合用户的历史行为、兴趣偏好等推荐给用户想到的服务商品以提升浏览点击率和交易转化率。
搜索服务必备
-
完整丰富的待搜索数据源
-
高效且稳定的搜索引擎
-
相关性语义的智能理解
推荐系统必备
-
完整丰富的训练及测试数据集
-
个性化千人千面的召回及排序机器学习算法
-
可解释且有实际意义的评价指标
后续改善