基于Python+Hadoop的豆瓣图书推荐可视化系统设计与实现

💗博主介绍:✌全网粉丝15W+,CSDN全栈领域优质创作者,博客之星、掘金/知乎/b站/华为云/阿里云等平台优质作者、专注于Java、小程序/APP、python、大数据等技术领域和毕业项目实战,以及程序定制化开发、文档编写、答疑辅导等。
👇🏻 精彩专栏 推荐订阅👇🏻
计算机毕业设计精品项目案例(持续更新)
🌟文末获取源码+数据库+文档🌟
感兴趣的可以先收藏起来,还有大家在毕设选题,项目以及论文编写等相关问题都可以和学长沟通,希望帮助更多的人

一.前言

在这里插入图片描述

随着信息技术的飞速发展,特别是互联网和移动通信技术的普及,数字化阅读逐渐成为人们获取知识和信息的重要方式。在这样的背景下,电子图书以其便捷性和丰富性受到了广泛欢迎。随着电子图书市场的不断扩大,书籍的种类和数量也在急剧增加,这为用户挑选书籍带来了挑战。为了解决信息过载的问题,个性化推荐系统应运而生,并逐渐成为在线阅读平台不可或缺的一部分。基于用户历史行为数据进行推荐的协同过滤算法尤为流行。处理庞大的用户群体和海量的图书数据需要强大的计算能力,传统的单机计算模式已无法满足需求。Hadoop作为一个开源的分布式计算平台,以其高容错性、高扩展性和对大数据处理的优秀能力,成为大数据分析的首选工具。因此,利用Hadoop来构建电子图书推荐系统,不仅可以有效处理和分析大规模数据集,提升推荐质量,还能保证系统的可扩展性和稳定性。
基于Hadoop的豆瓣电子图书推荐系统的研究与实现能够为用户提供更加精准和个性化的阅读推荐,从而优化用户体验,提高用户满意度和平台黏性。通过分析用户的历史阅读行为和偏好,系统可以发现用户的阅读模式,进而推荐更符合个人兴趣的书籍,帮助用户节省筛选时间,增强阅读效率。对于电子图书平台来说,一个高效的推荐系统可以促进更多优质内容的分发,增加用户流量和书籍销量,从而带动平台的经济效益。该系统的建立还有助于推动数据挖掘和机器学习技术在实际应用中的发展,为相关领域提供宝贵的实践经验和研究成果。最后,随着数据处理技术的不断进步,该研究还可以为未来电子图书推荐系统的改进提供理论基础和技术支持,具有长远的研究和应用价值。


二.技术环境

开发语言:Python
python框架:Django
数据库:mysql 5.7或更高版本
数据库工具:Navicat11
爬虫框架:Scrapy
大数据框架:Hadoop
开发软件:Pycharm
前端框架:vue.js


三.功能设计

本课题要求实现优质的豆瓣电子图书推荐系统,就一定要包含有前台页面和后端数据库、服务器相联系,从而实现系统的功能运转。系统分为用户模块和管理员模块两部分;
管理员包括登录、个人信息修改,对用户管理、豆瓣高分管理、论坛交流、系统管理等功能进行查询,修改和删除等。管理员用例如下:
在这里插入图片描述

用户包括注册登录、个人信息修改,对我的发布、我的收藏等进行管理;用户用例如下:

在这里插入图片描述

系统功能结构图是系统设计阶段,系统功能结构图只是这个阶段一个基础,整个系统的架构决定了系统的整体模式,是系统的根据。本系统的整个设计结构如图所示。

在这里插入图片描述

四.数据设计

关系型数据库是目前使用人数最多的数据库,既是面向对象系统设计,所以它的数据库设计主要是面向对象的。现在主要考虑如何对类进行持久化操作,即如何将对象类映射到关系数据库的二维表。目前可以采用数据库建模工具来实现。
然后根据功能需求来对本系统的e-r图实现分解来得到几种实体—关系模型,以下为部分实体—关系模型。在系统中将对“用户、豆瓣高分、通知公告、系统简介”等作为实体,它们的局部E-R如图所示:

在这里插入图片描述

五.部分效果展示

前台用户功能实现效果

当人们打开系统的网址后,首先看到的就是首页界面。在这里,人们能够看到系统的导航条,通过导航条导航进入各功能展示页面进行操作。系统首页界面如图所示:
在这里插入图片描述

在注册流程中,用户在Vue前端填写必要信息(如用户名、密码等)并提交。前端将这些信息通过HTTP请求发送到Java后端。后端处理这些信息,检查用户名是否唯一,并将新用户数据存入MySQL数据库。完成后,后端向前端发送注册成功的确认,前端随后通知用户完成注册。这个过程实现了新用户的数据收集、验证和存储。注册页面如图所示:

在这里插入图片描述

豆瓣高分:在豆瓣高分页面的输入栏中输入书名、作者、出版社和标签进行查询,可以查看到豆瓣高分详细信息,并进行评论或收藏操作;豆瓣高分页面如图所示:
在这里插入图片描述

个人中心:在个人中心页面可以对个人中心、修改密码、我的发布、我的收藏等进行详细操作;如图所示:
在这里插入图片描述

后台管理员功能实现效果

在登录流程中,用户首先在Vue前端界面输入用户名和密码。这些信息通过HTTP请求发送到Java后端。后端接收请求,通过与MySQL数据库交互验证用户凭证。如果认证成功,后端会返回给前端,允许用户访问系统。这个过程涵盖了从用户输入到系统验证和响应的全过程。如图所示。
在这里插入图片描述

管理员进入主页面,主要功能包括对用户管理、豆瓣高分管理、论坛交流、系统管理、个人中心等进行操作。管理员主页面如图所示:
在这里插入图片描述

豆瓣高分管理功能在视图层(view层)进行交互,比如点击“查询、添加、删除或爬取数据”按钮或填写豆瓣高分信息表单。这些豆瓣高分表单动作被视图层捕获并作为请求发送给相应的控制器层(controller层)。控制器接收到这些请求后,调用服务层(service层)以执行相关的业务逻辑,例如验证输入数据的有效性和与数据库的交互。服务层处理完这些逻辑后,进一步与数据访问对象层(DAO层)交互,后者负责具体的数据操作如查看、修改、查看评论或删除豆瓣高分信息,并将操作结果返回给控制器。最终,控制器根据这些结果更新视图层,以便豆瓣高分功能可以看到最新的信息或相应的操作反馈。如图5-8所示:

在这里插入图片描述

管理员进行爬取数据后,点击主页面右上角的看板,可以查看到系统简介、书名、作者统计、价格统计、出版社、评分统计、豆瓣高分总数、豆瓣高分信息等实时的分析图进行可视化管理;如图所示:
在这里插入图片描述

论坛交流功能在视图层(view层)进行交互,比如点击“查询或删除”按钮或填写论坛交流信息表单。这些论坛交流表单动作被视图层捕获并作为请求发送给相应的控制器层(controller层)。控制器接收到这些请求后,调用服务层(service层)以执行相关的业务逻辑,例如验证输入数据的有效性和与数据库的交互。服务层处理完这些逻辑后,进一步与数据访问对象层(DAO层)交互,后者负责具体的数据操作如查看、查看评论或删除论坛交流信息,并将操作结果返回给控制器。最终,控制器根据这些结果更新视图层,以便论坛交流功能可以看到最新的信息或相应的操作反馈。如图所示:

在这里插入图片描述

六.部分功能代码

# # -*- coding: utf-8 -*-

# 数据爬取文件

# # -*- coding: utf-8 -*-

# 数据爬取文件

