爬虫实战-豆瓣top250

准备工作

开始前需要准备这些工具和库:

  • Python环境:推荐 Python 3.8+,我用的是 Python 3.10
  • 编辑器:VS Code 或 PyCharm 都行,我习惯用 VS Code
  • 必备库:requests(发送网络请求)、BeautifulSoup(解析网页)、csv(保存数据)

安装命令在这里:

1
pip install requests beautifulsoup4

7e7b16649372fbdeff2761bd9b8f0623.png

核心功能实现

1. 伪装浏览器请求

直接用 Python 发送请求会被豆瓣识别并拒绝,所以第一步要设置请求头,伪装成浏览器访问。代码里这样写:

1
2
3
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'
}

这个 User-Agent 字符串是 Chrome 浏览器的标识,你也可以换成 Firefox 或 Edge 的,网上一搜就能找到。

2. 获取榜单页面

豆瓣 Top250 每页显示 25 部电影,共 10 页。我们需要循环获取这 10 个页面,URL 规律是 https://movie.douban.com/top250?start=0(start=0 是第一页,start=25 是第二页,以此类推)。

核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
base_url = "https://movie.douban.com/top250"

def get_page(url):
response = requests.get(url, headers=headers)
if response.status_code != 200:
print(f"请求失败,状态码: {response.status_code}")
return None
return response.text

# 循环获取10页数据
for start in range(0, 250, 25):
url = f"{base_url}?start={start}"
html = get_page(url)
# 解析页面...

3. 提取电影基本信息

拿到网页 HTML 后,用 BeautifulSoup 解析。先看豆瓣榜单页面的结构,每部电影信息都在 class="item" 的 div 里:

豆瓣电影列表页面结构

我们需要提取排名、标题、评分、详情页链接这些信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def parse_page(html):
soup = BeautifulSoup(html, 'html.parser')
movies = soup.find_all('div', class_='item')

for movie in movies:
# 提取排名
rank = movie.find('em').text.strip()
# 提取标题
title = movie.find('span', class_='title').text.strip()
# 提取评分
rating = movie.find('span', class_='rating_num').text.strip()
# 提取详情页链接
link = movie.find('a')['href']

# 这里还可以提取导演、主演等信息...

4. 深入详情页获取更多数据

榜单页面只有基础信息,想获取类型、制片国家、剧情简介等详细数据,需要跳转到每部电影的详情页。比如《肖申克的救赎》的详情页是 https://movie.douban.com/subject/1292052/

以提取电影类型为例:

1
2
3
4
5
6
7
8
9
10
def get_movie_details(url):
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, 'html.parser')

# 提取类型
type_span = soup.find('span', class_='pl', string=re.compile('类型:'))
if type_span:
movie_type = type_span.find_next_sibling('span', class_='v:genre').text.strip()

# 类似方法提取制片国家、语言、上映日期等...

5. 数据保存到 CSV

爬下来的数据需要持久化保存,CSV 格式最适合。用 Python 内置的 csv 模块就能实现:

