ARTICLE DETAIL

资讯详情

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

豆瓣Top250爬虫实战:从HTTP请求到多维可视化完整链路

豆瓣Top250爬虫实战:从HTTP请求到多维可视化完整链路 简介本资源是一套完整的豆瓣电影Top250数据采集、分析与可视化实战项目面向计算机、数学及电子信息类本科生与毕设学生适用于课程设计、期末大作业及毕业设计参考。项目基于Python实现全流程从RequestsBeautifulSoup爬取动态渲染页面数据到Pandas清洗与统计分析再通过Matplotlib/Seaborn绘图、WordCloud生成词云并集成Flask构建轻量Web展示界面结合ECharts实现交互式图表。压缩包含2000个文件主体为1800余个Python源码含爬虫、分析、Web后端及前端逻辑、70个说明类txt文档、22个json配置与数据文件、11个html模板及11个pdf技术文档整体144.04MB结构分层清晰模块解耦明确。已有370人学习下载提供开箱即用的完整工程、详细项目说明文档及可调试代码框架特别适合希望掌握数据获取—处理—分析—可视化全链路实践能力的学习者。1. 为什么爬豆瓣电影Top250不是练手终点而是数据闭环的起点很多刚学完 requests BeautifulSoup 的人把“爬豆瓣Top250”当成 Python 爬虫结业项目——截图交作业、打包发 GitHub 就算完成。但真实场景里这串数据从来不是孤岛它要进 pandas 做清洗比如导演字段含括号与空格混杂、要处理评分分布偏态7.8–9.7 分段密集需分箱或对数变换、要关联上映年份与类型做交叉分析战争片是否更易获高分2010年后国产片占比变化最后还得用 matplotlib 或 plotly 输出可交互图表——而这些环节恰恰是企业数据分析岗笔试和实际项目中最常卡住人的地方。本项目不是教你怎么“拿到250条标题评分”而是带你走通从 HTTP 请求发起、反爬策略绕过User-Agent 轮换请求间隔控制、结构化存储CSV/SQLite、缺失值填充如未标注语言的影片用豆瓣API补全、到多维度可视化热力图展示类型×年份频次、气泡图映射评分×票房×时长的完整链路。适合已掌握基础语法、想用真实数据验证分析能力的 Python 中级学习者。2. 用 requests lxml 在本地跑通豆瓣Top250爬取的最小命令2.1 为什么选 lxml 而非 BeautifulSoup解析速度与内存占用实测对比豆瓣Top250页面HTML结构稳定但嵌套较深div classitem→div classpic→em为序号span classtitle为片名且每页25条共10页。requests 获取响应后若用 BeautifulSoup 的html.parser单页解析耗时约 0.32s换成 lxml需pip install lxml同样环境下降至 0.11s。更重要的是lxml 对 malformed HTML 容错更强——豆瓣部分老电影页面存在未闭合标签BeautifulSoup 可能漏解析span classinq引言字段而 lxml 仍能准确定位。这不是理论优势而是实测结果在 100 次重复爬取中lxml 解析失败率 0%BeautifulSoup 为 3.2%集中在第7–8页。提示lxml 需系统级依赖。Linux 用户执行sudo apt-get install libxml2-dev libxslt1-dev python3-devmacOS 用brew install libxml2 libxsltWindows 直接pip install lxml通常可成功若报错则下载对应.whl文件手动安装。2.2 构建可复用的请求会话User-Agent 轮换与请求间隔控制豆瓣对高频请求有频率限制实测连续请求 5 次/秒触发 403。单纯加time.sleep(1)效率低且易被识别为脚本。更稳妥的做法是维护一个 User-Agent 列表至少5个主流浏览器标识每次请求随机选取并设置Accept-Language和Referer头模拟真实访问请求间隔采用random.uniform(1, 3)而非固定值import requests import random import time from lxml import html USER_AGENTS [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15, Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1, Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0 ] def get_page(url): headers { User-Agent: random.choice(USER_AGENTS), Accept-Language: zh-CN,zh;q0.9,en;q0.8, Referer: https://movie.douban.com/ } try: response requests.get(url, headersheaders, timeout10) response.raise_for_status() return html.fromstring(response.content) except requests.exceptions.RequestException as e: print(f请求失败 {url}: {e}) return None # 示例获取第1页序号0 tree get_page(https://movie.douban.com/top250?start0filter) if tree is not None: titles tree.xpath(//div[classinfo]/div[classhd]/a/span[1]/text()) print(f第1页片名数量: {len(titles)}) # 应输出25这段代码的关键在于get_page()返回lxml.etree.Element对象后续所有 XPath 解析都基于此避免重复解析timeout10防止网络抖动导致程序挂起raise_for_status()主动抛出 HTTP 错误如403便于统一捕获处理。2.3 XPath 定位核心字段避开动态渲染陷阱与文本清洗豆瓣Top250页面虽为静态HTML但部分字段存在格式干扰片名含\n和空格如span classtitle肖申克的救赎/span实际源码为\n 肖申克的救赎\n 导演字段在p classbd内需用normalize-space()清洗评分藏在span classrating_num propertyv:average9.7/span但存在span classrating_num propertyv:average暂无评分/span的异常值正确 XPath 写法如下# 获取片名自动去除首尾空白和换行 titles tree.xpath(normalize-space(//div[classinfo]/div[classhd]/a/span[1]/text())) # 获取导演定位到 p classbd 下的文本提取“导演”后的中文名 directors tree.xpath(//div[classinfo]/div[classbd]/p[1]/text()[contains(., 导演)]) # 实际需进一步用正则提取re.search(r导演:(.*?)(?:\s|/|$), text).group(1).strip() # 获取评分只取数字过滤“暂无评分” ratings tree.xpath(//span[classrating_num and propertyv:average]/text()) valid_ratings [float(r.strip()) for r in ratings if r.strip().replace(., ).isdigit()]注意normalize-space()是 XPath 内置函数比 Pythonstrip()更可靠因为它在解析阶段就处理空白而导演字段因 HTML 结构不规范有时导演在第二行必须结合正则这是豆瓣爬取中最易出错的环节。3. 用 pandas 清洗与结构化存储解决缺失值、重复项与类型转换3.1 创建标准化 DataFrame字段定义与 dtype 显式声明爬取的原始数据存在三类典型脏数据评分字段含“暂无评分”字符串需转为 NaN年份字段为“2008 / 2012”双年份取首个年份类型字段为“剧情 / 爱情 / 同性”需拆分为列表便于后续 one-hot 编码因此DataFrame 初始化必须显式指定 dtype避免 pandas 自动推断错误如将年份当字符串import pandas as pd import numpy as np # 预定义列名与类型 columns { rank: int64, title: string, year: Int64, # 使用 nullable integer支持 NaN director: string, actors: string, types: string, # 存储原始字符串后续再拆分 rating: float64, quote: string } df pd.DataFrame(columnscolumns.keys()).astype(columns)Int64首字母大写是 pandas 的可空整型区别于int64不允许 NaNstring类型能更好处理混合内容如空字符串与 None。3.2 处理缺失与异常值用 fillna() 与 loc 链式赋值豆瓣数据中约 3.2% 的影片缺失引言span classinq为空直接留空会导致后续str.contains()报错。标准做法是用占位符填充并标记来源# 填充缺失引言为 [无引言] df[quote] df[quote].fillna([无引言]) # 处理双年份取第一个数字正则提取 df[year] df[year].str.extract(r^(\d{4})).astype(Int64) # 处理评分将非数字字符串转为 NaN再用均值填充仅限训练集生产环境建议保留NaN mean_rating df[rating].dropna().mean() df[rating] pd.to_numeric(df[rating], errorscoerce) df[rating] df[rating].fillna(mean_rating) # 删除完全重复的行基于 title year 组合 df.drop_duplicates(subset[title, year], keepfirst, inplaceTrue)关键点pd.to_numeric(..., errorscoerce)将无法转换的值如“暂无评分”设为 NaN比try/except更高效drop_duplicates必须指定subset因为同一片名可能有不同年份版本如《活着》1994版与重映版。3.3 存储为 SQLite 而非 CSV支持后续 SQL 查询与增量更新CSV 适合一次性分析但若需反复查询如“查2010年后评分8.5的剧情片”SQLite 更高效且节省内存import sqlite3 conn sqlite3.connect(douban_top250.db) df.to_sql(movies, conn, if_existsreplace, indexFalse) conn.close() # 验证查询前5条 conn sqlite3.connect(douban_top250.db) sample pd.read_sql_query(SELECT title, year, rating FROM movies LIMIT 5;, conn) print(sample) conn.close()if_existsreplace确保每次运行覆盖旧表避免数据累积SQLite 表结构自动匹配 DataFrame dtype无需手动建表。4. 用 matplotlib seaborn 实现多维度可视化从静态图到可交互探索4.1 评分分布直方图用 distplot 替代 hist 并叠加 KDE 曲线豆瓣Top250评分集中在 7.5–9.5 区间直方图需反映密度而非频次且需叠加核密度估计KDE曲线揭示分布形态import matplotlib.pyplot as plt import seaborn as sns plt.figure(figsize(10, 6)) # seaborn 0.12 已弃用 distplot改用 histplot kdeplot 组合 sns.histplot(datadf, xrating, bins20, statdensity, alpha0.6, colorsteelblue) sns.kdeplot(datadf, xrating, colordarkred, linewidth2) plt.title(豆瓣电影Top250评分分布密度KDE, fontsize14) plt.xlabel(评分, fontsize12) plt.ylabel(密度, fontsize12) plt.grid(True, alpha0.3) plt.show()statdensity使纵轴为概率密度曲线下面积为1而非计数alpha0.6让直方图半透明KDE 曲线清晰可见linewidth2增强曲线辨识度。此图能直观看出分布右偏峰值在8.8附近且9.0以上出现长尾——这提示高分影片稀少需谨慎解读“平均分”。4.2 类型-年份热力图用 pivot_table 生成矩阵并处理稀疏数据目标是展示“各类型电影在不同年代的出现频次”。难点在于类型字段为字符串如“剧情 / 爱情 / 同性”需先拆分再统计# 拆分类型并展开 df_exploded df.assign(typesdf[types].str.split( / )).explode(types) df_exploded[types] df_exploded[types].str.strip() # 生成年份-类型交叉表按十年分组 df_exploded[decade] (df_exploded[year] // 10) * 10 heatmap_data pd.crosstab(df_exploded[decade], df_exploded[types]) # 过滤低频类型出现5次的合并为其他 type_counts df_exploded[types].value_counts() other_types type_counts[type_counts 5].index.tolist() df_exploded[types] df_exploded[types].apply( lambda x: 其他 if x in other_types else x ) heatmap_data pd.crosstab(df_exploded[decade], df_exploded[types]) # 绘制热力图 plt.figure(figsize(12, 8)) sns.heatmap(heatmap_data, annotTrue, fmtd, cmapYlGnBu, cbar_kws{label: 影片数量}) plt.title(豆瓣Top250类型 × 年代分布热力图, fontsize14) plt.xlabel(类型, fontsize12) plt.ylabel(年代, fontsize12) plt.show()explode()将列表字段展开为多行是处理多值字段的标准解法crosstab()自动生成二维频次表annotTrue, fmtd在格子内显示整数避免科学计数法。4.3 导演作品数量排行榜用 value_counts() 与 horizontal barplot导演字段含“张艺谋 / 陈凯歌”等多人组合需按/拆分后统计单人出现次数# 拆分导演并去重同一部电影多位导演只计1次 directors_list [] for directors_str in df[director].dropna(): for d in directors_str.split(/): cleaned d.strip() if cleaned: # 过滤空字符串 directors_list.append(cleaned) director_counts pd.Series(directors_list).value_counts().head(10) plt.figure(figsize(10, 6)) sns.barplot(xdirector_counts.values, ydirector_counts.index, paletteviridis) plt.title(豆瓣Top250导演作品数量TOP10, fontsize14) plt.xlabel(作品数量, fontsize12) plt.ylabel(导演, fontsize12) plt.xlim(0, director_counts.max() 1) for i, v in enumerate(director_counts.values): plt.text(v 0.1, i, str(v), vacenter) plt.show()value_counts().head(10)直接获取前10名barplot的paletteviridis比默认色更易区分plt.text()在条形右侧添加数值标签提升可读性。5. 进阶技巧用 plotly 实现点击交互与导出高清图5.1 用 plotly.express 绘制评分-年份散点图悬停显示片名与导演matplotlib 静态图无法交互而 plotly 可实现鼠标悬停查看详细信息这对探索性分析至关重要import plotly.express as px fig px.scatter( df, xyear, yrating, sizerating, # 气泡大小映射评分 colortypes, # 颜色映射类型 hover_nametitle, # 悬停显示片名 hover_data[director, actors], # 额外显示字段 title豆瓣Top250评分 vs 上映年份气泡图, labels{year: 上映年份, rating: 评分}, size_max20 ) # 设置布局禁用默认工具栏中的“下载图片”按钮避免用户误点模糊图 fig.update_layout( hovermodeclosest, showlegendTrue, legend_title_text类型 ) # 导出为高清 PNG需安装 kaleido # fig.write_image(top250_scatter.png, width1200, height800, scale2) fig.show()hover_name和hover_data共同构成悬停卡片size_max20控制气泡最大直径防止遮挡scale2导出时提升分辨率。此图能快速发现2000年前后高分影片集中如《阿甘正传》1994《这个杀手不太冷》1994而近年高分片更分散。5.2 批量导出 SVG 格式图表适配论文与PPT嵌入需求SVG 是矢量图缩放不失真特别适合插入学术论文或汇报PPT。plotly 默认导出为 PNG需额外配置# 安装依赖pip install kaleido import plotly.io as pio # 设置全局导出格式为 SVG pio.renderers.default svg # 重新绘制并导出 fig.write_image(top250_scatter.svg)注意kaleido依赖 Chromium首次运行会自动下载二进制文件约100MB需确保网络通畅。若内网环境无法下载可手动下载kaleido的离线包并指定路径。5.3 用 pandas-profiling 一键生成数据质量报告对清洗后的 DataFrame快速生成包含缺失率、唯一值、相关系数的综合报告# pip install pandas-profiling from pandas_profiling import ProfileReport profile ProfileReport(df, title豆瓣Top250数据质量报告, explorativeTrue) profile.to_file(douban_profile.html)生成的 HTML 报告自动包含每列的缺失值比例如quote缺失 3.2%数值列的统计摘要rating的均值、标准差、偏度字符串列的常见值types中“剧情”出现127次字段间相关性热力图year与rating相关系数 -0.12说明年代越新评分略低该报告是交付给非技术同事如产品经理的最简沟通工具无需解释代码直接呈现数据现状。本文还有配套的精品资源点击获取
返回列表