import scrapy
import pymysql
import pymssql
from ..items import DianzitushuItem
import time
from datetime import datetime,timedelta
import datetime as formattime
import re
import random
import platform
import json
import os
import urllib
from urllib.parse import urlparse
import requests
import emoji
import numpy as np
import pandas as pd
from sqlalchemy import create_engine
from selenium.webdriver import ChromeOptions, ActionChains
from scrapy.http import TextResponse
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
# 电子图书
class DianzitushuSpider(scrapy.Spider):
    name = 'dianzitushuSpider'
    spiderUrl = 'https://read.douban.com/j/kind/'
    start_urls = spiderUrl.split(";")
    protocol = ''
    hostname = ''
    realtime = False


    def __init__(self,realtime=False,*args, **kwargs):
        super().__init__(*args, **kwargs)
        self.realtime = realtime=='true'

    def start_requests(self):

        plat = platform.system().lower()
        if not self.realtime and (plat == 'linux' or plat == 'windows'):
            connect = self.db_connect()
            cursor = connect.cursor()
            if self.table_exists(cursor, '0n4b129m_dianzitushu') == 1:
                cursor.close()
                connect.close()
                self.temp_data()
                return
        pageNum = 1 + 1

        for url in self.start_urls:
            if '{}' in url:
                for page in range(1, pageNum):

                    next_link = url.format(page)
                    yield scrapy.Request(
                        url=next_link,
                        callback=self.parse
                    )
            else:
                yield scrapy.Request(
                    url=url,
                    callback=self.parse
                )

    # 列表解析
    def parse(self, response):
        _url = urlparse(self.spiderUrl)
        self.protocol = _url.scheme
        self.hostname = _url.netloc
        plat = platform.system().lower()
        if not self.realtime and (plat == 'linux' or plat == 'windows'):
            connect = self.db_connect()
            cursor = connect.cursor()
            if self.table_exists(cursor, '0n4b129m_dianzitushu') == 1:
                cursor.close()
                connect.close()
                self.temp_data()
                return
        data = json.loads(response.body)
        try:
            list = data["list"]
        except:
            pass
        for item in list:
            fields = DianzitushuItem()


            try:
                fields["title"] = emoji.demojize(self.remove_html(str( item["title"] )))

            except:
                pass
            try:
                fields["picture"] = emoji.demojize(self.remove_html(str( item["cover"] )))

            except:
                pass
            try:
                fields["salesprice"] = float( item["salesPrice"]/100)
            except:
                pass
            try:
                fields["wordcount"] = int( item["wordCount"])
            except:
                pass
            try:
                fields["author"] = emoji.demojize(self.remove_html(str(','.join(str(i['name']) for i in  item["author"]) )))

            except:
                pass
            try:
                fields["biaoqian"] = emoji.demojize(self.remove_html(str( item.get("biaoqian", "小说") )))

            except:
                pass
            try:
                fields["detailurl"] = emoji.demojize(self.remove_html(str('https://read.douban.com'+ item["url"] )))

            except:
                pass
            detailUrlRule = item["url"]

            if '["url"]'.startswith('http'):
                if '{0}' in '["url"]':
                    detailQueryCondition = []
                    detailUrlRule = '["url"]'
                    i = 0
                    while i < len(detailQueryCondition):
                        detailUrlRule = detailUrlRule.replace('{' + str(i) + '}', str(detailQueryCondition[i]))
                        i += 1
            else:
                detailUrlRule =item["url"]

            detailUrlRule ='https://read.douban.com'+ detailUrlRule

            if detailUrlRule.startswith('http') or self.hostname in detailUrlRule:
                pass
            else:
                detailUrlRule = self.protocol + '://' + self.hostname + detailUrlRule
                fields["laiyuan"] = detailUrlRule
            yield scrapy.Request(url=detailUrlRule, meta={'fields': fields}, callback=self.detail_parse)

    # 详情解析
    def detail_parse(self, response):
        fields = response.meta['fields']
        try:
            if '(.*?)' in '''span[itemprop="genre"]::text''':
                fields["genre"] = str( re.findall(r'''span[itemprop="genre"]::text''', response.text, re.S)[0].strip())

            else:
                if 'genre' != 'xiangqing' and 'genre' != 'detail' and 'genre' != 'pinglun' and 'genre' != 'zuofa':
                    fields["genre"] = str( self.remove_html(response.css('''span[itemprop="genre"]::text''').extract_first()))

                else:
                    try:
                        fields["genre"] = str( emoji.demojize(response.css('''span[itemprop="genre"]::text''').extract_first()))

                    except:
                        pass
        except:
            pass
        try:
            fields["chubanshe"] = str( response.xpath('''//span[text()="出版社"]/../span[@class="labeled-text"]/span[1]/text()''').extract()[0].strip())

        except:
            pass
        try:
            fields["cbsj"] = str( response.xpath('''//span[text()="出版社"]/../span[@class="labeled-text"]/span[2]/text()''').extract()[0].strip())

        except:
            pass
        try:
            if '(.*?)' in '''a[itemprop="provider"]::text''':
                fields["provider"] = str( re.findall(r'''a[itemprop="provider"]::text''', response.text, re.S)[0].strip())

            else:
                if 'provider' != 'xiangqing' and 'provider' != 'detail' and 'provider' != 'pinglun' and 'provider' != 'zuofa':
                    fields["provider"] = str( self.remove_html(response.css('''a[itemprop="provider"]::text''').extract_first()))

                else:
                    try:
                        fields["provider"] = str( emoji.demojize(response.css('''a[itemprop="provider"]::text''').extract_first()))

                    except:
                        pass
        except:
            pass
        try:
            if '(.*?)' in '''span.score::text''':
                fields["score"] = float( re.findall(r'''span.score::text''', response.text, re.S)[0].strip())
            else:
                if 'score' != 'xiangqing' and 'score' != 'detail' and 'score' != 'pinglun' and 'score' != 'zuofa':
                    fields["score"] = float( self.remove_html(response.css('''span.score::text''').extract_first()))
                else:
                    try:
                        fields["score"] = float( emoji.demojize(response.css('''span.score::text''').extract_first()))
                    except:
                        pass
        except:
            pass
        try:
            if '(.*?)' in '''span.amount::text''':
                fields["pingjiashu"] = int( re.findall(r'''span.amount::text''', response.text, re.S)[0].strip().replace('评价',''))
            else:
                if 'pingjiashu' != 'xiangqing' and 'pingjiashu' != 'detail' and 'pingjiashu' != 'pinglun' and 'pingjiashu' != 'zuofa':
                    fields["pingjiashu"] = int( self.remove_html(response.css('''span.amount::text''').extract_first()).replace('评价',''))
                else:
                    try:
                        fields["pingjiashu"] = int( emoji.demojize(response.css('''span.amount::text''').extract_first()).replace('评价',''))
                    except:
                        pass
        except:
            pass
        return fields

    # 数据清洗
    def pandas_filter(self):
        engine = create_engine('mysql+pymysql://root:123456@localhost/spider0n4b129m?charset=UTF8MB4')
        df = pd.read_sql('select * from dianzitushu limit 50', con = engine)

        # 重复数据过滤
        df.duplicated()
        df.drop_duplicates()

        #空数据过滤
        df.isnull()
        df.dropna()

        # 填充空数据
        df.fillna(value = '暂无')

        # 异常值过滤

        # 滤出 大于800 和 小于 100 的
        a = np.random.randint(0, 1000, size = 200)
        cond = (a<=800) & (a>=100)
        a[cond]

        # 过滤正态分布的异常值
        b = np.random.randn(100000)
        # 3σ过滤异常值,σ即是标准差
        cond = np.abs(b) > 3 * 1
        b[cond]

        # 正态分布数据
        df2 = pd.DataFrame(data = np.random.randn(10000,3))
        # 3σ过滤异常值,σ即是标准差
        cond = (df2 > 3*df2.std()).any(axis = 1)
        # 不满⾜条件的⾏索引
        index = df2[cond].index
        # 根据⾏索引,进⾏数据删除
        df2.drop(labels=index,axis = 0)

    # 去除多余html标签
    def remove_html(self, html):
        if html == None:
            return ''
        pattern = re.compile(r'<[^>]+>', re.S)
        return pattern.sub('', html).strip()

    # 数据库连接
    def db_connect(self):
        type = self.settings.get('TYPE', 'mysql')
        host = self.settings.get('HOST', 'localhost')
        port = int(self.settings.get('PORT', 3306))
        user = self.settings.get('USER', 'root')
        password = self.settings.get('PASSWORD', '123456')

        try:
            database = self.databaseName
        except:
            database = self.settings.get('DATABASE', '')

        if type == 'mysql':
            connect = pymysql.connect(host=host, port=port, db=database, user=user, passwd=password, charset='utf8')
        else:
            connect = pymssql.connect(host=host, user=user, password=password, database=database)
        return connect

    # 断表是否存在
    def table_exists(self, cursor, table_name):
        cursor.execute("show tables;")
        tables = [cursor.fetchall()]
        table_list = re.findall('(\'.*?\')',str(tables))
        table_list = [re.sub("'",'',each) for each in table_list]

        if table_name in table_list:
            return 1
        else:
            return 0

    # 数据缓存源
    def temp_data(self):

        connect = self.db_connect()
        cursor = connect.cursor()
        sql = '''
            insert into `dianzitushu`(
                id
                ,title
                ,picture
                ,salesprice
                ,wordcount
                ,author
                ,biaoqian
                ,detailurl
                ,genre
                ,chubanshe
                ,cbsj
                ,provider
                ,score
                ,pingjiashu
            )
            select
                id
                ,title
                ,picture
                ,salesprice
                ,wordcount
                ,author
                ,biaoqian
                ,detailurl
                ,genre
                ,chubanshe
                ,cbsj
                ,provider
                ,score
                ,pingjiashu
            from `0n4b129m_dianzitushu`
            where(not exists (select
                id
                ,title
                ,picture
                ,salesprice
                ,wordcount
                ,author
                ,biaoqian
                ,detailurl
                ,genre
                ,chubanshe
                ,cbsj
                ,provider
                ,score
                ,pingjiashu
            from `dianzitushu` where
                `dianzitushu`.id=`0n4b129m_dianzitushu`.id
            ))
            order by rand()
            limit 50;
        '''

        cursor.execute(sql)
        connect.commit()
        connect.close()



源码及文档获取

文章下方名片联系我即可~
大家点赞、收藏、关注、评论啦 、查看👇🏻获取联系方式👇🏻
精彩专栏推荐订阅:在下方专栏👇🏻

最新计算机毕业设计选题篇-选题推荐
小程序毕业设计精品项目案例-200套
Java毕业设计精品项目案例-200套
Python毕业设计精品项目案例-200套
大数据毕业设计精品项目案例-200套
💟💟如果大家有任何疑虑,欢迎在下方位置详细交流。

  • 21
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一点毕设

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值