尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于Python爬虫的豆瓣数据分析系统完整实战指南

基于Python爬虫的豆瓣数据分析系统完整实战指南 作为一个过来人我太懂看到“基于python爬虫的豆瓣数据分析系统”这类课题时的心情了。它几乎是每个计算机专业学生在课程设计或毕业设计阶段都会碰到的一类项目看起来门槛不高但真要做得完整、拿得出手涉及的知识点却横跨了网络请求、数据解析、结构化存储、数据清洗再到可视化展示的完整链路。这就像从零开始做一个mini版的数据产品任何一个环节卡住整个项目都会停摆。我决定把当初完成这个项目时踩过的坑、走过的弯路、以及最终沉淀下来的一套可复用的思路完整写出来。无论你是正在为课设发愁还是想通过一个完整案例来串联Python生态的核心技能这篇文章都会给你一条清晰的路线图。1. 项目整体设计与技术选型先别急着写代码。拿到“基于python爬虫的XX数据分析系统”这类需求第一反应应该是做减法明确三点爬什么、存哪里、展示什么。1.1 需求拆解从标题反推系统骨架项目标题的核心词是“豆瓣电影、音乐、图书”和“数据分析系统”。豆瓣本身是一个UGC社区它的数据结构非常规整条目有标题、评分、评价人数、类型、年份、国家、导演/作者等标准字段。这就天然决定了我们爬虫的目标结构。系统要覆盖三类数据但它们的页面形态略有不同电影有经典榜单如“豆瓣电影Top250”分页清晰评分、主演、导演、类型都在条目页里。音乐大多是唱片/专辑形态热门音乐新碟榜或特定标签下的条目字段包括表演者、流派、发行时间、曲目列表。图书以“豆瓣图书Top250”最方便入手字段包含作者、出版社、页数、定价、ISBN。为了保证系统的完整性我当时的设计是三层架构采集层爬虫负责把三类数据抓下来存储层统一落库分析展示层将统计结果可视化并输出报告。这种分层的好处是任何一层出问题都可以独立调试而且答辩时描述起来逻辑非常清晰。工具选型上也直接决定后期的开发效率。这里我明确推荐requestsBeautifulSoup4pandasSQLAlchemypyecharts/matplotlib。这套组合是社区最通用的方案遇到问题搜解决方案几乎一搜一个准。1.2 技术栈选型背后的理由为什么不用Scrapy对于单一站点的有限页面采集一般是几百页量级Scrapy的异步引擎和中间件反而显得笨重而且它在Windows环境下的部署和依赖配置相对啰嗦。requests配合time.sleep()的同步方案完全够用而且代码直观适合教学展示。如果以后要扩展到千万级页面再考虑Scrapy也不迟。数据存储我选了SQLAlchemyORM 而不是裸写SQL。ORM定义模型类的方式让代码结构非常清晰建表、增删改查不用手写SQL语句对新手更友好。而且SQLAlchemy底层可以无缝兼容SQLite和MySQL——前期验证逻辑用SQLite零配置跑通如果数据量大了或者答辩演示时想要更正式的数据库改一行连接字符串就能切到MySQL这个灵活度很关键。可视化部分的取舍matplotlib是基础款中文乱码需要设置字体但胜在生成图片保存到本地方便贴进文档和PPTpyecharts交互效果好生成的HTML图表能在浏览器里动答辩演示时更抓眼球。我的做法是二者并用整体趋势图用matplotlib需要交互的排行榜用pyecharts。1.3 数据模型设计三张表的字段规划既然要支撑分析数据的原始字段必须丰富。我在设计表结构时花了较多心思因为后期所有分析都依赖这一步。三张表的公共字段包括字段名电影音乐图书标题title片名title专辑名title书名首要创作者director导演artist表演者author作者年份year上映年份year发行年份year出版年份类型genre剧情/喜剧/科幻style摇滚/民谣/电子category小说/历史/科技评分ratingratingrating评价人数rating_countrating_countrating_count演职员/扩展cast主演列表track_list曲目表publisher出版社这里有一个容易忽略的细节评价人数是个关键字段。豆瓣评分只展示到小数点后一位但评价人数能精确反映热度分析“评分和热度是否相关”时这个字段就是核心衡量指标。2. 爬虫模块的合规设计与实现细节爬虫是整个系统的数据源头也是技术含量最集中的部分。这个环节最容易出问题的不在于解析逻辑而在于请求策略和页面结构分析的周全程度。2.1 页面结构分析与URL规律总结豆瓣不同版块有不同的URL模式但都有规律可循。以电影Top250为例首页是https://movie.douban.com/top250往下翻页第二页URL变为https://movie.douban.com/top250?start25filter也就是说每页25条第二页就是从第26条到第50条。start参数的步长就是25。图书Top250的规律完全相同只是域名路径换成https://book.douban.com/top250?start25。音乐板块略有不同新碟榜或分类页是https://music.douban.com/collect?start30这类格式而且音乐专辑没有统一的250排行榜这时可以用分类标签页的列表列表来采集。页面里需要提取的字段分散在不同HTML结构中列表页如电影Top250的ul能直接拿到标题、评分、评价人数、一句话简介。但导演、主演、类型、年份等更详细的字段通常只在详情页里。所以我的做法是先从列表页拿基础数据并解析出详情页链接再去请求详情页补齐全量字段。这种“列表页详情页”的两段式采集是实战中非常通用的模式几乎适用于所有UGC网站。2.2 requests请求段与异常重试机制请求代码的核心不只是get请求本身而是稳定性和礼貌性。我的请求函数带了两层防护请求头伪装和重试机制。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, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/avif,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8 } def safe_request(url, max_retries3): for attempt in range(max_retries): try: resp requests.get(url, headersheaders, timeout10) if resp.status_code 200: return resp elif resp.status_code 403: sleep(2 * (attempt 1)) continue else: sleep(1) continue except requests.RequestException as e: print(f[重试{attempt1}] {url} 请求异常: {e}) sleep(2) return None其中User-Agent必须认真设置。豆瓣对无UA的脚本请求识别度极高稍有不规范就会直接返回403。正常情况下我们以time.sleep(random.uniform(1, 3))的频率来访问页面——这个延时既可以模拟真实用户节奏也符合站点运营方对正常访问流量的基本期待。礼仪提醒控制请求频率不是为了“作弊”而是为了给目标服务器留出充足的处理空间。一个负责任的爬虫开发者应该把“不造成对方访问压力”作为基本功。2.3 解析模块BeautifulSoup的选择器匹配解析我用的是BeautifulSoup的find和select方法。以电影条目详情页举例解析核心字段的代码通常长这样from bs4 import BeautifulSoup def parse_movie_detail(detail_html): soup BeautifulSoup(detail_html, html.parser) info {} title_tag soup.find(h1) if title_tag: info[title] title_tag.find(span, propertyv:itemreviewed).get_text().strip() year_tag soup.find(span, class_year) if year_tag: info[year] year_tag.get_text().strip(()) rating_tag soup.find(strong, class_ll rating_num) if rating_tag: info[rating] float(rating_tag.get_text().strip()) # 导演人员信息常位于“导演”角色的a标签中 directors soup.find_all(a, relv:directedBy) info[director] / .join(director.get_text() for director in directors) return info实际开发中细节坑是最多的。比如评分字段隐藏在strong.ll.rating_num里而标题真正的片名在h1标签内嵌套的span[propertyv:itemreviewed]中年份则在另一个带有year类的span里。如果直接soup.h1.get_text()会将片名和年份拼在一起得到“肖申克的救赎(1994)”后面清洗起来就很麻烦。所以解析时必须用细粒度选择器少用粗粒度的get_text()。另一个容易踩的坑是类型字段。豆瓣的类型都写在span[propertyv:genre]里多个类型会生成多个标签正确的做法是把所有匹配都收集起来再用“/”拼接。我第一次写代码时没留意“所有匹配”这个概念用了find只拿第一个导致喜剧片全被记成“剧情”后期统计类型分布时数据严重失真。2.4 反爬应对的常规策略豆瓣的反爬机制在教程里被讨论很多实际碰到的无非几种请求频率过高触发封禁IP特征大量请求返回418或403UA异常被识别需要登录才能查看的条目少数冷门条目或需要登录态才显示完整评分针对这三类问题我的解决路径很务实控制采集速度每两次请求之间至少间隔2秒并且随机抖动避免机器人式固定间隔。不使用公开代理池。个人课程设计级别的项目访问量很小完全用不着代理反而容易引入不稳定因素。个别需要登录态的场景手动登录一次把Cookie复制进代码即可适用范围是少量条目。有朋友可能会纠结“要不要解决验证码”我的答案是如果你的采集节奏足够礼貌根本不会触发验证码。与其研究绕过策略不如先把自己的代码处理好不要给服务器增加负担。3. 数据持久化与预处理环节爬虫把数据堆到内存里只是第一步数据分析系统的核心在于后续存储和清洗。3.1 SQLAlchemy ORM建表与入库实操定义模型类的代码非常直观我这里贴一段图书表的定义作为示例from sqlalchemy import create_engine, Column, Integer, String, Float, Text, DateTime from sqlalchemy.orm import declarative_base, sessionmaker from datetime import datetime Base declarative_base() class Book(Base): __tablename__ book_info id Column(Integer, primary_keyTrue, autoincrementTrue) title Column(String(100), nullableFalse, indexTrue) author Column(String(100), default) publisher Column(String(200), default) year Column(Integer, default0) category Column(String(100), default) rating Column(Float, default0.0) rating_count Column(Integer, default0) quote Column(Text) # 豆瓣条目下的一句话简介 detail_url Column(String(200), uniqueTrue) created_at Column(DateTime, defaultdatetime.now)建表时我给detail_url字段加了唯一约束这个是后面去重逻辑的关键依赖。如果爬虫因网络原因重试重复入库时直接通过uniqueTrue由数据库层面兜底去重比在业务代码里先查询再插入要可靠得多。入库时批量操作注意SQLAlchemy的session.bulk_insert_mappings比逐条add快很多。我当时爬了约2000条数据逐条插入明显卡顿改成批量后秒级完成。3.2 pandas数据清洗三板斧采集到的原始数据不会直接可用的。我清洗的经验总结为三板斧缺失值、异常值、格式统一。缺失值在豆瓣数据里很常见老电影没有类型标签、图书没有出版社、部分音乐专辑没有发行年份。处理策略要分情况讨论import pandas as pd df pd.read_sql(select * from book_info, engine) # 1. 评分缺失的条目要么是尚未评分要么是解析失败直接标记并过滤 df df[df[rating].notna()] # 2. 年份为0的无意义值做填充或剔除保留有效分布性 df df[df[year] 1900] # 3. 文本类字段的缺失值统一用 未知 填充 for col in [author, publisher, category]: df[col] df[col].fillna(未知)异常值方面最高评分的书籍不可能超过10评价人数不可能为负。针对这些越界数据要在分析前统一剔除。这里我的实践是保留一个pandas清洗函数每个表跑一遍把数据做完提交后转存成cleaned_xxx.csv这样后续做图表和写文档时不需要反复连数据库。3.3 数据合并的维度设计三类数据存储在三张表里做分析时往往需要横向拼在一起。比如论文答辩时如果想对比“电影、音乐、图书三类内容的评分整体分布差异”就需要把三张表的rating字段提取出来并加一个category标识列再用pd.concat合并。movie_df pd.read_sql(select title, rating, year from movie_info, engine) music_df pd.read_sql(select title, rating, year from music_info, engine) book_df pd.read_sql(select title, rating, year from book_info, engine) movie_df[category] 电影 music_df[category] 音乐 book_df[category] 图书 all_df pd.concat([movie_df, music_df, book_df], ignore_indexTrue)合并之后再做透视分析例如:category分组下评分的均值、中位数、标准差。年份与评分的关系看哪一年是电影/音乐/图书的“高光年份”。评价人数与评分之间的相关性。4. 核心功能与可视化展示模块数据分析系统的“分析”二字最终要落在图表和结论上。4.1 可视化需求清单与成品示例我做这个项目时一整张报告需要的主要图表类型是分析主题图表类型展示工具评分分布整体形态直方图 核密度曲线matplotlib / seaborn历年产出数量趋势折线图pyechartsTop20高评分作品排行横向条形图pyecharts / matplotlib类型占比构成饼图或环形图pyecharts评分与评价人数关系散点图 回归线matplotlib我记得最有说服力的一张图是电影和图书的“评价人数 vs 评分”散点图。数据拉出来之后能看到一个明显的右上方稀疏分布趋势——高评分高人气作品永远是少数而绝大多数作品集中在评分7-8分、评价人数几千这个区间。这个发现被写进课设报告后答辩老师追问了好几分钟反而是加分项。4.2 pyecharts交互式看板的搭建思路pyecharts生成的是HTML页面可以嵌入图片或做成一个本地看板。一个简单的评分Top20条形图核心代码量其实不大from pyecharts.charts import Bar from pyecharts import options as opts top20 df.nlargest(20, rating) bar ( Bar() .add_xaxis(top20[title].tolist()) .add_yaxis(评分, top20[rating].round(1).tolist()) .set_global_opts( title_optsopts.TitleOpts(title豆瓣图书Top20评分排行), xaxis_optsopts.AxisOpts(axislabel_optsopts.LabelOpts(rotate30)), ) ) bar.render(book_top20.html)这里要用nlargest(20, rating)而不是sort_values(..., ascendingFalse).head(20)因为nlargest用的是快速选择算法效率更高逻辑也更简洁。4.3 Word文档与答辩PPT的材料组织很多人忽略了文档和PPT在整体项目中的占比。其实课设和毕设的评分中文档权重常常占到40%以上。我的经验是文档结构务必跟上系统架构走第1章课题背景与研究意义重点写清楚为什么要做豆瓣数据分析。第2章相关技术介绍requests、BeautifulSoup、SQLAlchemy、pandas各写一页。第3章系统分析与设计这是重头戏画清楚三层架构图和数据库ER图表格列出每张表的字段设计。第4章系统实现贴核心代码片段配合关键运行截图。第5章结果分析把直方图、折线图、散点图插进来每张图给两三百字的结论解析。第6章总结与展望写自己的心得体会和改进方向。PPT则遵循“每页不超过三句话”原则。答辩评委最在意的是你做了什么数据从哪来结论是什么所以封面、目录、技术栈、系统架构图、爬虫流程、数据库设计、可视化结果、结论这几页必须内容精炼且配有截图。我开发过程中做了一键导出功能图表自动生成成PNG/HTML文件所有中间结果CSV自动保存到output目录。这样写文档时截图直接取用根本不需要重新跑一遍图。这个细节虽然不起眼却能节省大量时间。5. 开发全流程踩坑实录与排查技巧最后这部分我把自己反复折腾出来最值得分享的坑按排雷顺序写出来。每一个都是真实发生的而且都不怎么容易第一时间查出来。5.1 403状态码编年史403是爬虫新手最常碰见的报错。初期我一度以为是自己的IP被拉黑后来发现原因极简单忘记设置headers。requests默认的User-Agent是python-requests/2.31.0这种UA特征在豆瓣面前等同于直接明牌“我是爬虫”。排查思路先确认是不是偶发连续请求几次看规律。再检查headers里UA是否完整Cookie是否有必要带上。最后检查采集间隔是否过短。按这个顺序排查基本能定位95%的403问题。5.2 中文编码乱码问题BeautifulSoup解析文本时偶尔出现乱码根因是页面实际编码与resp.encoding推断不一致。豆瓣页面统一是UTF-8但某些重定向页面或CDN节点可能返回其他编码。解决方案是使用resp.content手动指定编码resp requests.get(url, headersheaders, timeout10) resp.encoding utf-8 html resp.text对于其他我们无法确定编码的网页更稳妥的做法是用requests.get()后直接resp.apparent_encoding来动态推断让resp.text按实际编码解析不过这会牺牲一点点性能好在豆瓣完全不需要担心。5.3 评分字段float强转失败的异常数据清洗时遇见过一个极其隐蔽的坑部分评价人数超长的条目豆瓣在页面里显示成“9.7万”这种简写格式直接用float()强转会报ValueError。解决方案是先写一个单位转换函数def parse_wan_count(text): text text.strip() if 万 in text: number float(text.replace(万, )) return int(number * 10000) return int(text)后来我在发现图书板块某些条目评价人数会显示为“4.6万”这种格式用int()转换也会报错正是这个函数帮忙化解了bug。此类细节如果不在清洗阶段处理好后面做散点图和排序时一旦对上千行数据执行强转程序就会中断。5.4 反爬与DataFrame索引错乱的坑多轮清洗后DataFrame的索引会因过滤变得不连续。如果直接按索引切片或拼接可能会引用到不存在的行导致分析结果错乱。我的习惯是在清洗最后一步总是加上df.reset_index(dropTrue)让索引重新归零。这个操作在执行concat、merge之前做尤其重要否则莫名的行错位bug会浪费大量排查时间。5.5 项目目录结构与说明文档的组织项目做到后期目录结构一定要干净清晰不然交作业前自己都会迷路。我的最终目录布局给大家参考DoubanDataAnalysis/ ├── crawler/ │ ├── movie_crawler.py │ ├── music_crawler.py │ ├── book_crawler.py │ └── common/公共请求函数与解析工具 ├── database/ │ ├── models.py │ └── db_manager.py ├── analysis/ │ ├── clean_data.py │ ├── movie_analysis.py │ ├── music_analysis.py │ └── book_analysis.py ├── visualization/ │ ├── charts/生成的图片和HTML │ └── dashboard.py ├── output/ │ ├── movie_cleaned.csv │ ├── music_cleaned.csv │ └── book_cleaned.csv ├── docs/ │ ├── 课程设计报告.docx │ └── 答辩PPT.pptx ├── requirements.txt └── README.md这种结构的企业级习惯会让你在答辩时更受认可说明你不只是会写脚本还有软件工程的基本素养。README里写清楚环境安装方式和运行命令交接项目时别人能直接跑起来这比代码本身更能体现系统完整性。6. 项目扩展与心得收尾跑通整个系统只是起点这个项目的扩展空间其实非常大。如果你时间充裕以下方向能大幅提升项目上限。推荐做的是预测模型的引入。基于已有历史评分数据用线性回归或决策树去预测一部作品的评分区间特征可以用年份、类型、评价人数等。这在课设里是妥妥的加分项并且技术上也不会特别复杂一个带sklearn.train_test_split的简单回归模型即可。还有一种是评论情感的粗粒度分析。爬取热门条目下的评论做分词和情感值计算输出一个情感分布饼图。这需要安装jieba和snownlp模块成本低但产出的结果很有话题性。比如“高评分作品是否一定有更低负面评论比例”这个结论放在报告里非常有说服力。我在实际完成这个项目后最大的体会是课程设计的本质是训练你解决一个“全流程小问题”的闭环能力而不是追求技术多么高深。爬虫爬下来只是开始数据存得规范、清洗得干净、分析得有逻辑、表达得清晰这些都是同样的价值甚至比爬虫本身更值得花时间打磨。最后再分享一个小技巧项目全程用Git管理版本哪怕只是一个人开发。每完成一个模块提交一次commit消息写清楚“完成xxx模块”。答辩时老师如果看到你的提交记录你能自己主动展示开发过程和迭代顺序这个印象分是很加分的作品也显得更扎实。这套豆瓣数据分析系统承载的不仅是一个打分任务更是一条完整的工程化思维训练路径。按着文章里的结构一步步来你也能做出一套经得起推敲的完整作品。
返回列表