首页 / 资讯中心 / 文章详情

Python爬虫实战:TOP250电影数据采集与可视化分析

Python爬虫实战:TOP250电影数据采集与可视化分析 ★ FEATURED ARTICLE
简介面向Python学习者与毕设学生的豆瓣TOP250电影爬虫与数据分析可视化项目覆盖爬虫采集、数据清洗、数据库建表、可视化出图完整流程适合作为课程设计、期末大作业或毕业设计的参考源码。资源共11个文件包含3个Python程序文件分别负责爬取入库、清洗处理和可视化生成2个SQL脚本用于创建电影与评论明细数据库同时提供清洗后数据Excel、可视化HTML报告、爬虫文档报告docx及讲解PPT整包仅2.32MB结构紧凑便于下载后快速查阅与按需复用。目前已有571人学习使用代码注释清晰项目说明与PPT讲解能够帮助初学者快速理解从网络爬虫到数据分析可视化的各个环节。下载运行后即可看到豆瓣TOP250电影的排行、评分分布等可视化结果适合具备基础编程能力并希望积累完整项目经验的学习者参考二次开发。1. 把爬虫、数据分析可视化一次串完这份TOP250资源适合谁刚接触Python爬虫的开发者最容易遇到的情况是跟教程敲完了requests和BeautifulSoup抓下来几十条数据却不知道下一步能做什么。而真正要交毕业设计或课程设计时光有爬虫又不够还得有数据分析、可视化图表、一份能讲清楚的说明文档。这份基于某评分平台TOP250电影榜单的爬虫与数据分析可视化项目恰好把整条链路补齐了源码负责抓数据pandas负责做统计matplotlib和词云负责出图说明文档和PPT负责把过程讲明白。适合正在找Python项目实例、需要完整源码做课设或毕设的开发者也适合想看看“爬虫之后还能干什么”的初学者直接复现。我把这个项目从下载到跑通的过程拆了一遍包括页面字段怎么选、反爬怎么绕、哪些地方最容易翻车以及最后怎么把它改造成一个可以反复使用的工具。下面按我实际的执行顺序来写。2. 先摸清数据源TOP250页面的结构、字段和反爬边界2.1 榜单页与详情页的关系字段从哪里取TOP250榜单页面上一部电影通常包含这些信息片名、年份、地区、导演、主演、评分、评价人数、一句话短评。这8个字段基本覆盖了后续数据分析的所有维度所以不需要再去逐一点开250个详情页只抓榜单页就能满足需求。我一般先把榜单页的HTML结构打印出来确认每个字段所在标签的class属性。常见的做法是选中一个电影条目观察它的div classitem结构然后逐个定位。这个项目的爬虫源码里用的就是基于item条目和info信息块的选择器写法。有一个细节要注意同一个页面上“评分”和“评价人数”都在star这个class里需要用span的序号来区分。选出第1个span是星星第2个是评分第4个是评价人数。这种细节最好先确认代码写起来才不会有歧义。数据字段的选型也要同步考虑后续分析。比如“年份”后面要做分布统计就应该存纯数字如2009而不是“2009年”这种带单位字符串地区字段会出现“中国大陆 / 香港”这种多值情况落到CSV里后清洗时必须单独处理。项目里已经把这些字段存成了独立的列省掉了重新拆分的麻烦。2.2 反爬机制UA、Cookie、限速和重试TOP250的页面对爬虫不算特别严格但设置得太粗糙照样会被拦。第一次尝试时我用默认的Python UA直接请求很快返回了418状态码。之后把请求头补全问题就消失了。常见做法是同时带上User-Agent、Accept、Accept-Language这几个请求头其中UA建议填成浏览器的版本而不是Python的默认值。限速是另一个必须做的动作。榜单总共有10页每页25条数据如果3秒内连续请求完即使不被封也会触发临时限制。项目源码里设置了一个随机延时每次请求后time.sleep(random.uniform(1, 3))这个1到3秒的随机区间在实际测试中效果不错。再配合重试机制如果请求失败间隔2秒后重试最多重试3次。我把反爬相关的参数都集中放在脚本开头的配置区方便调整# config 区域 BASE_URL https://目标站点/top250?start{}filter # 分页规律是每页25条 HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, } DELAY_RANGE (1, 3) # 随机延时区间单位秒 MAX_RETRIES 3 # 单页最大重试次数 TIMEOUT 10 # 请求超时时间这里的start{}对应分页参数第1页是0第2页是25依此类推。延时设置成随机区间而不是固定值是为了避免形成固定频率的访问节奏被识别成脚本特征。超时时间给到10秒是为了在网络波动时不至于因为等待过久拖慢整个抓取流程。2.3 数据落盘CSV是最稳妥的中间格式抓取完成后的数据我建议先落到CSV而不是直接塞进数据库。原因有三个一是CSV方便用Excel或WPS直接打开检查中途出了问题很容易定位二是后面做pandas分析时read_csv一步就能加载三是这个项目的可视化部分不依赖数据库CSV足够支撑。源码里落盘时用了一个容易踩坑的细节——编码。如果你用encodingutf-8写入CSVExcel打开时大概率会乱码。项目里用的是utf-8-sig这个格式在文件开头加了BOM标记Excel能正确识别成UTF-8。数据库和统计分析读取时也统一按utf-8-sig处理。如果你准备把这个CSV导入MySQL之类的数据库注意先转成不带BOM的UTF-8否则首字段名会带上不可见字符。3. 爬虫代码落地从请求到CSV的完整实现3.1 请求模块带重试的通用get函数爬虫的骨架是请求函数这个项目里没有把请求逻辑散落在各处而是封装成了一个通用函数。这样单页请求失败时重试逻辑只写一遍代码结构也更干净。import requests import time import random def fetch_page(url, headersHEADERS, retriesMAX_RETRIES): for attempt in range(retries): try: resp requests.get(url, headersheaders, timeoutTIMEOUT) if resp.status_code 200: resp.encoding resp.apparent_encoding # 动态识别页面编码 return resp.text else: print(f第{attempt1}次请求返回状态码: {resp.status_code}) except requests.RequestException as e: print(f第{attempt1}次请求异常: {e}) time.sleep(DELAY_RANGE[0] random.random() * (DELAY_RANGE[1]-DELAY_RANGE[0])) return Noneresp.encoding resp.apparent_encoding这行很关键。目标页面返回的响应头里写的可能是老旧的字符集但页面实际内容是UTF-8。用apparent_encoding让requests去探测真实编码能避免后续解析时出现乱码。重试逻辑里每次失败都会走随机延时不会出现失败后立即猛冲的情况。3.2 解析模块BeautifulSoup提取8个字段解析逻辑是爬虫的核心。用BeautifulSoup定位条目然后对每个字段做提取和清洗。这里我按项目源码的写法用select_one精准定位from bs4 import BeautifulSoup import csv def parse_page(html_text): if html_text is None: return [] soup BeautifulSoup(html_text, lxml) items soup.select(div.item) # 每个电影条目 rows [] for it in items: title it.select_one(span.title).get_text(stripTrue) info_lines it.select_one(div.bd p).get_text(\n, stripTrue).split(\n) year info_lines[0].split(/)[0].strip() # 年份在导演信息段的首位 rating it.select_one(span.rating_num).get_text(stripTrue) people_tag it.select(div.star span)[3] # 第4个span是评价人数 people people_tag.get_text(stripTrue) quote_tag it.select_one(p.quote) quote quote_tag.get_text(stripTrue) if quote_tag else rows.append([title, year, rating, people, quote]) return rows字段提取时有两个地方容易出错一是年份并不是单独放在一个标签里而是混合在“导演、主演、年份、地区”的整段文字里必须先按/拆分再取首段二是quote一句话短评并不是每部电影都有用if quote_tag else 做兜底避免None值导致写入CSV时报错。解析完一页后把数据追加到CSV里边抓边存。这比全部抓完再一次性写入要稳得多中途挂了还能保留已经抓到的数据。3.3 主流程翻页循环与断点续抓主流程的核心是按分页参数循环抓取10页每页25条。这里需要一个计数器来控制页数同时判断当前是否已经有数据落盘避免重复运行脚本时抓出250条以上的重复数据。def main(): csv_path data/movie_top250.csv seen_titles set() # 如果CSV已存在先读取已有片名做到断点续抓 if os.path.exists(csv_path): with open(csv_path, r, encodingutf-8-sig) as f: reader csv.DictReader(f) for row in reader: seen_titles.add(row[title]) with open(csv_path, a, newline, encodingutf-8-sig) as f: writer csv.writer(f) if not seen_titles: # 首次新建时写入表头 writer.writerow([title, year, rating, people, quote]) for page in range(10): url BASE_URL.format(page * 25) html fetch_page(url) rows parse_page(html) for r in rows: if r[0] in seen_titles: continue writer.writerow(r) seen_titles.add(r[0]) print(f第{page1}页完成当前累计 {len(seen_titles)} 条) time.sleep(random.uniform(DELAY_RANGE[0], DELAY_RANGE[1])) if __name__ __main__: main()这个循环里有两个关键设计。第一用seen_titles集合记录已抓片名脚本重跑时不会产生重复数据第二用追加模式写入而不是覆盖模式配合断点集合实现“挂了接着跑”的效果。每次写入后也做了延时而不是只在一页开始前延时一次这样节奏更接近人工浏览。4. 数据分析与可视化pandas清洗加matplotlib出图4.1 pandas清洗处理年份、地区和多值字段爬下来的CSV里年份字段基本干净但地区字段可能出现“中国大陆 / 香港”这种多值情况。做分析前先做一步拆分。常见做法是只取第一个地区作为主分类后面做饼图时不会因为类别太多而显得拥挤。import pandas as pd df pd.read_csv(data/movie_top250.csv, encodingutf-8-sig) df[year] pd.to_numeric(df[year], errorscoerce) # 强制转数值 df[main_area] df[area].str.split(/).str[0].str.strip() # 取首个地区errorscoerce的作用是遇到无法解析成数字的年份时置为NaN不会让整列崩溃。拆分地区时用str.split(/)再取第0个元素能把“中国大陆 / 香港”简化为“中国大陆”。处理后要顺手看一遍数据质量检查是否有空值、是否有年份为0的记录、评分是否都在0到10之间。这些检查用df.info()和df.describe()就能做到。项目里的说明文档专门提醒过这一步因为后续所有图表都建立在这个清洗结果之上如果清洗时漏了脏数据可视化阶段会看到各种离谱的突刺。4.2 评分分布、年份趋势与地区占比三张图拿到干净数据后我做了三个维度的可视化这三个图基本就是PPT里的核心内容。第一个是评分分布直方图直接把250部电影的评分切成10个区间看集中在哪个分数段import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # 解决中文显示问题 plt.rcParams[axes.unicode_minus] False # 解决负号显示问题 df[rating].plot(kindhist, bins10, edgecolorwhite) plt.xlabel(评分) plt.ylabel(电影数量) plt.title(TOP250电影评分分布) plt.savefig(output/rating_dist.png, dpi200, bbox_inchestight)bins10把评分区间切成10份每份是1分。保存图片时dpi200保证PPT里放大不糊bbox_inchestight会自动裁掉多余的留白。第二个是年份趋势折线图统计每年上榜的电影数量year_count df[year].value_counts().sort_index() year_count.plot(kindline, markero) plt.xlabel(年份) plt.ylabel(上榜数量) plt.title(TOP250电影年份分布趋势) plt.savefig(output/year_trend.png, dpi200, bbox_inchestight)从这张图能明显看出哪些十年是经典电影的高产期。这里要注意折线图适合看趋势但年份跨度大时数据点会很密建议在plot里加markero让数据点可见。第三个是地区占比饼图area_count df[main_area].value_counts().head(8) area_count.plot(kindpie, autopct%.1f%%) plt.ylabel() plt.title(TOP250电影主要地区占比) plt.savefig(output/area_pie.png, dpi200, bbox_inchestight)head(8)限制只显示前8个主要地区其他归为“其他”饼图不会出现几十个挤在一起的扇区。autopct%.1f%%控制显示一位小数的百分比否则占比数字会挤成一团。4.3 词云从一句话短评中提取关键词这个项目比普通爬虫项目进阶的地方是还加了一个词云图。数据源不是片名而是每部电影的一句话短评。先做中文分词再生成词云import jieba from wordcloud import WordCloud comments .join(df[quote].dropna().tolist()) seg_list jieba.cut(comments) seg_text .join(seg_list) wc WordCloud( font_pathfonts/simhei.ttf, # 中文字体路径不给会出方块 width800, height600, background_colorwhite, max_words150, ).generate(seg_text) wc.to_file(output/wordcloud.png)font_path必须指定一个支持中文的字体文件否则生成的词云图全是方块。max_words150控制词云中显示的词数量调大了会出现太多低频词调小了会丢掉有意义的关键词150是我试过几次后比较合适的值。dropna()是去掉没有短评的电影直接拼接会出现None导致分词报错。5. 避坑汇总编码乱码、字段漏抓、频繁请求封禁的翻车现场5.1 用Excel打开CSV全乱码编码选错没有后悔药现象抓取正常代码不报错但用Excel打开生成的CSV文件中文全部变成乱码。原因写入CSV时用了encodingutf-8。Excel对于没有BOM标记的UTF-8文件会默认按本地编码如GBK解析中文字符自然显示不对。解决写入时改用encodingutf-8-sig。这个编码会在文件开头写入一个不可见的BOM头Excel识别到后就会按UTF-8处理。遇到之前已经生成的乱码CSV不用重跑爬虫用文本编辑器另存为UTF-8 with BOM格式即可。5.2 评分和评价人数全是空值标签层级搞反了现象电影标题都正常抓到年份也正常但评分和评价人数两列全是空。原因用了select去查找span如果CSS选择器定位到了别的同名class或者选择的元素比预期早了一级找出来的就不是目标节点。特别是在div.star下有多个span同时存在下标没对上就会取到空值。解决先打印选中元素的get_text()确认内容再决定下标。评分字段直接select_one(span.rating_num)评价人数取select(div.star span)[3]。如果打印后发现下标对不上用len()输出span的个数逐个核对不要靠猜。5.3 抓了几页后请求返回403请求太快触发临时限制现象前3页正常第4页开始连续返回403或418脚本报错退出。原因请求频率超过了页面服务的容忍阈值。虽然设置了延时但延时区间是1到3秒实际打印日志发现经常在1秒出头就发出下一次请求整体节奏偏快。解决把延时区间放大到2到5秒同时配合重试机制。我在这个项目里把DELAY_RANGE调整成(2, 3)之后完整跑完10页没有触发过一次拦截。如果你需要频繁重跑建议每页延时后再加上一个随机数抖动进一步打乱访问节奏。5.4 年份字段混进了导演名字split切分时位置错位现象年份列里有部分行显示的不是“2009”这种数字而是导演或主演的名字。原因榜单页“导演 / 主演 / 年份 / 地区”是整段文字放在同一个p标签里的。按/拆分后年份可能不在第0位因为有的条目没有导演信息或主演信息缺失导致数组错位。解决不要用固定下标改为在拆分后的列表中逐个匹配正则表达式找到形如“2009”或“2008”的项作为年份找到包含“中国大陆”“美国”等关键字的项作为地区。正则写法re.search(r(\d{4}), part)。这样即使字段顺序变化也能准确提取。5.5 词云图输出全黑方块中文字体路径缺失现象代码不报错但生成的词云图里所有文字都是黑色方块。原因WordCloud默认字体不支持中文字符没有显式指定中文字体路径时渲染出来的就是方框。解决从系统字体目录里找一个中文字体文件比如simhei.ttf或msyh.ttf复制到项目的fonts目录并把font_path指向它。如果用的是服务器环境先确认字体文件存在不要直接给一个不存在的路径。还有一个通用排查技巧每次代码改动后先只抓1页数据验证再跑完整流程。10页全量抓取遇到问题再回头排查时耗时是单页验证的10倍。6. 进阶技巧把一次性爬虫改造成可复用的工具包这个项目跑通之后我建议你做一件让代码价值翻倍的事把脚本参数化让它不再只是“TOP250专用爬虫”而是能切换目标榜单、控制抓取范围、自动生成图表的工具包。我在改造时加了一个config.py把URL规则、字段映射、页面数量、延时区间都抽成了配置项。命令行入口用argparse支持三个参数--pages控制抓取页数--output指定CSV路径--skip-viz跳过可视化直接只爬数据。这样同一个工具既能全量抓取250部电影也能只抓前2页做快速验证。可视化部分也用脚本组合了跑完爬虫后自动执行数据分析和出图三个模块通过main.py串联。# main.py 入口 python main.py --pages 10 --output data/top250.csv --with-viz如果你的环境缺少依赖项目说明文档里已经列出了requirements.txt的内容requests、beautifulsoup4、lxml、pandas、matplotlib、jieba、wordcloud。用pip install -r requirements.txt安装后就能直接跑。最后说一个我自己的习惯。每次拿到别人的爬虫项目我不会直接跑完整代码而是先看三样东西请求头配置、解析函数里的字段下标、落盘时的编码。这三处是爬虫项目最容易“按教程写却跑不通”的位置。这个项目里三处都处理得比较规范复现过程基本顺畅。从那以后我每次交付给别人的爬虫项目都强制自己走一遍“单页验证、限速检查、编码确认”这三个步骤既是对代码负责也是给接手的人少留一点坑。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站