项目:获取电影榜单上的电影详情信息
1.发送请求,获取高分电影榜单数据
python
response = requests.get(BASE_URL, timeout=60)
2.解析数据,获取电影列表
python
document = html.fromstring(response.text)
movie_list = document.xpath("//*[@id='page_1']/div[@class='card style_1']")
(1) 如果遇到response.text 是空的,这极有可能是因为目标网站有反爬虫机制。 User-Agent 缺失: 默认的 requests.get() 会发送一个类似 python-requests/2.x.x 的请求头。大多数现代网站会直接拒绝这种非浏览器的请求,返回 403 Forbidden 或者返回一个空的响应。此时需要给请求添加请求头(Headers),伪装成浏览器访问。(具体header信息需访问网站查看)
python
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 Edg/146.0.0.0',
'Cookie': '...',
'Referer': '...',
'X-Requested-With':'...'
}
(2) 也可以设置编码: response.encoding = response.apparent_encoding 防止因中文乱码导致的解析问题。
(3)如果修改后仍然报错, 打印 response.text 可以让你看到服务器到底返回了什么(通常是"Access Denied" 或者一段 JavaScript,而不是你期望的 HTML)。request只能获取服务器返回的原始 HTML 源码,它不会执行 JavaScript,由于很多网站(特别是电影、电商类)的HTML源码里,<div id="app">...</div>往往是一个空壳子。真实的数据是通过JS脚本在浏览器里运行后才填充进去的。验证方法:
-
在PyCharm的调试窗口中,输入 print(response.text) 并查看输出。如果输出里包含电影名字,说明数据在源码里,是原因1(xpath写错了)
-
如果输出里只有<div id="app">...</div>,说明数据是JS加载的,request抓不到
解决办法:
-
寻找网页背后的API接口(在浏览器按F12->Network->XHR/Fetch查看),直接请求那个接口
-
改用 Selenium 或 Playwright等自动化工具,他们能模拟浏览器运行JS
python
response = requests.get(base_url, headers=headers, timeout=10)
response.raise_for_status() # 检查 HTTP 状态码
data = response.json()
3.检查HTTP状态码:response.raise_for_status()
response.raise_for_status() 是 requests 库中 Response 对象的一个便捷方法,它的作用是自动检查 HTTP 响应的状态码。 核心功能检查状态码:如果请求成功(状态码在 200-399 范围内),它不执行任何操作,程序继续往下走。如果请求失败(状态码在 400-599 范围内,如 404 Not Found, 500 Internal Server Error),它会自动抛出一个 requests.exceptions.HTTPError 异常。
如果没有 raise_for_status(): 如果请求返回了 404(页面不存在)或 500 (服务器内部错误),response.status_code 会是 404 或 500。 但程序会继续执行 response.json()。response.text 可能是一个 HTML 错误页面(比如 "Page Not Found" 的 HTML),而不是你期望的 JSON 数据。 当 response.json() 尝试解析这个 HTML 时,就会抛出 ValueError(JSON 解析错误)。 你只能在 except ValueError 中捕获错误,看到的是"JSON 解析失败"的信息,而不知道最初的问题是 HTTP 状态码错误(比如 404)。
4.从返回的结果中提取所需数据:get_movie_info()
python
for movie in data['items']:
movie_info = get_movie_info(movie)
all_movies.append(movie_info)
save_all_movies(all_movies)
python
# 获取电影详情
def get_movie_info(movie):
title = movie.get("title","未知电影")
rating = movie.get("rating",{})
rating_value = rating.get("value","")
card_subtitle = movie.get("card_subtitle")
card_subtitle_result = card_subtitle.split('/')
year = card_subtitle_result[0].strip() # 使用 strip() 清理可能的空格
area = card_subtitle_result[1].strip()
movie_type = card_subtitle_result[2].strip()
director = card_subtitle_result[3].strip()
actors = card_subtitle_result[4].strip()
id = movie.get("id","")
movie_info = {
"电影名": title if title else '',
"评分": rating_value if rating_value else '',
"年份": year if year else '',
"地区": area if area else '',
"类型": movie_type if movie_type else '',
"导演": director if director else '',
"演员": actors if actors else '',
"id": id if id else ''
}
return movie_info
5.将提取的数据转换为csv文件
python
# 保存电影数据, 保存为 csv 文件
def save_all_movies(all_movies):
with open(MOVIE_LIST_FILE, "w", encoding="utf-8", newline="") as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=["电影名", "评分", "年份", "地区", "类型", "导演", "演员", "id"])
writer.writeheader() # 写入表头
writer.writerows(all_movies) # 写入数据
6.分页查询更多网页信息
要实现"加载更多"并爬取后续的电影信息,核心在于理解豆瓣分页数据的获取逻辑,也就是通过修改API接口中的 start 参数来控制页码(URL中包含start=0&limit=20),以下是具体的实现原理和修改方案
(1)核心原理:修改start 参数
豆瓣的列表接口通常遵循 offset/limit 的分页逻辑,limit=20 表示每页只返回20条数据,start=0 表示从第0条数据开始取(即第一页),start=20 表示从第20条数据开始取(即第2页), start=40 表示从第40条数据开始取(即第3页)。只需要在一个循环中不断增加start的值,就能一次获取后面的数据
python
BASE_URL = "https://m.douban.com/rexxar/api/v2/subject/recent_hot/movie?start={}&limit=20"
start_index = START_INDEX # 起始索引
limit = LIMIT # 每页数量
total_to_fetch = TOTAL_TO_FETCH # 你想爬取的总数量,可根据需要调整
current_url = BASE_URL.format(start_index)
print(f"正在爬取第 {start_index // limit + 1} 页数据 (start = {start_index})...")
完整代码:
python
import time
import requests
import csv
from lxml import html
BASE_URL = "https://m.douban.com/rexxar/api/v2/subject/recent_hot/movie?start={}&limit=20"
MOVIE_LIST_FILE = "csv_data/movie_list.csv"
START_INDEX = 0
LIMIT = 20
TOTAL_TO_FETCH = 100
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 Edg/146.0.0.0',
'Referer':'https://movie.douban.com/explore',
'X-Requested-With':'com.douban.frodo'
}
# 保存电影数据, 保存为 csv 文件
def save_all_movies(all_movies):
with open(MOVIE_LIST_FILE, "w", encoding="utf-8", newline="") as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=["电影名", "评分", "年份", "地区", "类型", "导演", "演员", "id"])
writer.writeheader() # 写入表头
writer.writerows(all_movies) # 写入数据
# 获取电影详情
def get_movie_info(movie):
title = movie.get("title","未知电影")
rating = movie.get("rating",{})
rating_value = rating.get("value","")
card_subtitle = movie.get("card_subtitle")
card_subtitle_result = card_subtitle.split('/')
# 确保列表长度足够,避免 IndexError
while len(card_subtitle_result) < 5:
card_subtitle_result.append("")
year = card_subtitle_result[0].strip() # 使用 strip() 清理可能的空格
area = card_subtitle_result[1].strip()
movie_type = card_subtitle_result[2].strip()
director = card_subtitle_result[3].strip()
actors = card_subtitle_result[4].strip()
id = movie.get("id","")
movie_info = {
"电影名": title if title else '',
"评分": rating_value if rating_value else '',
"年份": year if year else '',
"地区": area if area else '',
"类型": movie_type if movie_type else '',
"导演": director if director else '',
"演员": actors if actors else '',
"id": id if id else ''
}
return movie_info
def main():
all_movies = []
start_index = START_INDEX # 起始索引
limit = LIMIT # 每页数量
total_to_fetch = TOTAL_TO_FETCH # 你想爬取的总数量,可根据需要调整
while len(all_movies) < total_to_fetch:
current_url = BASE_URL.format(start_index)
print(f"正在爬取第 {start_index // limit + 1} 页数据 (start = {start_index})...")
try:
response = requests.get(current_url, headers=headers, timeout=10)
response.raise_for_status() # 检查 HTTP 状态码
data = response.json()
if 'msg' in data and 'invalid_request' in data.get("msg",""):
print(f"!!! 接口报错: {data['msg']}(豆瓣反爬生效)")
print("请检查 Headers 或 Cookie")
break # 遇到反爬错误,停止爬取
# --- 关键部分:分行读取数据 ---
if 'items' in data:
items = data['items']
if not items: # 如果返回items是空列表,说明到底了
print("没有更多数据了,停止爬取")
break
print("找到电影数据,开始分行读取:")
# 3. 使用 for 循环遍历列表
for movie in items:
movie_info = get_movie_info(movie)
all_movies.append(movie_info)
# 如果已经到达了我们想要爬取的数量,就退出
if len(all_movies) >= total_to_fetch:
break
# 4.保存数据, 保存为 csv 文件
print("获取到所有的电影详情, 保存电影数据到CSV文件 ...")
else:
print(f"第{start_index // limit+1}页未找到'items'字段")
print("原始数据:",data) # 打印原始数据,看看到底是什么结构
break
except requests.exceptions.RequestException as e:
print("网络请求出错:", e)
break
except ValueError as e:
print("JSON 解析出错(可能是返回了HTML而不是JSON):", e)
print("服务器返回的内容预览:", response.text[:500])
break
# 重要:添加延时,避免请求过于频繁
time.sleep(1.5)
# 更新索引,准备爬取下一页
start_index += limit
if all_movies:
print("正在保存数据到CSV文件...")
save_all_movies(all_movies)
print(f"数据已保存至{MOVIE_LIST_FILE}")
else:
print("未能获取到任何数据")
if __name__ == '__main__':
main()