1
2
3
4
5
def save_to_csv(movie_list):
with open('douban_top250.csv', 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.DictWriter(f, fieldnames=movie_list[0].keys())
writer.writeheader() # 写入表头
writer.writerows(movie_list) # 写入所有数据

注意这里用了 utf-8-sig 编码,这样用 Excel 打开不会乱码。

关键技术点解析

1. 正则表达式提取导演名

豆瓣页面里导演和主演信息混在一起,比如 “导演: 弗兰克·德拉邦特 Frank Darabont 主演: 蒂姆·罗宾斯…”,需要用正则把导演名提取出来:

1
2
3
4
5
def extract_director_names(text):
# 匹配中文导演名
pattern = r"导演:\s*([\u4e00-\u9fa5·]+)"
match = re.search(pattern, text)
return match.group(1) if match else "未知导演"

这个正则会匹配 “导演:” 后面的中文字符(包括可能的 · 分隔符)。

2. 异常处理避免程序崩溃

爬取过程中经常会遇到各种意外情况(比如某个页面结构变了,或者网络突然断了),所以必须加异常处理:

1
2
3
4
5
try:
# 解析页面的代码...
except Exception as e:
print(f"处理电影时出错: {e}")
continue # 出错了就跳过这部电影,继续处理下一个

3. 反爬措施

豆瓣有基础的反爬机制,除了设置 User-Agent,还可以:

  • 不要爬太快,加个时间间隔(time.sleep(1)
  • 多个 User-Agent 轮换使用
  • 避免在短时间内频繁运行爬虫

运行步骤

  1. 把完整代码复制到 VS Code 中,保存为 douban_spider.py
  2. 安装依赖库:pip install requests beautifulsoup4
  3. 运行代码:python douban_spider.py
  4. 等待程序运行完成(大概需要 5-10 分钟)
  5. 在同目录下找到 douban_top250.csv 文件

033149586931b2dfcd3072a2d0bfaa32.png

结果展示

爬下来的数据包含这些字段:

  • 排名、电影名、别名、评分
  • 导演、编剧、主演
  • 类型、制片国家/地区、语言
  • 上映日期、片长、IMDb链接
  • 剧情简介

用 Excel 打开 CSV 文件后,可以做各种筛选和排序:

  • 按评分从高到低排序
  • 筛选特定类型(比如只看科幻片)
  • 查找某个导演的所有作品

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import csv
import requests
import re
from bs4 import BeautifulSoup

# 设置请求头,避免被豆瓣屏蔽
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'
}

# 获取豆瓣电影Top250的基础URL
base_url = "https://movie.douban.com/top250"
movie_list = []

def get_page(url):
"""获取网页内容"""
try:
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status() # 检查请求是否成功
return response.text
except requests.exceptions.RequestException as e:
print(f"请求失败: {e}")
return None

def extract_director_names(text):
"""从文本中提取导演中文名"""
if not text:
return "未知导演"
# 匹配"导演:"后面的中文序列
chinese_name_pattern = r"导演:\s*([\u4e00-\u9fa5·]+)"
chinese_match = re.search(chinese_name_pattern, text)
return chinese_match.group(1) if chinese_match else "未知导演"

def get_movie_details(url):
"""获取电影详情页信息"""
html = get_page(url)
if not html:
return {}

soup = BeautifulSoup(html, 'html.parser')
details = {}

# 获取编剧
screenwriter_span = soup.find('span', class_='pl', string=re.compile('编剧'))
if screenwriter_span:
screenwriters_span = screenwriter_span.find_next_sibling('span', class_='attrs')
if screenwriters_span:
details['screenwriters'] = [a.text.strip() for a in screenwriters_span.find_all('a')]

# 获取主演
actor_span = soup.find('span', class_='pl', string=re.compile('主演'))
if actor_span:
actors_container = actor_span.find_next_sibling('span', class_='attrs')
if actors_container:
details['actors'] = [a.text.strip() for a in actors_container.find_all('a', rel='v:starring')]

# 获取类型
type_span = soup.find('span', class_='pl', string=re.compile('类型:'))
if type_span:
details['move_type'] = type_span.find_next_sibling('span', class_='v:genre').text.strip()

# 获取制片国家/地区
diqu_span = soup.find('span', class_='pl', string=re.compile('制片国家/地区:'))
if diqu_span:
details['diqu'] = diqu_span.find_next_sibling(text=True).strip()

# 获取语言
language_span = soup.find('span', class_='pl', string=re.compile('语言:'))
if language_span:
details['language'] = language_span.find_next_sibling(text=True).strip()

# 获取上映日期
release_span = soup.find('span', class_='pl', string=re.compile('上映日期:'))
if release_span:
details['release_date'] = release_span.find_next_sibling('span', property='v:initialReleaseDate').text.strip()

# 获取片长
duration_span = soup.find('span', class_='pl', string=re.compile('片长:'))
if duration_span:
details['duration'] = duration_span.find_next_sibling('span', property='v:runtime').text.strip()

# 获取别名
aka_span = soup.find('span', class_='pl', string=re.compile('又名:'))
if aka_span:
details['aka'] = aka_span.find_next_sibling(text=True).strip()

# 获取IMDb链接
imdb_span = soup.find('span', class_='pl', string=re.compile('IMDb:'))
if imdb_span:
details['imdb'] = imdb_span.find_next_sibling(text=True).strip()

# 获取剧情简介
summary_span = soup.find('span', property='v:summary')
if summary_span:
details['summary'] = summary_span.text.strip()

return details

def parse_page(html):
"""解析电影列表页面"""
if not html:
return

soup = BeautifulSoup(html, 'html.parser')
movies = soup.find_all('div', class_='item')

for movie in movies:
try:
# 提取基本信息
rank = movie.find('em').text.strip()
title = movie.find('span', class_='title').text.strip()
link = movie.find('a')['href']
rating = movie.find('span', class_='rating_num').text.strip()

# 提取导演信息
bd_elem = movie.find('div', class_='bd')
director_actors = bd_elem.find('p').text.strip() if bd_elem and bd_elem.find('p') else ''
director = extract_director_names(director_actors)

# 获取详情页数据
details = get_movie_details(link)

# 整合电影信息
movie_info = {
'排名': rank,
'名字': title,
'别名': details.get('aka', ''),
'链接': link,
'评分': rating,
'导演': director,
'编剧': ','.join(details.get('screenwriters', [])),
'主演': ','.join(details.get('actors', [])),
'类型': details.get('move_type', ''),
'制片国家/地区': details.get('diqu', ''),
'语言': details.get('language', ''),
'上映日期': details.get('release_date', ''),
'片长': details.get('duration', ''),
'IMDb': details.get('imdb', ''),
'剧情简介': details.get('summary', '')
}

movie_list.append(movie_info)
print(f"已爬取: {rank}. {title}")

except Exception as e:
print(f"处理电影时出错: {e}")
continue

def save_to_csv():
"""保存数据到CSV文件"""
if not movie_list:
print("没有数据可保存")
return

with open('douban_top250.csv', 'w', newline='', encoding='utf-8-sig') as f:
fieldnames = movie_list[0].keys()
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(movie_list)
print(f"数据已保存到 douban_top250.csv,共 {len(movie_list)} 条记录")

def main():
"""主函数"""
print("开始爬取豆瓣电影Top250...")
for start in range(0, 250, 25):
url = f"{base_url}?start={start}"
print(f"正在爬取第 {start//25 + 1} 页: {url}")
html = get_page(url)
parse_page(html)

save_to_csv()

if __name__ == "__main__":
main()

最后想说的话

这个爬虫虽然简单,但包含了网络请求、网页解析、数据提取、文件保存等核心技能,是练手的好项目。不过要注意:爬取数据仅供个人学习使用,不要频繁请求服务器,也不要把数据用于商业用途

如果运行中遇到问题,可以在评论区留言,我会尽量回复。觉得有用的话别忘了点赞收藏,也欢迎分享给其他需要的朋友!