<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          新聞推薦實(shí)戰(zhàn)(五):自動(dòng)化構(gòu)建用戶及物料畫像

          共 18942字,需瀏覽 38分鐘

           ·

          2022-05-30 10:01


          新聞推薦實(shí)戰(zhàn)(四):scrapy爬蟲框架基礎(chǔ)

          新聞推薦實(shí)戰(zhàn)(三):Redis基礎(chǔ)

          新聞推薦實(shí)戰(zhàn)(二):MongoDB基礎(chǔ)

          新聞推薦實(shí)戰(zhàn)(一):MySQL基礎(chǔ)

          自動(dòng)化構(gòu)建用戶及物料畫像

          本節(jié)內(nèi)容主要講的是上圖中紅框框起來的部分,也就是離線自動(dòng)化構(gòu)建用戶和物料的畫像,這部分內(nèi)容在新聞推薦系統(tǒng)中是為系統(tǒng)源源不斷添加新物料的途徑,由于我們的物料是通過爬蟲獲取的,所以還需要對(duì)爬取的數(shù)據(jù)進(jìn)行處理,也就是構(gòu)造新聞的畫像。對(duì)于用戶側(cè)的畫像則是需要每天將新注冊(cè)的用戶添加到用戶畫像庫中,對(duì)于在系統(tǒng)中產(chǎn)生了行為的用戶,我們還需要定期的更新用戶的畫像(長(zhǎng)短期)。下面分別從物料側(cè)和用戶側(cè)兩個(gè)方面來詳細(xì)解釋這兩類畫像在系統(tǒng)中是如何自動(dòng)化構(gòu)建的。

          物料側(cè)畫像的構(gòu)建

          新物料來源

          首先要說的就是新物料的來源,物料是通過每天在新聞網(wǎng)站上爬取獲取的,爬取新聞詳細(xì)的內(nèi)容在前文:Scrapy基礎(chǔ)及新聞爬取實(shí)戰(zhàn)中已經(jīng)詳細(xì)的聊過了,這里要說明的一點(diǎn)就是,新聞爬取是每天凌晨的時(shí)候爬取前一天的新聞,這么做的原因是可以爬到更多的物料,缺點(diǎn)就是物料的時(shí)效性會(huì)延遲一天,新爬取的物料存儲(chǔ)在MongoDB中。

          物料畫像的更新

          物料畫像的更新主要有以下幾個(gè)方面:

          1. 新物料畫像添加到物料庫中
          2. 舊物料畫像,通過用戶的交互記錄進(jìn)行更新

          首先說一下新物料添加到物料庫的邏輯是什么,新物料添加到物料庫這件事情肯定是發(fā)生在新聞爬取之后的,然后要將新物料添加到物料庫還需要對(duì)新物料做一些簡(jiǎn)單的畫像處理,目前我們定義的畫像字段如下(處理后的畫像存儲(chǔ)在Mongodb):

          具體的邏輯就是遍歷今天爬取的所有文章,然后通過文章的title來判斷這篇文章是否已經(jīng)在物料庫中(新聞網(wǎng)站有可能有些相同的文章會(huì)出現(xiàn)在多天)來去重。然后再根據(jù)我們定義的一些字段,給畫像相應(yīng)的字段初始化,最后就是存入畫像物料池中。

          關(guān)于舊物料畫像的更新,這里就需要先了解一下舊物料哪些字段會(huì)被用戶的行為更新。下面是新聞列表展示頁,我們會(huì)發(fā)現(xiàn)前端會(huì)展示新聞的閱讀、喜歡及收藏次數(shù)。而用戶的交互(閱讀、點(diǎn)贊和收藏)會(huì)改變這些值。

          為了能夠?qū)崟r(shí)的在前端顯示新聞的這些動(dòng)態(tài)行為信息,我們提前將新聞的動(dòng)態(tài)信息存儲(chǔ)到了redis中,線上獲取的時(shí)候是直接從redis中獲取新聞的數(shù)據(jù),并且如果用戶對(duì)新聞產(chǎn)生了交互,那么這些動(dòng)態(tài)信息就會(huì)被更新,我們也是直接更新redis中的值,這樣做主要是為了能夠讓前端可以實(shí)時(shí)的獲取的新聞最新的動(dòng)態(tài)畫像信息。

          通過上面的內(nèi)容我們了解到,新聞的動(dòng)態(tài)畫像的更新是在redis中進(jìn)行的,而redis又是一個(gè)內(nèi)存數(shù)據(jù)庫,資源是非常寶貴的,我們不能一直將新聞的信息存儲(chǔ)在里面,而是每天進(jìn)行一次更新,只更新那些今天可能會(huì)被用來展示的新聞(有些新聞可能從發(fā)表到現(xiàn)在太久了,由于新聞的時(shí)效性就沒有必要再展示了)。所以為了能夠保存新聞歷史的動(dòng)態(tài)信息,系統(tǒng)還需要每天將redis中的動(dòng)態(tài)新聞信息更新到mongodb存儲(chǔ)的新聞畫像庫中,這里的邏輯也是每天會(huì)定時(shí)觸發(fā),這里的邏輯會(huì)放在更新完新物料的畫像之后,當(dāng)然這里兩個(gè)的先后順序并沒有什么影響,只需要注意更新物料動(dòng)態(tài)畫像的時(shí)候一定得再redis數(shù)據(jù)清空之前。

          其實(shí)這里還有個(gè)邏輯需要說明一下,新聞的畫像庫其實(shí)是有兩個(gè)的,一個(gè)被稱為是特征庫FeatureProtrail, 它存儲(chǔ)了物料的所有字段。還有一個(gè)是存儲(chǔ)前端展示內(nèi)容的畫像庫RedisProtrail, 這個(gè)畫像庫中的物料是一樣的多,只不過每個(gè)物料存儲(chǔ)的內(nèi)容不一樣,這個(gè)特征庫的內(nèi)容每天都會(huì)被更新,作為存儲(chǔ)在redis中的新聞內(nèi)容的備份內(nèi)容。所以在完成了新、舊物料畫像的更新之后,我們需要將最新的物料庫中的新聞信息往RedisProtrail物料庫中寫一份,并去掉一些前端展示不需要的字段內(nèi)容。

          關(guān)于物料畫像的更新的核心代碼:

          #?-*-?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?標(biāo)題
          2.?raw_key_words?(爬下來的關(guān)鍵詞,可能有缺失)
          3.?manual_key_words?(根據(jù)內(nèi)容生成的關(guān)鍵詞)
          4.?ctime?時(shí)間
          5.?content?新聞具體內(nèi)容
          6.?cate?新聞?lì)悇e
          7.?likes?新聞點(diǎn)贊數(shù)量
          8.?collections?新聞收藏?cái)?shù)量
          9.?read_nums?閱讀次數(shù)
          10.?url?新聞原始鏈接
          """


          class?NewsProtraitServer:
          ????def?__init__(self):
          ????????"""初始化相關(guān)參數(shù)
          ????????"""

          ????????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):
          ????????"""從數(shù)據(jù)庫中查找是否有相同標(biāo)題的新聞數(shù)據(jù)
          ????????數(shù)據(jù)庫存在當(dāng)前標(biāo)題的數(shù)據(jù)返回True,?反之返回Flase
          ????????"""

          ????????#?find方法,返回的是一個(gè)迭代器
          ????????find_res?=?collection.find({"title":?title})
          ????????if?len(list(find_res))?!=?0:
          ????????????return?True
          ????????return?False

          ????def?_generate_feature_protrail_item(self,?item):
          ????????"""生成特征畫像數(shù)據(jù),返回一個(gè)新的字典
          ????????"""

          ????????news_item?=?dict()
          ????????news_item['news_id']?=?item['news_id']
          ????????news_item['title']?=?item['title']
          ????????#?從新聞內(nèi)容中提取的關(guān)鍵詞沒有原始新聞爬取時(shí)的關(guān)鍵詞準(zhǔn)確,所以手動(dòng)提取的關(guān)鍵詞
          ????????#?只是作為一個(gè)補(bǔ)充,當(dāng)原始新聞中沒有提供關(guān)鍵詞的時(shí)候可以使用
          ????????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?#?初始化一個(gè)比較大的熱度值,會(huì)隨著時(shí)間進(jìn)行衰減
          ????????
          ????????return?news_item

          ????def?update_new_items(self):
          ????????"""將今天爬取的數(shù)據(jù)構(gòu)造畫像存入畫像數(shù)據(jù)庫中
          ????????"""

          ????????#?遍歷今天爬取的所有數(shù)據(jù)
          ????????for?item?in?self.sina_collection.find():
          ????????????#?根據(jù)標(biāo)題進(jìn)行去重
          ????????????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數(shù)據(jù)刪掉
          ????????"""

          ????????#?每天先刪除前一天的redis展示數(shù)據(jù),然后再重新寫入
          ????????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']?=?0
          ????????????news_item['collections']?=?0
          ????????????news_item['read_num']?=?0

          ????????????self.redis_mongo_collection.insert_one(news_item)
          ????????print("run?update_redis_mongo_protrail_data?success.")

          ????def?update_dynamic_feature_protrail(self):
          ????????"""用redis的動(dòng)態(tài)畫像更新mongodb的畫像
          ????????"""

          ????????#?遍歷redis的動(dòng)態(tài)畫像,將mongodb中對(duì)應(yīng)的動(dòng)態(tài)畫像更新????????
          ????????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("'",?'"'?)?#?將單引號(hào)都替換成雙引號(hào)
          ????????????news_dynamic_info_dict?=?json.loads(news_dynamic_info_str)
          ????????????
          ????????????#?查詢mongodb中對(duì)應(yīng)的數(shù)據(jù),并將對(duì)應(yīng)的畫像進(jìn)行修改
          ????????????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.")


          #?系統(tǒng)最終執(zhí)行的不是這個(gè)腳本,下面的代碼是用來測(cè)試的
          if?__name__?==?"__main__":
          ????news_protrait?=?NewsProtraitServer()
          ????#?新物料畫像的更新
          ????news_protrait.update_new_items()
          ????#?更新動(dòng)態(tài)特征
          ????news_protrait.update_dynamic_feature_protrail()
          ????#?redis展示新聞內(nèi)容的備份
          ????news_protrait.update_redis_mongo_protrail_data()

          上面的內(nèi)容說完了物料的更新,接下來介紹一下對(duì)于更新完的物料是如何添加到redis數(shù)據(jù)庫中去的。關(guān)于新聞內(nèi)容在redis中的存儲(chǔ),我們將新聞的信息拆成了兩部分,一部分是新聞不會(huì)發(fā)生變化的屬性(例如,創(chuàng)建時(shí)間、標(biāo)題、新聞內(nèi)容等),還有一部分是物料的動(dòng)態(tài)屬性,在redis中存儲(chǔ)的key的標(biāo)識(shí)分別為:static_news_detail:news_id和dynamic_news_detail:news_id 下面是redis中存儲(chǔ)的真實(shí)內(nèi)容

          這么做的目的是為了線上實(shí)時(shí)更改物料動(dòng)態(tài)信息的時(shí)候更加高效一點(diǎn)。當(dāng)需要獲取某篇新聞的詳細(xì)信息的時(shí)候需要查這兩份數(shù)據(jù)并將數(shù)據(jù)這兩部分?jǐn)?shù)據(jù)拼起來最終才發(fā)送給前端展示。這部分的代碼邏輯如下:

          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中的內(nèi)容
          ????????self._flush_redis_db()

          ????def?_flush_redis_db(self):
          ????????"""每天都需要?jiǎng)h除redis中的內(nèi)容,更新當(dāng)天新的內(nèi)容上去
          ????????"""

          ????????try:
          ????????????self.rec_list_redis.flushall()
          ????????except?Exception:
          ????????????print("flush?redis?fail?...?")

          ????def?_get_news_id_list(self):
          ????????"""獲取物料庫中所有的新聞id
          ????????"""

          ????????#?獲取所有數(shù)據(jù)的news_id,
          ????????#?暴力獲取,直接遍歷整個(gè)數(shù)據(jù)庫,得到所有新聞的id
          ????????#?TODO?應(yīng)該存在優(yōu)化方法可以通過查詢的方式只返回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):
          ????????"""將需要展示的畫像內(nèi)容存儲(chǔ)到redis
          ????????靜態(tài)不變的特征存到static_news_info_db_num
          ????????動(dòng)態(tài)會(huì)發(fā)生改變的特征存到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})?#?返回的是一個(gè)列表里面套了一個(gè)字典??
          ????????????news_item_dict.pop("_id")

          ????????????#?分離動(dòng)態(tài)屬性和靜態(tài)屬性
          ????????????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__":
          ????#?每次創(chuàng)建這個(gè)對(duì)象的時(shí)候都會(huì)把數(shù)據(jù)庫中之前的內(nèi)容刪除
          ????news_redis_server?=?NewsRedisServer()
          ????#?將最新的前端展示的畫像傳到redis
          ????news_redis_server.news_detail_to_redis()

          到此位置,離線物料畫像的更新邏輯就介紹完了,最后把上面的邏輯用代碼全部串起來的話就如下代碼:**下面的代碼是會(huì)在每天定時(shí)運(yùn)行的,這樣就將物料側(cè)的畫像構(gòu)建邏輯串起來了

          from?material_process.news_protrait?import?NewsProtraitServer
          from?material_process.news_to_redis?import?NewsRedisServer

          def?process_material():
          ????"""物料處理函數(shù)
          ????"""

          ????#?畫像處理
          ????protrail_server?=?NewsProtraitServer()
          ????#?處理最新爬取新聞的畫像,存入特征庫
          ????protrail_server.update_new_items()
          ????#?更新新聞動(dòng)態(tài)畫像,?需要在redis數(shù)據(jù)庫內(nèi)容清空之前執(zhí)行
          ????protrail_server.update_dynamic_feature_protrail()
          ????#?生成前端展示的新聞畫像,并在mongodb中備份一份
          ????protrail_server.update_redis_mongo_protrail_data()

          ????#?新聞數(shù)據(jù)寫入redis,?注意這里處理redis數(shù)據(jù)的時(shí)候是會(huì)將前一天的數(shù)據(jù)全部清空
          ????news_redis_server?=?NewsRedisServer()
          ????#?將最新的前端展示的畫像傳到redis
          ????news_redis_server.news_detail_to_redis()


          if?__name__?==?"__main__":
          ????process_material()?

          用戶側(cè)畫像的構(gòu)建

          對(duì)于用戶畫像的更新來說主要分為兩方面:

          1. 新注冊(cè)用戶畫像的更新
          2. 老用戶畫像的更新

          由于我們系統(tǒng)中將所有注冊(cè)過的用戶都放到了一個(gè)表里面(新、老用戶),所以每次更新畫像的話只需要遍歷一遍注冊(cè)表中的所有用戶。在說具體的畫像構(gòu)建邏輯之前,得先了解一下用戶畫像中包含哪些字段,下面是直接從mongo中查出來的

          從上面可以看出,主要是用戶的基本信息和用戶歷史信息相關(guān)的一些標(biāo)簽,對(duì)于用戶的基本屬性特征這個(gè)可以直接從注冊(cè)表中獲取,那么對(duì)于跟用戶歷史閱讀相關(guān)的信息,需要統(tǒng)計(jì)用戶歷史的所有閱讀、喜歡和收藏的新聞詳細(xì)信息。為了得到跟用戶歷史興趣相關(guān)的信息,我們需要對(duì)用戶的歷史閱讀、喜歡和收藏這幾個(gè)歷史記錄給存起來,其實(shí)這些信息都可以從日志信息中獲取得到,但是這里有個(gè)工程上的事情得先說明一下,先看下面這個(gè)圖,對(duì)于每個(gè)用戶點(diǎn)進(jìn)一篇新聞的詳情頁

          最底部有個(gè)喜歡和收藏,這個(gè)前端展示的結(jié)果是從后端獲取的數(shù)據(jù),那就意味著后端需要維護(hù)一個(gè)用戶歷史點(diǎn)擊及收藏過的文章列表,這里我們使用了mysql來存儲(chǔ),主要是怕redis不夠用。其實(shí)這兩個(gè)表不僅僅可以用來前端展示用的,還可以用來分析用戶的畫像,這都給我們整理好了用戶歷史喜歡和收藏了。

          此外前面也提到了我們可以使用用戶歷史閱讀的文章做用戶畫像,為了更好處理和理解,我們也維護(hù)了一份用戶歷史閱讀過的所有文章的mysql表(維護(hù)表的核心邏輯就是每天跑一邊用戶日志,更新一下用戶歷史閱讀的記錄),那么此時(shí)我們其實(shí)已經(jīng)有了用戶的閱讀、點(diǎn)贊和收藏三個(gè)用戶行為表了,接下來就直接可以通過這三個(gè)表來做具體的用戶興趣相關(guān)的畫像了,實(shí)現(xiàn)的具體邏輯如下:

          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查詢出來的結(jié)果轉(zhuǎn)換成字典存儲(chǔ)
          ????????"""

          ????????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的新聞?lì)悇e
          ????????????????info_dict["{}_{}_intr_key_words".format(type,time_range)]?=?feature_dict[type]["intr_key_words"]?#?歷史喜歡新聞的Top3的關(guān)鍵詞
          ????????????????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天內(nèi)喜歡的新聞數(shù)量
          ????????????else:
          ????????????????info_dict["{}_{}_intr_cate".format(type,time_range)]?=?""??#?歷史喜歡最多的Top3的新聞?lì)悇e
          ????????????????info_dict["{}_{}_intr_key_words".format(type,time_range)]?=?""?#?歷史喜歡新聞的Top3的關(guān)鍵詞
          ????????????????info_dict["{}_{}_avg_hot_value".format(type,time_range)]?=?0?#?用戶喜歡新聞的平均熱度
          ????????????????info_dict["{}_{}_news_num".format(type,time_range)]?=?0?#?用戶15天內(nèi)喜歡的新聞數(shù)量

          ????????return?info_dict

          ????def?update_user_protrail_from_register_table(self):
          ????????"""每天都需要將當(dāng)天注冊(cè)的用戶添加到用戶畫像池中
          ????????"""

          ????????#?遍歷注冊(cè)用戶表
          ????????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:
          ????????????????#?使用參數(shù)upsert設(shè)置為true對(duì)于沒有的會(huì)創(chuàng)建一個(gè)
          ????????????????#?replace_one?如果遇到相同的_id?就會(huì)更新
          ????????????????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):
          ????????"""獲取用戶歷史行為的統(tǒng)計(jì)特征?["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()
          ????????#?計(jì)算平均熱度
          ????????feature_dict["avg_hot_value"]?=?0?if?sum(history_hot_value)?0.001?else?sum(history_hot_value)?/?len(history_hot_value)

          ????????#?計(jì)算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

          ????????#?計(jì)算Top3的關(guān)鍵詞
          ????????word_dict?=?Counter(history_key_word)
          ????????word_list=?sorted(word_dict.items(),key?=?lambda?d:?d[1],?reverse=True)
          ????????#?TODO?關(guān)鍵字屬于長(zhǎng)尾?如果關(guān)鍵字的次數(shù)都是一次?該怎么去前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
          ????????#?新聞數(shù)目
          ????????feature_dict["news_num"]?=?len(history_new_id)

          ????????return?feature_dict


          if?__name__?==?"__main__":
          ????user_protrail?=?UserProtrail().update_user_protrail_from_register_table()

          到此位置用戶畫像的基本邏輯就介紹完了,下面是用戶畫像更新的總體邏輯代碼:

          from?user_process.user_to_mysql?import?UserMysqlServer
          from?user_process.user_protrail?import?UserProtrail

          """
          1. 將用戶的曝光數(shù)據(jù)從redis落到mysql中。
          2.?更新用戶畫像
          """

          ????
          def?process_users():
          ????"""將用戶數(shù)據(jù)落?Mysql
          ????"""

          ????#?用戶mysql存儲(chǔ)
          ????user_mysql_server?=?UserMysqlServer()
          ????#?用戶曝光數(shù)據(jù)落mysql
          ????user_mysql_server.user_exposure_to_mysql()

          ????#?更新用戶畫像
          ????user_protrail?=?UserProtrail()
          ????user_protrail.update_user_protrail_from_register_table()


          if?__name__?==?"__main__":
          ????process_users()?

          畫像自動(dòng)化構(gòu)建

          上面分別對(duì)用戶側(cè)和物料側(cè)的畫像構(gòu)建進(jìn)行了介紹,接下來就是要將上面所有的過程都自動(dòng)化運(yùn)行,并且設(shè)置好定時(shí)任務(wù),其實(shí)最核心的一點(diǎn)就是一定要在清除redis數(shù)據(jù)之前,完成用戶和物料畫像的構(gòu)建,下面是構(gòu)建整個(gè)自動(dòng)化的流程。

          物料更新腳本:process_material.py

          from?material_process.news_protrait?import?NewsProtraitServer
          from?material_process.news_to_redis?import?NewsRedisServer


          def?process_material():
          ????"""物料處理函數(shù)
          ????"""

          ????#?畫像處理
          ????protrail_server?=?NewsProtraitServer()
          ????#?處理最新爬取新聞的畫像,存入特征庫
          ????protrail_server.update_new_items()
          ????#?更新新聞動(dòng)態(tài)畫像,?需要在redis數(shù)據(jù)庫內(nèi)容清空之前執(zhí)行
          ????protrail_server.update_dynamic_feature_protrail()
          ????#?生成前端展示的新聞畫像,并在mongodb中備份一份
          ????protrail_server.update_redis_mongo_protrail_data()


          if?__name__?==?"__main__":
          ????process_material()?

          用戶畫像更新腳本: process_user.py

          from?user_process.user_to_mysql?import?UserMysqlServer
          from?user_process.user_protrail?import?UserProtrail

          """
          1. 將用戶的曝光數(shù)據(jù)從redis落到mysql中。
          2.?更新用戶畫像
          """


          ????
          def?process_users():
          ????"""將用戶數(shù)據(jù)落?Mysql
          ????"""

          ????#?用戶mysql存儲(chǔ)
          ????user_mysql_server?=?UserMysqlServer()
          ????#?用戶曝光數(shù)據(jù)落mysql
          ????user_mysql_server.user_exposure_to_mysql()

          ????#?更新用戶畫像
          ????user_protrail?=?UserProtrail()
          ????user_protrail.update_user_protrail_from_register_table()


          if?__name__?==?"__main__":
          ????process_users()?

          redis數(shù)據(jù)更新腳本:update_redis.py

          from?material_process.news_protrait?import?NewsProtraitServer
          from?material_process.news_to_redis?import?NewsRedisServer


          def?update():
          ????"""物料處理函數(shù)
          ????"""

          ????#?新聞數(shù)據(jù)寫入redis,?注意這里處理redis數(shù)據(jù)的時(shí)候是會(huì)將前一天的數(shù)據(jù)全部清空
          ????news_redis_server?=?NewsRedisServer()
          ????#?將最新的前端展示的畫像傳到redis
          ????news_redis_server.news_detail_to_redis()


          if?__name__?==?"__main__":
          ????update()?

          最后將上面三個(gè)腳本穿起來的shell腳本offline_material_and_user_process.sh:

          #!/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到我們想要運(yùn)行的目錄下面
          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中的數(shù)據(jù),更新最新今天最新的數(shù)據(jù)
          ${python}?update_redis.py
          if?[?$??-eq?0?];?then
          ????echo?"update_redis?success."
          else???
          ????echo?"update_redis?fail."
          fi


          echo?"?"

          crontab定時(shí)任務(wù):


          將定時(shí)任務(wù)拆解一下:

          0 0 * * * /home/recsys/news_rec_server/scheduler/crawl_news.sh >> /home/recsys/news_rec_server/logs/offline_material_process.log && 

          /home/recsys/news_rec_server/scheduler/offline_material_and_user_process.sh >> /home/recsys/news_rec_server/logs/material_and_user_process.log &&

          /home/recsys/news_rec_server/scheduler/run_offline.sh >> /home/recsys/news_rec_server/logs/offline_rec_list_to_redis.log

          前面的crontab語法表示的是每天0點(diǎn)運(yùn)行下面這一串腳本,上面命令中的 && 表示的是先運(yùn)行完符號(hào)前面的內(nèi)容再運(yùn)行后面的命令,所以這里的&&是為了將上面三個(gè)任務(wù)串聯(lián)起來,大致的執(zhí)行邏輯就是:

          1. 先爬取新聞數(shù)據(jù),這里需要注意的是,雖然是今天零點(diǎn)爬數(shù)據(jù),但是實(shí)際上爬的是前一天的新聞
          2. 數(shù)據(jù)爬完之后,離線更新用戶畫像,物料畫像及線上要存儲(chǔ)在redis中的畫像
          3. 最后其實(shí)是離線推薦的流程,離線將用戶的排序列表存到redis中,線上直接取就行了

          總結(jié)

          這篇文章主要講解了新聞推薦系統(tǒng)離線如何通過自動(dòng)化的形式構(gòu)建物料和用戶的畫像,文章比較長(zhǎng),但是整體上把文章最上面的那張圖中的邏輯講清楚了(細(xì)節(jié)方面的可能需要看代碼了)。

          近期閱讀學(xué)習(xí)推薦

          服務(wù)器被黑客攻擊,用來挖礦!怎么辦?

          Python超好用的命令行界面實(shí)現(xiàn)工具

          Python自動(dòng)化處理Excel表格實(shí)戰(zhàn)完整代碼分享(課表解析)

          如何找到我

          分享

          收藏

          點(diǎn)贊

          在看

          瀏覽 77
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  女人十八毛片a级毛片 | 国产呦精品 | 日韩激情视频一区二区三区 | 日日操B 日日色色 | 五月天成人激情视频 |