本推荐系统采用的是分层模型设计思想,第一层为前端页面模型设计,注重为实现页面的展示效果,主用的编程语言为JavaScript,和前端主流框架bootstrap。
第二层为后端模型设计,编程语言选了简单易懂的python,用Django作为后端框架进行开发,此框架是python web系统开发的首选框架,简单易用。
第三层为算法的设计与实现的逻辑,用协同过滤算法来实现,第四层为数据库表的设计,用SQLite数据库。
本系统web端的功能模块,主要实现动漫显示、动漫分类显示、热门动漫排序显示、收藏动漫排序显示、时间排序显示、评分排序显示、算法推荐、动漫搜索、动漫信息管理等功能,并进行数据库的详细设计,完成设计阶段的各项功能,并对此系统进行功能测试,最后,系统进行相关的实际应用操作,通过系统的使用,用户进入动漫推荐系统,此系统可以根据用户对动漫所打的标签行为,给用户推荐用户所感兴趣的动漫,准确率在百分之75左右,用户可以查看信息,观看动漫,给动漫评分等操作,本系统基本上完成了预期的基本功能。
该推荐系统基于协同过滤算法实现,采用了分层模型设计思想,并使用了多种技术栈进行开发。
技术栈:
1. 前端页面模型设计:JavaScript和前端主流框架bootstrap,用于实现页面的展示效果。
2. 后端模型设计:Python作为编程语言,Django作为后端框架进行开发。Django是一个简单易用的Python web开发框架。
3. 算法设计与实现逻辑:采用协同过滤算法来实现推荐功能。
4. 数据库表设计:使用SQLite数据库进行数据存储。
实现步骤:
1. 前端页面模型设计:使用JavaScript和bootstrap框架搭建用户界面,实现动漫显示、分类显示、排序显示等功能。
2. 后端模型设计:使用Django框架搭建后端服务器,处理前端请求,调用相应的算法进行推荐计算,并将结果返回给前端页面。
3. 算法设计与实现逻辑:采用协同过滤算法来实现推荐功能。具体步骤包括:
- 初始化数据:获取用户的浏览行为数据。
- 计算两个用户的皮尔逊相关系数:通过遍历整个数据集,计算用户之间的相似度。
- 寻找最相似的用户:根据用户之间的相似度,找到与当前用户最相似的N个用户。
- 推荐动漫给用户:根据最相似的用户的浏览行为,推荐动漫给当前用户。
4. 数据库表设计:使用SQLite数据库进行数据存储,包括用户信息、动漫信息、用户对动漫的评分等。
通过以上步骤,该推荐系统实现了基于用户和基于物品的推荐功能,根据用户的标签行为和评分情况,给用户推荐其感兴趣的动漫。系统具有良好的用户界面和高准确率的推荐效果,用户可以方便地查看动漫信息、观看动漫、给动漫评分等操作。整个系统的开发过程经过详细的设计和功能测试,并成功应用于实际场景中。
项目目录结构:
|-- comic_data.py # 动漫数据抓取模块
|-- db.sqlite3 # SQLite数据库文件
|-- manage.py # Django管理命令入口
|-- movie
| |-- __init__.py # 模块初始化文件
| |-- admin.py # Django后台管理配置文件
| |-- apps.py # Django应用配置文件
| |-- data.py # 动漫数据处理模块
| |-- forms.py # 表单定义文件
| |-- migrations # 数据库迁移文件夹
| |-- models.py # 数据库模型定义文件
| |-- serializers.py # 序列化器定义文件
| |-- templatetags # 模板标签文件夹
| | |-- __init__.py
| | |-- grav_tag.py # 自定义模板标签:用于生成星级评分显示
| | |-- is_like.py # 自定义模板标签:用于判断用户是否喜欢某部动漫
| | `-- list_slice.py # 自定义模板标签:用于切片列表
| |-- tests.py # 测试文件
| `-- views.py # 视图函数定义文件
|-- movie.sql # 动漫数据的SQL文件
|-- movie_it
| |-- cache_keys.py # 缓存键定义文件
| |-- data.json # 数据JSON文件
| |-- douban_crawler.py # 豆瓣爬虫模块
| |-- play_2.py # 播放模块
| |-- populate_movies_script.py # 动漫数据填充脚本
| `-- recommend_movies.py # 推荐动漫模块
|-- movierecomend
| |-- __init__.py # 模块初始化文件
| |-- settings.py # Django项目配置文件
| |-- templatetags # 模板标签文件夹
| | |-- __init__.py
| | |-- grav_tag.py # 自定义模板标签:用于生成星级评分显示
| | `-- list_slice.py # 自定义模板标签:用于切片列表
| |-- urls.py # URL配置文件
| `-- wsgi.py # WSGI应用程序入口
|-- readme.txt # 项目说明文档
|-- requirements.txt # 项目依赖库列表
|-- static # 静态文件目录
| |-- css # CSS文件
| |-- images # 图片文件
| `-- js # JavaScript文件
|-- templates # 模板文件目录
| |-- all_tags.html # 显示所有标签的页面模板
| |-- base.html # 基础模板
| |-- base_show.html # 基础展示模板
| |-- choose_tag.html # 选择标签的页面模板
| |-- items.html # 动漫列表展示页面模板
| |-- login.html # 登录页面模板
| |-- movie.html # 单个动漫展示页面模板
| |-- my_comment.html # 我的评论页面模板
| |-- my_rate.html # 我的评分页面模板
| |-- mycollect.html # 我的收藏页面模板
| |-- personal.html # 个人信息页面模板
| |-- register.html # 注册页面模板
| |-- results.html # 搜索结果页面模板
| `-- tag_movie.html # 标签对应动漫页面模板
核心算法代码分享如下:
import asyncio
import logging
import aiohttp
from bs4 import BeautifulSoup
from crawler_utils.utils import timer
base_url = 'https://movie.douban.com/top250'
results = {}
new_url = 'https://www.douban.com/doulist/30299/?start=0&sort=seq&playable=0&sub_type='
ids = 0
tasks = []
class Movie:
def __init__(self, id, title, description, star, leader, tags, years, country, director_description, image_link):
self.id = id
self.star = star
self.description = description
self.title = title
self.leader = leader
self.tags = tags
self.years = years
self.country = country
self.director_description = director_description
self.image_link = image_link
async def fetch(url):
async with aiohttp.ClientSession()as session:
async with session.get(url) as response:
print(response.status)
assert response.status == 200
return await response.text()
async def write_images(image_link, image_name):
print('write images....', image_link)
async with aiohttp.ClientSession()as session:
async with session.get(image_link) as response:
assert response.status == 200
with open('movie_images/' + image_name + '.png', 'wb')as opener:
while True:
chunk = await response.content.read(1024)
if not chunk:
break
opener.write(chunk)
async def parse_list(html):
soup = BeautifulSoup(html, 'html.parser')
movies = soup.find_all('div', {'class': 'doulist-item'})
movie_list = []
for movie in movies:
try:
title = movie.find('div', {'class': 'title'}).text.strip().replace('/', '_')
except Exception as e:
print(movie)
print(e)
continue
try:
image_link = movie.find('div', {'class': 'post'}).find('img')
if image_link is None:
continue
else:
image_link = image_link['src']
await write_images(image_link, title)
rate = movie.find('div', {'class': 'rating'})
dou_rate = rate.find('span', {'class': 'rating_nums'})
if dou_rate is None:
dou_rate = '0'
else:
dou_rate = dou_rate.text
# rate_num = rate.find_all('span')[-1].text
abstract = movie.find('div', {'class': 'abstract'}).text.strip().split('\n')
tags = country = leader = year = director_ = ''
for ab in abstract:
if len(ab.strip()) == 0:
continue
ab_list = ab.split(':')
key = ab_list[0].strip()
value = ab_list[1].strip()
if key == '导演':
director_ = value
elif key == '主演':
leader = value
elif key == '年份':
year = value
elif key == '制片国家/地区':
country = value
elif key == '类型':
tags = value
except Exception as e:
print(movie)
raise e
global ids
ids += 1
movie_list.append(Movie(image_link=image_link, title=title, star=dou_rate, leader=leader, tags=tags, country=country, director_description=director_, years=year, id=ids, description=''))
new_link = soup.find('span', {'class': 'next'})
if new_link is not None:
try:
new_link = new_link.a['href']
except Exception:
new_link = None
return movie_list, new_link
async def parse_250(html):
soup = BeautifulSoup(html, 'html.parser')
movies_info = soup.find('ol', {'class': 'grid_view'})
movies = []
for movie_info in movies_info.find_all('li'):
pic = movie_info.find('div', {'class': 'pic'})
picture_url = pic.find('img').attrs['src']
movie_id = pic.find('em').text
url = movie_info.find('div', {"class": "info"})
title = url.find('span', {'class': 'title'}).text
# 保存图片文件到本地
print('write image ', picture_url)
await write_images(picture_url, title)
info = url.find('div', {'class': 'bd'})
movie_detail = info.find('p')
quote = info.find('p', {'class': 'quote'})
if quote is not None:
description = quote.find('span').text
else:
description = ''
print(title + 'description is None')
star = info.find('div', {"class": 'star'}).find('span', {'class': 'rating_num'}).text
tags = movie_detail.text.strip().split('\n')[-1].split('/')[-1].split(' ')
tags = [tag.strip() for tag in tags]
years = movie_detail.text.strip().split('\n')[-1].split('/')[0].strip()
country = movie_detail.text.strip().split('\n')[-1].split('/')[1].strip()
temp = movie_detail.text.strip().split('\n')
try:
director_description = temp[0].split('/')[0].strip().split('\xa0')[0].split(':')[1]
except IndexError:
director_description = ''
try:
leader = temp[0].split('/')[0].strip().split('\xa0')[-1].split(':')[1]
except IndexError:
leader = ''
assert title is not None
assert star is not None
assert leader is not None
assert years is not None
assert country is not None
assert director_description is not None
assert tags is not None
assert picture_url is not None
assert movie_id is not None
movies.append(Movie(title=title, description=description, star=star, leader=leader, years=years, country=country, director_description=director_description, tags=tags, image_link=picture_url,
id=movie_id
)
)
next_page = soup.find('link', {'rel': 'next'})
if next_page is not None and next_page.attrs.get('href'):
next_link = base_url + next_page.attrs['href']
else:
print('finished!')
next_link = None
return movies, next_link
def write_movies(movies):
print('write movies..')
with open('movies_2.csv', 'a+')as opener:
if opener.tell() == 0:
opener.writelines(','.join(['id', 'title ', 'image_link ', 'country ', 'years ', 'director_description', 'leader', 'star ', 'description', 'tags', '\n']))
for movie in movies:
opener.write(','.join([str(movie.id), movie.title, movie.image_link, movie.country, movie.years, movie.director_description, movie.leader, movie.star, movie.description, '/'.join(movie.tags), '\n']))
async def get_results(url, parser):
html = await fetch(url)
movies, next_link = await parser(html)
write_movies(movies)
return next_link
# results[url] = movie
async def handle_tasks(work_queue, parser):
while not work_queue.empty():
current_url = await work_queue.get()
try:
next_link = await get_results(current_url, parser)
print('put link:', next_link)
if next_link is not None:
work_queue.put_nowait(next_link)
except Exception as e:
logging.exception('Error for {}'.format(current_url), exc_info=True)
@timer
def envent_loop():
q = asyncio.Queue()
q.put_nowait(new_url)
loop = asyncio.get_event_loop()
tasks.append(handle_tasks(q, parse_list))
loop.run_until_complete(asyncio.wait(tasks))
envent_loop()