
本文详解如何使用 Scrapy 实现「列表页→详情页」两级爬取,解决常见误区(如在错误响应上下文中解析数据),并通过分离 parse 与 parse_player 方法确保每条球员数据准确提取并返回。
本文详解如何使用 scrapy 实现「列表页→详情页」两级爬取,解决常见误区(如在错误响应上下文中解析数据),并通过分离 `parse` 与 `parse_player` 方法确保每条球员数据准确提取并返回。
在体育数据采集场景中,典型的网站结构是:首页/列表页展示摘要信息(如球员缩略图、姓名、排名)→ 每个条目链接指向独立详情页 → 详情页包含完整属性(如身高、体重、惯用脚等)。若直接在列表页响应中尝试提取详情页字段(如 response.css("h5.card-header::text").get()),必然失败——因为该 CSS 选择器在列表页 HTML 中根本不存在。
原代码的核心问题在于:
❌ 将详情页数据提取逻辑错误地写在 parse() 方法中(即列表页响应的处理函数内);
❌ 使用 response.follow(new_page, self.parse) 导致所有页面都交由同一 parse 方法处理,而该方法并未区分「列表页」和「详情页」语义;
❌ for guy in response.css("div.card mb-5"): 选择器语法错误(缺少空格或逗号),且该结构实际存在于详情页而非列表页。
✅ 正确解法是遵循 Scrapy 的「职责分离」设计哲学:
- parse() 负责发现链接:提取所有球员详情页 URL,并调用 response.follow(url, callback=self.parse_player);
- parse_player() 专用于解析详情页:在此方法中,response 对象已加载目标球员页面,CSS 选择器可精准定位真实 DOM 元素。
以下是经过验证的完整可运行代码:
import scrapy
class FifaIndexPlayerSpider(scrapy.Spider):
name = "fifa_players"
allowed_domains = ["www.fifaindex.com"]
def start_requests(self):
# 注意:URL 中 & 需为原始字符,非 HTML 实体 &
url = "https://www.fifaindex.com/players/?gender=0&league=13&order=desc"
yield scrapy.Request(url, self.parse)
def parse(self, response):
# 提取当前页所有球员详情页链接
player_links = response.css("figure.player a::attr(href)").getall()
for link in player_links:
# 使用 response.follow 自动拼接绝对 URL,并指定回调函数
yield response.follow(link, self.parse_player)
# 提取下一页链接(如存在)
next_page = response.css("li.ml-auto a::attr(href)").get()
if next_page:
yield response.follow(next_page, self.parse)
def parse_player(self, response):
# ✅ 此时 response 是球员详情页,可安全提取字段
name = response.css("h5.card-header::text").get()
# weight 位于 class="data-units data-units-metric" 的 span 中,通常第二个匹配项为体重(第一个可能是身高)
weight_elements = response.css("span.data-units.data-units-metric::text").getall()
weight = weight_elements[1].strip() if len(weight_elements) > 1 else None
yield {
"name": name.strip() if name else None,
"weight": weight,
"url": response.url # 可选:记录数据来源,便于溯源调试
}
关键注意事项与优化建议:
域名白名单严格设置
allowed_domains = ["www.fifaindex.com"] 可防止爬虫意外跳转至外部站点(如广告、赞助商链接),提升稳定性。链接提取健壮性增强
若某页无球员链接(如空列表),getall() 返回空列表,for link in [] 自然跳过,无需额外判空,代码更简洁。CSS 选择器精度校验
在浏览器开发者工具中右键检查目标元素 → “Copy selector”,推荐使用 span.data-units.data-units-metric(类名间用点连接表示同时具备多个 class),避免空格导致误匹配。-
反爬基础应对(生产环境必备)
- 在 settings.py 中启用 ROBOTSTXT_OBEY = False(需确认目标站 robots.txt 允许);
- 添加请求延迟:DOWNLOAD_DELAY = 1(每秒最多 1 个请求);
- 设置 User-Agent:DEFAULT_REQUEST_HEADERS = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'}。
-
数据导出与存储
运行命令支持多种格式:scrapy crawl fifa_players -o players.csv # CSV scrapy crawl fifa_players -o players.json # JSON scrapy crawl fifa_players -o players.jl # JSON Lines(流式,适合大数据)
通过本方案,爬虫将清晰分层:列表页只做导航调度,详情页专注数据提取。这不仅解决了“返回 None”的问题,更构建了可维护、易扩展的工业级爬取流程——后续如需增加“年龄”“国籍”“俱乐部”等字段,仅需在 parse_player() 中追加对应 CSS 提取逻辑即可。











