news 2026/11/6 2:13:27

Python爬虫动漫数据分析与可视化:采集清洗到指标呈现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python爬虫动漫数据分析与可视化:采集清洗到指标呈现

简介:围绕B站历年番剧数据展开的Python爬虫与数据分析实践包,面向具备Python基础、希望打通采集—清洗—分析—可视化完整链路的学习者。项目以总榜动漫为入口获取粗略信息与直达链接,再逐条访问详情页补充追番人数、评分等字段,可用于观察ACG文化发展脉络与番剧热度变化。压缩包共23个文件、约3.58MB,含3个Jupyter Notebook分别承担爬虫、数据预处理与分析,2个Excel数据表、10张可视化结果图、5个XML配置文件及1个Markdown说明文档,代码与结果相互对应,便于复现和对照学习。已有2985人学习,适合用作爬虫入门参考:既能参考请求与解析细节、数据清洗思路,也能查看评分与追番人数的图表呈现,并借助说明文档快速理解项目结构与分析结论。

1. 从一次课程作业的困境说起:为什么用 Python 爬虫做动漫数据分析与可视化

做课程设计最难受的,是选题看着简单、真正动手才发现无从下手。有个做数据分析方向的同学,选题定成“某视频平台动漫数据研究”,第一版方案只有一个爬虫脚本加几张柱状图,导师看完只回了一句:这堆图想说明什么?问题不在代码,而在于整个方案从头就没想清楚“采集哪几列、每列怎么定义、清洗到什么程度、最后回答哪个问题”。重新设计后,他把流程拆成采集、清洗、入库、指标计算、可视化五个环节,每环都有明确输入输出和验收口径,方案才真正立住。Python 爬虫、数据分析、数据可视化三件事单独看都不难,难的是串成一条能自证结论的链路,这也是我写这篇方案的方向。适合正在做爬虫或数据分析类毕设、课设的人,可以直接照结构改。

2. 数据采集层设计:从列表页到详情页的三级抓取链路

采集是整个方案的地基。做视频平台的动漫数据,不能一上来就写requests.get循环,得先把数据来源分层。通常的目标站点信息结构是三层:番剧列表页给出候选条目,番剧详情页承载总播放量、追番人数、评分、简介等主体字段,评论区或弹幕切片的接口提供用户侧数据。三层字段的更新频率差很多,评分、追番是慢变量,播放量、评论数是快变量,采集策略必须区别对待,否则每次全量重爬一遍,既费时间又容易触发对方限制。

2.1 三级抓取链路的职责划分与字段定义

我一般把采集脚本做成三个互相独立、按序执行的模块,而不是塞进一个文件。这样任何一层出问题都能单独重跑,不用从头再来。

# fetcher.py —— 三级链路中最外层:先拿列表,再进详情 import time import random import requests from urllib.parse import urljoin HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " "(KHTML, like Gecko) Chrome/120.0 Safari/537.36", "Referer": "https://example-anime-site.test/", # 站点根地址用占位域名 } def fetch_list(pages: int, interval=(1.2, 2.8)) -> list: """翻页抓列表页,返回 [(番剧ID, 详情页URL)]""" items = [] for p in range(1, pages + 1): url = f"https://example-anime-site.test/list?page={p}" resp = requests.get(url, headers=HEADERS, timeout=10) resp.raise_for_status() # 真实项目里这里用 lxml / parsel 解析,示例只保留结构 for node in parse_list_nodes(resp.text): items.append((node["aid"], urljoin(url, node["href"]))) time.sleep(random.uniform(*interval)) # 随机间隔,避免等频请求 return items def fetch_detail(detail_url: str, retry: int = 3) -> dict: """抓详情页,失败重试,返回单条记录""" for i in range(retry): try: resp = requests.get(detail_url, headers=HEADERS, timeout=10) if resp.status_code == 200: return parse_detail(resp.text) # 解析出字段字典 except requests.RequestException: time.sleep(2 ** i) # 指数退避:1s、2s、4s return {}

