开发记录:豆瓣电影榜单爬虫

开发记录:豆瓣电影榜单爬虫 概述旨在爬取豆瓣电影榜单https://movie.douban.com/chart中各电影的详细信息包括名称、年份、片长、类型、导演、演员和剧情简介并将数据保存为 CSV 文件。开发过程中逐步完善了请求头、XPath 解析、异常处理、数据持久化及反爬策略。版本迭代记录v0.1 (2026-07-20) - 基础框架搭建文件01_网络机器人-电影榜单.py主要更新定义基础请求头User-Agent、Referer 等。实现榜单页请求与状态码检查。使用lxml解析 HTML通过 XPath 定位电影条目//table[width100%]。预留get_movie_info()和save_all_movies()函数仅pass占位。遍历电影条目提取详情页链接并打印未调用详情函数。存在问题未实现详情页数据提取all_movie列表始终为空。缺少异常处理与重试机制。无随机延时容易被反爬。v0.2 (2026-07-22) - 详情页解析与数据保存文件02_网络机器人-电影榜单.py主要更新完善get_movie_info()发送详情页请求解析名称、年份、类型、导演、描述使用固定 XPath。实现save_all_movies()将数据写入 CSV 文件指定字段名。扩展全局HEADERS加入 Cookie、Accept-Encoding 等模拟真实浏览器。主函数中调用详情函数并打印调试信息。遗留缺陷关键 bugall_movie列表未被填充未执行append导致 CSV 始终只有表头。XPath 路径脆弱仅依赖单一表达式豆瓣页面结构变化时易失效。无请求失败重试网络波动可能中断程序。v0.3 (2026-07-25) - 健壮性与容错增强文件03_网络机器人-电影榜单.py主要更新修复核心 bug在main()循环中将获取到的movie_info通过all_movie.append(movie_info)正确添加到列表。增加异常处理对requests.get()捕获异常避免程序崩溃。增加内容长度校验详情页响应小于 1000 字节时视为被反爬打印前 500 字符辅助调试。扩展 XPath 备选路径针对名称、年份、类型、导演、描述均提供多种定位方式提高适应性。引入随机延时每部电影请求后time.sleep(random.uniform(1.0, 2.5))降低请求频率。自动创建 CSV 输出目录os.makedirs避免路径不存在错误。效果程序可完整获取榜单中所有电影的详情数据并正确写入 CSV。面对页面结构调整或临时反爬时容错性显著提升。v0.4 (2026-07-28) - 字段扩展与数据丰富文件04_网络机器人-电影榜单.py主要更新新增片长v:runtime和演员字段丰富电影信息维度。演员提取采用主演标签后的a文本多个演员用 / 连接。更新 CSV 列名[名称, 年份, 片长, 类型, 导演, 演员, 描述]。优化描述提取优先级优先使用v:summary其次span[classall]最后旧版路径。改进意义采集的数据更加全面满足后续分析或展示需求。字段结构更贴近电影主页实际内容。v0.5 (2026-07-30) - 最终稳定版本内容同 v0.4文件05_网络机器人-电影榜单.py说明此版本与 v0.4 代码完全一致未做功能改动。命名调整可能为了版本管理统一实际以 v0.4 为最终发行版。最终成果总结import requests import csv from lxml import html import os import time import random MOVIE_LIST_FILE csv_data/movie_list.csv DOUBAN_BASE_URL https://movie.douban.com DOUBAN_TOP_URL https://movie.douban.com/chart HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Cookie: ll118201; bidmV59O_L0Z88; _pk_id.100001.4cf6906f4c077c4e286a.1784944501.; _vwo_uuid_v2DA6D5EB58DC057096520FFE47A8EECEFD|78ce2ecada7a16f9034a20c3961fe216; dbcl2296323525:ylL/ts6TrE; push_noty_num0; push_doumail_num0; cksfI2; _pk_ref.100001.4cf6%5B%22%22%2C%22%22%2C1785149957%2C%22https%3A%2F%2Fcn.bing.com%2F%22%5D; _pk_ses.100001.4cf61; __utma30149280.570823089.1784944501.1784944501.1785149957.2; __utmb30149280.0.10.1785149957; __utmc30149280; __utmz30149280.1785149957.2.2.utmcsrcn.bing.com|utmccn(referral)|utmcmdreferral|utmcct/; __utma223695111.164060955.1784944501.1784944501.1785149957.2; __utmb223695111.0.10.1785149957; __utmc223695111; __utmz223695111.1785149957.2.2.utmcsrcn.bing.com|utmccn(referral)|utmcmdreferral|utmcct/; ap_v0,6.0; frodotk_dbb2d4bee9cf2e9d30187e3cbd6d8bd790, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Accept-Encoding: gzip, deflate, br, Connection: keep-alive, Upgrade-Insecure-Requests: 1, Sec-Fetch-Dest: document, Sec-Fetch-Mode: navigate, Sec-Fetch-Site: same-origin, } def get_movie_info(movie_info_url): 获取单部电影的详细信息 print(f正在请求详情页: {movie_info_url}) try: movie_response requests.get(movie_info_url, headersHEADERS, timeout15) except Exception as e: print(f请求异常: {e}) return None if movie_response.status_code ! 200: print(f请求失败状态码: {movie_response.status_code}) return None if len(movie_response.text) 1000: print(内容过短可能被反爬前500字符) print(movie_response.text[:500]) return None movie_document html.fromstring(movie_response.text) # 提取字段使用多种备选 XPath # 名称 name_list movie_document.xpath(//h1/span[1]/text()) if not name_list: name_list movie_document.xpath(//*[idcontent]/h1/span[1]/text()) name name_list[0].strip() if name_list else # 年份 year_list movie_document.xpath(//h1/span[2]/text()) if not year_list: year_list movie_document.xpath(//*[idcontent]/h1/span[2]/text()) year year_list[0].strip() if year_list else # 时长 time_list movie_document.xpath(//div[idinfo]//span[propertyv:runtime]/text()) time time_list[0].strip() if time_list else # 类型 type_elems movie_document.xpath(//span[contains(text(), 类型)]/following-sibling::a/text()) if not type_elems: type_elems movie_document.xpath(//*[idinfo]/span[5]/text()) type_str ,.join([t.strip() for t in type_elems if t.strip()]) # 导演 director_elems movie_document.xpath(//span[contains(text(), 导演)]/following-sibling::a/text()) if not director_elems: director_elems movie_document.xpath(//*[idinfo]/span[1]/span[2]/a/text()) director director_elems[0].strip() if director_elems else # 演员 actor_elems movie_document.xpath(//div[idinfo]//span[contains(text(), 主演)]/following-sibling::span/a/text()) if actor_elems: actors / .join([name.strip() for name in actor_elems]) else: actors # 描述剧情简介 desc_elems movie_document.xpath(//span[propertyv:summary]/text()) if not desc_elems: desc_elems movie_document.xpath(//div[idlink-report]//span[classall]/text()) if not desc_elems: desc_elems movie_document.xpath(//*[idlink-report-intra]/span[1]/text()) description .join([d.strip() for d in desc_elems if d.strip()]) movie_info { 名称: name, 年份: year, 片长: time, 类型: type_str, 导演: director, 演员: actors, 描述: description, } print(movie_info) # 控制台显示当前电影信息 return movie_info def save_all_movies(all_movies): 保存所有电影信息到 CSV 文件自动创建目录 dir_name os.path.dirname(MOVIE_LIST_FILE) if dir_name and not os.path.exists(dir_name): os.makedirs(dir_name) print(f已创建目录: {dir_name}) print(f准备写入 {len(all_movies)} 条数据到 CSV) # 调试显示数据条数 with open(MOVIE_LIST_FILE, modew, newline, encodingutf-8) as file: writer csv.DictWriter(file, fieldnames[名称, 年份,片长, 类型, 导演, 演员,描述]) writer.writeheader() if all_movies: writer.writerows(all_movies) print(f成功写入 {len(all_movies)} 条数据) else: print(警告没有数据可写入仅创建了表头) def main(): print(正在获取榜单页...) try: response requests.get(DOUBAN_TOP_URL, headersHEADERS, timeout20) except Exception as e: print(f请求榜单页异常: {e}) return print(f状态码: {response.status_code}, 内容长度: {len(response.text)}) if response.status_code ! 200 or len(response.text) 100: print(榜单页获取失败或内容为空) return document html.fromstring(response.text) # 定位电影条目多种备选 movie_list document.xpath(//table[width100%]) if not movie_list: movie_list document.xpath(//div[classitem]) if not movie_list: movie_list document.xpath(//*[idcontent]/div/div/div/div/table[width100%]) print(f找到 {len(movie_list)} 个电影条目) all_movie [] # 用于收集所有电影数据 for idx, movie in enumerate(movie_list, 1): # 提取详情页链接 url_elements movie.xpath(.//a[classnbg]/href) if not url_elements: url_elements movie.xpath(.//a/href) if not url_elements: print(f第 {idx} 个条目未找到链接跳过) continue movie_info_url url_elements[0] if not movie_info_url.startswith(http): movie_info_url DOUBAN_BASE_URL movie_info_url # 获取电影信息 movie_info get_movie_info(movie_info_url) if movie_info: all_movie.append(movie_info) # --- 关键修复添加数据到列表 print(f当前已收集 {len(all_movie)} 部电影) else: print(f第 {idx} 个详情页获取失败返回 None) # 随机延时避免请求过快 time.sleep(random.uniform(1.0, 2.5)) # 保存数据 save_all_movies(all_movie) if __name__ __main__: main()功能稳定爬取豆瓣电影榜单当前榜单页所有电影的 7 项信息并保存为csv_data/movie_list.csv。技术要点请求头模拟真实浏览器含 Cookie降低反爬风险。多 XPath 备选策略适配页面结构变化。异常捕获与超时设置增强鲁棒性。随机延时控制请求频率避免 IP 被封。自动目录创建提升用户体验。已知限制仅处理榜单第一页/chart未实现分页爬取。Cookie 可能过期需定期更新。描述字段可能包含过长文本CSV 显示需注意。后续优化建议实现多页榜单爬取通过?start...参数。将 Cookie 独立为配置文件便于更新。增加日志记录模块替代print调试输出。使用代理池轮换 IP进一步提高稳定性。