新闻推荐系统第一版代码

This commit is contained in:
RuyiLuo
2021-12-04 10:52:00 +08:00
parent 339ce9498a
commit 647ee4ab90
89 changed files with 3515 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
codes/news_rec_web/Vue-newsinfo/node_modules
+25
View File
@@ -0,0 +1,25 @@
该项目主要是新闻推荐系统的后端及推荐服务相关的内容,这个项目将前端单独拿出来了。
代码规范,先用python规范就行
# TODO
项目目录结构待完善
## 前端展示逻辑:
1. 开机页(放一张和app大小相同的页面,上面显示几个字),此时只能点击我的进行登录,否则无法看到内部的具体内容
2. 登录页,用户名,密码,登录,注册等相关界面
输入用户名和密码,如果后端返回ok, 就跳转到推荐页,否则提示账号或者密码错误
3. 推荐和热门面显示的内容不需要变
4. 点击某一篇文章之后,向后端发送 user_id, news_id, action= { read, likes, collections }
## 后端数据相关
1. log日志
2. 用户画像数据及更新
3. 新闻画像数据及更新
运行新闻推荐后端服务
python server.py
+4
View File
@@ -0,0 +1,4 @@
# 目录介绍
该目录主要存储的是新闻推荐系统的所有配置参数及文件
+40
View File
@@ -0,0 +1,40 @@
# 数据库相关的配置文件
user_info_db_name = "userinfo" # 用户数据相关的数据库
register_user_table_name = "register_user" # 注册用户数据表
user_likes_table_name = "user_likes" # 用户喜欢数据表
user_collections_table_name = "user_collections" # 用户收藏数据表
user_read_table_name = "user_read" # 用户阅读数据表
exposure_table_name_prefix = "exposure" # 用户曝光数据表的前缀
# log数据,每天都会落一个盘,并由时间信息进行命名
loginfo_db_name = "loginfo" # log数据库
loginfo_table_name_prefix = "log" # log数据表的前缀
# 默认配置
mysql_username = "root"
mysql_passwd = "123456"
mysql_hostname = "localhost"
mysql_port = "3306"
# MongoDB
mongo_hostname = "127.0.0.1"
mongo_port = 27017
# Sina原始数据
sina_db_name= "SinaNews"
sina_collection_name_prefix= "news"
# 物料池db name
material_db_name = "NewsRecSys"
# 特征画像 集合名称
feature_protrail_collection_name = "FeatureProtrail"
redis_mongo_collection_name = "RedisProtrail"
user_protrail_collection_name = "UserProtrail"
# Redis
redis_hostname = "127.0.0.1"
redis_port = 6379
reclist_redis_db_num = 0
static_news_info_db_num = 1
dynamic_news_info_db_num = 2
user_exposure_db_num = 3
+746
View File
@@ -0,0 +1,746 @@
$
0
1
2
3
4
5
6
7
8
9
?
_
一些
一何
一切
一则
一方面
一旦
一来
一样
一般
一转眼
万一
上下
不仅
不但
不光
不单
不只
不外乎
不如
不妨
不尽
不尽然
不得
不怕
不惟
不成
不拘
不料
不是
不比
不然
不特
不独
不管
不至于
不若
不论
不过
不问
与其
与其说
与否
与此同时
且不说
且说
两者
个别
为了
为什么
为何
为止
为此
为着
乃至
乃至于
之一
之所以
之类
乌乎
也好
也罢
二来
于是
于是乎
云云
云尔
人们
人家
什么
什么样
介于
仍旧
从此
从而
他人
他们
以上
以为
以便
以免
以及
以故
以期
以来
以至
以至于
以致
任何
任凭
似的
但凡
但是
何以
何况
何处
何时
余外
作为
你们
使
使得
例如
依据
依照
便于
俺们
倘使
倘或
倘然
倘若
假使
假如
假若
傥然
先不先
光是
全体
全部
关于
其一
其中
其二
其他
其余
其它
其次
具体地说
具体说来
兼之
再其次
再则
再有
再者
再者说
再说
况且
几时
凡是
凭借
出于
出来
分别
则甚
别人
别处
别是
别的
别管
别说
前后
前此
前者
加之
加以
即令
即使
即便
即如
即或
即若
又及
及其
及至
反之
反而
反过来
反过来说
受到
另一方面
另外
另悉
只当
只怕
只是
只有
只消
只要
只限
叮咚
可以
可是
可见
各个
各位
各种
各自
同时
后者
向使
向着
否则
吧哒
呜呼
呵呵
呼哧
咱们
哈哈
哎呀
哎哟
哪个
哪些
哪儿
哪天
哪年
哪怕
哪样
哪边
哪里
哼唷
唯有
啪达
啷当
喔唷
嗡嗡
嘎登
嘿嘿
因为
因了
因此
因着
因而
固然
在下
在于
基于
处在
多么
多少
大家
她们
如上
如上所述
如下
如何
如其
如同
如是
如果
如此
如若
始而
孰料
孰知
宁可
宁愿
宁肯
它们
对于
对待
对方
对比
尔后
尔尔
尚且
就是
就是了
就是说
就算
就要
尽管
尽管如此
岂但
已矣
巴巴
并且
并非
庶乎
庶几
开外
开始
归齐
当地
当然
当着
彼时
彼此
得了
怎么
怎么办
怎么样
怎奈
怎样
总之
总的来看
总的来说
总的说来
总而言之
恰恰相反
惟其
慢说
我们
或则
或是
或曰
或者
截至
所以
所在
所幸
所有
才能
打从
抑或
按照
换句话说
换言之
据此
接着
故此
故而
旁人
无宁
无论
既往
既是
既然
时候
是以
是的
替代
有些
有关
有及
有时
有的
朝着
本人
本地
本着
本身
来着
来自
来说
极了
果然
果真
某个
某些
某某
根据
正值
正如
正巧
正是
此地
此处
此外
此时
此次
此间
毋宁
每当
比及
比如
比方
没奈何
沿
沿着
漫说
然则
然后
然而
照着
犹且
犹自
甚且
甚么
甚或
甚而
甚至
甚至于
用来
由于
由是
由此
由此可见
的确
的话
直到
相对而言
省得
眨眼
着呢
矣乎
矣哉
竟而
等到
等等
简言之
类如
紧接着
纵令
纵使
纵然
经过
结果
继之
继后
继而
综上所述
罢了
而且
而况
而后
而外
而已
而是
而言
能否
自个儿
自从
自各儿
自后
自家
自己
自打
自身
至于
至今
至若
般的
若夫
若是
若果
若非
莫不然
莫如
莫若
虽则
虽然
虽说
要不
要不是
要不然
要么
要是
譬喻
譬如
许多
设使
设或
设若
诚如
诚然
说来
诸位
诸如
谁人
谁料
谁知
贼死
赖以
起见
趁着
越是
较之
还是
还有
还要
这一来
这个
这么
这么些
这么样
这么点儿
这些
这会儿
这儿
这就是说
这时
这样
这次
这般
这边
这里
进而
连同
逐步
通过
遵循
遵照
那个
那么
那么些
那么样
那些
那会儿
那儿
那时
那样
那般
那边
那里
鄙人
鉴于
针对
除了
除外
除开
除此之外
除非
随后
随时
随着
难道说
非但
非徒
非特
非独
顺着
首先
@@ -0,0 +1,19 @@
from dao.mysql_server import MysqlServer
from dao.entity.logitem import LogItem
import time
class LogController():
def __init__(self) -> None:
self.log_info_sql_session = MysqlServer().get_loginfo_session()
def save_one_log(self,log):
try:
self.log_info_sql_session.add(log)
self.log_info_sql_session.commit()
except Exception as e:
print(str(e))
return False
return True
@@ -0,0 +1,116 @@
from dao.mysql_server import MysqlServer
from dao.entity.user_exposure import UserExposure
from dao.entity.register_user import RegisterUser
from dao.entity.user_likes import UserLikes
from dao.entity.user_read import UserRead
from dao.entity.user_collections import UserCollections
# 初始化数据表
user = UserLikes()
user = UserCollections()
user = UserExposure()
user = UserRead()
class UserAction():
def __init__(self) -> None:
self.user_exposure_sql_session = MysqlServer().get_user_exposure_session()
self.register_user_sql_session = MysqlServer().get_register_user_session()
self.user_like_sql_session = MysqlServer().get_user_like_session()
self.user_collection_sql_session = MysqlServer().get_user_collection_session()
self.user_read_sql_session = MysqlServer().get_user_read_session()
def user_is_exist(self, user, user_type):
"""
1 表示正确;2 表示密码错误;0 表示用户不存在
"""
if user_type == "login":
if self.register_user_sql_session.query(RegisterUser).filter(RegisterUser.username == user.username, \
RegisterUser.passwd == user.passwd).count() > 0:
return 1
elif self.register_user_sql_session.query(RegisterUser).filter(RegisterUser.username == user.username).count() > 0:
return 2
else:
return 0
else:
if self.register_user_sql_session.query(RegisterUser).filter(RegisterUser.username == user.username).count() > 0:
return 1
else:
return 0
def save_user(self,user):
try:
self.register_user_sql_session.add(user)
self.register_user_sql_session.commit()
# self.register_user_sql_session.close()
except Exception as e:
print(str(e))
return False
return True
def get_user_id_by_name(self,username):
try:
userid = self.register_user_sql_session.query(RegisterUser.userid).filter(RegisterUser.username == username).one()[0]
except Exception as e:
print(str(e))
return None
return userid
def get_likes_counts_by_user(self,user_id,news_id):
return self.user_like_sql_session.query(UserLikes).filter(UserLikes.userid == user_id, UserLikes.newid == news_id).count()
def get_coll_counts_by_user(self,user_id,news_id):
return self.user_collection_sql_session.query(UserCollections).filter(UserCollections.userid == user_id,
UserCollections.newid == news_id).count()
def del_likes_by_user(self,user_id,news_id):
try:
print(user_id,news_id)
delItems = self.user_like_sql_session.query(UserLikes).filter(UserLikes.userid == user_id, UserLikes.newid == news_id)
# print(delItems.count())
if delItems.count() > 0:
self.user_like_sql_session.query(UserLikes).filter(UserLikes.userid == user_id, UserLikes.newid == news_id).delete()
self.user_like_sql_session.commit()
except Exception as e:
print(str(e))
return False
return True
def del_coll_by_user(self,user_id,news_id):
try:
delItems = self.user_collection_sql_session.query(UserCollections).filter(UserCollections.userid == user_id,
UserCollections.newid == news_id)
if delItems.count() > 0:
self.user_collection_sql_session.query(UserCollections).filter(UserCollections.userid == user_id,
UserCollections.newid == news_id).delete()
self.user_collection_sql_session.commit()
except Exception as e:
print(str(e))
return False
return True
def save_one_action(self,action):
if isinstance(action, UserLikes):
try:
self.user_like_sql_session.add(action)
self.user_like_sql_session.commit()
except Exception as e:
print(str(e))
return False
elif isinstance(action, UserCollections):
try:
self.user_collection_sql_session.add(action)
self.user_collection_sql_session.commit()
except Exception as e:
print(str(e))
return False
elif isinstance(action, UserRead):
try:
self.user_read_sql_session.add(action)
self.user_read_sql_session.commit()
except Exception as e:
print(str(e))
return False
return True
+16
View File
@@ -0,0 +1,16 @@
DAO层主要是做数据持久层的工作,主要与数据库进行交互。DAO层首先会创建DAO接口,然后会在配置文件中定义该接口的实现类,
接着就可以在模块中就可以调用DAO 的接口进行数据业务的而处理,并且不用关注此接口的具体实现类是哪一个类。DAO 层的数据源和数据库连接的参数数都是在配置文件中进行配置的。
TODO: 设置开机自启动
启动mongodb(服务器断电之后就会断开链接):
sudo ./mongod --dbpath=/usr/local/mongodb/data/ --fork --logpath=/usr/local/mongodb/log
TODO: 设置开机自启动
启动redis
redis-server --daemonize yes --port 6378 --requirepass 123456
redis-cli --raw
# TODO
MySQL 使用SQLAlchemy 插入中文会报错
@@ -0,0 +1,38 @@
import sys
sys.path.append("../../")
import time
from sqlalchemy import Column, String, Integer,DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql import func
from dao.mysql_server import MysqlServer
from conf.dao_config import loginfo_db_name, loginfo_table_name_prefix
# 定义基类
Base = declarative_base()
# 定义映射关系
class LogItem(Base):
"""log日志数据
"""
postfix = time.strftime("%Y_%m_%d", time.localtime())
# 每天都会创建一个新的表,带有时间信息
__tablename__ = '{}_{}'.format(loginfo_table_name_prefix, postfix)
index = Column(Integer(), primary_key=True)
userid = Column(String(30))
newsid = Column(String(100))
# 阅读、点赞、收藏
actiontype = Column(String(20))
actiontime = Column(DateTime(timezone=True), server_default=func.now())
def __init__(self):
# 与数据库绑定映射关系
engine = MysqlServer().get_loginfo_engine()
Base.metadata.create_all(engine)
def new(self,userid,newsid,actiontype):
self.userid = userid
self.newsid = newsid
self.actiontype = actiontype
@@ -0,0 +1,25 @@
from sqlalchemy import Column, String, Integer
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql.sqltypes import BigInteger
from conf.dao_config import register_user_table_name
from dao.mysql_server import MysqlServer
Base = declarative_base()
class RegisterUser(Base):
"""用户注册数据
"""
__tablename__ = register_user_table_name
index = Column(Integer(), primary_key=True)
userid = Column(BigInteger())
username = Column(String(30))
passwd = Column(String(500))
gender = Column(String(30))
age = Column(String(5))
city = Column(String(10))
def __init__(self):
# 与数据库绑定映射关系
engine = MysqlServer().get_register_user_engine()
Base.metadata.create_all(engine)
@@ -0,0 +1,30 @@
from sqlalchemy import Column, String, Integer, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql.sqltypes import BigInteger, DateTime
from conf.dao_config import user_collections_table_name
from dao.mysql_server import MysqlServer
from sqlalchemy.sql import func
Base = declarative_base()
class UserCollections(Base):
"""用户收藏新闻数据
"""
__tablename__ = user_collections_table_name
index = Column(Integer(), primary_key=True,autoincrement=True)
userid = Column(BigInteger())
username = Column(String(30))
newid = Column(String(100))
curtime = Column(DateTime(timezone=True), server_default=func.now())
def __init__(self):
# 与数据库绑定映射关系
engine = MysqlServer().get_user_collection_engine()
Base.metadata.create_all(engine)
def new(self,userid,username,newid):
self.userid = userid
self.username = username
self.newid = newid
# self.curtime = curtime
@@ -0,0 +1,35 @@
from sqlalchemy import Column, String, Integer
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql.sqltypes import BigInteger,DateTime
from conf.dao_config import exposure_table_name_prefix
from dao.mysql_server import MysqlServer
from sqlalchemy.sql import func
import time
Base = declarative_base()
class UserExposure(Base):
"""用户曝光数据
"""
postfix = time.strftime("%Y_%m_%d", time.localtime())
# 每天都会创建一个新的表,带有时间信息
__tablename__ = '{}_{}'.format(exposure_table_name_prefix, postfix)
index = Column(Integer(), primary_key=True,autoincrement=True)
userid = Column(BigInteger())
newid = Column(String(600))
curtime = Column(String(50))
# curtime = Column(DateTime(timezone=True), server_default=func.now())
def __init__(self):
# 与数据库绑定映射关系
engine = MysqlServer().get_user_exposure_engine()
Base.metadata.create_all(engine)
def new(self,userid,newid,curtime):
self.userid = userid
self.newid = newid
self.curtime = curtime
@@ -0,0 +1,30 @@
from sqlalchemy import Column, String, Integer, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql.sqltypes import BigInteger
from conf.dao_config import user_likes_table_name
from dao.mysql_server import MysqlServer
from sqlalchemy.sql import func
Base = declarative_base()
class UserLikes(Base):
"""用户喜欢新闻数据
"""
__tablename__ = user_likes_table_name
index = Column(Integer(), primary_key=True,autoincrement=True)
userid = Column(BigInteger())
username = Column(String(30))
newid = Column(String(100))
curtime = Column(DateTime(timezone=True), server_default=func.now())
def __init__(self):
# 与数据库绑定映射关系
engine = MysqlServer().get_user_like_engine()
Base.metadata.create_all(engine)
def new(self,userid,username,newid):
self.userid = userid
self.username = username
self.newid = newid
# self.curtime = curtime
@@ -0,0 +1,28 @@
from sqlalchemy import Column, String, Integer, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql.sqltypes import BigInteger, DateTime
from conf.dao_config import user_read_table_name
from dao.mysql_server import MysqlServer
from sqlalchemy.sql import func
Base = declarative_base()
class UserRead(Base):
"""用户阅读新闻数据
"""
__tablename__ = user_read_table_name
index = Column(Integer(), primary_key=True, autoincrement=True)
userid = Column(BigInteger())
newid = Column(String(100))
curtime = Column(DateTime(timezone=True), server_default=func.now())
def __init__(self):
# 与数据库绑定映射关系
engine = MysqlServer().get_user_read_engine()
Base.metadata.create_all(engine)
def new(self, userid, newid, actiontime):
self.userid = userid
self.newid = newid
self.curtime = str(actiontime)
+54
View File
@@ -0,0 +1,54 @@
import sys
import datetime
sys.path.append("../")
import pymongo
from conf.dao_config import mongo_hostname, mongo_port
from conf.dao_config import sina_db_name, sina_collection_name_prefix
from conf.dao_config import material_db_name, feature_protrail_collection_name
from conf.dao_config import redis_mongo_collection_name
from conf.dao_config import user_protrail_collection_name
class MongoServer(object):
def __init__(self, _mongo_hostname=mongo_hostname, _mongo_port=mongo_port, _sina_db_name=sina_db_name,
_sina_collection_name_prefix=sina_collection_name_prefix, _material_db_name=material_db_name,
_feature_protrail_collection_name=feature_protrail_collection_name,
_redis_mongo_collection_name=redis_mongo_collection_name,
_user_protrail_collection_name=user_protrail_collection_name):
self._hostname = _mongo_hostname
self._port = _mongo_port
self._sina_db_name = _sina_db_name
self._sina_collection_name_prefix = _sina_collection_name_prefix
self._material_db_name = _material_db_name
self._feature_protrail_collection_name = _feature_protrail_collection_name
self._redis_mongo_collection_name = _redis_mongo_collection_name
self._user_protrail_collection_name = user_protrail_collection_name
self._mongo_client = self._mongodb()
def _mongodb(self):
"""连接mongo数据库,并返回数据库
"""
client = pymongo.MongoClient(self._hostname, self._port)
return client
def get_feature_protrail_collection(self):
"""特征画像集合
"""
return self._mongo_client[self._material_db_name][self._feature_protrail_collection_name]
def get_sina_news_collection(self):
"""原始新闻画像集合, 新闻爬取数据collection会以当天的时间命名
"""
sina_collection_name = self._sina_collection_name_prefix + "_" + \
"".join(str(datetime.date.today()).split('-'))
return self._mongo_client[self._sina_db_name][sina_collection_name]
def get_redis_mongo_collection(self):
"""redis中的mongo备份数据集合
"""
return self._mongo_client[self._material_db_name][self._redis_mongo_collection_name]
def get_user_protrail_collection(self):
"""用户画像的数据集合
"""
return self._mongo_client[self._material_db_name][self._user_protrail_collection_name]
+102
View File
@@ -0,0 +1,102 @@
import sys
sys.path.append("../")
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from conf.dao_config import loginfo_db_name, user_info_db_name
class MysqlServer(object):
def __init__(self, username="root", passwd="123456", hostname="localhost", port="3306",
user_info_db_name=user_info_db_name, loginfo_db_name=loginfo_db_name):
self.username = username
self.passwd = passwd
self.hostname = hostname
self.port = port
self.user_info_db_name = user_info_db_name
self.loginfo_db_name = loginfo_db_name
def session(self, db_name):
"""链接数据库,绑定映射关系
"""
# 创建引擎
engine = create_engine("mysql+pymysql://{}:{}@{}:{}/{}".format(
self.username, self.passwd, self.hostname, self.port, db_name
), encoding="utf-8", echo=False)
# 创建会话
session = sessionmaker(bind=engine)
# 返回engine 和 session, 前者用来绑定本地数据,后者用来本地操作数据库
return engine, session()
def get_register_user_session(self):
"""获取注册用户session
"""
_, sess = self.session(self.user_info_db_name)
return sess
def get_loginfo_session(self):
"""获取log日志的session
"""
_, sess = self.session(self.loginfo_db_name)
return sess
def get_user_like_session(self):
"""获取用户喜欢新闻的session
"""
_, sess = self.session(self.user_info_db_name)
return sess
def get_user_collection_session(self):
"""获取用户收藏新闻的session
"""
_, sess = self.session(self.user_info_db_name)
return sess
def get_user_exposure_session(self):
"""获取用户曝光的session
"""
_, sess = self.session(self.user_info_db_name)
return sess
def get_user_read_session(self):
"""获取用户阅读的session
"""
_, sess = self.session(self.user_info_db_name)
return sess
def get_register_user_engine(self):
"""
"""
engine, _ = self.session(self.user_info_db_name)
return engine
def get_loginfo_engine(self):
"""
"""
engine, _ = self.session(self.loginfo_db_name)
return engine
def get_user_like_engine(self):
"""获取用户喜欢新闻的engine
"""
engine, _ = self.session(self.user_info_db_name)
return engine
def get_user_collection_engine(self):
"""获取用户收藏新闻的engine
"""
engine, _ = self.session(self.user_info_db_name)
return engine
def get_user_read_engine(self):
"""获取用户阅读新闻的engine
"""
engine, _ = self.session(self.user_info_db_name)
return engine
def get_user_exposure_engine(self):
"""获取用户曝光的engine
"""
engine, _ = self.session(self.user_info_db_name)
return engine
+42
View File
@@ -0,0 +1,42 @@
import sys
sys.path.append("../")
import redis
from conf.dao_config import redis_hostname, redis_port, static_news_info_db_num, dynamic_news_info_db_num, reclist_redis_db_num
from conf.dao_config import user_exposure_db_num
class RedisServer(object):
def __init__(self, _redis_hostname=redis_hostname, _port=redis_port, _static_news_info_db_num=static_news_info_db_num,
_dynamic_news_info_db_num = dynamic_news_info_db_num, _reclist_redis_db_num=reclist_redis_db_num,
_user_exposure_db_num=user_exposure_db_num):
self.hostname = _redis_hostname
self.port = _port
self.static_news_info_db_num = _static_news_info_db_num
self.dynamic_news_info_db_num = _dynamic_news_info_db_num
self.reclist_redis_db_num = _reclist_redis_db_num
self.user_exposure_db_num = _user_exposure_db_num
def _redis_db(self, db_num=0):
res_db = redis.StrictRedis(host=self.hostname, port=self.port, db=db_num, decode_responses=True)
return res_db
def get_static_news_info_redis(self):
"""获取静态新闻信息数据库
"""
return self._redis_db(self.static_news_info_db_num)
def get_dynamic_news_info_redis(self):
"""获取动态新闻信息数据库
"""
return self._redis_db(self.dynamic_news_info_db_num)
def get_reclist_redis(self):
"""用户推荐列表redis数据库
"""
return self._redis_db(self.reclist_redis_db_num)
def get_exposure_redis(self):
"""用户曝光列表redis数据库
"""
return self._redis_db(self.user_exposure_db_num)
@@ -0,0 +1,10 @@
2021-12-04-00-10-21
run update_new_items success.
update_dynamic_feature_protrail success.
delete RedisProtrail ...
run update_redis_mongo_protrail_data success.
process_material success.
process_user.py success.
news detail info are saved in redis db.
update_redis success.
@@ -0,0 +1,151 @@
d04ec960-fb54-44c4-93d8-27aee82a14a5
9eb67338-c4dd-4fab-b9f6-dd3d1f635078
8e744b2e-283e-4880-a35d-010e22f9b6d1
64a9131f-7bef-4026-af19-a437258b698b
5735d3ba-2ae7-44b0-87b1-a4212042dfd5
2ec76526-1734-4631-85d5-38b53c289724
2631c157-4bd1-469e-a69a-5cc40d56087e
0ed4e74d-5133-42fe-b9e9-c2f4a217aae6
e6feadd0-b0ca-4dad-9cf0-e51d59208741
de8f34ed-894f-454a-8af5-498cb5bfa416
d6bfbcb5-e2aa-43af-85c1-a53776ee55da
c83f6e63-3614-46d4-9c3b-56acad2c6053
ae39b902-7e4c-4392-972d-97d876e09802
a2b457b0-232f-4175-a618-08bf257bff13
8ce201a5-a59a-45e2-9c80-d1530213dd76
5f52e821-b2f1-4328-85f0-62bc8e0b36e7
328a2cc0-89bf-4626-b9ee-f3ee4c6873f8
30fb8ac3-7cc8-4666-9aac-c66cd4cd8e20
148e8b52-5407-4545-9c5a-1745236f8139
06ab8ab1-0170-4fca-8f7a-900a82872378
f9f4c879-005a-4d7a-827e-099666396bd4
d04ec960-fb54-44c4-93d8-27aee82a14a5
9eb67338-c4dd-4fab-b9f6-dd3d1f635078
8e744b2e-283e-4880-a35d-010e22f9b6d1
64a9131f-7bef-4026-af19-a437258b698b
5735d3ba-2ae7-44b0-87b1-a4212042dfd5
2ec76526-1734-4631-85d5-38b53c289724
2631c157-4bd1-469e-a69a-5cc40d56087e
0ed4e74d-5133-42fe-b9e9-c2f4a217aae6
e6feadd0-b0ca-4dad-9cf0-e51d59208741
de8f34ed-894f-454a-8af5-498cb5bfa416
d6bfbcb5-e2aa-43af-85c1-a53776ee55da
c83f6e63-3614-46d4-9c3b-56acad2c6053
ae39b902-7e4c-4392-972d-97d876e09802
a2b457b0-232f-4175-a618-08bf257bff13
8ce201a5-a59a-45e2-9c80-d1530213dd76
5f52e821-b2f1-4328-85f0-62bc8e0b36e7
328a2cc0-89bf-4626-b9ee-f3ee4c6873f8
30fb8ac3-7cc8-4666-9aac-c66cd4cd8e20
148e8b52-5407-4545-9c5a-1745236f8139
06ab8ab1-0170-4fca-8f7a-900a82872378
f9f4c879-005a-4d7a-827e-099666396bd4
d04ec960-fb54-44c4-93d8-27aee82a14a5
9eb67338-c4dd-4fab-b9f6-dd3d1f635078
8e744b2e-283e-4880-a35d-010e22f9b6d1
64a9131f-7bef-4026-af19-a437258b698b
5735d3ba-2ae7-44b0-87b1-a4212042dfd5
2ec76526-1734-4631-85d5-38b53c289724
2631c157-4bd1-469e-a69a-5cc40d56087e
0ed4e74d-5133-42fe-b9e9-c2f4a217aae6
e6feadd0-b0ca-4dad-9cf0-e51d59208741
de8f34ed-894f-454a-8af5-498cb5bfa416
d6bfbcb5-e2aa-43af-85c1-a53776ee55da
c83f6e63-3614-46d4-9c3b-56acad2c6053
ae39b902-7e4c-4392-972d-97d876e09802
a2b457b0-232f-4175-a618-08bf257bff13
8ce201a5-a59a-45e2-9c80-d1530213dd76
5f52e821-b2f1-4328-85f0-62bc8e0b36e7
328a2cc0-89bf-4626-b9ee-f3ee4c6873f8
30fb8ac3-7cc8-4666-9aac-c66cd4cd8e20
148e8b52-5407-4545-9c5a-1745236f8139
06ab8ab1-0170-4fca-8f7a-900a82872378
f9f4c879-005a-4d7a-827e-099666396bd4
d04ec960-fb54-44c4-93d8-27aee82a14a5
9eb67338-c4dd-4fab-b9f6-dd3d1f635078
8e744b2e-283e-4880-a35d-010e22f9b6d1
64a9131f-7bef-4026-af19-a437258b698b
5735d3ba-2ae7-44b0-87b1-a4212042dfd5
2ec76526-1734-4631-85d5-38b53c289724
2631c157-4bd1-469e-a69a-5cc40d56087e
0ed4e74d-5133-42fe-b9e9-c2f4a217aae6
e6feadd0-b0ca-4dad-9cf0-e51d59208741
de8f34ed-894f-454a-8af5-498cb5bfa416
d6bfbcb5-e2aa-43af-85c1-a53776ee55da
c83f6e63-3614-46d4-9c3b-56acad2c6053
ae39b902-7e4c-4392-972d-97d876e09802
a2b457b0-232f-4175-a618-08bf257bff13
8ce201a5-a59a-45e2-9c80-d1530213dd76
5f52e821-b2f1-4328-85f0-62bc8e0b36e7
328a2cc0-89bf-4626-b9ee-f3ee4c6873f8
30fb8ac3-7cc8-4666-9aac-c66cd4cd8e20
148e8b52-5407-4545-9c5a-1745236f8139
06ab8ab1-0170-4fca-8f7a-900a82872378
f9f4c879-005a-4d7a-827e-099666396bd4
c1ea3624-7e16-41e3-91ca-5f2237c90016
d04ec960-fb54-44c4-93d8-27aee82a14a5
9eb67338-c4dd-4fab-b9f6-dd3d1f635078
8e744b2e-283e-4880-a35d-010e22f9b6d1
64a9131f-7bef-4026-af19-a437258b698b
5735d3ba-2ae7-44b0-87b1-a4212042dfd5
2ec76526-1734-4631-85d5-38b53c289724
2631c157-4bd1-469e-a69a-5cc40d56087e
0ed4e74d-5133-42fe-b9e9-c2f4a217aae6
e6feadd0-b0ca-4dad-9cf0-e51d59208741
de8f34ed-894f-454a-8af5-498cb5bfa416
d6bfbcb5-e2aa-43af-85c1-a53776ee55da
c83f6e63-3614-46d4-9c3b-56acad2c6053
ae39b902-7e4c-4392-972d-97d876e09802
a2b457b0-232f-4175-a618-08bf257bff13
8ce201a5-a59a-45e2-9c80-d1530213dd76
5f52e821-b2f1-4328-85f0-62bc8e0b36e7
328a2cc0-89bf-4626-b9ee-f3ee4c6873f8
30fb8ac3-7cc8-4666-9aac-c66cd4cd8e20
148e8b52-5407-4545-9c5a-1745236f8139
06ab8ab1-0170-4fca-8f7a-900a82872378
f9f4c879-005a-4d7a-827e-099666396bd4
c1ea3624-7e16-41e3-91ca-5f2237c90016
d04ec960-fb54-44c4-93d8-27aee82a14a5
9eb67338-c4dd-4fab-b9f6-dd3d1f635078
8e744b2e-283e-4880-a35d-010e22f9b6d1
64a9131f-7bef-4026-af19-a437258b698b
5735d3ba-2ae7-44b0-87b1-a4212042dfd5
2ec76526-1734-4631-85d5-38b53c289724
2631c157-4bd1-469e-a69a-5cc40d56087e
0ed4e74d-5133-42fe-b9e9-c2f4a217aae6
e6feadd0-b0ca-4dad-9cf0-e51d59208741
de8f34ed-894f-454a-8af5-498cb5bfa416
d6bfbcb5-e2aa-43af-85c1-a53776ee55da
c83f6e63-3614-46d4-9c3b-56acad2c6053
ae39b902-7e4c-4392-972d-97d876e09802
a2b457b0-232f-4175-a618-08bf257bff13
8ce201a5-a59a-45e2-9c80-d1530213dd76
5f52e821-b2f1-4328-85f0-62bc8e0b36e7
328a2cc0-89bf-4626-b9ee-f3ee4c6873f8
30fb8ac3-7cc8-4666-9aac-c66cd4cd8e20
148e8b52-5407-4545-9c5a-1745236f8139
06ab8ab1-0170-4fca-8f7a-900a82872378
f9f4c879-005a-4d7a-827e-099666396bd4
c1ea3624-7e16-41e3-91ca-5f2237c90016
d04ec960-fb54-44c4-93d8-27aee82a14a5
9eb67338-c4dd-4fab-b9f6-dd3d1f635078
8e744b2e-283e-4880-a35d-010e22f9b6d1
64a9131f-7bef-4026-af19-a437258b698b
5735d3ba-2ae7-44b0-87b1-a4212042dfd5
2ec76526-1734-4631-85d5-38b53c289724
2631c157-4bd1-469e-a69a-5cc40d56087e
0ed4e74d-5133-42fe-b9e9-c2f4a217aae6
e6feadd0-b0ca-4dad-9cf0-e51d59208741
de8f34ed-894f-454a-8af5-498cb5bfa416
d6bfbcb5-e2aa-43af-85c1-a53776ee55da
c83f6e63-3614-46d4-9c3b-56acad2c6053
ae39b902-7e4c-4392-972d-97d876e09802
a2b457b0-232f-4175-a618-08bf257bff13
8ce201a5-a59a-45e2-9c80-d1530213dd76
5f52e821-b2f1-4328-85f0-62bc8e0b36e7
328a2cc0-89bf-4626-b9ee-f3ee4c6873f8
30fb8ac3-7cc8-4666-9aac-c66cd4cd8e20
148e8b52-5407-4545-9c5a-1745236f8139
06ab8ab1-0170-4fca-8f7a-900a82872378
f9f4c879-005a-4d7a-827e-099666396bd4
c1ea3624-7e16-41e3-91ca-5f2237c90016
@@ -0,0 +1,74 @@
2021-11-30-19-03-01
scrapy crawl sina_spider --pages success.
run python monitor_news.py success.
run update_new_items success.
delete RedisProtrail ...
run update_redis_mongo_protrail_data success.
news detail info are saved in redis db.
material to mongo and redis success.
2021-11-30-19-08-01
scrapy crawl sina_spider --pages success.
run python monitor_news.py success.
run update_new_items success.
delete RedisProtrail ...
run update_redis_mongo_protrail_data success.
news detail info are saved in redis db.
material to mongo and redis success.
material to mongo and redis fail.
material to mongo and redis fail.
2021-12-02-09-13-04
scrapy crawl sina_spider --pages success.
the news nums of news_20211202 collection is 251 and less then 1000.
run python monitor_news.py success.
material to mongo and redis fail.
material to mongo and redis fail.
update_dynamic_feature_protrail success.
material to mongo and redis fail.
update_dynamic_feature_protrail success.
run update_new_items success.
delete RedisProtrail ...
run update_redis_mongo_protrail_data success.
news detail info are saved in redis db.
material to mongo and redis success.
2021-12-02-23-00-01
scrapy crawl sina_spider --pages success.
the news nums of news_20211202 collection is 644 and less then 1000.
run python monitor_news.py success.
material to mongo and redis fail.
material to mongo and redis fail.
material to mongo and redis fail.
update_dynamic_feature_protrail success.
run update_new_items success.
delete RedisProtrail ...
run update_redis_mongo_protrail_data success.
news detail info are saved in redis db.
material to mongo and redis success.
2021-12-03-09-38-39
scrapy crawl sina_spider --pages success.
the news nums of news_20211203 collection is 659 and less then 1000.
run python monitor_news.py success.
2021-12-03-09-50-04
scrapy crawl sina_spider --pages success.
run python monitor_news.py success.
update_dynamic_feature_protrail success.
run update_new_items success.
delete RedisProtrail ...
run update_redis_mongo_protrail_data success.
news detail info are saved in redis db.
material to mongo and redis success.
2021-12-04-00-00-01
scrapy crawl sina_spider --pages success.
run python monitor_news.py success.
@@ -0,0 +1,93 @@
2021-11-30-19-03-01
a sorted news_ids are saved into redis.
run /home/recsys/miniconda3/envs/news_rec_py3/bin/python /home/recsys/news_rec_server/recprocess/offline.py success.
2021-11-30-19-08-19
a sorted news_ids are saved into redis.
run /home/recsys/miniconda3/envs/news_rec_py3/bin/python /home/recsys/news_rec_server/recprocess/offline.py success.
2021-12-01-01-00-01
a sorted news_ids are saved into redis.
run /home/recsys/miniconda3/envs/news_rec_py3/bin/python /home/recsys/news_rec_server/recprocess/offline.py success.
2021-12-02-01-00-01
a sorted news_ids are saved into redis.
run /home/recsys/miniconda3/envs/news_rec_py3/bin/python /home/recsys/news_rec_server/recprocess/offline.py success.
2021-12-02-09-13-30
a sorted news_ids are saved into redis.
run /home/recsys/miniconda3/envs/news_rec_py3/bin/python /home/recsys/news_rec_server/recprocess/offline.py success.
2021-12-02-09-18-07
a sorted news_ids are saved into redis.
run /home/recsys/miniconda3/envs/news_rec_py3/bin/python /home/recsys/news_rec_server/recprocess/offline.py success.
2021-12-02-09-23-18
a sorted news_ids are saved into redis.
run /home/recsys/miniconda3/envs/news_rec_py3/bin/python /home/recsys/news_rec_server/recprocess/offline.py success.
2021-12-03-01-00-02
a sorted news_ids are saved into redis.
a hot rec list are saved into redis.....
run /home/recsys/miniconda3/envs/news_rec_py3/bin/python /home/recsys/news_rec_server/recprocess/offline.py success.
2021-12-03-09-32-44
a sorted news_ids are saved into redis.
a hot rec list are saved into redis.....
run /home/recsys/miniconda3/envs/news_rec_py3/bin/python /home/recsys/news_rec_server/recprocess/offline.py success.
2021-12-03-09-33-10
a sorted news_ids are saved into redis.
a hot rec list are saved into redis.....
run /home/recsys/miniconda3/envs/news_rec_py3/bin/python /home/recsys/news_rec_server/recprocess/offline.py success.
2021-12-03-09-33-54
a sorted news_ids are saved into redis.
a hot rec list are saved into redis.....
run /home/recsys/miniconda3/envs/news_rec_py3/bin/python /home/recsys/news_rec_server/recprocess/offline.py success.
2021-12-03-10-05-18
a sorted news_ids are saved into redis.
a hot rec list are saved into redis.....
run /home/recsys/miniconda3/envs/news_rec_py3/bin/python /home/recsys/news_rec_server/recprocess/offline.py success.
2021-12-03-10-13-03
a sorted news_ids are saved into redis.
a hot rec list are saved into redis.....
run /home/recsys/miniconda3/envs/news_rec_py3/bin/python /home/recsys/news_rec_server/recprocess/offline.py success.
2021-12-03-10-14-12
a sorted news_ids are saved into redis.
a hot rec list are saved into redis.....
run /home/recsys/miniconda3/envs/news_rec_py3/bin/python /home/recsys/news_rec_server/recprocess/offline.py success.
2021-12-03-10-18-59
a sorted news_ids are saved into redis.
a hot rec list are saved into redis.....
run /home/recsys/miniconda3/envs/news_rec_py3/bin/python /home/recsys/news_rec_server/recprocess/offline.py success.
2021-12-03-10-22-57
a sorted news_ids are saved into redis.
a hot rec list are saved into redis.....
run /home/recsys/miniconda3/envs/news_rec_py3/bin/python /home/recsys/news_rec_server/recprocess/offline.py success.
2021-12-03-10-27-21
a sorted news_ids are saved into redis.
a hot rec list are saved into redis.....
run /home/recsys/miniconda3/envs/news_rec_py3/bin/python /home/recsys/news_rec_server/recprocess/offline.py success.
2021-12-03-10-28-49
a sorted news_ids are saved into redis.
a hot rec list are saved into redis.....
run /home/recsys/miniconda3/envs/news_rec_py3/bin/python /home/recsys/news_rec_server/recprocess/offline.py success.
2021-12-03-10-45-22
a sorted news_ids are saved into redis.
a hot rec list are saved into redis.....
run /home/recsys/miniconda3/envs/news_rec_py3/bin/python /home/recsys/news_rec_server/recprocess/offline.py success.
2021-12-04-00-11-59
a sorted news_ids are saved into redis.
a hot rec list are saved into redis.....
run /home/recsys/miniconda3/envs/news_rec_py3/bin/python /home/recsys/news_rec_server/recprocess/offline.py success.
+29
View File
@@ -0,0 +1,29 @@
# 物料库
## news_scrapy/
scrapy项目结构的细节可以参考开源项目对应的文档
- 每天定时从新浪新闻上面爬取当天的新闻,并将新闻存入到mongodb数据库中
- 爬取新闻是一个增量的过程,每天只爬取当天的新闻
- 新闻爬取的时候需要提前之前物料池中的所有物料的标题给存下来用来去重
TODO:
1. 启动crontab,定时爬取(联调的时候再使用)
2. 爬取新闻的数量,最后需要改大一点
## materials/
- 根据爬取的数据制作物料画像
- 新闻物料去重逻辑的实现,需要依赖materials/中最新的物料画像
TODO:
1. 将前端展示数据根据news_id存储到redis中
注意:Redis的value存储中文后,get之后显示16进制的字符串”\xe4\xb8\xad\xe5\x9b\xbd”,如何解决?
启动redis-cli时,在其后面加上--raw即可,汉字即可显示正常, 如上面所示的内容
注意:zrange rec_list 0 9 返回的是排序之后的前10个元素,并且是按照value从小到大进行排序的
@@ -0,0 +1,5 @@
当前目录是用来处理数据的
1. 将爬取的新闻原始数据处理成画像数据,对应的脚本:update_news_protrait.py
2. 将处理好的特征画像中需要展示的数据放到redis中,对应的脚本:news_to_redis.py
@@ -0,0 +1,32 @@
import sys
sys.path.append('../../')
from dao.mysql_server import MysqlServer
from dao.entity.user_read import UserRead
from dao.entity.logitem import LogItem
from controller.user_action_controller import UserAction
class LogProcess(object):
def __init__(self):
# 建立获取日志信息表的会话
self.user_log_sql_session = MysqlServer().get_loginfo_session()
def readlog_to_mysql(self):
# 拿到当天的用户阅读日志表
logtables = self.user_log_sql_session.query(LogItem).filter_by(actiontype="read").all()
# 遍历当天的用户日志表
# 对于每条数据, 建立UserRead类, 通过UserAction保存到数据库里面去
for log in logtables:
#print(log.userid, log.newsid, log.actiontype, log.actiontime)
user_read = UserRead()
user_read.new(log.userid, log.newsid, log.actiontime)
UserAction().save_one_action(user_read)
if __name__ == "__main__":
LogProcess().readlog_to_mysql()
@@ -0,0 +1,134 @@
# -*- coding: utf-8 -*-
from re import S
import sys
import json
sys.path.append("../")
from material_process.utils import get_key_words
from dao.mongo_server import MongoServer
from dao.redis_server import RedisServer
"""
新闻画像中包含的字段:
0. news_id 新闻的id
1. title 标题
2. raw_key_words (爬下来的关键词,可能有缺失)
3. manual_key_words (根据内容生成的关键词)
4. ctime 时间
5. content 新闻具体内容
6. cate 新闻类别
7. likes 新闻点赞数量
8. collections 新闻收藏数量
9. read_nums 阅读次数
10. url 新闻原始链接
"""
class NewsProtraitServer:
def __init__(self):
"""初始化相关参数
"""
self.mongo_server = MongoServer()
self.sina_collection = self.mongo_server.get_sina_news_collection()
self.material_collection = self.mongo_server.get_feature_protrail_collection()
self.redis_mongo_collection = self.mongo_server.get_redis_mongo_collection()
self.news_dynamic_feature_redis = RedisServer().get_dynamic_news_info_redis()
def _find_by_title(self, collection, title):
"""从数据库中查找是否有相同标题的新闻数据
数据库存在当前标题的数据返回True, 反之返回Flase
"""
# find方法,返回的是一个迭代器
find_res = collection.find({"title": title})
if len(list(find_res)) != 0:
return True
return False
def _generate_feature_protrail_item(self, item):
"""生成特征画像数据,返回一个新的字典
"""
news_item = dict()
news_item['news_id'] = item['news_id']
news_item['title'] = item['title']
# 从新闻内容中提取的关键词没有原始新闻爬取时的关键词准确,所以手动提取的关键词
# 只是作为一个补充,当原始新闻中没有提供关键词的时候可以使用
news_item['raw_key_words'] = item['raw_key_words']
key_words_list = get_key_words(item['content'])
news_item['manual_key_words'] = ",".join(key_words_list)
news_item['ctime'] = item['ctime']
news_item['content'] = item['content']
news_item['cate'] = item['cate']
news_item['url'] = item['url']
news_item['likes'] = 0
news_item['collections'] = 0
news_item['read_num'] = 0
news_item['hot_value'] = 1000 # 初始化一个比较大的热度值,会随着时间进行衰减
return news_item
def update_new_items(self):
"""将今天爬取的数据构造画像存入画像数据库中
"""
# 遍历今天爬取的所有数据
for item in self.sina_collection.find():
# 根据标题进行去重
if self._find_by_title(self.material_collection, item["title"]):
continue
news_item = self._generate_feature_protrail_item(item)
# 插入物料池
self.material_collection.insert_one(news_item)
print("run update_new_items success.")
def update_redis_mongo_protrail_data(self):
"""每天都需要将新闻详情更新到redis中,并且将前一天的redis数据删掉
"""
# 每天先删除前一天的redis展示数据,然后再重新写入
self.redis_mongo_collection.drop()
print("delete RedisProtrail ...")
# 遍历特征库
for item in self.material_collection.find():
news_item = dict()
news_item['news_id'] = item['news_id']
news_item['title'] = item['title']
news_item['ctime'] = item['ctime']
news_item['content'] = item['content']
news_item['cate'] = item['cate']
news_item['url'] = item['url']
news_item['likes'] = item['likes']
news_item['collections'] = item['collections']
news_item['read_num'] = item['read_num']
self.redis_mongo_collection.insert_one(news_item)
print("run update_redis_mongo_protrail_data success.")
def update_dynamic_feature_protrail(self):
"""用redis的动态画像更新mongodb的画像
"""
# 遍历redis的动态画像,将mongodb中对应的动态画像更新
news_list = self.news_dynamic_feature_redis.keys()
for news_key in news_list:
news_dynamic_info_str = self.news_dynamic_feature_redis.get(news_key)
news_dynamic_info_str = news_dynamic_info_str.replace("'", '"' ) # 将单引号都替换成双引号
news_dynamic_info_dict = json.loads(news_dynamic_info_str)
# 查询mongodb中对应的数据,并将对应的画像进行修改
news_id = news_key.split(":")[1]
mongo_info = self.material_collection.find_one({"news_id": news_id})
new_mongo_info = mongo_info.copy()
new_mongo_info['likes'] = news_dynamic_info_dict["likes"]
new_mongo_info['collections'] = news_dynamic_info_dict["collections"]
new_mongo_info['read_num'] = news_dynamic_info_dict["read_num"]
self.material_collection.replace_one(mongo_info, new_mongo_info, upsert=True) # upsert为True的话,没有就插入
print("update_dynamic_feature_protrail success.")
if __name__ == "__main__":
# TODO 需要放到 其他逻辑中,将物料这块的逻辑打通
news_protrait = NewsProtraitServer()
# news_protrait.update_new_items()
news_protrait.update_redis_mongo_protrail_data()
# news_protrait.update_dynamic_feature_protrail()
@@ -0,0 +1,83 @@
import sys
sys.path.append("../../")
from dao.mongo_server import MongoServer
from dao.redis_server import RedisServer
class NewsRedisServer(object):
def __init__(self):
self.rec_list_redis = RedisServer().get_reclist_redis()
self.static_news_info_redis = RedisServer().get_static_news_info_redis()
self.dynamic_news_info_redis = RedisServer().get_dynamic_news_info_redis()
self.redis_mongo_collection = MongoServer().get_redis_mongo_collection()
# 删除前一天redis中的内容
self._flush_redis_db()
def _flush_redis_db(self):
"""每天都需要删除redis中的内容,更新当天新的内容上去
"""
try:
self.rec_list_redis.flushall()
except Exception:
print("flush redis fail ... ")
def _get_news_id_list(self):
"""获取物料库中所有的新闻id
"""
# 获取所有数据的news_id,
# 暴力获取,直接遍历整个数据库,得到所有新闻的id
# TODO 应该存在优化方法可以通过查询的方式只返回new_id字段
news_id_list = []
for item in self.redis_mongo_collection.find():
news_id_list.append(item["news_id"])
return news_id_list
def _set_info_to_redis(self, redisdb, content):
"""将content添加到指定redis
"""
try:
redisdb.set(*content)
except Exception:
print("set content fail".format(content))
def news_detail_to_redis(self):
"""将需要展示的画像内容存储到redis
静态不变的特征存到static_news_info_db_num
动态会发生改变的特征存到dynamic_news_info_db_num
"""
news_id_list = self._get_news_id_list()
for news_id in news_id_list:
news_item_dict = self.redis_mongo_collection.find_one({"news_id": news_id}) # 返回的是一个列表里面套了一个字典
news_item_dict.pop("_id")
# 分离动态属性和静态属性
static_news_info_dict = dict()
static_news_info_dict['news_id'] = news_item_dict['news_id']
static_news_info_dict['title'] = news_item_dict['title']
static_news_info_dict['ctime'] = news_item_dict['ctime']
static_news_info_dict['content'] = news_item_dict['content']
static_news_info_dict['cate'] = news_item_dict['cate']
static_news_info_dict['url'] = news_item_dict['url']
static_content_tuple = "static_news_detail:" + str(news_id), str(static_news_info_dict)
self._set_info_to_redis(self.static_news_info_redis, static_content_tuple)
dynamic_news_info_dict = dict()
dynamic_news_info_dict['likes'] = news_item_dict['likes']
dynamic_news_info_dict['collections'] = news_item_dict['collections']
dynamic_news_info_dict['read_num'] = news_item_dict['read_num']
dynamic_content_tuple = "dynamic_news_detail:" + str(news_id), str(dynamic_news_info_dict)
self._set_info_to_redis(self.dynamic_news_info_redis, dynamic_content_tuple)
print("news detail info are saved in redis db.")
if __name__ == "__main__":
# 每次创建这个对象的时候都会把数据库中之前的内容删除
news_redis_server = NewsRedisServer()
# 将最新的前端展示的画像传到redis
news_redis_server.news_detail_to_redis()
@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
"""
这里主要实现一些工具类的函数
"""
import re
import sys
sys.path.append("../../")
import jieba
import jieba.analyse
def get_key_words(words_str):
"""提取中文中的关键词
"""
# 字符串进行清洗
# 去除一些符号
words_str.replace('\n', '').replace('\u3000', '').replace('\u00A0', '')
# 去除数字,特殊字符
words_str = re.sub('[a-zA-Z0-9.。:,]', '', words_str)
# 切词
words_list = jieba.cut(words_str)
# 加载停用词
stopword_set = set()
# TODO 改成变量而不是写死
with open('/home/recsys/news_rec_server/conf/stop_words.txt', encoding="utf-8") as f:
line = f.readline().rstrip()
stopword_set.add(line)
# 去除停用词
new_words_list = []
for word in words_list:
if word in stopword_set:
continue
new_words_list.append(word)
new_words_str = " ".join(new_words_list)
# 提取关键词
# 默认是TF-IDF
key_words_list_tfidf = jieba.analyse.extract_tags(new_words_str, topK=10, withWeight=False, allowPOS=('ns', 'n', 'vn', 'v'))
key_words_list_textrank = jieba.analyse.textrank(new_words_str, topK=10, withWeight=False, allowPOS=('ns', 'n', 'vn', 'v'))
# print("key_words_list_tfidf", key_words_list_tfidf)
# print("key_words_list_textrank", key_words_list_textrank)
tfidf_textrank_list = list(set(key_words_list_tfidf) & set(key_words_list_textrank))[:3]
# print(tfidf_textrank_list)
return tfidf_textrank_list
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
import sys, time
import pymongo
from sinanews.settings import MONGO_HOST, MONGO_PORT, SINA_DB_NAME, COLLECTION_NAME_PRFIX
if __name__ == "__main__":
news_num = int(sys.argv[1])
time_str = time.strftime("%Y%m%d", time.localtime())
# 实际的collection_name
collection_name = COLLECTION_NAME_PRFIX + "_" + time_str
# 链接数据库
client = pymongo.MongoClient(MONGO_HOST, MONGO_PORT)
db = client[SINA_DB_NAME]
collection = db[collection_name]
# 查找当前集合中所有文档的数量
cur_news_num = collection.count()
if (cur_news_num < news_num):
print("the news nums of {}_{} collection is {} and less then {}.".\
format(COLLECTION_NAME_PRFIX, time_str, cur_news_num, news_num))
@@ -0,0 +1,11 @@
# Automatically created by: scrapy startproject
#
# For more information about the [deploy] section see:
# https://scrapyd.readthedocs.io/en/latest/deploy.html
[settings]
default = sinanews.settings
[deploy]
#url = http://localhost:6800/
project = sinanews
@@ -0,0 +1,19 @@
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
from scrapy import Item, Field
# 定义新闻数据的字段
class SinanewsItem(scrapy.Item):
"""数据格式化,数据不同字段的定义
"""
title = Field() # 新闻标题
ctime = Field() # 新闻发布时间
url = Field() # 新闻原始url
raw_key_words = Field() # 新闻关键词(爬取的关键词)
content = Field() # 新闻的具体内容
cate = Field() # 新闻类别
news_id = Field() # 新闻id
@@ -0,0 +1,103 @@
# Define here the models for your spider middleware
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/spider-middleware.html
from scrapy import signals
# useful for handling different item types with a single interface
from itemadapter import is_item, ItemAdapter
class SinanewsSpiderMiddleware:
# Not all methods need to be defined. If a method is not defined,
# scrapy acts as if the spider middleware does not modify the
# passed objects.
@classmethod
def from_crawler(cls, crawler):
# This method is used by Scrapy to create your spiders.
s = cls()
crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
return s
def process_spider_input(self, response, spider):
# Called for each response that goes through the spider
# middleware and into the spider.
# Should return None or raise an exception.
return None
def process_spider_output(self, response, result, spider):
# Called with the results returned from the Spider, after
# it has processed the response.
# Must return an iterable of Request, or item objects.
for i in result:
yield i
def process_spider_exception(self, response, exception, spider):
# Called when a spider or process_spider_input() method
# (from other spider middleware) raises an exception.
# Should return either None or an iterable of Request or item objects.
pass
def process_start_requests(self, start_requests, spider):
# Called with the start requests of the spider, and works
# similarly to the process_spider_output() method, except
# that it doesnt have a response associated.
# Must return only requests (not items).
for r in start_requests:
yield r
def spider_opened(self, spider):
spider.logger.info('Spider opened: %s' % spider.name)
class SinanewsDownloaderMiddleware:
# Not all methods need to be defined. If a method is not defined,
# scrapy acts as if the downloader middleware does not modify the
# passed objects.
@classmethod
def from_crawler(cls, crawler):
# This method is used by Scrapy to create your spiders.
s = cls()
crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
return s
def process_request(self, request, spider):
# Called for each request that goes through the downloader
# middleware.
# Must either:
# - return None: continue processing this request
# - or return a Response object
# - or return a Request object
# - or raise IgnoreRequest: process_exception() methods of
# installed downloader middleware will be called
return None
def process_response(self, request, response, spider):
# Called with the response returned from the downloader.
# Must either;
# - return a Response object
# - return a Request object
# - or raise IgnoreRequest
return response
def process_exception(self, request, exception, spider):
# Called when a download handler or a process_request()
# (from other downloader middleware) raises an exception.
# Must either:
# - return None: continue processing this exception
# - return a Response object: stops process_exception() chain
# - return a Request object: stops process_exception() chain
pass
def spider_opened(self, spider):
spider.logger.info('Spider opened: %s' % spider.name)
@@ -0,0 +1,74 @@
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
# useful for handling different item types with a single interface
import time
import datetime
import pymongo
from pymongo.errors import DuplicateKeyError
from sinanews.items import SinanewsItem
from itemadapter import ItemAdapter
# 新闻item持久化
class SinanewsPipeline:
"""数据持久化:将数据存放到mongodb中
"""
def __init__(self, host, port, db_name, collection_name):
self.host = host
self.port = port
self.db_name = db_name
self.collection_name = collection_name
@classmethod
def from_crawler(cls, crawler):
"""自带的方法,这个方法可以重新返回一个新的pipline对象,并且可以调用配置文件中的参数
"""
return cls(
host = crawler.settings.get("MONGO_HOST"),
port = crawler.settings.get("MONGO_PORT"),
db_name = crawler.settings.get("SINA_DB_NAME"),
# mongodb中数据的集合按照日期存储
collection_name = crawler.settings.get("COLLECTION_NAME_PRFIX") + \
"_" + time.strftime("%Y%m%d", time.localtime())
)
def open_spider(self, spider):
"""开始爬虫的操作,主要就是链接数据库及对应的集合
"""
self.client = pymongo.MongoClient(self.host, self.port)
self.db = self.client[self.db_name]
self.collection = self.db[self.collection_name]
def close_spider(self, spider):
"""关闭爬虫操作的时候,需要将数据库断开
"""
self.client.close()
def process_item(self, item, spider):
"""处理每一条数据,注意这里需要将item返回
注意判断新闻是否是今天的每天只保存当天产出的新闻这样可以增量的添加新的新闻数据源
"""
if isinstance(item, SinanewsItem):
try:
# TODO 物料去重逻辑,根据title进行去重,去重逻辑在画像处理环节实现
# 当前新闻的时间戳
cur_time = int(time.mktime(time.strptime(item['ctime'], '%Y-%m-%d %H:%M')))
# str_today = str(datetime.date.today())
# 每次爬取新闻的时候是爬取前一天的
yestoday = str(datetime.date.today() - datetime.timedelta(days=1))
min_time = int(time.mktime(time.strptime(yestoday + " 00:00:00", '%Y-%m-%d %H:%M:%S')))
max_time = int(time.mktime(time.strptime(yestoday + " 23:59:59", '%Y-%m-%d %H:%M:%S')))
if cur_time > min_time and cur_time <= max_time:
self.collection.insert_one(dict(item))
except DuplicateKeyError:
"""
说明有重复
"""
pass
return item
@@ -0,0 +1,4 @@
from scrapy import cmdline
# 注意这里pages需要进行调整
cmdline.execute('scrapy crawl sina_spider --nolog -a pages=50'.split())
@@ -0,0 +1,101 @@
# Scrapy settings for sinanews project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
# https://docs.scrapy.org/en/latest/topics/settings.html
# https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
# https://docs.scrapy.org/en/latest/topics/spider-middleware.html
from typing import Collection
import sys
sys.path.append("/home/recsys/news_rec_server")
from conf.dao_config import mongo_hostname, mongo_port, sina_db_name, sina_collection_name_prefix
BOT_NAME = 'sinanews'
SPIDER_MODULES = ['sinanews.spiders']
NEWSPIDER_MODULE = 'sinanews.spiders'
# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'sinanews (+http://www.yourdomain.com)'
# Obey robots.txt rules
ROBOTSTXT_OBEY = True
# Configure maximum concurrent requests performed by Scrapy (default: 16)
#CONCURRENT_REQUESTS = 32
# Configure a delay for requests for the same website (default: 0)
# See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
# DOWNLOAD_DELAY = 3
# The download delay setting will honor only one of:
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
#CONCURRENT_REQUESTS_PER_IP = 16
# Disable cookies (enabled by default)
#COOKIES_ENABLED = False
# Disable Telnet Console (enabled by default)
#TELNETCONSOLE_ENABLED = False
# Override the default request headers:
#DEFAULT_REQUEST_HEADERS = {
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
# 'Accept-Language': 'en',
#}
# Enable or disable spider middlewares
# See https://docs.scrapy.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
# 'sinanews.middlewares.SinanewsSpiderMiddleware': 543,
#}
# Enable or disable downloader middlewares
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
#DOWNLOADER_MIDDLEWARES = {
# 'sinanews.middlewares.SinanewsDownloaderMiddleware': 543,
#}
# Enable or disable extensions
# See https://docs.scrapy.org/en/latest/topics/extensions.html
#EXTENSIONS = {
# 'scrapy.extensions.telnet.TelnetConsole': None,
#}
# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
# 如果需要使用itempipline来存储item的话需要将这段注释打开
ITEM_PIPELINES = {
'sinanews.pipelines.SinanewsPipeline': 300,
}
# Enable and configure the AutoThrottle extension (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/autothrottle.html
#AUTOTHROTTLE_ENABLED = True
# The initial download delay
#AUTOTHROTTLE_START_DELAY = 5
# The maximum download delay to be set in case of high latencies
#AUTOTHROTTLE_MAX_DELAY = 60
# The average number of requests Scrapy should be sending in parallel to
# each remote server
#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# Enable showing throttling stats for every response received:
#AUTOTHROTTLE_DEBUG = False
# Enable and configure HTTP caching (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
#HTTPCACHE_ENABLED = True
#HTTPCACHE_EXPIRATION_SECS = 0
#HTTPCACHE_DIR = 'httpcache'
#HTTPCACHE_IGNORE_HTTP_CODES = []
#HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
MONGO_HOST = mongo_hostname
MONGO_PORT = mongo_port
SINA_DB_NAME = sina_db_name
COLLECTION_NAME_PRFIX = sina_collection_name_prefix
@@ -0,0 +1,4 @@
# This package will contain the spiders of your Scrapy project
#
# Please refer to the documentation for information on how to create and manage
# your spiders.
@@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-
import re
import uuid
import json
import random
import scrapy
from scrapy import Request
from ..items import SinanewsItem
from datetime import datetime
class SinaSpider(scrapy.Spider):
# spider的名字
name = 'sina_spider'
def __init__(self, pages=None):
super(SinaSpider).__init__()
self.total_pages = int(pages)
# base_url 对应的是新浪新闻的简洁版页面,方便爬虫,并且不同类别的新闻也很好区分
self.base_url = 'https://feed.mix.sina.com.cn/api/roll/get?pageid=153&lid={}&k=&num=50&page={}&r={}'
# lid和分类映射字典
self.cate_dict = {
"2510": "国内",
"2511": "国际",
"2669": "社会",
"2512": "体育",
"2513": "娱乐",
"2514": "军事",
"2515": "科技",
"2516": "财经",
"2517": "股市",
"2518": "美股"
}
def start_requests(self):
"""返回一个Request迭代器
"""
# 遍历所有类型的论文
for cate_id in self.cate_dict.keys():
for page in range(1, self.total_pages + 1):
lid = cate_id
# 这里就是一个随机数,具体含义不是很清楚
r = random.random()
# cb_kwargs 是用来往解析函数parse中传递参数的
yield Request(self.base_url.format(lid, page, r), callback=self.parse, cb_kwargs={"cate_id": lid})
def parse(self, response, cate_id):
"""解析网页内容,并提取网页中需要的内容
"""
json_result = json.loads(response.text) # 将请求回来的页面解析成json
# 提取json中我们想要的字段
# json使用get方法比直接通过字典的形式获取数据更方便,因为不需要处理异常
data_list = json_result.get('result').get('data')
for data in data_list:
item = SinanewsItem()
# 给当前文章生成一个唯一的id
item['news_id'] = str(uuid.uuid4()) # 通过随机数来生成UUID. 使用的是伪随机数有一定的重复概率.
item['cate'] = self.cate_dict[cate_id]
item['title'] = data.get('title')
item['url'] = data.get('url')
item['raw_key_words'] = data.get('keywords')
ctime = datetime.fromtimestamp(int(data.get('ctime')))
ctime = datetime.strftime(ctime, '%Y-%m-%d %H:%M')
# 保留格式化之后的时间戳
item['ctime'] = ctime
# meta参数传入的是一个字典,在下一层可以将当前层的item进行复制
yield Request(url=item['url'], callback=self.parse_content, meta={'item': item})
def parse_content(self, response):
"""解析文章内容
"""
item = response.meta['item']
content = ''.join(response.xpath('//*[@id="artibody" or @id="article"]//p/text()').extract())
content = re.sub(r'\u3000', '', content)
content = re.sub(r'[ \xa0?]+', ' ', content)
content = re.sub(r'\s*\n\s*', '\n', content)
content = re.sub(r'\s*(\s)', r'\1', content)
content = ''.join([x.strip() for x in content])
item['content'] = content
yield item
@@ -0,0 +1,20 @@
from material_process.news_protrait import NewsProtraitServer
from material_process.news_to_redis import NewsRedisServer
def process_material():
"""物料处理函数
"""
# 画像处理
protrail_server = NewsProtraitServer()
# 处理最新爬取新闻的画像,存入特征库
protrail_server.update_new_items()
# 更新新闻动态画像, 需要在redis数据库内容清空之前执行
protrail_server.update_dynamic_feature_protrail()
# 生成前端展示的新闻画像,并在mongodb中备份一份
protrail_server.update_redis_mongo_protrail_data()
if __name__ == "__main__":
process_material()
@@ -0,0 +1,25 @@
from user_process.user_to_mysql import UserMysqlServer
from user_process.user_protrail import UserProtrail
"""
1. 将用户的曝光数据从redis落到mysql中
2. 更新用户画像
"""
def process_users():
"""将用户数据落 Mysql
"""
# 用户mysql存储
user_mysql_server = UserMysqlServer()
# 用户曝光数据落mysql
user_mysql_server.user_exposure_to_mysql()
# 更新用户画像
user_protrail = UserProtrail()
user_protrail.update_user_protrail_from_register_table()
if __name__ == "__main__":
process_users()
@@ -0,0 +1,16 @@
from material_process.news_protrait import NewsProtraitServer
from material_process.news_to_redis import NewsRedisServer
def update():
"""物料处理函数
"""
# 新闻数据写入redis, 注意这里处理redis数据的时候是会将前一天的数据全部清空
news_redis_server = NewsRedisServer()
# 将最新的前端展示的画像传到redis
news_redis_server.news_detail_to_redis()
if __name__ == "__main__":
update()
@@ -0,0 +1,142 @@
import sys
import datetime
from collections import Counter, defaultdict
from sqlalchemy.sql.expression import table
sys.path.append("../../")
from dao.mongo_server import MongoServer
from dao.mysql_server import MysqlServer
from dao.entity.register_user import RegisterUser
from dao.entity.user_read import UserRead
from dao.entity.user_likes import UserLikes
from dao.entity.user_collections import UserCollections
class UserProtrail(object):
def __init__(self):
self.user_protrail_collection = MongoServer().get_user_protrail_collection()
self.material_collection = MongoServer().get_feature_protrail_collection()
self.register_user_sess = MysqlServer().get_register_user_session()
self.user_collection_sess = MysqlServer().get_user_collection_session()
self.user_like_sess = MysqlServer().get_user_like_session()
self.user_read_sess = MysqlServer().get_user_read_session()
def _user_info_to_dict(self, user):
"""将mysql查询出来的结果转换成字典存储
"""
info_dict = dict()
# 基本属性特征
info_dict["userid"] = user.userid
info_dict["username"] = user.username
info_dict["passwd"] = user.passwd
info_dict["gender"] = user.gender
info_dict["age"] = user.age
info_dict["city"] = user.city
# 兴趣爱好
behaviors=["like","collection"]
time_range = 15
_, feature_dict = self.get_statistical_feature_from_history_behavior(user.userid,time_range,behavior_types=behaviors)
for type in feature_dict.keys():
if feature_dict[type]:
info_dict["{}_{}_intr_cate".format(type,time_range)] = feature_dict[type]["intr_cate"] # 历史喜欢最多的Top3的新闻类别
info_dict["{}_{}_intr_key_words".format(type,time_range)] = feature_dict[type]["intr_key_words"] # 历史喜欢新闻的Top3的关键词
info_dict["{}_{}_avg_hot_value".format(type,time_range)] = feature_dict[type]["avg_hot_value"] # 用户喜欢新闻的平均热度
info_dict["{}_{}_news_num".format(type,time_range)] = feature_dict[type]["news_num"] # 用户15天内喜欢的新闻数量
else:
info_dict["{}_{}_intr_cate".format(type,time_range)] = "" # 历史喜欢最多的Top3的新闻类别
info_dict["{}_{}_intr_key_words".format(type,time_range)] = "" # 历史喜欢新闻的Top3的关键词
info_dict["{}_{}_avg_hot_value".format(type,time_range)] = 0 # 用户喜欢新闻的平均热度
info_dict["{}_{}_news_num".format(type,time_range)] = 0 # 用户15天内喜欢的新闻数量
return info_dict
def update_user_protrail_from_register_table(self):
"""每天都需要将当天注册的用户添加到用户画像池中
"""
# 遍历注册用户表
for user in self.register_user_sess.query(RegisterUser).all():
user_info_dict = self._user_info_to_dict(user)
old_user_protrail_dict = self.user_protrail_collection.find_one({"username": user.username})
if old_user_protrail_dict is None:
self.user_protrail_collection.insert_one(user_info_dict)
else:
# 使用参数upsert设置为true对于没有的会创建一个
# replace_one 如果遇到相同的_id 就会更新
self.user_protrail_collection.replace_one(old_user_protrail_dict, user_info_dict, upsert=True)
def get_statistical_feature_from_history_behavior(self, user_id, time_range, behavior_types):
"""获取用户历史行为的统计特征 ["read","like","collection"] """
fail_type = []
sess, table_obj, history = None, None, None
feature_dict = defaultdict(dict)
end = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
start = (datetime.datetime.now()+datetime.timedelta(days=-time_range)).strftime("%Y-%m-%d %H:%M:%S")
for type in behavior_types:
if type == "read":
sess = getattr(self,"user_{}_sess".format(type))
table_obj = UserRead
elif type == "like":
sess = getattr(self,"user_{}_sess".format(type))
table_obj = UserLikes
elif type == "collection":
sess = getattr(self,"user_{}_sess".format(type))
table_obj = UserCollections
try:
history = sess.query(table_obj).filter(table_obj.userid==user_id).filter(table_obj.curtime>=start).filter(table_obj.curtime<=end).all()
except Exception as e:
print(str(e))
fail_type.append(type)
continue
feature_dict[type] = self._gen_statistical_feature(history)
return fail_type, feature_dict
def _gen_statistical_feature(self,history):
""""""
# 为history 获取特征
if not len(history): return None
history_new_id = []
history_hot_value = []
history_new_cate = []
history_key_word = []
for h in history:
news_id = h.newid
newsquery = {"news_id":news_id}
result = self.material_collection.find_one(newsquery)
history_new_id.append(result["news_id"])
history_hot_value.append(result["hot_value"])
history_new_cate.append(result["cate"])
history_key_word += result["manual_key_words"].split(",")
feature_dict = dict()
# 计算平均热度
feature_dict["avg_hot_value"] = 0 if sum(history_hot_value) < 0.001 else sum(history_hot_value) / len(history_hot_value)
# 计算Top3的类别
cate_dict = Counter(history_new_cate)
cate_list= sorted(cate_dict.items(),key = lambda d: d[1], reverse=True)
cate_str = ",".join([item[0] for item in cate_list[:3]] if len(cate_list)>=3 else [item[0] for item in cate_list] )
feature_dict["intr_cate"] = cate_str
# 计算Top3的关键词
word_dict = Counter(history_key_word)
word_list= sorted(word_dict.items(),key = lambda d: d[1], reverse=True)
# TODO 关键字属于长尾 如果关键字的次数都是一次 该怎么去前3
word_str = ",".join([item[0] for item in word_list[:3]] if len(cate_list)>=3 else [item[0] for item in word_list] )
feature_dict["intr_key_words"] = word_str
# 新闻数目
feature_dict["news_num"] = len(history_new_id)
return feature_dict
if __name__ == "__main__":
user_protrail = UserProtrail().update_user_protrail_from_register_table()
# user_protrail = UserProtrail().get_statistical_feature_from_history_behavior(user_id="4563323680741920769")
@@ -0,0 +1,43 @@
import sys
sys.path.append("./../")
from dao.redis_server import RedisServer
from dao.mysql_server import MysqlServer
from dao.entity.user_exposure import UserExposure
class UserMysqlServer(object):
def __init__(self):
self.user_exposure_redis = RedisServer().get_exposure_redis()
self.user_exposure_sql_session = MysqlServer().get_user_exposure_session()
def user_exposure_to_mysql(self):
exposure = UserExposure() # 为了通过__init__()函数构建表
vals = []
keys = self.user_exposure_redis.keys()
for key in keys:
news_list = self.user_exposure_redis.smembers(key)
user_id = key.split(":")[1]
val = self._transfor_json_for_user(user_id,news_list)
vals +=val
self.user_exposure_sql_session.bulk_insert_mappings(UserExposure,vals)
self.user_exposure_sql_session.commit()
def _transfor_json_for_user(self,user_id,news_list):
"""针对每个用户转换成批量存储的形式
"""
# 对用户的每一个曝光进行存储
vals = []
for item in news_list:
item = item.split(":")
vals.append({
"userid":user_id,
"newid":item[0],
"curtime":item[1]})
return vals
if __name__ == "__main__":
user_mysql_server = UserMysqlServer()
user_mysql_server.user_exposure_to_mysql()
@@ -0,0 +1,4 @@
当前文件主要是存放个性化推荐流程相关的内容
recsys.py 就是一个单例类,为后端提供推荐服务
@@ -0,0 +1,62 @@
import sys
sys.path.append('../../')
from dao.mongo_server import MongoServer
from dao.redis_server import RedisServer
from datetime import datetime
# 这里设计冷启动规则
# 针对每个用户,构造出三部分最终将计算好的物料热度,对物料进行排序
class HotRecall(object):
def __init__(self):
self.feature_protrail_collection = MongoServer().get_feature_protrail_collection()
self.reclist_redis = RedisServer().get_reclist_redis()
def get_hot_rec_list(self):
"""获取物料的点赞,收藏和创建时间等信息,计算热度并生成热度推荐列表存入redis
"""
# 遍历物料池里面的所有文章
for item in self.feature_protrail_collection.find():
news_id = item['news_id']
news_cate = item['cate']
news_ctime = item['ctime']
news_likes_num = item['likes']
news_collections_num = item['collections']
news_read_num = item['read_num']
news_hot_value = item['hot_value']
#print(news_id, news_cate, news_ctime, news_likes_num, news_collections_num, news_read_num, news_hot_value)
# 时间转换与计算时间差 前提要保证当前时间大于新闻创建时间,目前没有捕捉异常
news_ctime_standard = datetime.strptime(news_ctime, "%Y-%m-%d %H:%M")
cur_time_standard = datetime.now()
time_day_diff = (cur_time_standard - news_ctime_standard).days
time_hour_diff = (cur_time_standard - news_ctime_standard).seconds / 3600
# 只要最近3天的内容
if time_day_diff > 3:
continue
# 计算热度分,这里使用魔方秀热度公式, 可以进行调整, read_num 上一次的 hot_value 上一次的hot_value用加? 因为like_num这些也都是累加上来的, 所以这里计算的并不是增值,而是实时热度吧
# news_hot_value = (news_likes_num * 6 + news_collections_num * 3 + news_read_num * 1) * 10 / (time_hour_diff+1)**1.2
# 72 表示的是3天,
news_hot_value = (news_likes_num * 0.6 + news_collections_num * 0.3 + news_read_num * 0.1) * 10 / (1 + time_hour_diff / 72)
#print(news_likes_num, news_collections_num, time_hour_diff)
# 更新物料池的文章hot_value
item['hot_value'] = news_hot_value
self.feature_protrail_collection.update({'news_id':news_id}, item)
#print("news_hot_value: ", news_hot_value)
# 保存到redis中
self.reclist_redis.zadd('hot_list', {'{}_{}'.format(news_cate, news_id): news_hot_value}, nx=True)
@@ -0,0 +1,69 @@
import sys
sys.path.append("../")
from dao.mongo_server import MongoServer
from dao.redis_server import RedisServer
from recall.hot_recall import HotRecall
from datetime import datetime
# 这个类是用来实现离线推荐流程的,给每个用户都存储一个倒排索引列表
# 对于热门页的内容,初始化的时候每个用户都是一样的
# 这个类的实例会在处理完物料之后,生成当前最新的用户推荐列表,热门列表,及冷启动内容模板
class OfflineServer(object):
def __init__(self):
self.redis_mongo_collection = MongoServer().get_redis_mongo_collection()
self.reclist_redis = RedisServer().get_reclist_redis()
self.hot_recall = HotRecall()
def hot_list_to_redis(self):
"""热门页面,初始化的时候每个用户都是同样的内容
"""
self.hot_recall.get_hot_rec_list()
print("a hot rec list are saved into redis.....")
def rec_list_to_redis(self):
"""个性化推荐列表,千人千面
"""
pass
def cold_start_template_to_redis(self):
"""冷启动列表模板
"""
pass
def _get_user_id_list(self):
"""获取所有注册用户的id
"""
pass
def _get_news_id_list(self):
"""获取所有新闻id
"""
# 获取所有数据的news_id,
# 暴力获取,直接遍历整个数据库,得到所有新闻的id
# TODO 应该存在优化方法可以通过查询的方式只返回new_id字段
news_id_list = []
for item in self.redis_mongo_collection.find():
news_id_list.append(item["news_id"])
return news_id_list
def test(self):
"""给redis中加入一个倒排索引用于测试
"""
news_id_list = self._get_news_id_list()
value_list = [i for i in range(len(news_id_list))]
for news_id, val in zip(news_id_list, value_list):
# print({news_id: val})
self.reclist_redis.zadd("rec_list", {news_id: val}, nx=True)
print("a sorted news_ids are saved into redis.")
if __name__ == "__main__":
# 生成统一的,用于测试的倒排索引
# TODO 待测试
offline_server = OfflineServer().test()
offline_server = OfflineServer().hot_list_to_redis()
+262
View File
@@ -0,0 +1,262 @@
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"))
@@ -0,0 +1,62 @@
import sys
sys.path.append('../../')
from dao.mongo_server import MongoServer
from dao.redis_server import RedisServer
from datetime import datetime
# 这里需要从物料库中获取物料的信息,然后更新物料当天最新的热度信息
# 最终将计算好的物料热度,对物料进行排序
class HotRecall(object):
def __init__(self):
self.feature_protrail_collection = MongoServer().get_feature_protrail_collection()
self.reclist_redis = RedisServer().get_reclist_redis()
def get_hot_rec_list(self):
"""获取物料的点赞,收藏和创建时间等信息,计算热度并生成热度推荐列表存入redis
"""
# 遍历物料池里面的所有文章
for item in self.feature_protrail_collection.find():
news_id = item['news_id']
news_cate = item['cate']
news_ctime = item['ctime']
news_likes_num = item['likes']
news_collections_num = item['collections']
news_read_num = item['read_num']
news_hot_value = item['hot_value']
#print(news_id, news_cate, news_ctime, news_likes_num, news_collections_num, news_read_num, news_hot_value)
# 时间转换与计算时间差 前提要保证当前时间大于新闻创建时间,目前没有捕捉异常
news_ctime_standard = datetime.strptime(news_ctime, "%Y-%m-%d %H:%M")
cur_time_standard = datetime.now()
time_day_diff = (cur_time_standard - news_ctime_standard).days
time_hour_diff = (cur_time_standard - news_ctime_standard).seconds / 3600
# 只要最近3天的内容
if time_day_diff > 3:
continue
# 计算热度分,这里使用魔方秀热度公式, 可以进行调整, read_num 上一次的 hot_value 上一次的hot_value用加? 因为like_num这些也都是累加上来的, 所以这里计算的并不是增值,而是实时热度吧
# news_hot_value = (news_likes_num * 6 + news_collections_num * 3 + news_read_num * 1) * 10 / (time_hour_diff+1)**1.2
# 72 表示的是3天,
news_hot_value = (news_likes_num * 0.6 + news_collections_num * 0.3 + news_read_num * 0.1) * 10 / (1 + time_hour_diff / 72)
#print(news_likes_num, news_collections_num, time_hour_diff)
# 更新物料池的文章hot_value
item['hot_value'] = news_hot_value
self.feature_protrail_collection.update({'news_id':news_id}, item)
#print("news_hot_value: ", news_hot_value)
# 保存到redis中
self.reclist_redis.zadd('hot_list', {'{}_{}'.format(news_cate, news_id): news_hot_value}, nx=True)
+57
View File
@@ -0,0 +1,57 @@
async-generator==1.10
attrs==21.2.0
Automat==20.2.0
certifi==2021.10.8
cffi==1.15.0
click==8.0.3
constantly==15.1.0
cryptography==35.0.0
cssselect==1.1.0
EasyProcess==0.3
Flask==2.0.2
Flask-Cors==3.0.10
greenlet==1.1.2
h11==0.12.0
h2==3.2.0
hpack==3.0.0
hyperframe==5.2.0
hyperlink==21.0.0
idna==3.3
incremental==21.3.0
itemadapter==0.4.0
itemloaders==1.0.4
itsdangerous==2.0.1
jieba==0.42.1
Jinja2==3.0.2
jmespath==0.10.0
lxml==4.6.3
MarkupSafe==2.0.1
outcome==1.1.0
parsel==1.6.0
priority==1.3.0
Protego==0.1.16
pyasn1==0.4.8
pyasn1-modules==0.2.8
pycparser==2.20
PyDispatcher==2.0.5
pymongo==3.12.1
PyMySQL==1.0.2
pyOpenSSL==21.0.0
queuelib==1.6.2
redis==3.5.3
Scrapy==2.5.1
selenium==4.0.0
service-identity==21.1.0
six==1.16.0
sniffio==1.2.0
sortedcontainers==2.4.0
SQLAlchemy==1.4.26
trio==0.19.0
trio-websocket==0.9.2
Twisted==21.7.0
typing-extensions==3.10.0.2
urllib3==1.26.7
w3lib==1.22.0
Werkzeug==2.0.2
wsproto==1.0.0
zope.interface==5.4.0
+31
View File
@@ -0,0 +1,31 @@
#!/bin/bash
# 这个脚本每天凌晨2点30会自动跑
# 设置python环境
python=/home/recsys/miniconda3/envs/news_rec_py3/bin/python
news_recsys_path="/home/recsys/news_rec_server"
# 得跳转到这个目录才能执行下面爬虫的命令
cd ${news_recsys_path}/materials/news_scrapy
# 系统正式运行的时候需要修改pages的值
pages=30
min_news_num=1000
echo "$(date -d today +%Y-%m-%d-%H-%M-%S)"
# 爬虫
${python} ${news_recsys_path}/materials/news_scrapy/sinanews/run.py --pages=${pages}
if [ $? -eq 0 ]; then
echo "scrapy crawl sina_spider --pages ${page} success."
else
echo "scrapy crawl sina_spider --pages ${page} fail."
fi
# 检查今天爬取的数据是否少于min_news_num篇文章,这里也可以配置邮件报警
${python} ${news_recsys_path}/materials/news_scrapy/monitor_news.py ${min_news_num}
if [ $? -eq 0 ]; then
echo "run python monitor_news.py success."
else
echo "run python monitor_news.py fail."
fi
@@ -0,0 +1,37 @@
#!/bin/bash
python=/home/recsys/miniconda3/envs/news_rec_py3/bin/python
news_recsys_path="/home/recsys/news_rec_server"
echo "$(date -d today +%Y-%m-%d-%H-%M-%S)"
# 为了更方便的处理路径的问题,可以直接cd到我们想要运行的目录下面
cd ${news_recsys_path}/materials
# 更新物料画像
${python} process_material.py
if [ $? -eq 0 ]; then
echo "process_material success."
else
echo "process_material fail."
fi
# 更新用户画像
${python} process_user.py
if [ $? -eq 0 ]; then
echo "process_user.py success."
else
echo "process_user.py fail."
fi
# 清除前一天redis中的数据,更新最新今天最新的数据
${python} update_redis.py
if [ $? -eq 0 ]; then
echo "update_redis success."
else
echo "update_redis fail."
fi
echo " "
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
python=/home/recsys/miniconda3/envs/news_rec_py3/bin/python
news_recsys_path="/home/recsys/news_rec_server"
cd ${news_recsys_path}/recprocess
echo "$(date -d today +%Y-%m-%d-%H-%M-%S)"
# 离线将推荐列表和热门列表存入redis
${python} offline.py
if [ $? -eq 0 ]; then
echo "run ${python} ${news_recsys_path}/recprocess/offline.py success."
else
echo "run ${python} ${news_recsys_path}/recprocess/offline.py fail."
fi
echo " "
+228
View File
@@ -0,0 +1,228 @@
import sys
sys.path.append("./")
import json
from flask_cors import *
from flask import Flask, jsonify, request
import snowflake.client
from dao.mysql_server import MysqlServer
from dao.entity.register_user import RegisterUser
from dao.entity.logitem import LogItem
from dao.entity.user_likes import UserLikes
from dao.entity.user_collections import UserCollections
from controller.user_action_controller import UserAction
from controller.log_controller import LogController
from recprocess.online import OnlineServer as RecsysServer
app = Flask(__name__)
# 允许跨域访问
CORS(app, supports_credentials=True)
# 定义推荐服务的实例, 是一个单例类
recsys_server = RecsysServer()
@app.route('/recsys/register', methods=["POST"])
def register():
"""用户注册
"""
request_str = request.get_data()
request_dict = json.loads(request_str)
# print(request_dict)
user = RegisterUser()
user.username = request_dict["username"]
user.passwd = request_dict["passwd"]
# 查询当前用户名是否已经被用过了
result = UserAction().user_is_exist(user, "register")
if result != 0:
return jsonify({"code": 500, "mgs": "this username is exists"})
user.userid = snowflake.client.get_guid() # 雪花算法
user.age = request_dict["age"]
user.gender = request_dict["gender"]
user.city = request_dict["city"]
# 添加注册用户
save_res = UserAction().save_user(user)
if not save_res:
return jsonify({"code": 500, "mgs": "register fail."})
return jsonify({"code": 200, "msg": "register success."})
@app.route('/recsys/login', methods=["POST"])
def login():
"""用户登录
"""
request_str = request.get_data()
request_dict = json.loads(request_str)
user = RegisterUser()
user.username = request_dict["username"]
user.passwd = request_dict["passwd"]
# 查询数据库中的用户名或者密码是否存在
try:
result = UserAction().user_is_exist(user, "login")
# print(result,"login")
if result == 1:
return jsonify({"code": 200, "msg": "login success"})
elif result == 2:
# 密码错误
return jsonify({"code": 500, "msg": "passwd is error"})
else:
return jsonify({"code": 500, "msg": "this username is not exist!"})
except Exception as e:
return jsonify({"code": 500, "mgs": "login fail."})
@app.route('/recsys/rec_list', methods=["GET"])
def rec_list():
"""推荐页
"""
user_name = request.args.get('user_id')
page_id = request.args.get('page_id')
# 查询用户的id
user_id = UserAction().get_user_id_by_name(user_name)
if not user_id:
return False
if user_id is None or page_id is None:
return jsonify({"code": 2000, "msg": "user_id or page_id is none!"})
try:
rec_news_list = recsys_server.get_rec_list(user_id, page_id)
if len(rec_news_list) == 0:
return jsonify({"code": 500, "msg": "rec_list data is empty."})
return jsonify({"code": 200, "msg": "request rec_list success.", "data": rec_news_list, "user_id": user_id})
except Exception as e:
print(str(e))
return jsonify({"code": 500, "msg": "redis fail."})
@app.route('/recsys/hot_list', methods=["GET"])
def hot_list():
"""热门页面
"""
if request.method == "GET":
user_name = request.args.get('user_id')
page_id = request.args.get('page_id')
if user_name is None or page_id is None:
return jsonify({"code": 2000, "msg": "user_name or page_id is none!"})
# 查询用户的id
user_id = UserAction().get_user_id_by_name(user_name)
if not user_id:
return False
try:
# 这里需要改成get_hot_list, 当前get_hot_list方法还没有实现
rec_news_list = recsys_server.get_hot_list(user_id)
# 下面这个接口是用来前端测试的
# rec_news_list = recsys_server.get_rec_list(user_id, page_id)
if len(rec_news_list) == 0:
return jsonify({"code": 200, "msg": "request redis data fail."})
# rec_news_list = recsys_server.get_hot_list(user_id, page_id)
return jsonify({"code": 200, "msg": "request hot_list success.", "data": rec_news_list, "user_id": user_id})
except Exception as e:
print(str(e))
return jsonify({"code": 2000, "msg": "request hot_list fail."})
@app.route('/recsys/news_detail', methods=["GET"])
def news_detail():
"""一篇文章的详细信息
"""
user_name = request.args.get('user_name')
news_id = request.args.get('news_id')
user_id = UserAction().get_user_id_by_name(user_name)
# if news_id is None or user_id is None:
if news_id is None or user_name is None:
return jsonify({"code": 2000, "msg": "news_id is none or user_name is none!"})
try:
news_detail = recsys_server.get_news_detail(news_id)
# recsys_server.save_user_consume(user_id,news_id) # 记录用户消费的
if UserAction().get_likes_counts_by_user(user_id,news_id) > 0:
news_detail["likes"] = True
else:
news_detail["likes"] = False
if UserAction().get_coll_counts_by_user(user_id,news_id) > 0:
news_detail["collections"] = True
else:
news_detail["collections"] = False
# print("test",news_detail)
return jsonify({"code": 0, "msg": "request news_detail success.", "data": news_detail})
except Exception as e:
print(str(e))
return jsonify({"code": 2000, "msg": "error"})
@app.route('/recsys/action', methods=["POST"])
def actions():
"""用户的行为:阅读,点赞,收藏
"""
request_str = request.get_data()
request_dict = json.loads(request_str)
username = request_dict.get('user_name')
newsid = request_dict.get('news_id')
actiontype = request_dict.get("action_type")
actiontime = request_dict.get("action_time")
userid = UserAction().get_user_id_by_name(username) # 获取用户 id
if not userid:
return jsonify({"code": 2000, "msg": "user not register"})
#TODO 先判断当前的action_type是否是取消的意思,如果是的话,需要将数据库中对应的操作删掉
action_type_list = actiontype.split(":")
# print(actiontype)
if len(action_type_list) == 2:
_action_type = action_type_list[0]
if action_type_list[1] == "false": # 如果数据库中这个参数为false的话
# 删除数据
if _action_type=="likes":
UserAction().del_likes_by_user(userid,newsid) # 删除用户喜欢记录
elif _action_type=="collections":
UserAction().del_coll_by_user(userid,newsid) # 删除用户收藏记录
else:
if _action_type=="likes":
userlikes = UserLikes()
userlikes.new(userid,username,newsid)
UserAction().save_one_action(userlikes) # 记录用户喜欢记录
elif _action_type=="collections":
usercollections = UserCollections()
usercollections.new(userid,username,newsid)
UserAction().save_one_action(usercollections) # 记录用户收藏记录
try:
# 落日志
logitem = LogItem()
logitem.new(userid,newsid,action_type_list[0])
LogController().save_one_log(logitem)
# 更新redis中的展示数据 新闻侧
# if action_type_list[0] in ["read","likes","collections"]:
recsys_server.update_news_dynamic_info(news_id=newsid,action_type=action_type_list)
return jsonify({"code": 200, "msg": "action success"})
except Exception as e:
print(str(e))
return jsonify({"code": 2000, "msg": "action error"})
if __name__ == '__main__':
# 允许服务器被公开访问
app.run(debug=True, host='0.0.0.0', port=3000, threaded=True)
# 只能被自己的机子访问
# app.run(debug=True, host='127.0.0.1', port=10086, threaded=True)
Submodule codes/news_rec_web/Vue-newsinfo added at e8ade14ff0
Binary file not shown.