三个参数值得单独说。interval设成随机区间而不是固定值,是因为固定间隔的请求序列在服务端日志里很像机器行为,随机化后更像正常浏览节奏,注意这不等于可以高频请求,单机并发我一般控制在 4 以内。timeout=10必须显式设置,不设的话遇到响应慢的页面脚本会长时间阻塞。retry配合2 ** i的指数退避,让偶发的超时或 5xx 不会直接丢掉整条记录。列表页负责“发现”,详情页负责“补全”,两层分开的好处是列表可以反复跑、详情只对新增条目补采。

2.2 解析层与增量更新的落地方式

解析层建议单独成文件,把 HTML 选择器集中管理。站点改版是这类项目最常翻车的地方,选择器散落在各处会让修改变成灾难。我的习惯是写一个selectors.py,每个字段一行,改版时只动这一个文件。字段清单通常包括:番剧 ID、标题、类型标签、总播放量、追番人数、评分、评分人数、简介、更新状态。

增量更新靠番剧 ID 判断,入库前先查已有 ID 集合,已存在的跳过详情页抓取,只更新快变量字段。下面这段是入库前的去重逻辑:

# store.py —— 增量入库:慢变量只采一次,快变量每次更新 import sqlite3 def upsert_anime(conn: sqlite3.Connection, rec: dict) -> None: sql = """ INSERT INTO anime (aid, title, tags, score, score_cnt, follow_cnt, play_cnt, status, intro, updated_at) VALUES (:aid, :title, :tags, :score, :score_cnt, :follow_cnt, :play_cnt, :status, :intro, datetime('now')) ON CONFLICT(aid) DO UPDATE SET play_cnt = excluded.play_cnt, -- 快变量:每次覆盖 follow_cnt = excluded.follow_cnt, score = excluded.score, updated_at = excluded.updated_at -- 慢变量 title/tags/intro 不更新,避免误覆盖 """ conn.execute(sql, rec)

这里的关键设计是慢变量和快变量的更新策略不同。标题、简介这类字段一旦入库就不该被后续抓取覆盖,因为偶发解析错误会把好数据冲掉;播放量、评分数则是每次都要更新。SQLite 的ON CONFLICT DO UPDATE语法省掉了一次“先查再写”的往返,几万条数据的量级完全够用。这里只做机制层面的限速、缓存和增量控制,不涉及任何绕过平台限制的手段,方案的可复现性优先于采集速度。

3. 数据清洗与指标体系:从原始字段到可分析宽表的转换规则

爬下来的原始数据几乎不能直接用:播放量是“1.2亿”这种带单位的字符串,评分有人打 0 分有人不打,简介字段里混着 HTML 标签和换行,类型标签是多个值挤在一个单元格里。分析与可视化的所有结论都建立在这张宽表上,清洗规则不明确,后面图表全是玄学。

3.1 字段标准化与缺失值处理规则

我一般按“先转换类型、再处理缺失、最后做业务校验”的顺序走,每一步都可单独复跑。下面这段是清洗脚本的核心部分:

# clean.py —— 原始表 -> 分析宽表 import re import pandas as pd UNIT = {"万": 10_000, "亿": 100_000_000} def parse_count(s) -> float: """把 '1.2亿' / '3456.7万' / '8721' 统一成整数""" if pd.isna(s): return None s = str(s).strip() m = re.match(r"^([\d.]+)\s*([万亿]?)$", s) if not m: return None num, unit = float(m.group(1)), m.group(2) return round(num * UNIT.get(unit, 1)) def build_wide_table(raw: pd.DataFrame) -> pd.DataFrame: df = raw.copy() df["play_cnt"] = df["play_raw"].map(parse_count) df["follow_cnt"] = df["follow_raw"].map(parse_count) df["score"] = pd.to_numeric(df["score_raw"], errors="coerce") # 评分缺失但有评分人数的,按类型中位数回填;两者都缺则剔除 mask = df["score"].isna() & df["score_cnt"].notna() df.loc[mask, "score"] = df.groupby("main_tag")["score"].transform("median") df = df[df["score"].notna() & df["play_cnt"].notna()] # 标签拆分:一部番可有多个类型,拆成首标签 + 标签列表 df["tag_list"] = df["tags"].fillna("").str.split("/") df["main_tag"] = df["tag_list"].map(lambda x: x[0] if x else "未分类") df["episodes"] = pd.to_numeric(df["episodes_raw"], errors="coerce") return df.reset_index(drop=True)

