Files
fun-rec/codes/news_rec_server/recprocess/online.py
T
2021-12-04 10:52:00 +08:00

263 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import sys
sys.path.append("../../")
sys.path.append("../")
import json
import time
import threading
from dao.redis_server import RedisServer
from controller.user_action_controller import UserAction
redis_server = RedisServer()
class OnlineServer(object):
"""单例模式推荐服务类
"""
_instance_lock = threading.Lock()
def __init__(self,):
self.reclist_redis_db = redis_server.get_reclist_redis()
self.static_news_info_redis_db = redis_server.get_static_news_info_redis()
self.dynamic_news_info_redis_db = redis_server.get_dynamic_news_info_redis()
self.exposure_redis_db = redis_server.get_exposure_redis()
def __new__(cls, *args, **kwargs):
if not hasattr(OnlineServer, "_instance"):
with OnlineServer._instance_lock:
if not hasattr(OnlineServer, "_instance"):
OnlineServer._instance = object.__new__(cls)
return OnlineServer._instance
def get_rec_list(self, user_id, page_id):
"""给定页面的展示范围进行展示
user_id 后面做个性化推荐的时候需要用到
"""
# 根据page id计算需要获取redis中哪些范围的news_id, 假设每一页展示10个新闻
s = (int(page_id) - 1) * 10
e = s + 9
# 返回的是一个news_id列表
news_id_list = self.reclist_redis_db.zrange("rec_list", start=s, end=e)
# print(news_id_list)
# 根据news_id获取新闻的具体内容,并返回一个列表,列表中的元素是按照顺序展示的新闻信息字典
news_info_list = []
news_expose_list = []
for news_id in news_id_list:
news_info_dict = self._get_news_simple(news_id)
# news_info_str = news_info_str.replace("'", '"' ) # 将单引号都替换成双引号
# news_info_dict = json.loads(news_info_str)
# 需要确认一下前端接收的json,key需要是单引号还是双引号
news_info_list.append(news_info_dict)
news_expose_list.append(news_info_dict["news_id"]) # 记录在用户曝光表上[user_exposure]
# UserAction().save_user_exposure(user_id,news_expose_list) # 曝光落表
self._save_user_exposure(user_id,news_expose_list)
return news_info_list
def _get_news_simple(self, news_id):
"""获取新闻的简略信息,用于刚进来时的展示
"""
news_info_str = self.static_news_info_redis_db.get("static_news_detail:" + news_id)
news_info_str = news_info_str.replace("'", '"' ) # 将单引号都替换成双引号
news_info_str = json.loads(news_info_str)
news_dynamic_info_str = self.dynamic_news_info_redis_db.get("dynamic_news_detail:" + news_id)
news_dynamic_info_str = news_dynamic_info_str.replace("'", '"' ) # 将单引号都替换成双引号
news_dynamic_info_str = json.loads(news_dynamic_info_str)
simple = ["news_id","title","ctime","cate"]
for k in simple:
news_dynamic_info_str[k] = news_info_str[k]
return news_dynamic_info_str
def get_hot_list(self, user_id):
"""热门页列表结果"""
hot_list_key_prefix = "user_id_hot_list:"
hot_list_user_key = hot_list_key_prefix + str(user_id)
user_exposure_prefix = "user_exposure:"
user_exposure_key = user_exposure_prefix + str(user_id)
# 当数据库中没有这个用户的数据,就从热门列表中拷贝一份
if self.reclist_redis_db.exists(hot_list_user_key) == 0: # 存在返回1,不存在返回0
print("copy a hot_list for {}".format(hot_list_user_key))
# 给当前用户重新生成一个hot页推荐列表, 也就是把hot_list里面的列表复制一份给当前user key换成user_id
self.reclist_redis_db.zunionstore(hot_list_user_key, ["hot_list"])
# 一页默认10个item, 但这里候选20条,因为有可能有的在推荐页曝光过
article_num = 50
# 返回的是一个news_id列表 zrevrange排序分值从大到小
candiate_id_list = self.reclist_redis_db.zrevrange(hot_list_user_key, 0, article_num-1)
print("candiate_id_list", candiate_id_list)
if len(candiate_id_list) > 0:
# 根据news_id获取新闻的具体内容,并返回一个列表,列表中的元素是按照顺序展示的新闻信息字典
news_info_list = []
selected_news = [] # 记录真正被选了的
cou = 0
# 曝光列表
print("self.reclist_redis_db.exists(key)",self.exposure_redis_db.exists(user_exposure_key))
if self.exposure_redis_db.exists(user_exposure_key) > 0:
exposure_list = self.exposure_redis_db.smembers(user_exposure_key)
news_expose_list = set(map(lambda x: x.split(':')[0], exposure_list))
else:
news_expose_list = set()
for i in range(len(candiate_id_list)):
candiate = candiate_id_list[i]
news_id = candiate.split('_')[1]
# 去重曝光过的,包括在推荐页以及hot页
if news_id in news_expose_list:
continue
# TODO 有些新闻可能获取不到静态的信息,这里应该有什么bug
# bug 原因是,json.loads() redis中的数据会报错,需要对redis中的数据进行处理
# 可以在物料处理的时候过滤一遍,json无法load的新闻
try:
news_info_dict = self.get_news_detail(news_id)
except Exception as e:
with open("/home/recsys/news_rec_server/logs/news_bad_cases.log", "a+") as f:
f.write(news_id + "\n")
print("there are not news detail info for {}".format(news_id))
continue
# news_info_str = news_info_str.replace("'", '"' ) # 将单引号都替换成双引号
# news_info_dict = json.loads(news_info_str)
# 需要确认一下前端接收的json,key需要是单引号还是双引号
news_info_list.append(news_info_dict)
news_expose_list.add(news_id)
# 注意,原数的key中是包含了类别信息的
selected_news.append(candiate)
cou += 1
if cou == 10:
break
if len(selected_news) > 0:
# 手动删除读取出来的缓存结果, 这个很关键, 返回被删除的元素数量,用来检测是否被真的被删除了
removed_num = self.reclist_redis_db.zrem(hot_list_user_key, *selected_news)
print("the numbers of be removed:", removed_num)
# 曝光重新落表
self._save_user_exposure(user_id,news_expose_list)
#print(news_expose_list, len(news_expose_list))
# print(news_info_list)
return news_info_list
else:
#TODO 临时这么做,这么做不太好
self.reclist_redis_db.zunionstore(hot_list_user_key, ["hot_list"])
print("copy a hot_list for {}".format(hot_list_user_key))
# 如果是把所有内容都刷完了再重新拷贝的数据,还得记得把今天的曝光数据给清除了
self.exposure_redis_db.delete(user_exposure_key)
return self.get_hot_list(user_id)
def get_news_detail(self, news_id):
"""获取新闻展示的详细信息
"""
# print(1111)
news_info_str = self.static_news_info_redis_db.get("static_news_detail:" + news_id)
# print(222)
# print(news_info_str)
news_info_str = news_info_str.replace('\'', '\"' ) # 将单引号都替换成双引号
# print(333)
# print(news_info_str)
news_info_dit = json.loads(news_info_str)
# print(444)
# print("news_info_dit:", news_info_dit)
news_dynamic_info_str = self.dynamic_news_info_redis_db.get("dynamic_news_detail:" + news_id)
news_dynamic_info_str = news_dynamic_info_str.replace("'", '"' ) # 将单引号都替换成双引号
news_dynamic_info_dit = json.loads(news_dynamic_info_str)
# print("news_info_dit:", news_dynamic_info_dit)
for k in news_dynamic_info_dit.keys():
news_info_dit[k] = news_dynamic_info_dit[k]
return news_info_dit
def update_news_dynamic_info(self, news_id,action_type):
"""更新新闻展示的详细信息
"""
news_dynamic_info_str = self.dynamic_news_info_redis_db.get("dynamic_news_detail:" + news_id)
news_dynamic_info_str = news_dynamic_info_str.replace("'", '"' ) # 将单引号都替换成双引号
news_dynamic_info_dict = json.loads(news_dynamic_info_str)
# print("update",news_id,action_type)
if len(action_type) == 2:
if action_type[1] == "true":
news_dynamic_info_dict[action_type[0]] +=1
elif action_type[1] == "false":
news_dynamic_info_dict[action_type[0]] -=1
else:
news_dynamic_info_dict["read_num"] +=1
# print("update",news_dynamic_info_dict)
news_dynamic_info_str = json.dumps(news_dynamic_info_dict)
# print("update",news_dynamic_info_str)
news_dynamic_info_str = news_dynamic_info_str.replace('"', "'" )
res = self.dynamic_news_info_redis_db.set("dynamic_news_detail:" + news_id, news_dynamic_info_str)
return res
def _save_user_exposure(self, user_id, newslist):
"""记录用户曝光到redis"""
if len(newslist) == 0: return False # 无曝光数目
ctime = str(round(time.time()*1000)) # 曝光时间戳
key = "user_exposure:" + str(user_id) # 为key拼接
# 将历史曝光记录与newlist(最新曝光)的交集新闻提出来 并将该部分删除,防止重复存储曝光新闻
exposure_news_set = self.exposure_redis_db.smembers(key) # 历史曝光记录
del_exposure_news = [] # 历史曝光记录与newlist(最新曝光)的交集新闻,需要删除
if exposure_news_set.__len__() != 0:
del_exposure_news = [item for item in exposure_news_set if item.split(":")[0] in newslist]
# 为曝光过的新闻拼接时间
news_save = []
for news_id in newslist:
val = news_id+":"+ctime
val = val.replace('"', "'" ) # 将双引号都替换成单引号
news_save.append(val)
# 存储redis
try:
if del_exposure_news.__len__() != 0:
self.exposure_redis_db.srem(key,*del_exposure_news)
self.exposure_redis_db.sadd(key,*news_save)
except Exception as e:
print(str(e))
return False
return True
# def save_user_consume(self, user_id, new_id):
# """
# 记录用户消费过的新闻
# """
# key = "user_consume:" + str(user_id)
# new_id = new_id.replace('"', "'" ) # 将双引号都替换成单引号
# try:
# self.consume_redis_db.sadd(key,new_id)
# except Exception as e:
# print(str(e))
# return False
# return True
if __name__ == "__main__":
# 测试单例模式
oneline_server = OnlineServer()
oneline_server.get_hot_list("4563333734895456257")
# print(oneline_server.get_hot_list("4563333734895456257"))
# print("***********************")
# print(oneline_server.get_hot_list("4563333734895456257"))