分享一个基于python爬虫的网络小说数据分析可视化系统(源码、调试、LW、开题、PPT)

💕💕作者:计算机源码社
💕💕个人简介:本人 八年开发经验,擅长Java、Python、PHP、.NET、Node.js、Android、微信小程序、爬虫、大数据、机器学习等,大家有这一块的问题可以一起交流!
💕💕学习资料、程序开发、技术解答、文档报告
💕💕如需要源码,可以扫取文章下方二维码联系咨询

💕💕Java项目
💕💕微信小程序项目
💕💕Android项目
💕💕Python项目
💕💕PHP项目
💕💕ASP.NET项目
💕💕Node.js项目
💕💕选题推荐

项目实现|基于Python爬虫的网络小说数据分析系统源码

1、选题背景

  近年来,随着互联网的普及和移动设备的广泛使用,网络文学特别是网络小说在中国蓬勃发展,成为了一种重要的文化现象和产业。作为中国最大的原创文学网站之一,"起点中文网"汇聚了海量的网络小说作品和用户数据。然而,这些数据的价值尚未被充分挖掘和利用。传统的人工分析方法已经无法满足对海量数据的深入分析需求。同时,随着大数据技术的发展,利用计算机技术对文学作品进行数据化分析已成为可能。在这样的背景下,开发一个基于Python的网络小说数据分析系统不仅能够满足学术研究的需求,还能为出版社、文学网站以及作者提供有价值的数据洞察,帮助他们更好地了解市场趋势和读者偏好。

2、研究目的和意义

  本系统的开发目的是构建一个全面、高效的网络小说数据分析平台。通过使用Scrapy爬虫技术,系统能够自动从"起点中文网"获取大量小说数据,包括但不限于小说名称、作者、分类、字数、点击量等信息。这些原始数据经过处理和清理后,将被存储到MySQL数据库中,形成结构化的数据资源。系统的核心目标是利用这些数据,通过Python强大的数据处理能力和Flask框架的Web开发优势,结合Echarts可视化技术,为用户提供直观、动态的数据分析结果。具体而言,系统旨在实现小说分类占比统计、作者作品数量统计、热门小说名称词云展示、小说数量趋势分析、优秀作者推荐等功能。此外,系统还将提供用户管理、小说数据管理和系统公告管理等功能,以满足不同用户群体的需求。
  本系统的开发具有重要的理论和实践意义。在理论层面,它为网络文学研究提供了一个数据驱动的新方法。通过对海量小说数据的定量分析,研究者可以发现网络文学创作的规律、主题演变趋势以及读者喜好的变化,从而深化对当代网络文学发展的理解。在实践层面,该系统可以为多个相关领域带来价值。对于出版社和文学网站,系统提供的数据分析结果可以辅助他们制定更精准的运营策略和内容规划。作家可以通过系统了解市场趋势,调整创作方向。读者则可以利用系统推荐功能,更容易发现感兴趣的作品。本系统的开发也是对Python、Flask、Scrapy等技术在大数据分析和可视化领域应用的一次有益探索,可为类似系统的开发提供参考。总的来说这个系统的开发将促进网络文学产业的健康发展,推动数据分析技术在人文领域的应用,具有广泛的社会和经济价值。

3、系统功能设计

基于Python的网络小说数据分析系统设计
1. 系统架构
系统采用前后端分离的架构,主要包括以下几个部分:
爬虫模块: 使用Scrapy框架
数据处理模块: 使用Python进行数据清洗和预处理
数据存储: MySQL数据库
后端API: Flask框架
前端展示: HTML/CSS/JavaScript, 使用ECharts进行数据可视化
用户界面: 管理员和普通用户两种角色

2. 功能模块设计
2.1 爬虫模块

使用Scrapy框架爬取"起点小说网"的小说数据
定期自动运行,更新数据库中的小说信息
爬取的数据包括:小说名称、作者、分类、简介、字数、评分等

2.2 数据处理模块
数据清洗:去除无效数据,处理缺失值
文本预处理:分词、去停用词等
数据格式化:统一数据格式,便于存储和分析

2.3 数据存储模块
使用MySQL数据库存储处理后的数据,主要包括以下表:
小说信息表
作者信息表
用户表
系统公告表