parse_count是这类项目最实用的一个函数,单位换算写成字典便于以后加“千”这类单位。缺失值处理我选的是“按类型中位数回填”,而不是全局均值:不同题材的评分分布差异很大,用全局均值会把热血番和治愈番拉平,回填反而制造噪声。既没评分又没有评分人数的记录直接剔除,因为这类条目通常刚上线,指标不稳定。main_tag取标签列表第一个,是为了给后面的分组聚合提供一个确定的维度,多标签的完整信息保留在tag_list里不丢。

3.2 派生指标的构造与口径说明

原始字段只能描述“是什么”,要回答“哪类番更受欢迎”“高分番有什么共同特征”,得造派生指标。下面几个是我每次必加的:

指标名计算口径用途
追播比追番人数 / 播放量衡量内容黏性,比值高说明观众愿意长期跟随
评分置信度min(评分人数 / 5000, 1.0)给评分加权,避免几十人打出的 9.9 分喧宾夺主
加权评分评分 × 评分置信度排行榜排序用,比裸评分稳健
集均播放播放量 / 集数跨集数比较的公平口径
上线年从开播日期提取做时间趋势分析的分组维度

“评分置信度”这个设计是我踩过坑之后加的:第一版排行榜直接按评分倒序,前二十名全是只有几十人评价的新番,结论完全站不住。设一个 5000 人的阈值做线性加权,样本不足的条目自然被压下去,榜单的可信度立刻不一样。集均播放同理,一部 12 集和一部 100 集的番直接比总播放量是不公平的,除一下才可比。这些口径必须在文档里写清楚,答辩时被问“你这个排名怎么来的”,能一句话说清计算方式的方案才是合格的方案。

4. 可视化与结论呈现:四类图回答四个业务问题

图表不是越多越好,每张图都应该对应一个能说出口的问题。我在这个方案里固定用四类图,分别回答“整体分布如何”“类型之间怎么比”“指标之间有没有关系”“时间上有什么趋势”,多一张都是凑数。

4.1 分布、对比、相关、趋势四类图的实现

# viz.py —— 四图联动,统一风格 import matplotlib.pyplot as plt import seaborn as sns sns.set_theme(style="whitegrid", font_scale=1.1) plt.rcParams["font.sans-serif"] = ["SimHei"] # 中文显示 plt.rcParams["axes.unicode_minus"] = False def plot_four(df, out_dir="figs"): # 1) 分布:加权评分的直方图 + 核密度 fig, ax = plt.subplots(figsize=(8, 5)) sns.histplot(df["weighted_score"], bins=30, kde=True, ax=ax) ax.set_title("加权评分分布") fig.savefig(f"{out_dir}/dist_score.png", dpi=150, bbox_inches="tight") plt.close(fig) # 2) 对比:各类型集均播放量 Top10 条形图 top = (df.groupby("main_tag")["play_per_ep"] .median().sort_values(ascending=False).head(10)) fig, ax = plt.subplots(figsize=(9, 5)) sns.barplot(x=top.values, y=top.index, ax=ax) ax.set_xlabel("集均播放量(中位数)") fig.savefig(f"{out_dir}/bar_tag.png", dpi=150, bbox_inches="tight") plt.close(fig) # 3) 相关:追番人数 vs 播放量,对数坐标 + 回归线 fig, ax = plt.subplots(figsize=(8, 6)) sns.regplot(data=df, x="play_cnt", y="follow_cnt", scatter_kws={"alpha": 0.4, "s": 18}, ax=ax) ax.set_xscale("log"); ax.set_yscale("log") fig.savefig(f"{out_dir}/scatter_follow.png", dpi=150, bbox_inches="tight") plt.close(fig) # 4) 趋势:按上线年统计的番剧数量与中位评分 yearly = df.groupby("year").agg(cnt=("aid", "count"), score=("weighted_score", "median")) fig, ax1 = plt.subplots(figsize=(9, 5)) ax1.bar(yearly.index, yearly["cnt"], alpha=0.6) ax2 = ax1.twinx() ax2.plot(yearly.index, yearly["score"], color="crimson", marker="o") fig.savefig(f"{out_dir}/trend_year.png", dpi=150, bbox_inches="tight") plt.close(fig)

