ARTICLE DETAIL

资讯详情

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

Python爬虫与数据可视化实战:豆瓣数据采集清洗全流程详解

Python爬虫与数据可视化实战:豆瓣数据采集清洗全流程详解 简介本资源是一套面向本科毕业设计的Python数据挖掘与可视化实战项目聚焦豆瓣电影/图书数据采集、清洗与多维呈现适用于计算机、大数据、信息管理等专业学生完成课程设计或毕设选题。项目完整实现网络爬虫开发、Pandas数据清洗、Matplotlib/Seaborn图表绘制及Bootstrap前端交互式展示涵盖从原始数据获取到成果汇报的全流程技术栈。压缩包共103个文件含8个HTML页面、20个JS脚本、11个CSS样式表、38张PNG图表及4个核心Python爬取与分析脚本辅以CSV原始数据与字体资源整体3.54MB结构清晰、即开即用。已有9590人学习下载提供可直接运行的源码、响应式可视化界面及典型数据处理范式特别适合缺乏真实项目经验的学生快速掌握数据采集—清洗—分析—展示闭环能力。 豆瓣的数据结构这些年没少折腾人尤其做毕业设计的时候很多人上来就想着“我爬一下豆瓣电影Top250再画几个图表交差”。真做起来才发现反爬策略、数据字段解析、可视化选型每一环都能卡你好几天。这套毕业设计源码我断断续续改过不少次核心思路就是一条用Python把豆瓣网站的数据爬下来清洗干净再用可视化把“数据里藏着的信息”呈现出来。整个过程会碰到很多细节坑这篇就把完整实现路径、参数设计思路、常见报错和排查方法一次讲透。不管你是打算直接复现还是想改造成自己的课题方向都能找到对应的落地参考。1. 整体设计与思路拆解1.1 核心需求解析这个毕设到底要做什么从交付物来看毕业设计题目是“基于Python豆瓣网站数据爬取与可视化实现”源码包已经说明了两条主线一是数据采集二是数据呈现。很多同学容易把重心全放在“爬”上忽略了可视化的价值。实际上答辩时老师更关心的是两件事你的数据从哪来、怎么保证可信你的可视化呈现出了什么结论、能不能讲出故事。我做完这版源码之后重新梳理了整个需求可以拆成下面这几个核心模块爬虫模块负责从豆瓣指定页面比如电影Top250、某类图书榜单、短评列表获取HTML页面解析出结构化字段排名、片名、评分、评价人数、导演、主演、年份、国家、类型等。数据清洗模块对爬取结果做去重、空值处理、类型转换、字段拆分。豆瓣页面里不少字段是混在一起的比如导演和主演、年份和地区必须二次整理。数据存储模块把清洗后的数据写入本地常见选择是CSV或SQLite方便后续读取与可视化。可视化模块基于清洗后的数据生成统计图表比如评分分布直方图、年份产量折线图、国家/地区饼图、类型词云、评分与评价人数散点图等。数据解读模块在答辩或者报告中基于图表输出结论比如“近年豆瓣高分片集中在哪类题材”“评分和评价人数是否存在正相关”。很多初次做毕设的同学忽略了第5个模块最后摆了一堆图表却讲不出所以然。我自己在实际做的时候会提前想清楚答辩时我要围绕哪3个核心问题来讲解然后让图表为这3个问题服务。1.2 技术选型为什么是Python、Requests、Pandas和Pyecharts技术选型不能只看着“热门”要结合自己的掌握程度和项目完成度来权衡。豆瓣爬取可视化这个课题最常见的组合是Python 3.8语法友好生态成熟爬虫和数据分析都有现成库。Requests BeautifulSoup发起HTTP请求并解析静态HTML。豆瓣的电影列表页、榜单页基本都是服务端渲染的用这一套组合足够。Pandas做数据清洗、聚合统计、DataFrame操作比纯手写字典和列表方便太多。Pyecharts生成交互式ECharts图表导出HTML后可以直接在浏览器里看悬浮提示、数据缩放都自带比Matplotlib更适合展示型毕设。SQLite / CSV数据存储SQLite适合做“有查询交互”的增强功能CSV适合快速提交数据成果。为什么不用ScrapyScrapy确实强大但对于毕设来说学习成本偏高而且豆瓣页面结构并不复杂Requests BeautifulSoup在代码可读性上更友好答辩讲解时也更容易说清楚每一步做了什么。对于“快速交付”的毕设项目来说简单、可控、能讲明白优先级高于工程上的“优雅”。还有一点值得说明虽然豆瓣有开放API但很多接口需要申请key且数据字段有限。爬虫方案的灵活性在于你想爬哪个列表页、想解析哪些字段都可以自己定制。不过要注意爬虫必须遵守目标网站的robots协议和服务条款控制请求频率不能对目标服务器造成压力。2. 爬虫模块核心细节与实操要点2.1 目标页面与请求头配置豆瓣电影Top250是爬虫入门必练的经典目标页面URL规则非常规律https://movie.douban.com/top250?start0filter https://movie.douban.com/top250?start25filter https://movie.douban.com/top250?start50filterstart参数每页偏移25条一共10页250条。这个规律意味着我们可以通过for循环自动翻页不需要单独解析“下一页”的URL。真正容易出错的地方是请求头。豆瓣对异常请求的识别很敏感如果没有设置User-Agent大概率直接返回418或者302跳转到验证页。我实际用的请求头配置如下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, Connection: keep-alive }有了这些请求头正常情况下可以稳定请求。要注意的是Requests库默认携带的User-Agent是python-requests/x.x.x一眼就会被识别为脚本。设置头信息时不需要复制浏览器里所有的请求头只需要保留关键几个即可。除此之外如果频繁请求豆瓣会要求登录或者出现验证码我建议将每次请求的间隔设置在2到4秒之间。2.2 页面解析BeautifulSoup提取关键字段拿到HTML之后用BeautifulSoup解析结构。以Top250为例每个电影条目都包裹在li标签中内部有div classitem。解析的核心代码块大致是这样的from bs4 import BeautifulSoup import requests import pandas as pd url https://movie.douban.com/top250?start0filter resp requests.get(url, headersheaders) soup BeautifulSoup(resp.text, html.parser) movie_items soup.select(.grid_view .item) for item in movie_items: rank item.select_one(.pic em).get_text() title item.select_one(.hd .title).get_text() info item.select_one(.bd p).get_text(stripTrue) quote item.select_one(.quote .inq) quote_text quote.get_text() if quote else rating item.select_one(.rating_num).get_text() people item.select_one(.star span:nth-of-type(4)).get_text() img item.select_one(.pic img)[src]我在第一次写解析逻辑时踩过一个坑info字段里包含导演、主演、年份、国家、类型等多种信息但它们是混在一段文本中的需要进一步切割。更麻烦的是不同电影的信息段格式略有差异有的主演信息是完整的有的却是“主演: 某某 / 某某”有的甚至没有主演行。这种“脏数据”恰恰是展示数据清洗能力的好素材答辩时能讲很多内容。字段解析完成之后先存成Python字典列表再转成DataFrame随后进入清洗阶段。2.3 翻页循环与异常处理翻页是爬虫模块的重头戏。不能简单地抛出一个for循环就完事需要考虑到网络波动、超时、反爬拦截等异常。我的处理方式是在循环内做重试和退避import time all_data [] for page in range(10): start page * 25 url fhttps://movie.douban.com/top250?start{start}filter for retry in range(3): try: resp requests.get(url, headersheaders, timeout10) if resp.status_code 200: # 解析数据追加到all_data break elif resp.status_code 403: print(触发反爬等待更长时间) time.sleep(10) else: print(f异常状态码: {resp.status_code}) time.sleep(5) except requests.exceptions.RequestException as e: print(f请求异常: {e}) time.sleep(5) time.sleep(3)为什么建议加这层“异常处理 重试”因为爬虫期间一次网络抖动就可能导致整批数据缺失如果没有断点续爬逻辑就得清空重来。最稳妥的方案是每爬完一页就立即保存一次增量数据不要等全部完成再统一写文件。注意爬虫不是无限重试的暴力过程一定要设置合理的重试次数和间隔。频繁请求被ban之后靠单纯增加等待时间未必能立刻恢复需要配合更换User-Agent、降低频率甚至过段时间再继续。3. 数据清洗与存储的实现方案3.1 字段拆分从混合文本到结构化数据豆瓣电影列表页中info字段的原始文本大概是这样的导演: 弗兰克·德拉邦特 Frank Darabont 主演: 蒂姆·罗宾斯 Tim Robbins /... 1994 / 美国 / 犯罪 剧情清洗的目标是把它拆成“导演”“主演”“年份”“国家/地区”“类型”五个独立字段。这里我用的是规则解析先按换行拆分出“导演/主演行”和“年份/地区/类型行”再对每一行做进一步切割。def parse_info(info_text): lines info_text.split(\n) director actors year country genres for line in lines: if 导演 in line: director line.replace(导演:, ).strip() if 主演 in line: actors line.replace(主演:, ).strip() if / in line and len(line.strip()) 30: parts [p.strip() for p in line.strip().split(/)] year parts[0] if parts else country parts[1] if len(parts) 1 else genres parts[2] if len(parts) 2 else return director, actors, year, country, genres不过用split(\n)时有坑不同页面下p标签换行可能是br/也可能带空白字符所以最好统一替换再分割。我在代码里加了replace(\xa0, )和replace(br/, \n)等预处理避免乱码和分割失败。年份字段提取后是字符串可视化前需要转成int。评分是字符串也需要float转换。评价人数类似“1537246人评价”需要去掉“人评价”再转成int。这些转换如果直接硬编码万一格式变化就容易报错所以更稳妥的做法是用正则import re def extract_number(text): nums re.findall(r\d, text) return int(.join(nums)) if nums else 03.2 去重与缺失值处理数据量不大时去重很简单用drop_duplicates()按片名和导演去重即可。真正需要留意的是缺失值部分条目可能没有“主演”字段或者没有“引用语”也就是那句经典台词。缺失的处理策略取决于后续使用方式如果缺失字段不参与统计可以直接置为空字符串。如果缺失字段要作为图表维度比如按导演统计评分建议单独剔除或标记为“未知”。我一般会在清洗完成后做一次数据质量检查输出每列的空值比例和类型信息写进报告里答辩时直接说“我们经过清洗获得250条有效数据覆盖率接近100%”。这比单纯说“爬了250条”更有说服力。3.3 存储到CSV和SQLite清洗完成的数据我会同时保存两份douban_top250.csv方便导师直接用Excel打开核对。douban.dbSQLite方便后续做条件查询和存档。SQLite的写入方式很简单用to_sql一行就能完成import sqlite3 from sqlalchemy import create_engine engine create_engine(sqlite:///douban.db) df.to_sql(movie_top250, engine, if_existsreplace, indexFalse)如果不想引入SQLAlchemy也可以用Python自带的sqlite3库逐行插入但对于250条数据用Pandas的to_sql已经足够方便。从可复现角度看CSV文件和SQLite文件都建议放进项目data/目录README里注明“数据采集时间”和“数据量”增强可信度。4. 可视化模块的实现与参数解析4.1 可视化图表选型用Pyecharts做出展示级效果很多教程推荐Matplotlib但对于毕业设计项目我最终选了Pyecharts。原因很直接Pyecharts生成的图表是HTML页面支持鼠标悬浮展示数值、区域缩放、图例开关视觉上比Matplotlib的静态图更有冲击力。答辩时可以直接投屏演示交互效果也可以导出PNG放入论文。一个典型应用是评分分布直方图用Pyecharts的Bar实现from pyecharts.charts import Bar from pyecharts import options as opts rating_counts df[评分].round(1).value_counts().sort_index() bar ( Bar() .add_xaxis([str(x) for x in rating_counts.index]) .add_yaxis(电影数量, rating_counts.values.tolist()) .set_global_opts( title_optsopts.TitleOpts(title豆瓣Top250评分分布), xaxis_optsopts.AxisOpts(name评分), yaxis_optsopts.AxisOpts(name数量), datazoom_opts[opts.DataZoomOpts()], ) ) bar.render(charts/rating_dist.html)这里加了datazoom_opts数据缩放组件当评分维度很多时用户可以在页面上拖拽缩放体验比静态图好很多。4.2 五类核心图表的实现思路我的源码包中可视化部分一共实现了5张图正好对应毕设里“数据可视化分析”章节的5个小节评分分布直方图Bar DataZoom观察评分集中在哪个分数段Top250中8.5分以上的电影数量。年份数量趋势折线图Line统计不同年份的电影数量观察出片高峰。国家/地区分布饼图Pie看看哪个国家和地区的电影入围最多。类型词云WordCloud统计类型字段中各关键词的出现频次形成直观词云。评分与评价人数散点图Scatter探查两个数值字段是否存在线性关系。其中第5个图很能体现“数据分析”的深度。代码实现大约是这样from pyecharts.charts import Scatter scatter ( Scatter() .add_xaxis(df[评价人数].tolist()) .add_yaxis(电影, df[评分].tolist()) .set_global_opts( title_optsopts.TitleOpts(title评分与评价人数关系), xaxis_optsopts.AxisOpts(name评价人数, type_log), yaxis_optsopts.AxisOpts(name评分, min_8, max_9.8), visualmap_optsopts.VisualMapOpts(max_9.5), ) ) scatter.render(charts/scatter_rating_people.html)这里的横轴我特意设置了type_log对数轴。因为评价人数跨度可以是从几千到几百万如果用线性轴数据点会大量挤在左侧看不出规律对数轴能让分布更清楚。这是很多初学者不会注意到的细节但放在论文或答辩里会显得很专业。4.3 全局布局与仪表盘整合只输出孤立图表在“可视化”层面还不够完整。我在项目里加了一个简单的仪表盘页面利用Pyecharts的Tab组件把5张图组合到一个HTML页面方便演示时一键浏览。from pyecharts.charts import Tab tab Tab() tab.add(bar, 评分分布) tab.add(line, 年份趋势) tab.add(pie, 国家/地区) tab.add(wordcloud, 类型词云) tab.add(scatter, 评分与评价人数) tab.render(charts/dashboard.html)这个Tab页面非常适合作答辩演示的开场打开一个HTML所有图表都在左侧标签栏里点击切换即可。相比之下如果每个图单独打开一个文件演示时来回切换窗口会很乱。5. 实操过程与关键代码解读5.1 项目目录结构我习惯在源码交付时保持清晰的目录划分对毕设来说目录本身就是“工程能力”的体现。这份源码的目录结构大致是douban_project/ ├── crawler/ │ ├── spider.py # 爬虫主逻辑 │ ├── parser.py # 页面解析函数 │ └── headers.py # 请求头配置 ├── analysis/ │ ├── clean.py # 数据清洗 │ └── stats.py # 统计分析 ├── charts/ │ ├── dashboard.html # 整合仪表盘 │ ├── rating_dist.html │ ├── year_trend.html │ ├── country_pie.html │ ├── genre_wordcloud.html │ └── scatter_rating_people.html ├── data/ │ ├── douban_top250.csv │ └── douban.db ├── requirements.txt └── README.md为什么把爬虫和解析分成两个模块因为在实际调试时页面结构可能发生变化解析逻辑会频繁改动。如果把请求和解析都写在一个文件里每次修改解析逻辑都要重跑请求浪费大量时间。拆分开后甚至可以把上次抓下来的HTML保存到本地用本地文件调试解析代码既快又不打扰服务器。5.2 完整运行流程拿到源码后建议按下面步骤运行安装依赖pip install -r requirements.txt。主要依赖是requests、beautifulsoup4、pandas、pyecharts、sqlalchemy、lxml。运行爬虫python crawler/spider.py控制台会显示每一页的抓取状态。运行清洗python analysis/clean.py在data/目录生成清洗后的CSV。运行可视化python charts/generate_charts.py生成全部HTML图表到charts/目录。打开charts/dashboard.html查看可视化结果。理想情况下全流程几分钟内完成。如果中途出现反爬拦截或超时可以稍等片刻再重新运行因为断点保存逻辑会保证已爬取的数据不会丢失。5.3 参数计算与选择过程为什么设置2秒间隔关于爬虫请求频率我用的是“2到4秒随机间隔”。这个间隔不是随意拍脑袋定的。毕设场景下总共只有10页Top250数据总量很小完全没有必要高并发快速抓取。设置间隔的深层目的是降低对目标站点的压力同时降低被封概率。我在代码中使用了time.sleep(random.uniform(2, 4))随机间隔比固定间隔更接近真人操作。固定间隔容易被识别出机器行为规律。如果说得更直白一点这不是技术难度问题而是爬虫的使用方式是否合理、是否对目标站点保持基本尊重的问题。6. 常见问题与排查技巧实录6.1 豆瓣返回418、403怎么处理这是被问到最多的问题。出现418通常意味着请求头被识别为疑似爬虫403则是权限不足或IP被临时限制。我实际排查时会按顺序做这几件事检查User-Agent是否完整是否还是默认的python-requests。增加Referer字段一般可设置为https://movie.douban.com/。检查程序请求频率如果之前跑得太快休息5到10分钟再试。如果提示需要登录可以在代码中维护一个Cookie通过浏览器登录后复制Cookie字符串填入请求头。如果IP被限制最简单的办法是更换网络环境或等待一段时间不建议在毕设阶段研究更复杂的绕过方案。实操中我发现给请求头加上Referer后403被拒概率明显下降。虽然这不是必然有效的通用手段但成本低值得加。6.2 解析结果为空、字段缺失页面结构偶尔会调整如果soup.select(.grid_view .item)返回空列表先别急着改代码把响应内容保存成HTML文件用浏览器打开检查选择器是否还匹配。这是最直接的排查思路。我早期犯过的错误是凭记忆写选择器结果页面结构调整后全盘白费。正确做法是先打印一小段HTML肉眼确认标签结构。有些条目缺少“主演”或“引用语”需要在解析代码里用条件判断或异常捕获兜底避免以None.get_text()的形式直接抛AttributeError。6.3 Pyecharts图表不显示Pyecharts渲染出来是HTML如果直接在Jupyter Notebook里用render_notebook()需要确认环境是否装了对应插件。如果直接用浏览器打开render()生成的HTML一般没问题。有时图表在本地打开是空白原因多为文件路径错误或者JS加载失败。因为Pyecharts生成的HTML会从CDN加载ECharts脚本如果所在环境无法访问外网图表就无法渲染。解决办法是使用本地资源模式或者提前下载ECharts的JS文件放到项目目录。6.4 评价人数数值格式转换失败“人评价”字段在不同语言环境下格式可能有差异例如“1537246人评价”。直接用int()会报错用正则提取数字是稳定的方案。如果遇到“评价”两字中间存在特殊空格比如\u00a0连续空格正则\d依然能正确提取不会受影响。7. 经验心得与避坑总结做完这套项目我最大的感受是毕设源码不要追求“花哨”而要追求“闭环”。你可以不用分布式爬虫不用机器学习预测评分但一定要把从爬虫到清洗到可视化到结论分析的完整链路跑通每一步都有产出物这样答辩时才能有底气。再分享一个比较隐蔽的坑豆瓣列表页里有些片子的年份、地区字段是中文括号或全角括号和英文括号混在一起写正则切割时如果没处理好年份列会混进其他字符。我的处理方法是把所有全角括号都统一替换成英文括号然后再做分列操作。另外源码包里的README.md建议写清楚这几部分项目背景、环境依赖、运行步骤、代码结构说明、结果截图、参考文献。这不仅是给老师看的也是给未来的自己看的。我见过很多同学代码写完两周后自己都看不懂就是因为没有文档记录。最后一点关于数据量虽然题目是Top250但如果想加一点“研究深度”可以在同一套代码框架下再爬一个榜单比如豆瓣读书Top250或者某分类下的电影排行榜然后做对比分析。技术栈完全不变只需要修改URL和解析规则但论文中的“数据来源多样性”和“分析维度”立刻就上了一个台阶。我在实际运行中还有一个习惯每跑完一轮爬虫就把当时的HTML页面压缩存档标上日期。如果后续解析代码改坏了还能用历史HTML回归验证这个技巧在长时间维护爬虫项目时特别实用。毕业设计不一定需要做到这个程度但如果想让代码更有“工程感”这会是一个不错的亮点。本文还有配套的精品资源点击获取
返回列表