2.4 后端API模块
使用Flask框架开发RESTful API,主要功能包括:
用户认证和授权
小说数据的CRUD操作
数据分析和统计
系统公告管理

2.5 前端展示模块
使用HTML/CSS/JavaScript开发,主要页面包括:
登录/注册页面
数据大屏展示页面
小说数据管理页面
用户管理页面
系统公告管理页面

2.6 数据可视化模块
使用ECharts实现以下可视化内容:
小说分类占比饼图
小说名称词云图
作者作品数量柱状图
小说数量随时间变化折线图
小说作者推荐排行榜
小说总数统计仪表盘

3. 数据流程
Scrapy爬虫定期从"起点小说网"爬取最新数据
数据处理模块对爬取的数据进行清洗和预处理
处理后的数据存入MySQL数据库
Flask后端API从数据库读取数据,进行分析和统计
前端通过API请求数据,使用ECharts进行可视化展示

4、系统页面设计

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如需要源码,可以扫取文章下方二维码联系咨询

5、参考文献

[1]李文娟.Python数据可视化的优势——以《三国演义》为例[J].中国教育技术装备,2023,(19):61-65.
[2]王萌.基于知识图谱的文学叙事可视化研究[D].桂林电子科技大学,2023. DOI:10.27049/d.cnki.ggldc.2023.001484.
[3]周存.基于在线评论数据挖掘的图像小说读者需求分析[D].河北大学,2023. DOI:10.27103/d.cnki.ghebu.2023.002536.
[4]王双.基于短句序列小说文本复述生成技术研究[D].西北民族大学,2023. DOI:10.27408/d.cnki.gxmzc.2023.000264.
[5]王宏伟.章回体小说主题与人物关系的多维可视分析[D].燕山大学,2023. DOI:10.27440/d.cnki.gysdu.2023.002391.
[6]谢志明,陈静婵.基于数据分析下的整本书阅读教学策略创新性研究[J].电脑与信息技术,2022,30(04):72-74+84.DOI:10.19414/j.cnki.1005-1228.2022.04.024.
[7]王凯琪,兰全祥.网络小说信息爬取与管理系统的设计与实现[J].信息记录材料,2022,23(05):116-119.DOI:10.16009/j.cnki.cn13-1295/tq.2022.05.026.
[8]郭长玉.基于用户行为的小说个性化推荐系统的设计与实现[D].北京邮电大学,2022. DOI:10.26969/d.cnki.gbydu.2022.001599.
[9]罗小晰.基于内容分析法的社会化阅读中的用户批注研究[D].武汉大学,2021. DOI:10.27379/d.cnki.gwhdu.2021.000092.
[10]刘帅帅.复杂网络视角下唐传奇小说中的长安城市结构及演变探析[D].天津大学,2020. DOI:10.27356/d.cnki.gtjdu.2020.002339.
[11]姜崇.基于数据挖掘的网络小说价值预测分析[D].沈阳航空航天大学,2018.
[12]林钊生.基于混合推荐算法的网络小说推荐系统设计与实现[D].华南理工大学,2017.

6、核心代码

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

# 数据爬取文件

