You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scrapy爬虫返回空值异常:同结构数据部分获取失败求助

问题排查:Scrapy爬取数据时部分数值返回空格异常

问题背景

爬取目标URL:https://www.rugbypass.com/live/bristol-vs-leicester/stats/?g=938938,使用Scrapy Spider提取比赛统计数据时,出现同结构下部分数值返回空格的异常。

相关代码

Spider核心代码

match_summary_stats = response.css("#statsSummary_stats > div.stat")

for stat in match_summary_stats:
    stat_name = stat.css("div.mid.smaller::text").get()
    home_value = stat.css("div:nth-child(2)::text").get()
    away_value = stat.css("div:nth-child(4)::text").get()

    home_team_row['stats'][stat_name] = home_value
    away_team_row['stats'][stat_name] = away_value

异常对应的HTML结构

<div class="stat">
   <div class="line lh"></div>
   <div>2</div>
   <div class="mid smaller">Penalty Goals</div>
   <div>0</div>
   <div class="line "></div>
</div>
<div class="stat">
   <div class="line lh"></div>
   <div>3</div>
   <div class="mid smaller">Tries</div>
   <div>2</div>
   <div class="line "></div>
</div>

当前输出

{"game_id": "938938", "stats": {"Penalty Goals": "2", "Tries": " "}}

预期输出

{"game_id": "938938", "stats": {"Penalty Goals": "2", "Tries": "3"}}

Scrapy Item定义

class TeamRow(scrapy.Item):
    game_id = scrapy.Field(
        input_processor=MapCompose(remove_tags),
        output_processor=TakeFirst()
    )
    team_name = scrapy.Field(
        input_processor=MapCompose(remove_tags),
        output_processor=TakeFirst()
    )
    opposition_team_name = scrapy.Field(
        input_processor=MapCompose(remove_tags),
        output_processor=TakeFirst()
    )
    team_score = scrapy.Field()
    opposition_team_score = scrapy.Field()
    win_or_lose = scrapy.Field()
    stats = scrapy.Field()

原因分析

问题出在div:nth-child(2)和div:nth-child(4)这类基于位置的CSS选择器上:

  1. 这类选择器依赖父元素下所有子元素的绝对位置,但页面中可能存在隐藏元素(如被CSS隐藏的<div>)或子文本包含换行/空白字符,导致::text提取到空白而非目标数值。
  2. 位置选择器鲁棒性极差,页面结构微调(比如新增/删除无关子元素)就会直接导致定位错误。

解决方案

方案1:基于兄弟元素关系定位(推荐)

利用统计名称元素的兄弟关系定位数值,不受其他无关子元素影响:

for stat in match_summary_stats:
    stat_name = stat.css("div.mid.smaller::text").get()
    # 取统计名称的前一个兄弟div文本(主场数值)
    home_value = stat.css("div.mid.smaller").xpath("preceding-sibling::div[1]/text()").get()
    # 取统计名称的后一个兄弟div文本(客场数值)
    away_value = stat.css("div.mid.smaller").xpath("following-sibling::div[1]/text()").get()

    # 处理空白字符,避免空值或空格
    home_value = home_value.strip() if home_value else None
    away_value = away_value.strip() if away_value else None

    home_team_row['stats'][stat_name] = home_value
    away_team_row['stats'][stat_name] = away_value

方案2:过滤无效div后取值

先过滤掉无有效文本的line类div,再按顺序提取数值:

for stat in match_summary_stats:
    stat_name = stat.css("div.mid.smaller::text").get()
    # 提取所有非line类div的文本,过滤空白内容
    valid_values = [text.strip() for text in stat.css("div:not(.line)::text").getall() if text.strip()]
    if len(valid_values) >= 2:
        home_value, away_value = valid_values[0], valid_values[1]
        home_team_row['stats'][stat_name] = home_value
        away_team_row['stats'][stat_name] = away_value

方案3:使用精确CSS选择器

直接定位非line、非mid类的数值div:

for stat in match_summary_stats:
    stat_name = stat.css("div.mid.smaller::text").get()
    # 获取所有目标数值div的文本
    raw_values = stat.css("div:not(.line):not(.mid)::text").getall()
    # 过滤空白后取值
    filtered_values = [v.strip() for v in raw_values if v.strip()]
    if filtered_values:
        home_value = filtered_values[0]
        away_value = filtered_values[1] if len(filtered_values) > 1 else None
        home_team_row['stats'][stat_name] = home_value
        away_team_row['stats'][stat_name] = away_value

额外优化建议

  • 始终对提取的文本做strip()处理,避免空白字符干扰
  • 尽量避免使用基于位置的选择器,改用元素关系或属性选择器,提升代码稳定性
  • 添加基础异常处理,避免页面结构变化导致爬虫崩溃

内容的提问来源于stack exchange,提问作者Luke Bray

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 21:55:07