简介:一份面向Python爬虫与数据可视化分析大作业的全套文件,覆盖从网络抓取、HTML解析、数据清洗到数据可视化的完整链路,适合高校信息类课程学生或Python数据分析初学者参考学习。压缩包内共5个文件,包含1个核心Python脚本、1份docx格式的作业说明文档、1份停用词表以及2个gif动图,整体大小仅2.72MB,内容紧凑便于快速上手。目前已有1243人浏览学习,反映出这套资料对同类大作业具有参考价值。配套的Reptile+Pyecharts.py脚本演示了如何用Requests与BeautifulSoup抓取网页、利用正则表达式提取信息,再借助pandas完成数据清洗与预处理,最后通过Pyecharts生成柱状图、折线图等可视化图表;docx文档补充了项目设计与实现思路,stopwords.txt为文本分析提供停用词表支撑,两个gif则直观展示代码运行效果,可帮助使用者快速验证结果。
1. python 爬虫数据可视化大作业:一套能直接交付的全套文件
又是一个课设季,总有人卡在同一个路口:题目选的是 python 爬虫数据可视化分析,爬虫吭哧吭哧写了两天,数据倒是抓下来了,结果卡在清洗、入库、画图这些环节上,最后交上去的作业只有一张控制台截图。这套全套文件就是冲这个场景来的——它不是单个爬虫脚本,而是一条从网页到最终可视化图表的完整流水线:requests 抓取公开页面数据,pandas 做清洗,SQLite 落库,pyecharts 生成能交互的图表,再配上一份可以直接改写的报告骨架。你拿到手之后,可以整套跑通,也可以把它当模板,换个数据源就是一份新作业。适合拿来做大作业参考,也适合第一次完整走一遍爬虫数据可视化流程的人。
2. 项目结构与技术选型:六个文件撑起一条数据流水线
2.1 全套文件清单:每个文件负责哪个环节
先看这套文件长什么样。解压之后是六个核心文件加一个数据目录,各管一段,路径和职责一目了然:
| 文件 | 职责 | 关键依赖 |
|---|---|---|
| spider.py | 网页抓取与字段解析,输出结构化记录 | requests、BeautifulSoup4、lxml |
| clean.py | 数据清洗,处理缺失值、类型转换、去重 | pandas |
| db.py | 建库建表,负责 SQLite 的写入与增量更新 | sqlite3(标准库) |
| analyze.py | 聚合统计并生成可视化图表 | pyecharts、pandas |
| requirements.txt | 一键安装全部依赖 | pip |
| README.md | 运行步骤、文件说明、报告写法建议 | 无 |
data/ 目录在第一次运行 db.py 后自动生成,里面是 books.db 和跑图表输出的 HTML 文件。这个分层是有意为之的:每一层都能单独运行,单独出问题单独排查,不用每次从上到下全跑一遍。
2.2 技术选型:为什么是 requests + pyecharts 而不是 scrapy + matplotlib
选型这步直接决定课设能不能在有限时间内做完。工程上 scrapy 当然更强大,但课设场景里 requests 更合适——它的请求逻辑直白,每一行都能在报告里讲清楚,而且调试起来没有框架的额外概念负担。解析用 BeautifulSoup 加 lxml 解析器,写起来比正则省心,选择器写错报错也友好。
可视化选 pyecharts 而不是 matplotlib,核心原因是两个:一是 matplotlib 出图要处理中文字体问题,课设机器上没配字体就直接方块字;二是 pyecharts 生成的是独立 HTML,图表可以缩放、悬停、看数据,评审时打开浏览器就能交互展示,观感完全不一样。它基于 ECharts,生成的图表在移动端打开也正常,省去配 flask 的功夫。
2.3 数据流向:采集层、清洗层、展示层的协作方式
整套文件按“采集 → 清洗 → 存储 → 分析展示”拆成四层,层与层之间只通过文件或数据库交接,不互相 import 业务函数。这样做的好处是每一层出错时,黑匣子范围很小:抓回来的数据不对,问题在 spider.py;写库之后数据不对,问题在 clean.py。数据流向可以用下面这段伪代码描述:
# 数据流:spider.py -> clean.py -> db.py -> analyze.py # 网页HTML -> DataFrame -> SQLite表 -> 图表HTML # 中间产物:records.json(爬虫输出) -> books.db(清洗后落库) -> output/*.html(分析结果)spider.py 跑完会把原始记录写成一个 JSON 文件作为中间产物,clean.py 读它而不是直接读网页,这样爬虫和分析两件事完全解耦。你甚至可以单独换掉爬虫,保留后面清洗分析的链路不动。实际踩坑时,先看中间产物 JSON 是否正常,再判断问题是出在抓取还是清洗,效率比从头排查高得多。
3. 爬虫模块实战:requests + lxml 把网页变成结构化数据
3.1 请求构造:UA、Referer 和频率控制不是玄学
爬虫第一步是拿到一段“正常得像真人”的请求。很多站点会校验 User-Agent,默认的 Python-requests 头很容易被识别直接拒掉。这里先构造一个 Session,统一带上浏览器标识和来源页:
import requests from time import sleep HEADERS = { "User-Agent": ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/120.0.0.0 Safari/537.36" ), "Referer": "https://example.com/", } session = requests.Session() session.headers.update(HEADERS) resp = session.get("https://example.com/top", timeout=10) resp.raise_for_status() sleep(1) # 控制请求频率,别把对方服务器当循环测试机打这里有几个参数值得留意:timeout 设成 10 秒,避免某个页面卡住导致整个爬虫挂起;raise_for_status() 会在返回 4xx/5xx 时直接抛异常,让程序快速失败而不是拿着错误页面继续解析;每条请求之间 sleep(1),是给服务器一个合理的间隔。别小看这行 sleep,不少同学图快把它删了,结果爬到一半 IP 被临时封掉,整个抓取前功尽弃——这种反爬拦截主要是频率问题,不是玄学。
3.2 列表页解析:CSS 选择器抽取书名、作者、评分
请求拿到 HTML 之后,接下来就是把页面里的信息“抠”出来。这里以某公开图书榜单为例,榜单每页有若干条目,每个条目包含书名、作者、出版社、评分、评论数、价格。用 BeautifulSoup 的 CSS 选择器可以实现字段抽取:
from bs4 import BeautifulSoup soup = BeautifulSoup(resp.text, "lxml") items = soup.select("div.item") rows = [] for item in items: title = item.select_one("h3 a").get_text(strip=True) author = item.select_one("span.author").get_text(strip=True) publisher = item.select_one("span.publisher").get_text(strip=True) rating_text = item.select_one("span.rating").get_text(strip=True) rating = float(rating_text) if rating_text else None rows.append({ "title": title, "author": author, "publisher": publisher, "rating": rating, })这段逻辑的核心在 select_one 和 get_text(strip=True) 的配合。select_one 返回第一个匹配 CSS 选择器的元素,选不中时返回 None,所以 rating 字段先判断再转 float,否则页面上偶尔缺评分会直接让程序崩溃。get_text(strip=True) 会把元素里的空白字符去掉,得到干净的文本。不同网站的字段位置不同,你只需要改括号里的选择器字符串——这是整个项目里唯一需要根据目标站点调整的地方。
3.3 健壮性兜底:超时重试与爬虫中断恢复
课设网站通常稳定,但网络波动和临时限流还是躲不掉。一个合格的爬虫脚本应该带重试机制,而不是一遇上超时就“当场死亡”。这里给 fetch 动作包了一层指数退避重试:
def fetch_page(session, url, retries=3): for attempt in range(retries): try: resp = session.get(url, timeout=10) resp.raise_for_status() return resp except (requests.Timeout, requests.ConnectionError) as e: if attempt == retries - 1: raise wait = 2 ** attempt print(f"第 {attempt + 1} 次失败,{wait} 秒后重试...") sleep(wait) return None这段代码的关键逻辑是:前两次失败分别等 1 秒、2 秒(指数退避),最后一次失败直接抛出异常,让外层循环知道这个页面确实抓不动了。注意它只捕获 Timeout 和 ConnectionError,HTTP 状态码错误由 raise_for_status 处理,两类错误分开处理更清晰。有了这个函数,多页抓取循环里只需要每页调用 fetch_page,失败自动重试,中间某页断了也能继续跑,不会影响已抓取的数据。
4. 存储与分析模块:SQLite 落库 + pyecharts 出图
4.1 数据清洗:去重、类型转换与缺失值处理
爬虫抓回来的原始记录是字符串和散落字段,直接入库会留下一堆坑:书名重复、评分变成文本、价格带着货币符号。清洗这一步用 pandas 把一切拉回正轨。先看建表和唯一索引,再看清洗逻辑:
CREATE TABLE IF NOT EXISTS books ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, author TEXT, publisher TEXT, rating REAL, price REAL, crawled_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE UNIQUE INDEX IF NOT EXISTS idx_book_title ON books(title, author);唯一索引是防重复的关键。它把“书名 + 作者”组合设为唯一键,后续再跑爬虫时,数据库层面就会拒绝完全相同的记录,避免重复数据越堆越多。接下来是清洗脚本:
import pandas as pd import sqlite3 df = pd.DataFrame(rows) df = df.drop_duplicates(subset=["title", "author"]) df["rating"] = pd.to_numeric(df["rating"], errors="coerce") df["price"] = df["price"].str.replace("¥", "", regex=False).astype(float) df = df.dropna(subset=["title"]) df.to_sql( "books", sqlite3.connect("data/books.db"), if_exists="append", index=False, )逐行看这里做了什么:drop_duplicates 按书名和作者去重,对应刚才的唯一索引;pd.to_numeric 把“8.5”这种字符串转成浮点数,转不了的置为 NaN;price 字段先去掉货币符号再转 float。最后 dropna 把没有书名的记录删掉,因为那是无意义的脏数据。to_sql 用 if_exists="append" 实现增量写入,重复运行时不会覆盖历史数据,配合唯一索引就构成了一套完整的防重复方案。
4.2 聚合查询:用 SQL 算出出版社 Top10 和评分分布
数据入库后,分析就有底气了。有了 books 表,统计分析就是写 SQL 的事。这套文件里预置了几个分析维度,都是答辩时容易被追问的统计口径:
-- 出版社图书数量 Top10 SELECT publisher, COUNT(*) AS cnt FROM books GROUP BY publisher ORDER BY cnt DESC LIMIT 10; -- 整体评分统计 SELECT AVG(rating), MAX(rating), MIN(rating) FROM books;第一个查询用 GROUP BY 按出版社分组,COUNT(*) 统计每个出版社的图书数量,按数量倒序取前十。第二个查询计算全量图书的平均分、最高分、最低分,用来回答“这批数据整体水平怎么样”的提问。如果你想把分析扩展成“评分高于 9 分的书有多少”,加 WHERE rating >= 9 即可。数据进了数据库,分析维度就是 SQL 熟练度的问题,而不是爬虫的问题了。
4.3 可视化输出:pyecharts 生成柱状图、饼图与散点图
最终交付的图表由 analyze.py 统一生成,它从数据库读数,用 pyecharts 输出独立 HTML。下面这段是一个出版社 Top10 柱状图的核心代码:
import sqlite3 import pandas as pd from pyecharts.charts import Bar from pyecharts import options as opts conn = sqlite3.connect("data/books.db") df = pd.read_sql_query( "SELECT publisher, COUNT(*) AS cnt FROM books " "GROUP BY publisher ORDER BY cnt DESC LIMIT 10", conn, ) bar = Bar() bar.add_xaxis(df["publisher"].tolist()) bar.add_yaxis("图书数量", df["cnt"].tolist()) bar.set_global_opts( title_opts=opts.TitleOpts(title="出版社图书数量 Top10"), xaxis_opts=opts.AxisOpts(name="出版社"), yaxis_opts=opts.AxisOpts(name="数量"), ) bar.render("output/publisher_top10.html")这段代码结构很固定:先查库得到 DataFrame,再把 DataFrame 的列传给 add_xaxis 和 add_yaxis,最后设置坐标轴名称后渲染到 HTML。pyecharts 的图表类型可以整体替换——把 Bar 换成 Pie 就是饼图,换成 Scatter 就是散点图,数据传参接口保持一致。做评分价格散点图时,只需要把 x 轴设为价格、y 轴设为评分,就可以观察“贵的书评分是否一定高”这类关联性问题。
5. 避坑记录:从抓取到交作业最容易翻车的五个环节
5.1 抓取环节的三类翻车现场
现象一:控制台打印出来全是乱码,根本没法解析。原因:requests 拿到的 resp.text 默认按响应头猜测编码,而部分站点响应头没写 charset 或写得不准确,导致中文被解码成乱码。 解决:在解析之前加一行 resp.encoding = resp.apparent_encoding,让 requests 根据页面内容自动判断编码;再不行就手动指定为 utf-8 或 gbk。
现象二:循环翻页,结果翻来翻去抓到的是同一页的数据。原因:翻页参数拼接错误。有的站点是 ?page=2,有的是 ?offset=20,还有的是 POST 请求里带参数。照抄别人的翻页代码而不看目标站点实际用的参数,是典型的“看着对其实是错的”。 解决:打开浏览器开发者工具,在 Network 面板里点击第 2 页,看实际发出的请求 URL 和表单参数,照着拼接。抓完第 1 页后打印当前页的元素数量,确认不是第 1 页的内容再继续。
现象三:爬了五十页后突然开始返回 403,请求被拒。原因:前半段正常,后半段被风控识别为机器行为,典型触发点是请求频率过高且一直用同一个 UA。 解决:降低请求频率,把 sleep 从 1 秒提高到 2-3 秒;固定用完整 UA 头而不是默认的 Python 标识;如果对方返回验证码页面,停止程序,不要尝试绕过验证码,等一段时间再恢复抓取。
5.2 存储与出图环节的常见问题
现象四:爬虫多跑了几次,数据库里相同书籍出现了好几条。原因:写入没有做去重,每次跑脚本都往表里 insert 全部记录。这是不设唯一索引的必然结果。 解决:在建表时加上 UNIQUE 索引,并把 to_sql 的 if_exists 参数改为 "append",配合写入前 drop_duplicates,三层保障可以确保同一本书不会重复落库。
现象五:图表做出来了,标题和坐标轴中文全是方块,英文正常。原因:用了 matplotlib 但没有配置中文字体,系统默认字体不支持中文。 解决:换 pyecharts 出图,它是基于 ECharts 的,图表在浏览器里渲染,字体跟着浏览器走,不存在中文乱码问题;如果坚持用 matplotlib,需要手动下载中文字体文件并设置 rcParams 指向它,比较折腾。
6. 二次开发思路:把通用模板换皮成自己的选题
6.1 换选题三步走
这套文件最大的价值不在“能跑”,而在“好改”。想把它变成一份新选题的作业,不需要动架构,只要改三个地方。
第一步,把 spider.py 里的目标地址和选择器换成你的数据源。选定一个公开、稳定、不需要登录的信息型站点,比如某个公开的排行榜或某类商品的列表页。在开发者工具里找到列表项的选择器,映射成字段。
第二步,调整 clean.py 的字段映射表。图书项目的字段是书名、作者、出版社,换选题后可能是“商品名、品牌、价格、销量”。映射逻辑不变,只需要在解析代码里把字段名和选择器替换掉,把类型转换的规则对应改一下。建议用统一的字段映射表,后续维护选择和存储逻辑都方便。
第三步,改 analyze.py 的统计维度。换一个分组维度,就换一条 GROUP BY 语句,比如从“出版社 Top10”改成“品类销量占比”;“评分价格散点图”改成“销量价格散点图”。图表的类型不用变,换数据后重新 render 就是一张新图。
字段映射可以按下面这种结构集中管理,避免把选择器散在代码各处:
FIELD_MAP = { "title": ("h3 a", "text"), "author": ("span.author", "text"), "publisher": ("span.publisher", "text"), "rating": ("span.rating", "float"), "price": ("span.price", "price"), }集中管理之后,换数据源时只需要维护这一张表。清洗逻辑和存储结构不随业务字段变化而大改,这是工程课设里很加分的意识。
6.2 让作业更完整的两个加分项
如果时间充裕,还可以做两点增强。一是给 analyze.py 增加一个“对比图表”功能——把数据按不同年份分组,分别生成综合对比柱状图,这样报告里就能写“不同年份的趋势差异”章节。二是如果熟悉 Flask,可以写一个约 50 行的 app.py,把 output 目录下的图表 HTML 拼到一个页面上,形成一个小型数据看板,答辩时直接浏览器展示,效果比贴静态截图好得多。
说句个人的血泪经验:我当年交完作业,评审老师问了一句“换个网站还能跑吗”,我当场愣住,因为我当时把页面解析逻辑写死在一个函数里,换站就得重写大半。从那以后我每次写爬虫相关的课设,都强制把采集、清洗、展示三层拆开,并且用一个集中的字段映射表做交接,哪怕只是简单的课程作业也不例外。这不仅是给老师看的架构意识,更是给下一个自己留的后悔药。希望这套文件能帮你少踩几个坑,把时间花在真正值得讲的分析结论上。整套文件拉下来后,照着 README 按顺序跑一遍,再按这套思路换皮成自己的选题,两个晚上就能交出一份结构完整、讲得清楚的作业。希望帮到你。
本文还有配套的精品资源,点击获取