import scrapy
import pymysql
import pymssql
from ..items import WangluoxiaoshuoItem
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 WangluoxiaoshuoSpider(scrapy.Spider):
    name = 'wangluoxiaoshuoSpider'
    spiderUrl = 'https://www.qidian.com/finish/chanId21-page{}/'
    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, 'di2zvh33_wangluoxiaoshuo') == 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, 'di2zvh33_wangluoxiaoshuo') == 1:
                cursor.close()
                connect.close()
                self.temp_data()
                return
        list = response.css('ul[class="all-img-list cf"] li')
        for item in list:
            fields = WangluoxiaoshuoItem()

            if '(.*?)' in '''div.book-mid-info h2 a::text''':
                try:
                    fields["name"] = str( re.findall(r'''div.book-mid-info h2 a::text''', item.extract(), re.DOTALL)[0].strip())

                except:
                    pass
            else:
                try:
                    fields["name"] = str( self.remove_html(item.css('''div.book-mid-info h2 a::text''').extract_first()))

                except:
                    pass
            if '(.*?)' in '''div.book-img-box a img::attr(src)''':
                try:
                    fields["picture"] = str('https:'+ re.findall(r'''div.book-img-box a img::attr(src)''', item.extract(), re.DOTALL)[0].strip())

                except:
                    pass
            else:
                try:
                    fields["picture"] = str('https:'+ self.remove_html(item.css('''div.book-img-box a img::attr(src)''').extract_first()))

                except:
                    pass
            if '(.*?)' in '''a.go-sub-type::text''':
                try:
                    fields["fenlei"] = str( re.findall(r'''a.go-sub-type::text''', item.extract(), re.DOTALL)[0].strip())

                except:
                    pass
            else:
                try:
                    fields["fenlei"] = str( self.remove_html(item.css('''a.go-sub-type::text''').extract_first()))

                except:
                    pass
            if '(.*?)' in '''p.intro::text''':
                try:
                    fields["miaoshu"] = str( re.findall(r'''p.intro::text''', item.extract(), re.DOTALL)[0].strip())

                except:
                    pass
            else:
                try:
                    fields["miaoshu"] = str( self.remove_html(item.css('''p.intro::text''').extract_first()))

                except:
                    pass
            if '(.*?)' in '''div.book-img-box a::attr(href)''':
                try:
                    fields["xqdz"] = str('https:'+ re.findall(r'''div.book-img-box a::attr(href)''', item.extract(), re.DOTALL)[0].strip())

                except:
                    pass
            else:
                try:
                    fields["xqdz"] = str('https:'+ self.remove_html(item.css('''div.book-img-box a::attr(href)''').extract_first()))

                except:
                    pass
            detailUrlRule = item.css('div.book-img-box a::attr(href)').extract_first()
            if self.protocol in detailUrlRule or detailUrlRule.startswith('http'):
                pass
            elif detailUrlRule.startswith('//'):
                detailUrlRule = self.protocol + ':' + detailUrlRule
            elif detailUrlRule.startswith('/'):
                detailUrlRule = self.protocol + '://' + self.hostname + detailUrlRule
                fields["laiyuan"] = detailUrlRule
            else:
                detailUrlRule = self.protocol + '://' + self.hostname + '/' + detailUrlRule
            detailUrlRule ='https:'+ detailUrlRule 
            yield scrapy.Request(url=detailUrlRule, meta={'fields': fields},  callback=self.detail_parse, dont_filter=True)

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

            else:
                if 'author' != 'xiangqing' and 'author' != 'detail' and 'author' != 'pinglun' and 'author' != 'zuofa':
                    fields["author"] = str( self.remove_html(response.css('''span.author::text''').extract_first()).replace('作者:',''))

                else:
                    try:
                        fields["author"] = str( emoji.demojize(response.css('''span.author::text''').extract_first()).replace('作者:',''))

                    except:
                        pass
        except:
            pass
        try:
            if '(.*?)' in '''p.count em::text''':
                fields["zishu"] = str( re.findall(r'''p.count em::text''', response.text, re.S)[0].strip())

            else:
                if 'zishu' != 'xiangqing' and 'zishu' != 'detail' and 'zishu' != 'pinglun' and 'zishu' != 'zuofa':
                    fields["zishu"] = str( self.remove_html(response.css('''p.count em::text''').extract_first()))

                else:
                    try:
                        fields["zishu"] = str( emoji.demojize(response.css('''p.count em::text''').extract_first()))

                    except:
                        pass
        except:
            pass
        try:
            if '(.*?)' in '''p.count em:nth-child(3)::text''':
                fields["zongtuijian"] = str( re.findall(r'''p.count em:nth-child(3)::text''', response.text, re.S)[0].strip())

            else:
                if 'zongtuijian' != 'xiangqing' and 'zongtuijian' != 'detail' and 'zongtuijian' != 'pinglun' and 'zongtuijian' != 'zuofa':
                    fields["zongtuijian"] = str( self.remove_html(response.css('''p.count em:nth-child(3)::text''').extract_first()))

                else:
                    try:
                        fields["zongtuijian"] = str( emoji.demojize(response.css('''p.count em:nth-child(3)::text''').extract_first()))

                    except:
                        pass
        except:
            pass
        try:
            if '(.*?)' in '''p.count em:nth-child(5)::text''':
                fields["zhoutuijian"] = int( re.findall(r'''p.count em:nth-child(5)::text''', response.text, re.S)[0].strip())
            else:
                if 'zhoutuijian' != 'xiangqing' and 'zhoutuijian' != 'detail' and 'zhoutuijian' != 'pinglun' and 'zhoutuijian' != 'zuofa':
                    fields["zhoutuijian"] = int( self.remove_html(response.css('''p.count em:nth-child(5)::text''').extract_first()))
                else:
                    try:
                        fields["zhoutuijian"] = int( emoji.demojize(response.css('''p.count em:nth-child(5)::text''').extract_first()))
                    except:
                        pass
        except:
            pass
        try:
            if '(.*?)' in '''div.work-number em.color-font-card::text''':
                fields["worknum"] = int( re.findall(r'''div.work-number em.color-font-card::text''', response.text, re.S)[0].strip())
            else:
                if 'worknum' != 'xiangqing' and 'worknum' != 'detail' and 'worknum' != 'pinglun' and 'worknum' != 'zuofa':
                    fields["worknum"] = int( self.remove_html(response.css('''div.work-number em.color-font-card::text''').extract_first()))
                else:
                    try:
                        fields["worknum"] = int( emoji.demojize(response.css('''div.work-number em.color-font-card::text''').extract_first()))
                    except:
                        pass
        except:
            pass
        try:
            if '(.*?)' in '''div.write em.color-font-card::text''':
                fields["writenum"] = str( re.findall(r'''div.write em.color-font-card::text''', response.text, re.S)[0].strip())

            else:
                if 'writenum' != 'xiangqing' and 'writenum' != 'detail' and 'writenum' != 'pinglun' and 'writenum' != 'zuofa':
                    fields["writenum"] = str( self.remove_html(response.css('''div.write em.color-font-card::text''').extract_first()))

                else:
                    try:
                        fields["writenum"] = str( emoji.demojize(response.css('''div.write em.color-font-card::text''').extract_first()))

                    except:
                        pass
        except:
            pass
        try:
            if '(.*?)' in '''div.days em.color-font-card::text''':
                fields["days"] = int( re.findall(r'''div.days em.color-font-card::text''', response.text, re.S)[0].strip())
            else:
                if 'days' != 'xiangqing' and 'days' != 'detail' and 'days' != 'pinglun' and 'days' != 'zuofa':
                    fields["days"] = int( self.remove_html(response.css('''div.days em.color-font-card::text''').extract_first()))
                else:
                    try:
                        fields["days"] = int( emoji.demojize(response.css('''div.days em.color-font-card::text''').extract_first()))
                    except:
                        pass
        except:
            pass
        return fields

    # 数据清洗
    def pandas_filter(self):
        engine = create_engine('mysql+pymysql://root:123456@localhost/spiderdi2zvh33?charset=UTF8MB4')
        df = pd.read_sql('select * from wangluoxiaoshuo 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 `wangluoxiaoshuo`(
                id
                ,name
                ,picture
                ,author
                ,fenlei
                ,miaoshu
                ,zishu
                ,zongtuijian
                ,zhoutuijian
                ,worknum
                ,writenum
                ,days
                ,xqdz
            )
            select
                id
                ,name
                ,picture
                ,author
                ,fenlei
                ,miaoshu
                ,zishu
                ,zongtuijian
                ,zhoutuijian
                ,worknum
                ,writenum
                ,days
                ,xqdz
            from `di2zvh33_wangluoxiaoshuo`
            where(not exists (select
                id
                ,name
                ,picture
                ,author
                ,fenlei
                ,miaoshu
                ,zishu
                ,zongtuijian
                ,zhoutuijian
                ,worknum
                ,writenum
                ,days
                ,xqdz
            from `wangluoxiaoshuo` where
                `wangluoxiaoshuo`.id=`di2zvh33_wangluoxiaoshuo`.id
            ))
            order by rand()
            limit 50;
        '''

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

💕💕作者:计算机源码社
💕💕个人简介:本人 八年开发经验,擅长Java、Python、PHP、.NET、Node.js、Android、微信小程序、爬虫、大数据、机器学习等,大家有这一块的问题可以一起交流!
💕💕学习资料、程序开发、技术解答、文档报告
💕💕如需要源码,可以扫取文章下方二维码联系咨询

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值