ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Python爬取起点中文网Top500小说:数据提取、存储与可视化实战

Python爬取起点中文网Top500小说:数据提取、存储与可视化实战 每年到了毕业设计选题的季节都有不少同学来问我大数据方向的题目怎么选选纯算法怕做不出来选纯Web开发又觉得不够“大数据”。如果你也在纠结这种事那“基于Python的中文起点网Top500小说数据提取”这个方向我觉得很值得认真聊一聊。它不光是爬个网页那么简单而是把网络爬虫、数据清洗、MySQL存储、数据分析与可视化这几个关键环节全部串起来了正好踩在大数据毕设的经典套路上。更务实的一点是起点中文网的榜单数据是公开可访问的字段丰富、结构化程度高反爬强度也适中非常适合用来做教学级别的实战项目。这篇文章会从选题价值说起一路讲到技术选型、环境搭建、爬虫实现、MySQL表设计、增量更新、可视化出图最后把我在实际调试中踩过的坑和答辩时可能被问到的问题都整理出来。不管你是准备照这个方向做毕设还是纯粹想练手爬虫和数据存储这篇东西都能给你省下不少时间。1. 这个选题为什么值得做不只是“爬个网页”那么简单1.1 大数据毕设的真正评分点在哪很多同学对“大数据毕业设计”有个误解觉得一定要上Hadoop、Spark、Flink这种分布式框架才算大数据。其实本科阶段的毕设老师真正看重的是你能不能完成一个完整的数据处理闭环数据从哪来、怎么采集、怎么存储、怎么清洗、怎么分析、怎么展示。你把这条链路跑通了再用一两个像样的可视化图表把结果呈现出来就已经是一个合格的大数据项目了。“起点中文网Top500小说数据提取”这个题目天然就覆盖了上述所有环节。你要写爬虫去获取榜单数据这是数据采集你要处理缺失值、重复值、异常值这是数据清洗你要把清洗后的数据写进MySQL这是数据存储你要统计分类占比、字数分布、推荐票排行这是数据分析你还要用图表把结论画出来这是数据可视化。一个题目把五件事全干了这在毕设选题里是很高效的。另外从答辩的角度看这个题目也有天然优势。评委老师不需要你解释业务背景起点中文网大家都听说过网文行业的基本逻辑也容易理解。你花三分钟把“我采集了什么数据、怎么处理的、得出了什么结论”讲清楚比讲一个复杂的算法模型要轻松得多也更容易让老师听懂你在做什么。1.2 为什么选起点Top500而不是其他爬虫目标选爬虫目标网站最怕的就是两种情况一种是网站反爬太强你三天都搞不定一个列表页另一种是数据太零散采集下来根本没法分析。起点中文网的排行榜恰恰避开了这两个极端。首先起点榜单页的服务端渲染程度相对较高排行榜这种页面主要数据都在HTML源码里可以直接提取不需要像很多电商网站那样去逆向分析接口签名。其次榜单数据本身就包含了书名、作者、分类、字数、点击量、推荐票、月票等多个字段这些字段拼在一起就能做很多维度的分析。最后Top500这个样本量也合适——比Top10丰富得多又不至于像全站几百万本书那样要搞分布式爬虫单机跑完全没问题。当然我这里要强调一下合规性。爬虫项目用于学习、研究、毕业设计是完全没有问题的但要注意控制请求频率不要给目标服务器造成压力也不要将采集到的数据用于商业用途。你在设计爬虫的时候就应该主动加上请求间隔、User-Agent伪装这些基本操作这既是技术上的要求也是做项目的基本素养。2. 技术栈选型与环境准备2.1 为什么是Pythonrequests组合而不是Scrapy一提到Python爬虫很多人第一反应就是Scrapy框架。Scrapy确实强大支持异步并发、分布式扩展、中间件机制但说实话在毕业设计这个场景下Scrapy的学习成本和使用复杂度反而是个负担。你需要理解框架的运行机制、配置各种中间件、调试Scrapy shell光是报错信息就能让不少新手崩溃半年。我个人更推荐用“requests BeautifulSoup pandas”的组合原因很简单它足够直观每一行代码你都知道在干什么。requests负责发HTTP请求BeautifulSoup负责解析HTMLpandas负责做数据清洗和结构化管理这三板斧组合起来几乎能处理90%的基础爬虫场景。等你把这一套逻辑吃透了再去看Scrapy你会发现它只是把这套流程封装成了框架而已理解起来会轻松很多。当然了这只是入门阶段的建议。如果你的毕设题目要求必须用Scrapy或者你想在简历上写“熟悉Scrapy分布式爬虫”那就另当别论。但从完成度和稳健性的角度讲requests组合在本科毕设里绝对够用而且出问题时你更容易定位到具体原因。2.2 MySQL在项目里的定位与表的初步构思MySQL在这个项目里不是花架子它承担着数据持久化和后续分析基础的双重角色。可能有人会问爬下来的数据存成CSV不就行了吗为什么非要MySQL这个问题的答案在毕设答辩里很重要。CSV存数据在数据量小的时候没问题但如果你要按分类筛选、按字数排序、跨时间比较榜单变化CSV的尴尬就出来了——每次都要全量读入内存再用pandas过滤代码繁琐不说效率也很低。MySQL作为一个成熟的关系型数据库用一条SQL就能完成上述操作而且你还能借助索引加快查询速度。更重要的是在“大数据”这个主题下MySQL作为数据仓库的角色是标准配置项目里有没有数据库设计直接决定了这个毕设的上限。在设计表结构的时候我建议至少分三张表小说基本信息表、榜单快照表、分类维表。小说基本信息表存每本书相对固定的属性比如书名、作者、分类、简介榜单快照表存每次抓取的排名和票数数据带一个抓取时间的字段这样你就能分析一本书在几天内排名的变化趋势分类维表可以单独维护也可以直接用冗余字段替代主要看你后续做分析的复杂度。这个设计在后面第4章我会给出更详细的建表语句。2.3 环境搭建的实操细节Python环境和MySQL环境的安装很多人觉得是小事但我在帮人调试的时候发现十个报错里有七个是环境问题。先说Python我建议直接用Anaconda来管理环境因为它自带conda命令、spyder、jupyter这些工具而且创建虚拟环境很方便。你不要太纠结版本用3.8以上就行但要注意requests、beautifulsoup4、pandas、pymysql这些库一定要装到你自己创建的项目环境里而不是全局装完就完事。再说MySQLWindows环境建议装MySQL 8.0版本安装时选Server only就够了一路默认配置就行。装完之后一定要记得给root用户设置密码很多教程里有“无密码登录”的操作但那是开发环境的临时做法毕设项目里最好规规矩矩设置好。安装完之后你需要装一个图形化管理工具免费好用的就是MySQL WorkbenchNavicat虽然界面更友好但收费如果你不想折腾Workbench完全够用。连接MySQL还需要一个驱动库Python里最常用的是pymysql直接pip安装即可。这里有个小的注意事项pymysql连接数据库时需要手动指定charsetutf8mb4否则中文数据在写入和读取的时候很容易出现乱码。这个细节我当年踩过坑后面在第6章的排查清单里会再提到。3. 数据提取的详细设计与实现3.1 网站结构分析与字段确认动手写代码之前先把目标网站的结构摸清楚。打开起点中文网的排行榜页面用浏览器的F12开发者工具查看一下HTML结构。建议你看看两个类型的页面榜单列表页和书籍详情页。榜单列表页是你数据的主要来源它通常会在一个ul或div容器里排列所有上榜书籍每一本书对应一块独立的区域里面包含排名、书名、作者、分类、简介、最近更新、字数等数据。列表页的好处是“一页顶十页”你只要遍历榜单翻页就能拿到几百本书的核心数据。书籍详情页则提供了更多维度的信息比如总点击、总推荐、月票数量、评论数等。做毕设的时候建议列表页和详情页都采集一下列表页用于构建基础数据详情页用于补充扩展字段。但要注意每抓一本详情页就得多发一次请求如果你要抓500本书反爬压力会成倍增加所以一定要控制频率别猛跑。字段确认方面我建议你采集这些书名、作者、分类、排行榜排名、总字数、总点击、总推荐、月票。如果你还想做更深入的分析可以再加一本书的简介、上架时间、最近更新时间这些字段。但字段不是越多越好你需要思考每个字段在后续分析中有什么用那些采集了但用不上的字段就是在浪费代码量。3.2 请求层请求头、会话与延时扰动爬虫写得好不好请求层的处理是关键。很多新手的代码被反爬拦截根本原因就是请求头太干净一看就是程序在访问。User-Agent是必须伪装的字段你随便在网上找一个桌面浏览器真实的UA字符串直接复制进去就行。Referer字段也要注意有些站点会校验这个字段你可以在爬取页面时把Referer设置为排行榜首页的URL模仿真实用户的访问路径。另外我建议用requests.Session()来维持会话这样能保持cookies的连贯性避免被服务器识别为多次断开重连的异常流量。延时扰动是很多新手会忽略的点。有些同学代码写完了循环一跑发现跟风一样快几百个请求三秒钟就发完了结果跑了不到二十条就被封了IP。正确的做法是每次请求之间随机sleep比如time.sleep(random.uniform(1, 3))让请求间隔在一个合理范围内分布。对于访问频率的控制我建议宁可慢一点也不要贪快毕设又不是抢票系统稳定比速度重要。3.3 解析层BeautifulSoup和XPath的取舍HTML解析库我用BeautifulSoup比较多因为它的API对新手很友好而且常见的选择器写法在网上一搜一大把。BeautifulSoup里有两种主要定位方式一种是find/find_all通过标签名和属性来查找另一种是select方法用CSS选择器来定位。我个人更喜欢用select因为CSS选择器的表达能力更强一条语句就能定位到很深的嵌套标签。还有一种方案是lxml配合XPathXPath的定位能力比CSS选择器更灵活比如“获取当前节点下的第三个p标签”这种操作用XPath很容易用CSS就麻烦。但XPath的语法需要额外学习而且如果页面结构调整XPath的容错率往往比CSS选择器差一些。对毕设项目来说我推荐用BeautifulSoup的select方法就够了遇到复杂问题再用XPath补充不必一开始就完全押注在某一种方案上。解析代码的写法示例如下注意这是一个通用结构的示范实际网站的CSS选择器需要你按F12检查后调整from bs4 import BeautifulSoup def parse_rank_page(html): soup BeautifulSoup(html, html.parser) books [] for item in soup.select(.rank-list li): rank item.select_one(.rank-num).text.strip() title item.select_one(.book-mid-info h2 a).text.strip() author item.select_one(.book-mid-info .author .name).text.strip() category item.select_one(.book-mid-info .author a).text.strip() words item.select_one(.book-info .total-word).text.strip() books.append({ rank: int(rank), title: title, author: author, category: category, words: words }) return books这段代码里的选择器是示意性的因为不同时期的起点页面结构会变化。你要做的是按类似的思路自己去看页面源码里的实际class名和标签层级写出来自己的一版。这也是爬虫项目里最需要花时间和耐心去调试的部分。3.4 去重与更新策略如果你只跑一次爬虫去重的问题还不明显。但毕设项目通常会连续运行几天可能每天抓一次榜单这时数据去重和更新策略就变得重要了。最简单的去重策略是在MySQL表里给“书名作者”加唯一索引然后使用INSERT ... ON DUPLICATE KEY UPDATE语句这样每次抓取时已存在的小说会更新最新数据新上榜的小说会插入新记录。这是最实用、代码量也最少的方案。增量更新的意义在于你能够在答辩时展示“连续采集了一周数据后某本书的排名如何变化”这种时间序列分析。如果你每次抓取都全量覆盖旧数据那这种分析就完全做不了。所以表设计上要预留crawl_time字段每次插入或更新时记录当前时间这样后续做趋势分析就顺手了。4. MySQL表设计与数据持久化4.1 表结构设计从业务角度出发先来说第一张表小说基本信息表。它存储小说的静态属性比如书名、作者、分类、简介、总字数等。这张表的唯一键应该是“书名作者”因为起点上理论上不存在两个同名作者写两本同名书的情况用这个组合做唯一标识是合理的。再来说第二张表榜单快照表。它的作用是记录每一次爬取时每本书的实时数据包括排名、点击量、推荐量、月票以及抓取时间。这张表不设唯一键因为它本质上是一张流水表同一天多次抓取是可以存在多条记录的。分析的时候你可以按时间字段去筛选同一个批次的数据进行对比。如果你还想要第三张维度表比如“小说分类表”也可以建一张但在毕设这个体量下直接用冗余字段存储分类名就足够了不必额外做规范化调整。你要知道过度设计在毕设里同样是问题简洁、可解释才是第一位的。下面是简化版的建表SQL你可以根据自己的需求调整字段CREATE DATABASE IF NOT EXISTS qidian_db DEFAULT CHARACTER SET utf8mb4; USE qidian_db; CREATE TABLE IF NOT EXISTS novel_info ( id INT AUTO_INCREMENT PRIMARY KEY, book_name VARCHAR(100) NOT NULL COMMENT 书名, author VARCHAR(50) NOT NULL COMMENT 作者, category VARCHAR(30) DEFAULT COMMENT 分类, intro TEXT COMMENT 简介, total_words INT DEFAULT 0 COMMENT 总字数万字, cover_url VARCHAR(255) DEFAULT COMMENT 封面链接, book_url VARCHAR(255) DEFAULT COMMENT 书籍详情页链接, upload_time DATETIME DEFAULT NULL COMMENT 上架时间, update_time DATETIME DEFAULT NULL COMMENT 最近更新时间, UNIQUE KEY uk_book_author (book_name, author) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT小说基本信息表; CREATE TABLE IF NOT EXISTS rank_snapshot ( id INT AUTO_INCREMENT PRIMARY KEY, rank_date DATE NOT NULL COMMENT 抓取日期, rank_no INT NOT NULL COMMENT 当日排名, book_name VARCHAR(100) NOT NULL COMMENT 书名, author VARCHAR(50) NOT NULL COMMENT 作者, category VARCHAR(30) DEFAULT COMMENT 分类, total_click BIGINT DEFAULT 0 COMMENT 总点击, total_recommend INT DEFAULT 0 COMMENT 总推荐, month_ticket INT DEFAULT 0 COMMENT 本月月票, word_count INT DEFAULT 0 COMMENT 当前字数, crawl_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 抓取时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT榜单快照表;4.2 使用pymysql完成批量写入爬虫端写好数据之后怎么把数据写进MySQL是很多人卡壳的地方。用pymysql的execute批量执行其实很简单但有几个要点。第一步是建立连接。注意指定charsetutf8mb4并且可以用cursorclasspymysql.cursors.DictCursor这样查询结果会以字典形式返回写代码的时候直观很多。第二步是写SQL语句。如果你要插入小说基本信息表可以写一个INSERT IGNORE语句用于首次入库再写一个UPDATE语句用于更新已有的字段。不过更简单的方式是用4.2节提到的ON DUPLICATE KEY UPDATE一条语句搞定插入和更新。第三步是批量操作。一次循环里拿到的几十本书的数据不要一条一条地写库而应该拼成一个executemany的批量操作。这样不仅速度更快而且数据库压力也小得多。下面给一个简化的写入示例import pymysql def save_novel_info(db_config, book_list): connection pymysql.connect( hostdb_config[host], userdb_config[user], passworddb_config[password], databasedb_config[database], charsetutf8mb4, cursorclasspymysql.cursors.DictCursor ) sql INSERT INTO novel_info (book_name, author, category, intro, total_words, cover_url, book_url) VALUES (%(title)s, %(author)s, %(category)s, %(intro)s, %(words)s, %(cover_url)s, %(book_url)s) ON DUPLICATE KEY UPDATE category VALUES(category), intro VALUES(intro), total_words VALUES(total_words) try: with connection.cursor() as cursor: cursor.executemany(sql, book_list) connection.commit() finally: connection.close()这里有个细节值得注意executemany的第二个参数是字典组成的列表SQL语句里用%(字段名)s来引用字典的键这样代码可读性会好很多。你在抓取阶段构造数据时只要保证每个字典都包含title、author、category这些键就行。4.3 数据质量检查数据入库不是跑完就结束了必须要做质量检查。我建议每次抓取结束后都写一个简单的校验脚本做几件事检查数据库中记录的总条数是否与预期一致检查书名和作者是否有空的记录检查重复记录数量检查某几个数值字段是否有明显异常值。这些检查看起来很基础但真到了毕设答辩环节如果老师问你“你的数据质量如何保证”你能拿出这套校验逻辑来回答比干巴巴地说“我爬下来就存进去了”要有说服力得多。你甚至可以把这个校验脚本做成一个check_data.py的函数每次跑完爬虫就自动执行结果输出到日志文件里。5. 数据分析与可视化5.1 用PyEcharts快速出图数据采集和存储都做完了接下来就到了最能“秀成果”的部分把数据变成图表。PyEcharts是Python里我个人很推荐的可视化库纯Python调用生成的是网页版的HTML图表交互性强图表美观度也高而且中文支持很好。你可以做这几个典型的分析图第一是分类分布饼图。统计Top500小说中各个分类的作品数量看看哪个分类是绝对霸主。这张图在答辩时可以直接说明你数据采集的分类覆盖度。第二是字数分布柱状图。把小说按字数区间分段比如100万字以下、100万-200万、200万-300万、300万以上统计每个区间的数量。这个图能得出一个常见结论“起点Top500小说普遍都在百万字以上”非常有说服力。第三是推荐票Top10横向条形图。按总推荐量排序展示前十名小说和对应的推荐量。这张图做得好看一点几乎就是整份毕设的门面之一。第四是排名变化趋势折线图。如果你连续爬了多天数据可以挑几本热门书画出它们在这几天内排名的变化曲线。这种图能展现你爬虫项目的持续运行能力算是比较高级的加分项。5.2 展示方式与答辩要点可视化的展示方式可以用Flask写一个简单的Web页面来呈现也可以直接把PyEcharts生成好的HTML文件放在一个目录里答辩时用浏览器打开。如果你前后端基础不错我当然建议用Flask包一层这样显得更完整如果只是纯后端选手直接把HTML文件打开给老师看也没有任何问题。答辩讲解的时候重点不是讲图表本身而是讲图表背后的数据链路。比如你展示分类分布饼图时可以讲这个图的原始数据是从起点排行榜页面采集的共收集了500本小说的分类字段经过清洗后统计出各类别数量最后用PyEcharts生成的这张图。这样的话老师听到的重点不是图好看而是你的“数据是真实采集的、清洗过、存储了、分析出结果了”——你是把整个链条走完了的人。5.3 一个可参考的可视化代码片段下面是一个用Pyecharts绘制分类饼图的示例数据从MySQL查询而来from pyecharts.charts import Pie from pyecharts import options as opts import pymysql conn pymysql.connect( hostlocalhost, userroot, password123456, databaseqidian_db, charsetutf8mb4 ) sql SELECT category, COUNT(*) AS cnt FROM novel_info GROUP BY category ORDER BY cnt DESC LIMIT 10; df pd.read_sql(sql, conn) conn.close() data_pair list(zip(df[category], df[cnt])) pie ( Pie() .add(series_name小说分类, data_pairdata_pair, radius[35%, 65%]) .set_global_opts( title_optsopts.TitleOpts(title起点Top500小说分类分布), legend_optsopts.LegendOpts(orientvertical, pos_top15%, pos_left2%) ) .set_series_opts(label_optsopts.LabelOpts(formatter{b}: {d}%)) ) pie.render(category_pie.html)这段代码里用到了pandas的read_sql需要提前安装pymysql和pandas。这里要注意如果你的分类字段存在“空字符串”或者“未知”的脏数据画图前最好先用pandas把非空的数据过滤掉避免图中出现一个很大的“未知”扇区影响展示效果。6. 常见问题排查与避坑清单6.1 采集过程中的典型报错爬虫项目跑起来之后报错是家常便饭。我整理几个最常遇到的以及排查的思路。第一个是requests请求超时。这种情况最常见的原因是网页响应时间太长或者网络不稳定。解决方法是给requests.get设置timeout参数同时配合重试机制。你可以在每次请求的时候加一个while循环请求失败后等待几秒再重试一次。第二个是解析结果为空。代码明明没报错但解析出来的列表是空的这通常是页面结构修改了或者请求被反爬拦截返回了一个验证页面而不是正常的列表页。排查方法是先打印一下返回的HTML内容肉眼看看里面有没有你预期的字段。如果看到的是“请输入验证码”之类的内容那基本就是被反爬了需要降低请求频率或者是你的请求头还不够真实。第三个是BeautifulSoup选择器报错。比如select_one返回了None然后你后面调用.text就报AttributeError。这同样是页面结构变化导致的。建议在做解析时先判断一下节点是否为None再取值避免一个错误导致整个程序崩溃。6.2 数据入库时的编码与字段问题数据写不进MySQL或者写入后中文乱码这个问题的根源几乎都在字符集上。MySQL连接的时候要指定charsetutf8mb4MySQL表本身的字符集也要是utf8mb4Python脚本里字符串的编码处理要正规。你只要把这三层都统一成utf8mb4中文乱码问题基本不会再出现。还有一个常见问题是字段长度不够。有的小说简介非常长如果你在表设计时把intro字段设为VARCHAR(255)插入时就会报超长错误。解决方案是把这类长文本字段设为TEXT类型TEXT类型最大可以存6万多个中文字符对小说简介来说绰绰有余。提前注意这种长度设计的问题能避免很多突发性崩溃。另一个容易踩的坑是字段类型不匹配。比如你爬取的数据中“总字数”是“123.4万”这种带单位的字符串直接插到INT字段就会报错。解决方法是先数据预处理把“123.4万”转换为数字。我建议在爬虫解析阶段就完成这种“单位转数字”的转换不要等到入库前再做因为解析阶段你还能看到原始数据的上下文处理逻辑写起来不容易出错。6.3 毕设答辩常被追问的问题答辩前建议你自己先预演一遍老师可能会问的问题。其中出现频率最高的几个我列一下参考思路。第一个是“你为什么要选这个题目”标准答法选这个题目是因为它覆盖了数据采集、清洗、存储、分析、可视化的完整流程且起点榜单数据公开透明适合做教学级实战研究结果对了解网文行业也有参考价值。第二个是“如果网站反爬增强了怎么办”本题考察你的临场发挥能力。你可以说会优先降低请求频率、完善请求头伪装再考虑引入代理IP池来缓解IP被封的问题。同时也会做好任务调度把采集任务分散到不同时间段执行降低瞬时压力。第三个是“你的数据质量如何保证”参考答法我做了三层校验第一层在采集阶段检查字段是否缺失第二层在入库阶段用唯一索引去重并且对异常值做了范围校验第三层在分析前用SQL做二次清洗保证进入图表的数据是干净可用的。第四个是“这个项目还能怎么扩展”可以提到引入Scrapy分布式爬虫实现全站数据采集、用Flink做实时榜单分析、把排行榜数据接入机器学习模型做爆款预测等。这些问题你不用真的全部做出来但要有自己的思考和规划这能体现出你对项目的理解深度。写在最后的几点体会整个项目做下来我最深的一个感触是爬虫这个方向的价值不在于代码写得多么花哨而在于你能不能把一条完整的数据链路跑通。很多同学一上来就纠结用Scrapy还是用requests纠结要不要上Scrapy-Redis分布式其实这些都不是毕设的核心矛盾。核心矛盾是你能否在有限时间内把数据从目标网站稳定、合规地拿下来干净地存进数据库然后做出有说服力的分析图表。另外一个体会是做这类项目一定要留出充足的时间来应对页面结构变化。今天能跑通的代码可能过两周网站改版就不能跑了。所以尽早把自己项目的自动化流程跑起来把数据抓紧采集到手后面分析、写论文、做系统的时候你手里始终有真实数据可以用这样心里才踏实。希望这篇文章能帮你把这个选题从“听起来不错”变成“真正落地”。如果你在跑代码的过程中又踩到了什么奇奇怪怪的坑欢迎按我前面列的排查思路先自查一遍——大多数问题真的都是请求头、编码、选择器这三个地方出的。祝你毕设顺利。
返回列表