ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python爬虫实战:豆瓣电影数据采集与可视化全流程解析

Python爬虫实战:豆瓣电影数据采集与可视化全流程解析 最近在做一个数据分析项目需要获取一些电影的评价数据来做趋势分析。豆瓣作为国内最权威的影视评分社区其数据自然是最佳选择。然而手动收集不仅效率低下而且难以进行大规模分析。于是一个自动化的豆瓣数据采集与可视化方案就成了刚需。本文将手把手带你实现一个完整的豆瓣电影数据爬虫并将采集到的数据进行清洗、存储最终通过可视化图表直观展示。无论你是刚接触Python爬虫的新手还是想学习数据可视化流程的开发者都能从这篇实战教程中获得一套可直接复用的代码和清晰的思路。1. 项目背景与核心概念在开始敲代码之前我们有必要厘清几个核心概念并明确本项目的目标和边界。1.1 什么是网络爬虫网络爬虫Web Crawler是一种按照既定规则自动抓取互联网信息的程序或脚本。你可以把它想象成一只不知疲倦的“蜘蛛”在互联网这张“大网”上爬行收集它遇到的信息。根据任务目标爬虫可以分为几类批量型爬虫一次性抓取大量数据常用于项目初期数据积累。我们本次要构建的就是这种。增量型爬虫只抓取网站上新增或更新的内容常用于监控和持续数据更新。垂直型爬虫针对某一特定领域如电商、招聘、影视进行深度抓取对页面解析逻辑要求更高。我们的“豆瓣电影爬虫”就是一个典型的垂直型、批量型爬虫。1.2 为什么选择豆瓣电影数据豆瓣电影提供了丰富的结构化数据包括电影名称、评分、评价人数、导演、演员、类型、简介、短评等。这些数据非常适合用于市场分析分析不同类型、不同导演电影的受欢迎程度。趋势预测观察评分与票房、口碑之间的关系。个人兴趣挖掘构建推荐系统的数据基础。学习实践数据结构清晰是练习爬虫和数据处理的绝佳对象。1.3 数据可视化的重要性原始数据只是一堆冰冷的数字和文本难以直接洞察规律。数据可视化通过图表如柱状图、折线图、词云将数据转化为直观的图形帮助我们快速发现模式与异常例如一眼看出哪种电影类型平均分最高。讲述数据故事用图表支撑你的分析结论更具说服力。辅助决策为产品设计、内容运营等提供数据依据。本项目将使用Matplotlib和Pyecharts这两个强大的Python库来完成可视化部分。1.4 法律与道德边界非常重要爬虫技术是一把双刃剑。在开始之前我们必须遵守以下原则遵守robots.txt访问https://www.douban.com/robots.txt查看豆瓣允许和禁止爬取的路径。尊重网站的规则。限制请求频率在代码中主动添加延时避免对豆瓣服务器造成压力这既是道德要求也能防止你的IP被封锁。仅用于个人学习与研究切勿将爬取的数据用于商业用途或进行恶意传播侵犯豆瓣及用户权益。不爬取个人隐私信息本项目仅爬取公开的电影信息不涉及用户个人主页、私密评论等。2. 环境准备与工具说明工欲善其事必先利其器。我们先来搭建开发环境。2.1 所需工具与库编程语言Python 3.7 或以上版本。建议使用 Python 3.8兼容性更好。开发环境任意你熟悉的IDE或编辑器如 PyCharm, VSCode, Jupyter Notebook。关键Python库requests用于发送HTTP请求获取网页HTML内容。BeautifulSoup4 (bs4)用于解析HTML提取结构化数据。lxmlBeautifulSoup的一个解析器速度快。pandas数据处理与分析的核心库用于清洗和整理数据。matplotlib基础绘图库用于生成静态图表。pyecharts基于ECharts的Python可视化库能生成交互式、美观的图表。fake-useragent随机生成User-Agent模拟不同浏览器访问降低被封风险。sqlite3(Python内置) 或pymysql/sqlalchemy用于将数据存储到数据库实现持久化。2.2 安装依赖库打开你的终端命令行使用pip命令一次性安装所有需要的库pip install requests beautifulsoup4 lxml pandas matplotlib pyecharts fake-useragent如果你打算使用MySQL还需要安装pymysqlpip install pymysql2.3 项目结构规划一个清晰的项目结构能让代码更易维护。建议创建如下目录和文件douban_movie_crawler/ │ ├── spider/ # 爬虫核心模块 │ ├── __init__.py │ ├── douban_spider.py # 爬虫主逻辑 │ └── utils.py # 工具函数如请求头处理、延时 │ ├── data/ # 数据存储 │ ├── raw/ # 原始HTML或JSON可选 │ ├── processed/ # 清洗后的数据文件 │ └── douban_movies.db # SQLite数据库文件运行后生成 │ ├── visualization/ # 可视化模块 │ ├── __init__.py │ ├── chart_generator.py # 图表生成逻辑 │ └── templates/ # Pyecharts生成的HTML模板运行后生成 │ ├── config.py # 配置文件如数据库连接、爬取页数 ├── main.py # 主程序入口 ├── requirements.txt # 项目依赖列表 └── README.md # 项目说明你可以先创建好这个骨架我们后续的代码将按此结构放置。3. 核心爬虫逻辑拆解爬虫的核心工作流程可以概括为请求 - 解析 - 存储。我们一步步拆解。3.1 发送请求与伪装直接使用requests.get()访问豆瓣很可能会收到403错误或验证码因为豆瓣有反爬机制。我们需要模拟真实浏览器的行为。关键点请求头Headers最重要的字段是User-Agent它告诉服务器我们使用的浏览器和操作系统信息。使用fake-useragent库可以随机生成。# spider/utils.py from fake_useragent import UserAgent import time import random def get_random_headers(): 生成随机的请求头重点模拟User-Agent。 ua UserAgent() headers { User-Agent: ua.random, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Connection: keep-alive, } return headers def delay(): 在请求之间添加随机延时避免请求过快。 time.sleep(random.uniform(1, 3)) # 随机休眠1到3秒3.2 解析HTML页面豆瓣电影榜单页例如TOP250的HTML结构是固定的。我们需要使用开发者工具F12找到包含电影信息的HTML标签。步骤打开豆瓣电影TOP250页面。按F12打开开发者工具使用元素选择工具箭头图标点击一个电影条目。观察发现每个电影条目都包裹在一个classitem的div标签内。电影名称在span classtitle里评分在span classrating_num里以此类推。BeautifulSoup 解析示例# spider/douban_spider.py (片段) from bs4 import BeautifulSoup def parse_movie_list(html_content): 解析榜单页HTML提取一页上的所有电影基本信息。 soup BeautifulSoup(html_content, lxml) movie_items soup.find_all(div, class_item) # 找到所有电影条目 movies_on_page [] for item in movie_items: movie {} # 提取电影名称可能包含中文名和英文名 title_elem item.find(span, class_title) movie[title] title_elem.get_text(stripTrue) if title_elem else N/A # 提取评分 rating_elem item.find(span, class_rating_num) movie[rating] float(rating_elem.get_text(stripTrue)) if rating_elem else 0.0 # 提取评价人数 comment_elem item.find(div, class_star).find_all(span)[-1] # 最后一个span是人数 comment_text comment_elem.get_text(stripTrue).replace(人评价, ) movie[comment_count] int(comment_text) if comment_text.isdigit() else 0 # 提取简介quote quote_elem item.find(span, class_inq) movie[quote] quote_elem.get_text(stripTrue) if quote_elem else # 提取详情页链接 link_elem item.find(a) movie[detail_url] link_elem[href] if link_elem else movies_on_page.append(movie) return movies_on_page3.3 处理分页与循环豆瓣TOP250共有10页每页25条。我们需要构建一个循环来遍历所有页面。观察URL规律https://movie.douban.com/top250?start0filterstart参数以25递增。# spider/douban_spider.py (片段) import requests from .utils import get_random_headers, delay def crawl_top250(start_page0, end_page10): 爬取豆瓣电影TOP250从 start_page 到 end_page 的数据。 base_url https://movie.douban.com/top250 all_movies [] for page in range(start_page, end_page): start page * 25 url f{base_url}?start{start}filter print(f正在爬取第 {page1} 页: {url}) try: headers get_random_headers() response requests.get(url, headersheaders, timeout10) response.raise_for_status() # 检查请求是否成功 response.encoding utf-8 # 调用解析函数 movies_on_page parse_movie_list(response.text) all_movies.extend(movies_on_page) print(f第 {page1} 页爬取完成共 {len(movies_on_page)} 部电影。) delay() # 爬取一页后延时 except requests.RequestException as e: print(f爬取第 {page1} 页时发生错误: {e}) break # 或者 continue根据需求决定 return all_movies4. 完整实战从爬取到可视化现在我们将所有模块组合起来完成一个端到端的流程。4.1 配置与主程序入口首先创建一个配置文件管理爬取页数、数据库路径等参数。# config.py # 爬虫配置 CRAWL_START_PAGE 0 # 起始页0代表第一页 CRAWL_END_PAGE 10 # 结束页10代表爬取10页即TOP250 BASE_URL https://movie.douban.com/top250 # 数据库配置 (使用SQLite示例) DB_PATH data/douban_movies.db TABLE_NAME movies_top250然后编写主程序main.py协调爬虫、存储和可视化流程。# main.py import os import sys from spider.douban_spider import crawl_top250 from data.db_handler import save_to_db, load_from_db from visualization.chart_generator import generate_all_charts import config def ensure_directories(): 确保必要的目录存在。 os.makedirs(data/raw, exist_okTrue) os.makedirs(data/processed, exist_okTrue) os.makedirs(visualization/templates, exist_okTrue) def main(): print(豆瓣电影TOP250数据采集与可视化项目启动...) ensure_directories() # 阶段一数据采集 print(\n 开始数据爬取 ) movies_data crawl_top250( start_pageconfig.CRAWL_START_PAGE, end_pageconfig.CRAWL_END_PAGE ) print(f爬取结束共获得 {len(movies_data)} 条电影数据。) if not movies_data: print(未获取到数据程序退出。) sys.exit(1) # 阶段二数据存储数据库 print(\n 保存数据到数据库 ) save_to_db(movies_data, config.DB_PATH, config.TABLE_NAME) # 阶段三从数据库加载数据 print(\n 从数据库加载数据 ) df load_from_db(config.DB_PATH, config.TABLE_NAME) print(f从数据库加载了 {len(df)} 条记录。) print(df.head()) # 预览前5条数据 # 阶段四数据可视化 print(\n 生成可视化图表 ) generate_all_charts(df) print(\n 项目执行完毕 ) print(请查看 visualization/templates/ 目录下的HTML文件用浏览器打开查看交互式图表。) if __name__ __main__: main()4.2 数据存储模块SQLite我们将数据存储到SQLite数据库便于查询和管理。# data/db_handler.py import sqlite3 import pandas as pd from typing import List, Dict def create_table(db_path: str, table_name: str): 创建电影数据表。 conn sqlite3.connect(db_path) cursor conn.cursor() create_sql f CREATE TABLE IF NOT EXISTS {table_name} ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, rating REAL, comment_count INTEGER, quote TEXT, detail_url TEXT, crawl_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) cursor.execute(create_sql) conn.commit() conn.close() print(f表 {table_name} 创建成功或已存在。) def save_to_db(movies: List[Dict], db_path: str, table_name: str): 将电影数据列表保存到数据库。 create_table(db_path, table_name) # 确保表存在 conn sqlite3.connect(db_path) cursor conn.cursor() insert_sql f INSERT INTO {table_name} (title, rating, comment_count, quote, detail_url) VALUES (?, ?, ?, ?, ?) data_to_insert [ (m[title], m[rating], m[comment_count], m[quote], m[detail_url]) for m in movies ] try: cursor.executemany(insert_sql, data_to_insert) conn.commit() print(f成功插入 {cursor.rowcount} 条数据到表 {table_name}。) except sqlite3.Error as e: print(f插入数据时发生错误: {e}) conn.rollback() finally: conn.close() def load_from_db(db_path: str, table_name: str) - pd.DataFrame: 从数据库加载数据到Pandas DataFrame。 conn sqlite3.connect(db_path) query fSELECT * FROM {table_name} df pd.read_sql_query(query, conn) conn.close() return df4.3 数据清洗与预处理从数据库加载数据后通常需要进行简单的清洗。# data/data_cleaner.py (可选可在chart_generator中集成) import pandas as pd def clean_movie_data(df: pd.DataFrame) - pd.DataFrame: 清洗电影数据。 df_clean df.copy() # 1. 去重基于电影标题和详情链接 df_clean df_clean.drop_duplicates(subset[title, detail_url]) # 2. 处理缺失值评分用中位数填充简介用空字符串填充 df_clean[rating].fillna(df_clean[rating].median(), inplaceTrue) df_clean[quote].fillna(, inplaceTrue) # 3. 确保数据类型正确 df_clean[comment_count] pd.to_numeric(df_clean[comment_count], errorscoerce).fillna(0).astype(int) print(f数据清洗完成。原始记录数{len(df)}清洗后记录数{len(df_clean)}) return df_clean4.4 可视化图表生成这是最有趣的部分我们将使用pyecharts生成交互式图表。# visualization/chart_generator.py import pandas as pd from pyecharts import options as opts from pyecharts.charts import Bar, Pie, Scatter, WordCloud, Page import os def generate_all_charts(df: pd.DataFrame): 生成所有可视化图表并保存为HTML。 # 确保输出目录存在 output_dir visualization/templates os.makedirs(output_dir, exist_okTrue) # 图表1评分分布直方图使用Matplotlib风格Pyecharts实现 rating_bar draw_rating_distribution(df) rating_bar.render(os.path.join(output_dir, rating_distribution.html)) # 图表2评分TOP10电影柱状图 top10_bar draw_top10_movies(df) top10_bar.render(os.path.join(output_dir, top10_movies.html)) # 图表3评价人数与评分关系散点图 scatter_chart draw_rating_vs_comments(df) scatter_chart.render(os.path.join(output_dir, rating_vs_comments.html)) # 图表4电影简介词云 wordcloud draw_quote_wordcloud(df) wordcloud.render(os.path.join(output_dir, quote_wordcloud.html)) # 图表5将所有图表组合到一个页面 page Page(layoutPage.SimplePageLayout) page.add( rating_bar, top10_bar, scatter_chart, wordcloud ) page.render(os.path.join(output_dir, all_charts.html)) print(f所有图表已生成保存在 {output_dir} 目录下。) def draw_rating_distribution(df): 绘制电影评分分布直方图。 # 将评分分段例如 9.0以上8.5-9.0等等 bins [0, 8.0, 8.5, 9.0, 9.5, 10] labels [8.0以下, 8.0-8.5, 8.5-9.0, 9.0-9.5, 9.5以上] df[rating_group] pd.cut(df[rating], binsbins, labelslabels, rightFalse) rating_counts df[rating_group].value_counts().sort_index() bar ( Bar() .add_xaxis(list(rating_counts.index)) .add_yaxis(电影数量, list(rating_counts.values)) .set_global_opts( title_optsopts.TitleOpts(title豆瓣TOP250电影评分分布, subtitle数据来源豆瓣电影), xaxis_optsopts.AxisOpts(name评分区间), yaxis_optsopts.AxisOpts(name电影数量), toolbox_optsopts.ToolboxOpts(), # 添加工具箱可下载图片等 ) ) return bar def draw_top10_movies(df): 绘制评分最高的10部电影柱状图。 top10 df.nlargest(10, rating)[[title, rating]] # 简化标题避免过长 top10[short_title] top10[title].apply(lambda x: x[:15] ... if len(x) 15 else x) bar ( Bar() .add_xaxis(list(top10[short_title])) .add_yaxis(评分, list(top10[rating].round(2))) .reversal_axis() # 横向柱状图 .set_global_opts( title_optsopts.TitleOpts(title豆瓣TOP250评分前十电影), xaxis_optsopts.AxisOpts(name评分), yaxis_optsopts.AxisOpts(name电影名称), visualmap_optsopts.VisualMapOpts( min_top10[rating].min(), max_top10[rating].max(), dimension0, # 对应Y轴数据 is_showFalse, range_color[#d7e3fc, #1d4ed8] # 渐变色 ), ) .set_series_opts( label_optsopts.LabelOpts(positionright, formatter{c}) # 在右侧显示数值 ) ) return bar def draw_rating_vs_comments(df): 绘制评分与评价人数关系的散点图。 # 取评论数最多的前100部电影避免图表过于密集 df_sample df.nlargest(100, comment_count) data list(zip(df_sample[comment_count], df_sample[rating])) scatter ( Scatter() .add_xaxis(df_sample[comment_count].tolist()) .add_yaxis( series_name评分, y_axisdf_sample[rating].tolist(), symbol_size10, label_optsopts.LabelOpts(is_showFalse), ) .set_global_opts( title_optsopts.TitleOpts(title电影评分 vs 评价人数 (Top100 by 评论数)), xaxis_optsopts.AxisOpts(name评价人数, type_value), yaxis_optsopts.AxisOpts(name评分), tooltip_optsopts.TooltipOpts( formatter{b}万评价br/评分: {c} ), ) ) return scatter def draw_quote_wordcloud(df): 根据电影简介生成词云。 from collections import Counter import jieba # 需要安装pip install jieba # 将所有简介拼接起来 text .join(df[quote].dropna().astype(str).tolist()) # 使用jieba进行中文分词 words jieba.lcut(text) # 过滤掉单字和停用词这里简单过滤长度 filtered_words [w for w in words if len(w) 1] # 统计词频 word_counts Counter(filtered_words).most_common(100) # 取前100个词 wordcloud ( WordCloud() .add( series_name电影简介词云, data_pairword_counts, word_size_range[20, 100], shapecircle, # 词云形状可选circle, cardioid等 ) .set_global_opts( title_optsopts.TitleOpts(title豆瓣TOP250电影简介词云), tooltip_optsopts.TooltipOpts(is_showTrue), ) ) return wordcloud4.5 运行与结果展示运行程序在项目根目录下执行python main.py。观察控制台你会看到爬虫逐页爬取、数据存入数据库、图表生成的过程。查看结果数据库文件data/douban_movies.db可以用SQLite浏览器打开查看。可视化图表在visualization/templates/目录下会生成多个HTML文件。用浏览器打开all_charts.html你将看到一个包含多个交互式图表的网页。你可以鼠标悬停查看详情、缩放、保存图片。生成的图表示例评分分布可以看到绝大多数TOP250电影评分集中在8.5-9.5之间。TOP10电影直观展示评分最高的电影如《肖申克的救赎》、《霸王别姬》等。评分vs评价人数观察是否“叫好又叫座”有些高评分电影评价人数可能不多文艺片而一些商业大片评价人数多但评分中等。简介词云从电影简介中提取高频词汇如“希望”、“人生”、“爱情”、“自由”等感受TOP250电影的主题倾向。5. 常见问题与排查思路在爬虫开发过程中你几乎一定会遇到一些问题。下面是一些常见问题及解决方法。问题现象可能原因排查与解决思路请求返回403 Forbidden1. 请求头User-Agent被识别为爬虫。2. IP被暂时封锁。1. 使用fake-useragent随机化User-Agent。2. 在请求头中添加更多常见浏览器字段如Accept,Accept-Language。3.显著增加请求间隔时间例如time.sleep(random.uniform(3, 7))。4. 考虑使用代理IP池高级话题本项目未涉及。爬取几页后数据停止或报错1. 触发了网站的反爬机制如验证码。2. 网络连接不稳定。3. HTML结构有微小变动。1. 立即停止爬虫手动访问页面看是否出现验证码。2. 在代码中添加更完善的异常捕获和日志记录。3. 检查解析逻辑是否还能匹配最新的HTML结构使用开发者工具复查。4. 将爬虫任务分多次进行每次爬取少量数据。BeautifulSoup找不到标签返回空列表1. 使用的解析器不对。2. 标签的class名称有变化或包含多个类。3. 网页内容是JavaScript动态加载的。1. 确保安装了lxml解析器 (pip install lxml)。2. 使用soup.find_all(‘div‘, class_‘item‘)时如果class有多个值可以用CSS选择器soup.select(‘div.item‘)。3. 对于动态加载的页面requests无法获取JS渲染后的内容。此时需要考虑使用Selenium或Playwright等浏览器自动化工具。豆瓣榜单页是静态页一般无此问题。数据库写入错误1. 数据库连接失败或路径错误。2. 数据格式与表结构不匹配。3. 主键或唯一约束冲突。1. 检查db_path是否正确是否有写入权限。2. 打印出准备插入的数据检查是否有None值或类型错误。3. 在插入前进行数据去重或使用INSERT OR IGNORE/REPLACE语句。pyecharts图表不显示或报错1. 版本不兼容。2. 数据格式不正确如非数值型数据用于坐标轴。3. 未正确安装或导入。1. 使用pip list检查pyecharts版本确保使用较新稳定版。2. 检查传递给图表的数据是否为列表List格式。3. 确保生成了HTML文件并用浏览器打开。在Jupyter Notebook中渲染需要调用.render_notebook()。词云图中文显示为方框未指定中文字体路径。在WordCloud的add方法中设置font_path参数指向一个中文字体文件如‘C:/Windows/Fonts/simhei.ttf‘。6. 最佳实践与进阶建议掌握了基础流程后以下建议能让你的爬虫项目更健壮、更专业。6.1 爬虫工程化建议配置化管理将所有可配置项如URL、请求头模板、数据库连接字符串、爬取间隔放在config.py或config.yaml中与代码分离。日志记录使用Python内置的logging模块替代print可以输出不同级别INFO, WARNING, ERROR的日志到文件和控制台方便后期排查。异常处理与重试为网络请求添加重试机制如使用tenacity库对短暂网络波动容错。增量爬取在数据库表中增加update_time字段。每次爬虫运行时先检查detail_url是否存在若存在则比较更新时间决定是否更新数据避免重复爬取。任务队列与分布式对于大规模爬取可以考虑使用Scrapy框架它内置了请求调度、并发处理、管道等强大功能。更进一步可以使用Redis作为任务队列实现分布式爬虫。6.2 数据存储优化数据去重除了在内存中去重在数据库层面应设置唯一约束如(title, detail_url)从根源避免重复数据。数据备份定期备份数据库文件。可以考虑将清洗后的数据额外导出为CSV或Parquet格式便于用其他工具如Excel, Power BI分析。使用ORM对于复杂项目使用SQLAlchemy等ORM库来管理数据库操作可以写出更易维护、数据库无关的代码。6.3 可视化进阶使用更专业的可视化库Plotly和Bokeh也能生成交互式图表且与Web框架如Dash集成更好。Seaborn是基于Matplotlib的统计图形库默认样式更美观。构建可视化仪表盘使用Dash由Plotly开发或Streamlit可以快速构建一个包含多个图表、筛选器的Web数据仪表盘实现真正的“数据可视化大屏”。探索更多图表类型根据数据特点选择图表。例如想展示电影类型分布可以用饼图或旭日图想展示电影随时间上映年份的评分变化可以用折线图或面积图。6.4 法律与伦理再强调严格遵守robots.txt这是网络爬虫的“交通规则”。控制访问速度这是体现技术人素养的关键。你的爬虫不应该影响目标网站的正常服务。明确数据用途本项目代码及思路仅供学习交流。切勿用于任何侵犯版权、隐私或干扰网站运营的用途。通过这个完整的项目你不仅学会了如何用Python爬取豆瓣电影数据还掌握了数据清洗、存储和可视化的全流程。这套方法可以举一反三应用到其他许多公开数据源上。记住爬虫技术是工具关键在于用它来创造价值、解决问题同时始终保持对规则和他人劳动的尊重。
返回列表