NewsFeeds
Newsfeeds website using nodejs as server and mongo as storage backends, including a simple recommendation system. 基于Node.js的新闻聚合网站, 支持基于用户行为推荐新闻.
Install / Use
/learn @stardust95/NewsFeedsREADME
NewsFeeds 开发报告
一. 项目概述
1.1 项目说明
本项目实现了一个新闻资讯收集、整理,进行个性化展示的网站。
网站服务器端使用Node.js作为服务器框架,使用MongoDB提供数据存储服务,并使用Redis作为数据缓存服务。在用户管理方面实现了邮箱认证注册登录,用户定制关键词等功能。
在新闻数据爬取上使用Python BeautifulSoup框架爬取并解析各大主流新闻网站的新闻页面,获取所需要的新闻数据;在新闻数据处理上通过维基百科提供的语料库,使用Word2Vec等自然语言处理相关的工具进行中文关键词建模,实现了按新闻关键词进行聚类,并根据用户感兴趣关键词进行新闻个性推荐;同时,使用了微软认知服务提供的推荐API,用于针对用户的特定行为进行建模推荐。
1.2 项目模块划分

- 新闻数据模块
- 新闻爬取模块:使用Python BeautifulSoup框架爬取并解析各大主流新闻网站的新闻页面,获取所需要的新闻数据存入数据库
- 新闻聚类模块:使用中文维基语料库建立关键词模型,并按新闻关键词进行聚类
- 用户推荐模块:使用微软认知服务-推荐系统提供的RESTful API进行用户行为统计并推荐新闻
- 网站模块
- 网站前端
- 网站服务器端
- Android客户端
1.3 主要源代码结构
NewsFeeds
├── Graphs 实际运行效果截图, 系统架构图等
├── README.md 开发文档
├── NewsFeed_Android 安卓客户端源代码
├── Scripts 新闻数据相关脚本
│ ├── word2vec 中文语料库训练脚本
│ ├── config.ini 配置文件
│ ├── modelUpdater.py 推荐系统相关实现脚本
│ ├── newsCrawler.py 新闻爬取脚本
│ ├── service.py 服务器后台常驻进程脚本
└── Server 新闻网站服务器
├── app.js Node.js入口脚本
├── bin 服务器启动脚本
├── config.json 网站配置文件(服务器地址,数据库源等)
├── package.json Node.js依赖模块声明
├── public 网站静态文件
├── routes Node.js路由
├── scripts 网站后端逻辑实现
└── views 网站前端实现
192 directories, 65 files
1.4 运行环境
- 操作系统:Windows/Linux/OSX
- 服务器软件:Node.js 6.9.0及以上
- 逻辑数据库:MongoDB 2.4.1及以上
- 缓存数据库:Redis 3.2.9及以上
1.5 使用说明
1.进入服务器所在目录 Server
2.根据各个服务器配置,按如下说明更改配置文件config.json:
{
"host": "服务器主机地址",
"port": "服务器主机端口",
"connect": "MongoDB连接字符串",
"newscol": "MongoDB新闻数据库名",
"usercol": "MongoDB用户数据库名",
"mailoptions": {
"service": "邮件服务",
"email": "邮箱账号",
"password": "邮箱密码"
},
"redis": {
"port": "Redis端口",
"host": "Redis服务器地址"
},
"database": {
"host": "MongoDB数据库服务器地址",
"port": "MongoDB服务器端口",
"user": "Mongo用户名",
"password": "密码"
},
"api": {
"modelid": "微软认知服务-推荐系统模型ID",
"endpoint": "https://westus.api.cognitive.microsoft.com/recommendations/v4.0",
"token": "微软认知服务认证Token"
},
"genres": {
"personal": "推荐",
"hot": "热门",
"society": "社会",
"domestic": "国内",
"global": "国际",
"technology": "科技",
"finance": "经济",
"war": "军事",
"education": "教育",
"car": "汽车",
"game": "游戏",
"discover": "探索",
"entertain": "娱乐",
"fashion": "时尚",
"health": "健康",
"history": "历史",
"mobile": "数码",
"sport": "体育",
"travel": "旅游"
}
}
3.安装Node.js依赖包:
npm install
4.启动服务器
npm start
二. 新闻数据模块
2.1 新闻数据库设计
由于本项目使用基于文档存储的MongDB,每一个新闻使用一种类似JSON的数据类型存储,不需要有固定的结构(Schema)。但为了便于后续的建模与显示,在新闻爬取模块需要将每个新闻网站爬取下来的数据按照以下字段及其对应含义存储到数据库中:
{
"_id" : 新闻id,
"title" : 新闻标题(唯一索引项),
"source" : 新闻来源,
"time" : 发表时间,
"abstract" : 新闻摘要,
"comments_count" : 新闻评论数,
"favorite_count" : 新闻收藏数,
"genre" : 新闻类别,
"has_image" : 是否有图片,
"imgurls" : 图片链接(数组),
"keywords" : 标题包含关键词(数组),
"related_words" : 其他相关关键词(数组),
"uploaded" : 是否已上传到微软认知服务模型
}
2.2 新闻爬取模块
本项目中新闻爬取模块主要实现了对腾讯、网易、凤凰网、今日头条等四个网站的新闻数据爬取。对于从各个网站解析出的每个新闻,最终存入数据库的字段及格式需要统一如前一小节中所述。
由于从网站上直接获取到的只是HTML数据(除了今日头条使用API能够直接获取到JSON格式的新闻数据),因此需要通过BeautifulSoup等HTML解析器来获取HTML DOM中有意义的那些项。以爬取腾讯新 闻的脚本实现为例:
首先找出腾讯每类新闻的网页链接,存到一个Dict对象中:
self.ADDRS_LIST = {
'http://society.qq.com/': 'society',
'http://ent.qq.com/': 'entertain',
'http://sports.qq.com/': 'sport',
'http://finance.qq.com/': 'finance',
'http://mil.qq.com/mil_index.htm': 'war',
'http://news.qq.com/world_index.shtml': 'global',
'http://cul.qq.com/': 'culture'
...
}
通过查看网页源码后可以发现每一类新闻网站的DOM树结构都类似,新闻列表中每一条新闻都包含在一个Q-pList元素内:

因此在解析HTML时首先要通过BeautifulSoup(response.text, "html.parser")将HTML字符串转化为DOM树结构,再用list.findAll('div', {'class' : ['Q-pList'] })方法得到每个Q-pList包含的DOM元素,之后使用同样的方法再获取其内部的标题,关键字等内容并存入数据库即可:
for (addr, catogory) in self.ADDRS_LIST.items():
response = requests.get(addr, HEADERS)
if response.status_code != 200:
print('response code = ', response.status_code)
exit()
soup = BeautifulSoup(response.text, "html.parser")
lists = soup.findAll('div', {'class' : 'list'})
for list in lists:
for item in list.findAll('div', {'class' : ['Q-tpWrap', 'Q-pList'] }):
try:
linkto = item.find('a', {'class' : 'linkto'})
title = linkto.text
docurl = linkto.get('href')
keywords = item.find('span',{'class':'keywords'}).text
imgs = [ a.get('src') if a.get('src') else a.get('_src') for a in item.findAll('img')]
comment = item.find('a', {'class' : 'discuzBtn'})
if comment:
commentNum = comment.text
commenturl = comment.get('href')
source = item.find('span', {'class' : 'from'}).text
print(catogory, title.strip())
except:
print_exc()
exit()
2.3 新闻聚类模块
新闻聚类模块的实现思路比较简单,主要通过使用Word2Vec建立关键词库模型后,找出每个新闻关键字(keywords字段)的近义词作为该新闻的related_words记录下来,若一个新闻的related_words与另一个新闻的keywords有交集则认为这两个新闻是相关的。
本项目中关键词库模型使用中文维基百科提供的繁体中文Wiki语料库建立,所使用的脚本均放在Scripts/word2vec文件夹内。首先需要将下载的xml的文件转换成txt文件,主要通过process_wiki.py这个脚本来进行,执行命令:
python3 process_wiki.py zhwiki-latest-pages-articles.xml.bz2 wiki.cn.text
整个过程约10分钟,处理完后得到如下所示的文本文件:

