
1. 从零开始构建豆瓣电影数据抓取系统作为一名长期从事数据爬取工作的开发者我经常需要从各类网站获取结构化数据。豆瓣电影Top250榜单作为中文互联网最权威的电影评分数据源一直是影视分析、推荐系统等领域的基础数据来源。今天我将分享一套完整的自动化解决方案涵盖从网页抓取到AIGC数据分析的全流程。在开始之前我们需要明确几个关键点首先豆瓣对爬虫有一定反爬机制需要合理设置请求间隔其次动态加载的内容需要特殊处理最后原始HTML数据需要经过清洗才能用于分析。这套方案使用了Python生态中最成熟的工具链请求库Requests Playwright组合兼顾静态页面效率和动态内容抓取解析工具BeautifulSoup lxml解析引擎处理复杂的HTML结构数据存储Pandas DataFrame CSV本地存储方便后续处理AIGC集成通过阿里云API调用通义千问模型进行文本分析重要提示实际操作中请严格遵守豆瓣的robots.txt规定设置合理的请求间隔建议每页间隔5-10秒避免对服务器造成压力。本文示例仅用于技术学习交流。2. 环境配置与工具选型2.1 Python环境搭建推荐使用Miniconda创建独立环境避免包冲突。以下是具体步骤conda create -n douban python3.9 conda activate douban pip install requests beautifulsoup4 playwright pandas playwright install # 安装浏览器驱动选择Python 3.9版本是因为它在第三方库兼容性和新特性之间取得了良好平衡。对于IDEVSCode配合Python插件足以满足开发需求关键是要配置好以下设置{ python.linting.enabled: true, python.formatting.provider: black, python.analysis.typeCheckingMode: basic }2.2 爬虫工具对比我们放弃了Scrapy等框架而选择手动组合工具主要基于以下考虑工具优势适用场景本方案采用原因Requests简单高效社区支持完善静态页面获取豆瓣主要页面仍是传统HTMLPlaywright完美处理动态内容支持无头浏览器SPA应用、JS渲染内容应对豆瓣部分AJAX加载的评价数据BeautifulSoup容错性强API友好复杂HTML解析豆瓣页面结构多变需要灵活处理Pandas内置数据清洗功能导出格式丰富中小规模结构化数据处理电影数据天然适合表格形式存储3. 豆瓣页面结构分析与爬取策略3.1 榜单页面逆向工程豆瓣Top250的页面结构以第一页为例具有以下特征基础URLhttps://movie.douban.com/top250?start{offset}每页展示25部电影通过start参数分页单个电影条目包含在div classitem中关键信息分布规律标题span classtitle第一个文本内容评分span classrating_num评价人数div classstar中的span最后一个文本简介span classinq可能不存在通过Chrome开发者工具分析我们发现评价数据是通过AJAX动态加载的常规请求无法获取。这就是需要Playwright的场合。3.2 混合爬取实现代码import requests from bs4 import BeautifulSoup from playwright.sync_api import sync_playwright import pandas as pd import time def get_static_page(url): headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } response requests.get(url, headersheaders) response.raise_for_status() return BeautifulSoup(response.text, lxml) def get_dynamic_content(movie_url): with sync_playwright() as p: browser p.chromium.launch(headlessTrue) page browser.new_page() page.goto(movie_url) # 等待评价区域加载 page.wait_for_selector(.rating_sum) content page.content() browser.close() return BeautifulSoup(content, lxml) def parse_movie(item): movie {} movie[title] item.find(span, class_title).text movie[rating] float(item.find(span, class_rating_num).text) star item.find(div, class_star) movie[votes] int(star.find_all(span)[-1].text[:-3]) movie[url] item.find(a)[href] return movie4. 数据清洗与增强实践4.1 原始数据标准化处理爬取的原始数据需要经过以下处理流程缺失值处理部分电影可能缺少简介需要统一填充为无类型转换将评分、投票数等转换为数值类型去重处理检查是否有重复条目文本清洗去除标题中的特殊字符和空白def clean_data(df): # 类型转换 df[rating] df[rating].astype(float) df[votes] df[votes].astype(int) # 添加排名字段 df[rank] range(1, len(df)1) # 按评分和投票数综合排序 df[score] df[rating] * (df[votes] / df[votes].max()) return df.sort_values(score, ascendingFalse)4.2 通过AIGC增强数据我们使用阿里云的通义千问API为电影生成简评需要提前申请API Keyimport dashscope def generate_comment(title, rating): prompt f作为专业影评人用100字左右点评电影《{title}》豆瓣评分{rating}分 response dashscope.Generation.call( modelqwen-turbo, promptprompt ) return response.output.text if response.status_code 200 else 暂无AI生成评论5. 完整工作流与异常处理5.1 主流程封装将上述模块组合成完整流程def scrape_top250(): base_url https://movie.douban.com/top250 all_movies [] for start in range(0, 250, 25): page_url f{base_url}?start{start} soup get_static_page(page_url) for item in soup.find_all(div, class_item): try: movie parse_movie(item) detail_soup get_dynamic_content(movie[url]) # 提取更多细节... all_movies.append(movie) time.sleep(5) # 遵守爬虫礼仪 except Exception as e: print(f处理{movie.get(title,未知)}时出错: {str(e)}) continue df pd.DataFrame(all_movies) return clean_data(df)5.2 常见问题解决方案在实际操作中会遇到以下典型问题IP被封禁症状连续请求后返回403状态码解决方案增加随机延迟2-10秒使用代理IP轮询动态内容加载失败症状Playwright获取的内容仍不完整解决方案调整wait_for_selector的超时时间添加重试机制数据结构变化症状解析失败找不到预期的HTML元素解决方案更新CSS选择器添加更灵活的备用选择路径# 增强版的解析函数 def robust_parse(element): data {} try: data[title] element.find(span, class_title).text except: data[title] element.find(a).find(img)[alt] # 其他字段类似处理... return data6. 数据分析与可视化6.1 基础统计指标对抓取的数据进行初步分析def basic_analysis(df): print(f平均评分: {df[rating].mean():.1f}) print(f中位评分: {df[rating].median():.1f}) print(f最高评分: {df[rating].max()} ({df.loc[df[rating].idxmax()][title]})) # 评分分布直方图 df[rating].plot.hist(bins10, alpha0.7) plt.title(豆瓣Top250评分分布) plt.xlabel(评分) plt.ylabel(电影数量)6.2 高级分析维度评分与投票数的关系使用散点图观察是否存在高评分但冷门的电影计算皮尔逊相关系数时间趋势分析从电影URL中提取年份信息分析不同年代电影的平均评分变化AIGC生成内容分析对生成的评论进行情感分析提取高频关键词形成词云# 示例提取电影年份 def extract_year(url): soup get_static_page(url) year_text soup.find(span, class_year).text return int(year_text.strip(()))7. 项目优化与扩展方向7.1 性能优化技巧并发控制使用aiohttp实现异步请求通过Semaphore限制并发数量缓存机制对已抓取页面进行本地缓存使用SQLite存储原始数据增量抓取记录最后抓取时间只更新发生变化的内容# 异步请求示例 import aiohttp async def fetch_page(session, url): async with session.get(url) as response: return await response.text()7.2 扩展应用场景自动化报告生成结合Jinja2模板生成HTML报告定期自动发送邮件简报推荐系统基础构建电影相似度矩阵实现基于内容的推荐多平台整合抓取IMDB数据进行比较分析整合猫眼等商业平台的票房数据这套系统我已经在生产环境运行了两年多期间经历了豆瓣至少三次大的页面改版。保持爬虫健壮性的关键在于模块化设计、完善的日志记录、及时更新的测试用例。当发现解析失败率超过5%时就应该触发告警并检查页面结构变化。