ARTICLE DETAIL

资讯详情

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

Python爬取豆瓣电影Top250:从数据获取到存储实践

Python爬取豆瓣电影Top250:从数据获取到存储实践 1. 项目背景与目标解析豆瓣电影Top250榜单是中文互联网最具公信力的电影评分排行榜之一它基于豆瓣用户的海量评分数据通过特定算法计算生成。这个榜单不仅反映了影迷群体的集体审美也成为许多人选片的重要参考依据。作为Python开发者获取这份榜单数据具有多重实用价值数据分析研究高分电影的共同特征类型、导演、国家、年代分布等个人影单管理构建自己的观影计划系统推荐系统开发作为基础数据用于电影推荐算法爬虫技术练习典型的静态网页爬取案例从技术角度看这个项目涉及以下几个核心环节网页请求获取豆瓣Top250页面的HTML源码数据解析从HTML中提取电影信息排名、标题、评分等数据存储将结构化数据保存到文件或数据库反爬应对处理豆瓣的反爬机制非必须但建议考虑提示豆瓣对爬虫有一定限制建议控制请求频率每秒不超过1次并添加合理的请求头信息。2. 环境准备与工具选型2.1 基础环境配置推荐使用Python 3.8版本主要依赖库如下pip install requests beautifulsoup4 pandas各库的作用说明requests发送HTTP请求获取网页内容beautifulsoup4HTML解析神器pandas数据清洗与存储如果需要进行更复杂的爬取如处理JavaScript渲染页面可以额外安装pip install selenium webdriver-manager2.2 开发工具建议对于这类数据采集项目我个人的工具组合是VS Code Python插件代码编写Jupyter Notebook数据探索Chrome开发者工具分析网页结构特别建议在Chrome中使用检查功能F12观察豆瓣Top250页面的DOM结构这对后续编写解析逻辑至关重要。3. 网页结构与数据定位分析豆瓣Top250的URL为https://movie.douban.com/top250采用分页显示每页25部电影共10页。我们需要处理的分页URL模式为https://movie.douban.com/top250?start{page*25}通过浏览器开发者工具分析可以发现每部电影的信息都包裹在一个classitem的div标签中。关键数据对应的HTML结构如下div classitem div classpic em class1/em !-- 排名 -- a href... img src... !-- 电影海报 -- /a /div div classinfo div classhd a href... class span classtitle肖申克的救赎/span !-- 中文名 -- span classtitle / The Shawshank Redemption/span !-- 英文名 -- /a /div div classbd p class导演: 弗兰克·德拉邦特/p !-- 导演信息 -- div classstar span classrating_num9.7/span !-- 评分 -- span...人评价/span !-- 评价人数 -- /div /div /div /div4. 核心爬取代码实现4.1 基础爬取函数首先实现获取单页内容的函数import requests from bs4 import BeautifulSoup def get_page(url): headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 } try: response requests.get(url, headersheaders) response.raise_for_status() # 检查请求是否成功 return response.text except requests.exceptions.RequestException as e: print(f请求失败: {e}) return None4.2 数据解析函数接着编写解析HTML提取电影信息的函数def parse_page(html): soup BeautifulSoup(html, html.parser) items soup.find_all(div, class_item) movies [] for item in items: rank item.find(em).text # 排名 titles item.find_all(span, class_title) chinese_title titles[0].text # 中文名 foreign_title titles[1].text.strip() if len(titles) 1 else # 外文名 bd item.find(div, class_bd) info bd.find(p, class_).text.strip().split(\n) director info[0].strip() # 导演信息 star item.find(div, class_star) rating star.find(span, class_rating_num).text # 评分 num_reviews star.find_all(span)[-1].text # 评价人数 movies.append({ rank: rank, chinese_title: chinese_title, foreign_title: foreign_title, director: director, rating: rating, num_reviews: num_reviews }) return movies4.3 分页爬取与数据存储实现完整的分页爬取流程import pandas as pd import time def crawl_top250(): base_url https://movie.douban.com/top250 all_movies [] for page in range(10): # 共10页 url f{base_url}?start{page*25} print(f正在爬取: {url}) html get_page(url) if html: movies parse_page(html) all_movies.extend(movies) time.sleep(2) # 礼貌性延迟避免被封 # 转换为DataFrame并保存 df pd.DataFrame(all_movies) df.to_csv(douban_top250.csv, indexFalse, encodingutf-8-sig) print(数据已保存为douban_top250.csv) return df5. 反爬策略与优化方案5.1 基础反爬应对豆瓣有一些基础的反爬措施我们需要设置合理的User-Agent添加请求间隔建议2-5秒使用代理IP如果需要大量爬取处理cookies对于登录后才能查看的内容改进后的请求头示例headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.8,zh-TW;q0.7,zh-HK;q0.5,en-US;q0.3,en;q0.2, Connection: keep-alive }5.2 使用Session保持会话session requests.Session() session.headers.update(headers) def get_page(url): try: response session.get(url) response.raise_for_status() return response.text except requests.exceptions.RequestException as e: print(f请求失败: {e}) return None5.3 随机延迟与代理池更专业的做法是引入随机延迟和代理IPimport random def random_delay(min1, max3): time.sleep(random.uniform(min, max)) # 使用代理示例 proxies { http: http://your.proxy.ip:port, https: http://your.proxy.ip:port } response session.get(url, proxiesproxies)6. 数据清洗与增强6.1 原始数据问题处理从网页抓取的数据通常需要清洗处理评价人数中的人评价文字拆分导演和主演信息提取电影上映年份处理可能存在的空值改进后的解析函数def parse_page(html): # ...前面的代码不变... for item in items: # ...其他字段获取... # 处理评价人数 num_reviews star.find_all(span)[-1].text num_reviews num_reviews.replace(人评价, ).strip() # 提取上映年份 year_info bd.find(p, class_).text.strip().split(\n)[-1].strip() year .join(filter(str.isdigit, year_info))[:4] # 提取前4位数字 # 拆分导演和主演 director_actors info[0].strip().split( ) director director_actors[0].replace(导演: , ) actors director_actors[1].replace(主演: , ) if len(director_actors) 1 else movies.append({ # ...其他字段... year: year, actors: actors, num_reviews: int(num_reviews) if num_reviews else 0 }) return movies6.2 数据存储优化除了CSV还可以考虑其他存储方式JSON格式import json with open(douban_top250.json, w, encodingutf-8) as f: json.dump(all_movies, f, ensure_asciiFalse, indent2)SQLite数据库import sqlite3 conn sqlite3.connect(movies.db) df.to_sql(douban_top250, conn, if_existsreplace, indexFalse) conn.close()7. 项目扩展与进阶方向7.1 获取更多电影详情可以进一步爬取每部电影的详情页获取剧情简介类型标签片长更多演职员信息获奖情况短评/长评数据实现思路从列表页获取每部电影的详情页URL编写新的解析函数处理详情页HTML建立电影ID与详情的关联7.2 定时自动更新使用APScheduler等工具实现定时爬取from apscheduler.schedulers.blocking import BlockingScheduler def scheduled_job(): print(开始执行定时爬取任务...) crawl_top250() scheduler BlockingScheduler() scheduler.add_job(scheduled_job, interval, days7) # 每周执行一次 scheduler.start()7.3 可视化分析使用matplotlib或pyecharts进行数据可视化import matplotlib.pyplot as plt # 评分分布直方图 df[rating] df[rating].astype(float) plt.hist(df[rating], bins20) plt.title(豆瓣Top250评分分布) plt.xlabel(评分) plt.ylabel(电影数量) plt.show()7.4 构建Web应用使用Flask或FastAPI构建简单的Web应用展示数据from flask import Flask, render_template app Flask(__name__) app.route(/) def show_movies(): df pd.read_csv(douban_top250.csv) return render_template(movies.html, moviesdf.to_dict(records)) if __name__ __main__: app.run()8. 常见问题与解决方案8.1 请求被拒绝403错误可能原因User-Agent被识别为爬虫请求频率过高IP被封禁解决方案轮换User-Agent增加请求间隔使用代理IP添加更多请求头信息8.2 数据解析失败可能原因网页结构发生变化某些字段可能缺失解决方案添加更健壮的解析逻辑使用try-except处理可能的异常定期检查爬虫是否仍能正常工作8.3 数据不完整可能原因分页处理不正确网络问题导致部分请求失败解决方案实现断点续爬功能记录已成功爬取的页面添加重试机制8.4 法律与道德考量重要提示遵守豆瓣的robots.txt协议控制请求频率不要对服务器造成负担仅用于个人学习不要大规模商用考虑使用豆瓣官方API如有9. 完整代码示例以下是整合所有优化后的完整代码import requests from bs4 import BeautifulSoup import pandas as pd import time import random from typing import List, Dict class DoubanTop250Spider: def __init__(self): self.base_url https://movie.douban.com/top250 self.headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36, Accept-Language: zh-CN,zh;q0.9 } self.session requests.Session() self.session.headers.update(self.headers) def get_page(self, url: str) - str: 获取单页HTML内容 try: response self.session.get(url) response.raise_for_status() return response.text except requests.exceptions.RequestException as e: print(f请求失败: {e}) return None def parse_page(self, html: str) - List[Dict]: 解析单页HTML提取电影信息 soup BeautifulSoup(html, html.parser) items soup.find_all(div, class_item) movies [] for item in items: try: rank item.find(em).text titles item.find_all(span, class_title) chinese_title titles[0].text foreign_title titles[1].text.strip() if len(titles) 1 else bd item.find(div, class_bd) info bd.find(p, class_).text.strip().split(\n) # 处理导演和演员信息 director_actors info[0].strip().split( ) director director_actors[0].replace(导演: , ) actors director_actors[1].replace(主演: , ) if len(director_actors) 1 else # 提取年份 year_info info[-1].strip() year .join(filter(str.isdigit, year_info))[:4] # 处理评分和评价人数 star item.find(div, class_star) rating star.find(span, class_rating_num).text num_reviews star.find_all(span)[-1].text num_reviews num_reviews.replace(人评价, ).strip() # 获取电影链接 link item.find(div, class_hd).find(a)[href] movies.append({ rank: int(rank), chinese_title: chinese_title, foreign_title: foreign_title, director: director, actors: actors, year: int(year) if year else None, rating: float(rating), num_reviews: int(num_reviews) if num_reviews else 0, link: link }) except Exception as e: print(f解析电影信息时出错: {e}) continue return movies def crawl_all_pages(self) - List[Dict]: 爬取所有页面数据 all_movies [] for page in range(10): # 共10页 url f{self.base_url}?start{page*25} print(f正在爬取: {url}) html self.get_page(url) if html: movies self.parse_page(html) all_movies.extend(movies) # 随机延迟2-5秒 time.sleep(random.uniform(2, 5)) return all_movies def save_to_csv(self, movies: List[Dict], filename: str douban_top250.csv): 保存数据到CSV文件 df pd.DataFrame(movies) df.to_csv(filename, indexFalse, encodingutf-8-sig) print(f数据已保存为{filename}) def run(self): 执行爬虫 movies self.crawl_all_pages() self.save_to_csv(movies) return movies if __name__ __main__: spider DoubanTop250Spider() top250_movies spider.run()10. 项目总结与个人心得在实际开发这个爬虫项目的过程中我积累了一些值得分享的经验网页结构分析技巧使用Chrome开发者工具时可以右键点击页面元素选择Copy selector快速获取CSS选择器路径这能大大加快解析代码的编写速度。异常处理的重要性豆瓣的页面结构虽然总体稳定但偶尔会有小的变动。为每个字段的提取添加try-except块可以避免因个别电影信息解析失败导致整个程序中断。请求间隔的权衡测试发现请求间隔小于1秒很容易触发豆瓣的反爬机制而间隔过长又会影响爬取效率。经过多次测试2-5秒的随机间隔是比较理想的选择。数据验证的必要性在保存数据前建议添加简单的数据验证逻辑比如检查评分是否在合理范围内(0-10)年份是否在电影发明后的合理区间等。增量爬取的实现如果要做定时更新可以记录已爬取电影的ID下次只爬取新增的电影避免重复工作和数据冗余。这个项目虽然看似简单但涵盖了爬虫开发的多个核心环节请求发送、页面解析、数据存储、反爬应对等。对于Python初学者来说是一个很好的综合练习项目。对于有经验的开发者可以在此基础上扩展更复杂的功能如自动监控榜单变化、构建推荐系统等。最后提醒一点爬虫开发要遵守法律法规和网站的使用条款控制请求频率不要对目标网站造成不必要的负担。爬取的数据仅用于个人学习和研究避免商用引发法律风险。
返回列表