由于维基百科中文语料库提供的只有繁体中文的语料,而且可以看出还有一些英文和其他标点字符,因此我们需要先转换成简体,对中文进行分词,再去掉英文等无用字符。 简繁体的转换主要通过OpenCC来实现:
opencc -i wiki.cn.text -o wiki.cn.text.jian -c t2s.json
中文的分词没有英文那么简单有天然的分隔符,但我们可以利用一些分词工具进行简单的分词工作。python上比较好用的分词软件是jieba中文分词 通过对其给出的示例进行部分修改后得到脚本seperate_words.py,执行命令:
python3 separate_words.py wiki.cn.text.jian wiki.cn.text.jian.seq
编写脚本remove_words.py,对分出来的词使用正则表达式匹配去除英文和标点字符,仅保留中文词汇:
python3 remove_words.py wiki.cn.text.jian.seq wiki.cn.text.jian.removed
最后,通过Google word2vec官方教程所给的脚本train_word2vec_model.py,对处理好的、符合格式要求的中文词汇集进行训练:
python3 train_word2vec_model.py wiki.cn.text.jian.removed wiki.en.text.jian.model wiki.en.text.jian.vector
训练完成之后得到的模型文件:

导入模型进行测试:

可以看到训练出的模型还是基本能够满足找近义词的要求的。
训练好模型之后,接下来就是使用Word2Vec找出每一条新闻关键字的近义词,并保存在related_words字段中。这样在之后查询某一条新闻的相关新闻时,只需要对其related_words集合与其他新闻的keywords集合做交集即可:
def buildRelated(self):
collection = getConnection('mongo')[self.colName]
model = gensim.models.Word2Vec.load(self.word2vecModel)
try:
for news in collection.find({ "related_words": { "$exists": 0 } }, projection={"keywords":1, "title": 1, "_id": 1}):
relatedWords = set()
for keyword in news["keywords"]:
try:
relatedWords |= set(map(lambda t: t[0], model.most_similar(keyword)))
except KeyError:
print("KeyError:", keyword)
continue
print(news["title"])
collection.update_one({"_id": news["_id"]},
{"$set": { "related_words": list(relatedWords) }})
except:
print_exc()
finally:
model = None
gc.collect()
2.4 个性化推荐模块
个性化推荐模块主要使用微软认知服务中的建议API服务,根据特定用户的购买历史记录,推荐引擎借助 Azure 机器学习进行构建,以提供专门针对该用户的建议,并使其享受个性化体验。
该服务内包含许多不同操作的RESTful API,每个API都有非常详尽的使用文档。该服务基本的使用模式:首先使用Create Model API建立一个推荐模型,然后按照规定格式的文本上传所有需要用于推荐的对象(Catalog Item,如本项目中的新闻),之后需要上传每个用户的使用记录(Usage)。之后便是调用最重要的Trigger Build API对已经上传入库的数据和记录进行建模分析,以下是该API的说明文档:

当一次Build完成之后,便能够通过Get user-to-item recommendations API来获取对某个用户id特定的推荐新闻了。
由于这部分内容的实现多半是构造请求体,发送HTTP请求调用RESTful API,各个步骤的代码实现大同小异,因此使用两个例子来展示具体的实现。
用户点击某新闻时,发送POST请求向API上传一条Usage数据:
function uploadUsage() {
let params = {
modelId: ""
}
let body = {
"userId": "string",
"buildId": 0,
"events": [
{
"eventType": "Click",
"itemId": "string",
"timestamp": "string",
"count": 0,
"unitPrice": 0.0
}
]
}
$.ajax({
url: "https://westus.api.cognitive.microsoft.com/recommendations/v4.0/models/28f64f3a-84a8-4f6d-889b-6c738d284aad/usage/events?"
+ $.param(params),
beforeSend: function(xhrObj){
// Request headers
xhrObj.setRequestHeader("Content-Type","application/json");
xhrObj.setRequestHeader("Ocp-Apim-Subscription-Key","62155e00332a4a62afbfe6478c8c9212");
},
type: "POST",
// Request body
data: body,
}).done(function(data) {
console.log("update success")
}).fail(function() {
alert("update error");
});
}
后端进程使用Trigger Build API启动一次统计建模(周期执行,如每天执行一次):
def triggerBuild(self):
collection = getConnection('mongo')[self.modelColName]
header = self.HEADERS_JSON
buildurl = self.endpoint + '/models/%s/builds?' % (self.modelid)
body = json.dumps({
"description": "Simple recomendations build",
"buildType": "recommendation",
"buildParameters": {
"recommendation": {
"numberOfModelIterations": 40,
"numberOfModelDimensions": 20,
"itemCutOffLowerBound": 1,
"itemCutOffUpperBound": 10,
"userCutOffLowerBound": 0,
"userCutOffUpperBound": 0,
"enableModelingInsights": False,