几个实现细节值得说。用中位数而不是均值做类型对比,是因为播放量分布严重右偏,均值会被几部爆款拉走。散点图必须开对数坐标,播放量跨了三四个数量级,线性坐标下所有点会挤在左下角,看不出任何关系。双轴图里柱子和折线共用一个 x 轴但量纲不同,左右轴要标清楚,否则读者会误读。这些都属于“画出来容易、读得懂难”的地方,答辩时图能不能自解释,比图漂不漂亮重要得多。

4.2 图表到结论的写作方法

每张图下面必须配三句话:这张图看什么、看到了什么、所以说明什么。比如类型对比图,第一句写“按集均播放中位数排前十的类型”,第二句写“某几个类型明显高于其余”,第三句写“说明这类题材的单集吸引力更强,但受集数影响总播放未必最高”。三句话结构逼着自己把图表和结论对齐,也是导师最常追问的部分。我一般会把图表和结论写进一份 Markdown 报告,图用相对路径引用,结论段落控制在百字以内,避免大段文字把图淹了。

5. 避坑与常见问题:这条链路上最容易翻车的五件事

5.1 翻车点一:全量重爬导致数据前后不一致

现象是同一份报告里,有的番剧播放量是上周的数、有的是今天的数,横向比较完全失真。原因是脚本每次跑都全量覆盖,而采集过程中部分记录失败被跳过,新旧数据混在一张表里。解决办法是入库带updated_at时间戳,分析前先按时间戳过滤出最近一个完整批次,或者干脆给每批采集打一个batch_id,报告只基于单一批次生成。

5.2 翻车点二:评分样本不足导致排行榜失真

现象是排行榜前列全是冷门新番,评分接近满分。原因就是前面说的裸评分排序,没有考虑评分人数。解决办法是引入评分置信度加权,或者简单粗暴地加一个“评分人数不少于 5000”的过滤条件。两种做法都能用,前者更平滑,后者更好解释,课程设计里用哪种都行,关键是要在文档里写明。

5.3 翻车点三:单位换算漏了边界值

现象是某几条记录播放量显示为 0 或 None,图里数据点异常缺失。原因多半是parse_count遇到“暂无”“--”这类非数字占位符时返回了 None,而下游没有处理。解决办法是在清洗阶段统计每列的 None 比例,超过阈值就打印出来人工核对,别等画图时才发现数据洞。

5.4 翻车点四:中文标签在图表里显示为方框

现象是坐标轴和标题全是方块。原因是 matplotlib 默认字体不含中文字形。解决办法是显式设置font.sans-serif为系统里存在的中文字体,同时把axes.unicode_minus关掉,否则负号也会变成方块。这个小问题每年都有人卡住,属于典型的血泪经验,配置一次写进模板文件就不会再犯。

5.5 翻车点五:采集频率失控引发请求失败

现象是跑到一半开始大量超时或返回错误码。原因是固定间隔加高并发,被服务端限流。解决办法是把并发降到 4 以内、间隔改成随机区间、失败走指数退避重试,并且给整批任务设一个总时长上限,超时就停下保存已采部分。采集任务要能随时中断、随时续跑,这比追求采集速度重要得多。

6. 验收与答辩:用一份可复跑的流水线证明方案成立

验收这件事,我的建议是把“能不能一键跑通”作为第一标准。具体做法是写一个run_all.py,按fetch → clean → build → viz → report的顺序串起五个阶段,每个阶段读上一阶段的输出、写下一阶段的产物,中间文件统一放data/和figs/目录。验收时从空目录开始执行一次,能在十分钟内产出全部图表和报告,这套方案就算自洽了。做不到一键跑通,说明阶段之间的依赖没理清,答辩时被追问数据从哪来、怎么变、到哪去,很容易卡壳。

