
本文详解如何用 scrapy 正确实现“列表页→详情页”两级爬取,解决常见误区(如在错误回调中解析、css 选择器失效、数据未 yield),并以 fifaindex.com 球员数据抓取为实例,提供可直接运行的健壮代码。
本文详解如何用 scrapy 正确实现“列表页→详情页”两级爬取,解决常见误区(如在错误回调中解析、css 选择器失效、数据未 yield),并以 fifaindex.com 球员数据抓取为实例,提供可直接运行的健壮代码。
在使用 Scrapy 进行多层级网页抓取时,一个典型且易错的场景是:从列表页(如球员总览页)提取大量详情链接,再逐个跳转至详情页提取结构化字段(如姓名、体重)。初学者常陷入“所有逻辑堆在 parse() 中”的陷阱——既试图在列表页解析详情数据,又混淆了 response.follow() 的回调机制,最终导致爬虫“走过场”,返回空结果。
✅ 核心原则:职责分离 + 显式回调
Scrapy 的设计哲学强调关注点分离:
- parse() 负责导航:提取下一页 URL 和所有详情页链接,并通过 response.follow(url, callback=xxx) 显式指定后续处理函数;
- 专用解析方法(如 parse_player)负责数据提取:只作用于单个详情页响应,专注 CSS/XPath 定位与字段清洗,并 yield 字典对象。
原代码问题剖析:
- ❌ 在 parse() 中直接调用 response.css(...).get() 提取球员名/体重 → 此时 response 是列表页,无目标元素;
- ❌ response.follow(new_page, self.parse) 复用 parse 方法 → 导致详情页响应被误当作列表页处理,无法提取数据;
- ❌ div.card mb-5 选择器语法错误(缺少空格分隔类名),且未验证元素存在性;
- ❌ weight 提取使用 .getall()[1] 风险高(索引越界、空列表)。
✅ 正确实现:两级解析 + 健壮容错
以下为优化后的完整 Spider 代码(兼容 Scrapy 2.6+,已实测可用):
import scrapy
class FifaIndexPlayerSpider(scrapy.Spider):
name = "fifa_players"
allowed_domains = ["fifaindex.com"]
def start_requests(self):
# 注意:URL 中 & 需为 &(非 &),否则请求失败
url = "https://www.fifaindex.com/players/?gender=0&league=13&order=desc"
yield scrapy.Request(url, callback=self.parse_list)
def parse_list(self, response):
# 提取当前页所有球员详情链接
player_links = response.css("figure.player a::attr(href)").getall()
for link in player_links:
# 使用 response.follow 自动拼接绝对 URL,并指定回调
yield response.follow(link, callback=self.parse_player)
# 提取下一页链接(注意:实际页面中可能是 .pagination .next a)
next_page = response.css("li.ml-auto a::attr(href)").get()
if next_page:
yield response.follow(next_page, callback=self.parse_list)
def parse_player(self, response):
# 安全提取姓名:使用 get() 返回 None 而非报错
name = response.css("h5.card-header::text").get()
if not name:
self.logger.warning(f"Missing name on {response.url}")
return
# 提取体重:定位含 'kg' 的 span,避免硬编码索引
weight_span = response.css("span.data-units.data-units-metric::text")
weight_texts = weight_span.getall()
weight = None
for text in weight_texts:
if "kg" in text.strip():
weight = text.strip()
break
# 构建结构化数据项
yield {
"name": name.strip(),
"weight": weight or "N/A",
"url": response.url,
}
? 关键优化说明
| 优化点 | 说明 |
|---|---|
| parse_list 与 parse_player 分离 | 彻底解耦导航逻辑与数据逻辑,符合 Scrapy 最佳实践,避免状态混淆。 |
| response.follow(..., callback=self.parse_player) | 明确将详情页响应交由专用方法处理,确保 response 上下文正确。 |
| CSS 选择器修正 | span.data-units.data-units-metric(点连接多类名,非空格);figure.player a 更稳定(避免依赖动态 ID)。 |
| 容错与日志 | 对 name 做空值校验并记录警告;weight 提取遍历匹配而非硬索引,提升鲁棒性。 |
| allowed_domains 设置 | 防止爬虫意外爬出目标域名,增强安全性。 |
? 运行与导出
启动爬虫并导出 CSV:
scrapy crawl fifa_players -o players.csv
生成的 players.csv 将包含 name, weight, url 三列,格式清晰可直接分析。
⚠️ 注意事项
- 反爬提示:fifaindex.com 无强反爬,但建议在 settings.py 中设置 DOWNLOAD_DELAY = 1(每秒 1 请求)以尊重网站资源;
- 动态内容:若目标站点使用 JavaScript 渲染(如部分数据通过 AJAX 加载),需改用 scrapy-splash 或 Playwright 驱动;
- 数据更新:该方案抓取的是实时公开页面数据,如需 FIFA23 官方数据集,建议结合 sofifa.com(如知识库所述)或官方 API。
掌握这种“列表→详情”的两级爬取模式,是构建工业级 Scrapy 项目的基石。它不仅适用于体育数据,同样可迁移至电商商品、新闻文章、学术论文等任何具有标准分页+详情结构的网站。











