正则表达式爬虫实战:抓取豆瓣电影Top250
一、前言在Python爬虫学习路线中正则表达式是解析网页源码最基础、最常用的技能之一适合快速提取结构化HTML文本中的目标数据。豆瓣电影Top250页面结构规整、反爬门槛低是入门爬虫的经典练手项目。本文将基于 requests 网络请求库 和 re 正则表达式库从零实现爬取豆瓣Top250单页电影数据提取电影名、导演、上映年份、评分、评价人数五大核心字段并将数据持久化保存到CSV文件。同时深度解析正则语法、 re.S 标志位、命名分组、爬虫基础反爬策略最后补充代码优化、分页爬取、异常处理等进阶内容适合零基础爬虫学习者。环境说明Python 3.7依赖库 requests内置库 re 、 os二、前期准备与网页分析2.1 安装依赖库打开终端/CMD执行以下命令安装网络请求库pip install requests2.2 网页结构分析目标地址https://movie.douban.com/top2501. 打开网页按 F12 进入开发者工具切换到元素(Elements)面板定位单部电影的HTML标签。2. 每一部电影都被包裹在 div classitem 标签内这是我们正则匹配的核心容器。3. 逐个定位目标字段标签电影名称 span classtitle电影名/span导演信息 p 标签内 导演: XXX 字段上映年份 br 换行后紧跟的年份字段电影评分 span classrating_num propertyv:average分数/span评价人数 spanXXX人评价/span2.3 爬虫反爬策略豆瓣具备基础反爬机制直接裸请求会被拦截因此必须设置请求头Headers伪装成浏览器访问。核心配置 User-Agent 模拟Chrome浏览器客户端。三、核心代码实现3.1 完整可运行代码import requests import re f open(top250.csv,modew,encodingutf-8) headers{ user-agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 } url fhttps://movie.douban.com/top250 resprequests.get(url,headersheaders) pageSource resp.text print(pageSource) #re.S 可以让正则中的.匹配换行符 obj re.compile(rdiv classitem.*?span classtitle(?Pname.*?)/span.*?p.*?导演:(?Pdirector.*?)nbsp;.*?br(?Pyear.*?)nbsp;.*?span classrating_num propertyv:average(?Pscore.*?)/span.*?span(?Pnum.*?)人评价/span,re.S) result obj.finditer(pageSource) for item in result: name item.group(name) director item.group(director) year item.group(year).strip() score item.group(score) num item.group(num) f.write(f{name},{director},{year},{score},{num}\n) f.close() resp.close() print(豆瓣TOP250提取完毕.)3.2 运行效果1. 运行代码后项目根目录会生成top250.csv文件。2. 文件内包含25部电影的完整信息字段分隔规范中文无乱码。3. 控制台输出豆瓣TOP250提取完毕表示执行成功四、核心知识点深度解析4.1 requests请求模块解析1. Headers请求头User-Agent 是浏览器身份标识豆瓣服务器会校验客户端类型未配置UA的请求会被判定为爬虫直接拒绝访问。2. 编码设置resp.encoding utf-8 强制指定网页编码彻底解决HTML源码中文乱码问题。3. 资源释放resp.close() 主动关闭请求连接避免大量请求导致内存占用过高。4.2 正则表达式核心语法4.2.1re.compile()预编译正则re.compile()会提前编译正则规则多次匹配时效率高 是爬虫正则的标准写法。4.2.2 非贪婪匹配.*?.* 贪婪匹配会尽可能匹配最长字符串容易跨标签匹配到多余内容。.*? 非贪婪匹配匹配最短内容精准定位单个标签内的数据爬虫解析HTML必备语法。4.2.3 命名分组 (?P分组名正则内容)普通分组通过下标 group(1) 、 group(2) 取值可读性差、易出错。命名分组格式 (?Pname.*?) 可以通过 group(name) 按名称取值代码语义清晰后期维护方便。本案例中共定义5个命名分组 name (电影名)、 director (导演)、 year (年份)、 score (评分)、 num (评价人数)。4.2.4 re.S 标志位默认情况下正则中的 . 点号无法匹配换行符 \n 。而HTML标签经常跨行分布直接匹配会导致匹配失败。re.S 的作用让 . 匹配包括换行符在内的所有字符完美解决HTML跨行解析问题 。4.2.5 finditer() 迭代器匹配pattern.finditer() 返回一个迭代器相比 findall() 优势海量数据下内存占用更低逐条遍历提取数据适合网页爬虫场景。用法通过 for 循环遍历迭代器对象再通过 group() 提取分组内容。4.3 CSV文件写入说明1. 打开文件指定 encodingutf-8 Windows系统默认GBK编码不指定会导致CSV中文乱码。2. 写入表头手动添加表头方便后续查看与数据分析。3. strip() 方法年份字段前后存在空白字符 strip() 清除首尾空格、换行符格式化数据。五、进阶扩展5.1 优化1增加异常捕获网络请求存在超时、断网、页面结构变更等问题添加 try-except 异常捕获防止程序直接崩溃import requests import re headers { user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 } url https://movie.douban.com/top250 try: resp requests.get(url, headersheaders, timeout10) resp.encoding utf-8 page_source resp.text resp.close() # 正则编译与匹配逻辑不变 pattern re.compile( rdiv classitem.*? rspan classtitle(?Pname.*?)/span.*? rp.*?导演:(?Pdirector.*?)nbsp;.*? rbr(?Pyear.*?)nbsp;.*? rspan classrating_num propertyv:average(?Pscore.*?)/span.*? rspan(?Pnum.*?)人评价/span, re.S ) movie_results pattern.finditer(page_source) with open(douban_top250.csv, modew, encodingutf-8) as f: f.write(电影名称,导演,上映年份,评分,评价人数\n) for movie in movie_results: name movie.group(name) director movie.group(director) year movie.group(year).strip() score movie.group(score) num movie.group(num) f.write(f{name},{director},{year},{score},{num}\n) print(数据提取完成) except requests.exceptions.Timeout: print(请求超时请检查网络) except Exception as e: print(f程序异常{e})补充说明使用 with open() 语句打开文件无需手动执行 close() 代码更简洁且能自动释放文件资源。5.2 优化2分页爬取完整250部电影豆瓣Top250共10页每页25部电影URL分页规则第1页 https://movie.douban.com/top250?start0第2页 https://movie.douban.com/top250?start25第3页 https://movie.douban.com/top250?start50规律 start 页码 * 25 总页数10页 start 取值 0、25 ... 225分页完整代码import requests import re import time headers { user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 } # 基础URL预留start参数占位符 base_url https://movie.douban.com/top250?start{} # 编译正则全局编译避免循环内重复编译 pattern re.compile( rdiv classitem.*? rspan classtitle(?Pname.*?)/span.*? rp.*?导演:(?Pdirector.*?)nbsp;.*? rbr(?Pyear.*?)nbsp;.*? rspan classrating_num propertyv:average(?Pscore.*?)/span.*? rspan(?Pnum.*?)人评价/span, re.S ) # 写入文件 with open(douban_top250_all.csv, modew, encodingutf-8) as f: f.write(电影名称,导演,上映年份,评分,评价人数\n) # 循环10页步长25 for offset in range(0, 250, 25): url base_url.format(offset) print(f正在爬取{url}) try: resp requests.get(url, headersheaders, timeout10) resp.encoding utf-8 page_source resp.text resp.close() # 匹配当前页数据 movie_results pattern.finditer(page_source) for movie in movie_results: name movie.group(name) director movie.group(director) year movie.group(year).strip() score movie.group(score) num movie.group(num) f.write(f{name},{director},{year},{score},{num}\n) # 延时1秒模拟人工浏览降低反爬风险 time.sleep(1) except Exception as e: print(f爬取 {url} 失败{e}) print(全部250部电影爬取完成)5.3 优化3数据清洗部分电影存在多导演、别名、年份附带地区等情况可基于正则二次清洗数据1. 提取纯年份 re.search(r(\d{4}), year).group(1) 匹配4位数字年份。2. 截取第一位导演 director.split( )[0] 分割多导演字符串。六、常见问题排查6.1 问题1匹配结果为空原因1未添加 re.S . 无法匹配换行符。解决方案正则末尾加上 re.S 。原因2HTML标签类名/结构变更。解决方案重新F12核对标签内容修正正则规则。原因3请求被拦截。解决方案检查 User-Agent 是否配置正确。6.2 问题2CSV文件中文乱码原因文件打开编码非UTF-8。解决方案 open() 函数强制指定 encodingutf-8 。6.3 问题3年份字段存在多余空格解决方案使用 strip() 方法清除首尾空白字符。6.4 问题4爬取多页被限制访问解决方案添加 time.sleep(1~3) 延时模拟人工访问频率避免高频请求触发反爬。七、总结1. 技术栈回顾本案例结合 requests 实现网络请求 re 正则解析HTML最终将数据落地到CSV文件覆盖爬虫入门三大核心流程请求 → 解析 → 存储。2. 正则核心要点 .*? 非贪婪匹配、 (?Pname) 命名分组、 re.S 跨行匹配这三个语法是HTML正则解析的基石。3. 爬虫规范伪装 User-Agent 、添加延时、异常捕获、主动释放资源是编写稳健爬虫的基本准则。4. 拓展方向在此基础上可继续扩展爬取电影简介、主演、电影类型或结合 pandas 做数据分析、存入MySQL数据库等。本文案例适合Python初学者巩固爬虫基础代码注释详细、逻辑清晰可直接复刻运行。如果对你有帮助欢迎点赞收藏