零基础实战:用Xpath精准抓取豆瓣电影Top250全流程指南
豆瓣电影Top250榜单作为互联网上最具公信力的电影评分集合之一,包含了影史经典与当代佳作。对于数据分析爱好者而言,这个列表不仅是观影指南,更是绝佳的数据分析素材。本文将带你从零开始,使用Python中最高效的lxml库配合Xpath语法,完整实现榜单数据的自动化采集。
1. 环境准备与目标分析
在开始编写爬虫之前,我们需要先搭建好开发环境。推荐使用Python 3.8+版本,这是目前最稳定的Python发行版。关键依赖库包括:
pip install lxml requests pandas- lxml:高性能HTML/XML解析库,支持Xpath 1.0语法
- requests:简洁优雅的HTTP请求库
- pandas:数据处理的瑞士军刀,用于最终的数据存储
提示:为避免对目标网站造成过大访问压力,建议在代码中添加适当的延时(如3-5秒/页),并设置合理的User-Agent头部。
豆瓣Top250页面结构非常规整,每页展示25部电影,共10页。我们需要提取的核心数据字段包括:
| 字段名 | Xpath定位特征 | 数据类型 |
|---|---|---|
| 电影名称 | class为"title"的span标签 | 字符串 |
| 评分 | class为"rating_num"的span标签 | 浮点数 |
| 评价人数 | 包含"人评价"文本的div | 整数 |
| 经典台词 | class为"quote"的span标签 | 字符串 |
| 详情链接 | a标签的href属性 | URL |
2. 页面结构深度解析
打开豆瓣电影Top250页面(https://movie.douban.com/top250),右键选择"检查"进入开发者工具。通过元素检查器可以发现,每部电影信息都包裹在一个class为"item"的div中,这将成为我们定位的基准点。
关键DOM结构如下:
<ol class="grid_view"> <li> <div class="item"> <div class="info"> <div class="hd"> <a href="..."> <span class="title">电影名称</span> </a> </div> <div class="bd"> <div class="star"> <span class="rating_num">9.6</span> <span>1234567人评价</span> </div> <p class="quote"> <span class="inq">经典台词</span> </p> </div> </div> </div> </li> <!-- 其他24部电影结构相同 --> </ol>基于此结构,我们可以设计出精准的Xpath表达式:
- 电影名称:
//div[@class='item']//span[@class='title'][1]/text() - 评分:
//div[@class='item']//span[@class='rating_num']/text() - 评价人数:
//div[@class='item']//div[@class='star']/span[last()]/text() - 经典台词:
//div[@class='item']//span[@class='inq']/text() - 详情链接:
//div[@class='item']//div[@class='hd']/a/@href
注意:电影名称有两个span元素(中文名和原名),我们通常只需要中文名,因此添加了[1]索引。
3. 核心爬取代码实现
下面我们分步骤实现完整的爬取逻辑。首先是基础请求函数:
import requests from lxml import html import time import pandas as pd HEADERS = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } def get_page(url): """获取页面HTML内容""" try: resp = requests.get(url, headers=HEADERS) resp.raise_for_status() return resp.text except requests.exceptions.RequestException as e: print(f"请求失败: {e}") return None接下来是解析单页电影数据的核心函数:
def parse_page(html_content): """解析单页电影数据""" tree = html.fromstring(html_content) movies = [] for item in tree.xpath('//div[@class="item"]'): movie = { 'title': item.xpath('.//span[@class="title"][1]/text()')[0], 'rating': float(item.xpath('.//span[@class="rating_num"]/text()')[0]), 'votes': int(item.xpath('.//div[@class="star"]/span[last()]/text()')[0].replace('人评价', '')), 'quote': item.xpath('.//span[@class="inq"]/text()')[0] if item.xpath('.//span[@class="inq"]/text()') else '', 'url': item.xpath('.//div[@class="hd"]/a/@href')[0] } movies.append(movie) return movies处理分页逻辑时,我们观察到豆瓣的翻页URL规律明显:
第1页:https://movie.douban.com/top250 第2页:https://movie.douban.com/top250?start=25&filter= 第3页:https://movie.douban.com/top250?start=50&filter= ...基于此规律,我们可以构建完整的分页爬取流程:
def crawl_top250(): """爬取完整Top250榜单""" base_url = "https://movie.douban.com/top250" all_movies = [] for start in range(0, 250, 25): url = f"{base_url}?start={start}&filter=" if start > 0 else base_url print(f"正在抓取: {url}") html_content = get_page(url) if html_content: all_movies.extend(parse_page(html_content)) time.sleep(3) # 礼貌性延时 return all_movies4. 数据存储与后续处理
获取到的数据可以保存为多种格式,这里我们展示最常用的CSV和JSON两种方式:
def save_data(movies, format='csv'): """保存电影数据""" df = pd.DataFrame(movies) if format == 'csv': df.to_csv('douban_top250.csv', index=False, encoding='utf_8_sig') elif format == 'json': df.to_json('douban_top250.json', orient='records', force_ascii=False) else: raise ValueError("不支持的格式,请选择csv或json")调用示例:
if __name__ == '__main__': movies = crawl_top250() save_data(movies, 'csv') print(f"成功抓取{len(movies)}部电影数据!")5. 高级技巧与异常处理
在实际爬取过程中,可能会遇到各种异常情况。以下是几个常见问题的解决方案:
反爬虫应对策略
- 随机User-Agent:使用fake_useragent库生成随机头部
from fake_useragent import UserAgent ua = UserAgent() HEADERS['User-Agent'] = ua.random- IP代理池:对于大规模爬取,建议使用代理服务
PROXIES = {'http': 'http://your.proxy:port'} resp = requests.get(url, headers=HEADERS, proxies=PROXIES)数据清洗技巧
评价人数字段原始值为"1234567人评价",我们需要提取数字部分:
votes_text = item.xpath('.//div[@class="star"]/span[last()]/text()')[0] votes = int(votes_text.replace('人评价', '').replace(',', ''))缺失值处理
不是所有电影都有经典台词,因此需要判断:
quote = item.xpath('.//span[@class="inq"]/text()')[0] if item.xpath('.//span[@class="inq"]/text()') else None6. 数据分析延伸应用
获取到数据后,我们可以进行各种有趣的分析:
# 读取数据 df = pd.read_csv('douban_top250.csv') # 评分分布分析 rating_dist = df['rating'].value_counts().sort_index() print("评分分布:\n", rating_dist) # 评价人数Top10 top10_voted = df.sort_values('votes', ascending=False).head(10)[['title', 'votes']] print("\n评价人数Top10:\n", top10_voted) # 评分与评价人数关系 df.plot.scatter(x='rating', y='votes', logy=True, title='评分 vs 评价人数')通过这些分析,我们可以发现哪些电影叫好又叫座,哪些是高分小众作品等有趣现象。