Flutter网页爬取遇RangeError(index)索引越界问题求助
解决Dart爬取网页数据时的RangeError(index)问题
开发拉取网站数据的测验应用时,爬取过程中遇到RangeError(index): Invalid value: Valid value range is empty: 0错误。已知页面中items类包含50条数据,尝试改用for循环后问题仍未解决。
问题代码
import 'dart:async'; import 'dart:convert'; import 'package:get/get.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:html/parser.dart' as parser; import '../../../models/sorular.dart'; import 'bitisekran/bitis_ekrani.dart'; void main() => runApp(sorubir()); class sorubir extends StatefulWidget { const sorubir({super.key}); @override State<sorubir> createState() => _sorubirState(); } class _sorubirState extends State<sorubir> { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: Scaffold( appBar: AppBar( leading: BackButton( onPressed: () => Navigator.of(context).pop(), ), title: Text('Güncel Çıkmış Sorular'), ), body: sorubirIskele(), ), ); } } class sorubirIskele extends StatefulWidget { @override State<sorubirIskele> createState() => _sorubirIskeleState(); } _sorubirIskeleState benimTestim = _sorubirIskeleState(); class _sorubirIskeleState extends State<sorubirIskele> { List<Sorular> sorular = []; var soruIndex = 0; bool isLoading = false; var url = Uri.parse('https://www.ehliyettestleri.com/coz/24ha2023e'); Future getData() async { setState(() { isLoading = true; }); var res = await http.get(url); final String body = utf8.decode(res.bodyBytes); final document = parser.parse(body); var response = document .getElementsByClassName('items')[0] .getElementsByClassName('mtq_question mtq_scroll_item-1') .forEach((element) { setState(() { sorular.add(Sorular( soruno: element.children[0].text.toString(), sorugetir: element.children[1].text.toString(), imggetir: element.querySelector('p > img')?.attributes['src'].toString(), agetir: element.children[2].children[1].children[0].children[1].text .toString(), bgetir: element.children[2].children[1].children[1].children[1].text .toString(), cgetir: element.children[2].children[1].children[2].children[1].text .toString(), dgetir: element.children[2].children[1].children[3].children[1].text .toString(), )); }); setState(() { isLoading = false; }); }); }
错误原因
该错误是直接访问了空列表的索引导致的。代码中多处使用children[0]、children[1]等硬编码索引,一旦网页DOM结构和预期不符(比如某个节点的子元素为空),就会触发范围错误。另外,getData方法中在forEach内部频繁调用setState,会导致不必要的重绘,且isLoading会被提前设置为false。
修复方案
Future getData() async { setState(() { isLoading = true; }); try { var res = await http.get(url); final String body = utf8.decode(res.bodyBytes); final document = parser.parse(body); // 先检查items元素是否存在,避免直接访问[0]报错 final itemsElement = document.getElementsByClassName('items').firstOrNull; if (itemsElement == null) { setState(() { isLoading = false; }); return; } final questionElements = itemsElement.getElementsByClassName('mtq_question mtq_scroll_item-1'); List<Sorular> tempSorular = []; for (var element in questionElements) { // 检查当前问题节点的子元素数量是否符合预期 if (element.children.length < 3) continue; final soruno = element.children[0].text.trim(); final sorugetir = element.children[1].text.trim(); final imggetir = element.querySelector('p > img')?.attributes['src']; // 检查选项容器是否存在 final optionsContainer = element.children[2].children.isNotEmpty ? element.children[2].children[1] : null; if (optionsContainer == null || optionsContainer.children.length < 4) continue; // 逐个检查选项节点的子元素,避免索引越界 final agetir = optionsContainer.children[0].children.length > 1 ? optionsContainer.children[0].children[1].text.trim() : ''; final bgetir = optionsContainer.children[1].children.length > 1 ? optionsContainer.children[1].children[1].text.trim() : ''; final cgetir = optionsContainer.children[2].children.length > 1 ? optionsContainer.children[2].children[1].text.trim() : ''; final dgetir = optionsContainer.children[3].children.length > 1 ? optionsContainer.children[3].children[1].text.trim() : ''; tempSorular.add(Sorular( soruno: soruno, sorugetir: sorugetir, imggetir: imggetir, agetir: agetir, bgetir: bgetir, cgetir: cgetir, dgetir: dgetir, )); } // 一次性更新状态,减少重绘次数 setState(() { sorular = tempSorular; isLoading = false; }); } catch (e) { print('爬取错误详情: $e'); setState(() { isLoading = false; }); } }
关键优化点
- 使用
firstOrNull替代[0]获取items元素,避免列表为空时直接访问索引报错 - 用临时列表存储爬取数据,最后一次性调用
setState更新状态,减少UI重绘 - 对每一层节点的子元素数量做检查,确保访问索引时列表不为空
- 添加
try-catch捕获异常,方便调试具体错误原因
内容的提问来源于stack exchange,提问作者Mehmet Hanifi Dağaçan
相关产品推荐
相关产品推荐