答辩陈述我建议按四段讲,每段控制在两分钟以内:先讲问题定义,也就是这份数据要回答哪几个问题;再讲采集与清洗链路,重点说清字段口径和增量策略;然后讲指标体系,把追播比、加权评分这几个派生指标的计算方式讲明白,这是最能体现设计深度的地方;最后讲结论与局限,主动说出样本覆盖范围有限、评分受粉丝行为影响等不足。主动承认局限比被问出来强得多,导师通常更在意你有没有意识到边界。

下面这份自查表我会在提交前逐项过一遍:

检查项通过标准
一键复跑空目录执行run_all.py能产出全部图表
字段口径每个派生指标在文档里有一句话定义
数据可追溯每张图能对应到具体的表、字段和过滤条件
缺失处理清洗日志里记录了每列 None 比例
图表自解释每张图配“看什么、看到什么、说明什么”三句话
局限说明报告里有专门一段讲样本与方法的边界

有次评审,一位同学的图表做得非常漂亮,但被问到“这个追播比是怎么算的”时答不上来,整个方案的可信度当场就打折了。从那以后我每次交付数据分析类方案,都强制走一遍“指标口径能不能一句话说清、流水线能不能从空目录跑通”这两道检查。这份 Python 爬虫加数据分析与可视化的完整方案,采集、清洗、建表、绘图、报告五个阶段的代码和文档都在,拿去按自己的选题改字段就能用,希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/6 2:12:39

数字IC设计AI助手:基于Docker与OpenCode的本地化部署方案

1. 项目背景与核心价值在数字IC设计领域,工程师每天需要处理大量重复性工作:代码补全、语法检查、文档查询、脚本调试等。传统工作流中,这些任务分散在不同工具链中完成,效率低下且学习成本高。我们团队基于实际项目痛点&#xff…

作者头像 李华
网站建设 2026/11/6 2:10:13

C# WinForms 教务系统实战:三层架构、ADO.NET 与选课事务

简介:这份压缩包面向计算机专业课程设计、毕业设计与 C# 桌面开发入门者,提供一套基于 Windows 窗体和 SQL Server 的教务管理示例,覆盖学生信息管理、成绩管理与选课三类业务。系统区分学生与管理员两类角色,管理员可维护管理员及…

作者头像 李华
网站建设 2026/11/6 2:10:11

VS Code GDB 调试 Apollo:断点空心与 Bazel 容器实战

简介:面向使用 VS Code 调试 Apollo 自动驾驶项目的开发者,这份 zip 资源围绕 GDB 断点调试场景整理,适合已具备 C 与 Bazel 基础、希望快速跑通调试链路并理解工程运行流程的初中级人员。包内共 5 个文件,由 4 个 JSON 配置文件和…

作者头像 李华
网站建设 2026/11/6 2:10:10

eNSP实战IPv6校园网:地址规划、OSPFv3与DHCPv6

简介:面向网络工程、计算机网络专业学生及备考网络实验的学习者,这是一套基于eNSP搭建IPv6校园网的规划与设计实验资料,可用于课程设计、实验复现与综合排错练习。实验覆盖虚拟局域网划分、单臂路由、VRRP6、OSPFv3、防火墙安全策略、DHCPv6、…

作者头像 李华
网站建设 2026/11/6 2:10:08

医院门诊导诊区数字人设备适配指南

每天早高峰的门诊大厅,总能看到这样的画面:导诊台前排起长龙,患者焦急地询问“挂号在哪”、“心内科怎么走”,而护士们一边重复解答,一边还要处理各种突发状况。这种高负荷的运转不仅让医护人员疲惫不堪,也…

作者头像 李华
网站建设 2026/11/6 2:10:06

iOS 17 免越狱虚拟定位环境搭建与 GPX 轨迹回放排错

简介:面向 iOS 17 及以上系统、希望在免越狱条件下研究虚拟定位与模拟跑步打卡的开发者与进阶用户,提供一套跨平台的命令行脚本工具,支持在线拾取路径,并可对跑步距离、时间、速度等参数进行设置,便于搭建测试环境或验…

作者头像 李华