Flutter调用NSE Nifty期权链API偶发FormatException该如何解决?
问题根因
你遇到的格式化报错本质是服务端返回了HTML格式的拦截页面而非预期JSON,而现有代码没有做前置校验就直接解析内容,具体问题点有三个:
- NSE的公开接口有严格的反爬校验机制:会校验请求携带的浏览器标识、来源页信息、会话Cookie,不满足要求的请求会被直接返回HTML格式的拦截跳转页,也就是你报错栈里看到的
<!DOCTYPE html>开头的内容。 - 现有代码对返回内容没有任何校验:不管接口返回什么状态、什么格式的内容,都直接把响应体丢给
jsonDecode,一旦拿到HTML内容就必然触发格式化异常。 - 重启应用偶发恢复的原因是:NSE的临时拦截策略基于客户端IP、临时会话标记随机触发,裸请求的无状态特性导致拦截结果不稳定,但只要请求头和会话不合规,随机触发拦截是必然结果,不是偶发的代码bug。
修复方案
1. 补全接口要求的合规请求逻辑
NSE接口要求请求必须携带和浏览器访问一致的基础请求头,同时需要维护可复用的会话客户端来自动保存服务端下发的Cookie,否则大概率被拦截。修改后的请求代码如下:
import 'dart:convert'; import 'package:http/http.dart'; final optionChainUrl = 'https://www.nseindia.com/api/option-chain-indices?symbol=NIFTY'; // 全局复用HTTP客户端,自动维护会话Cookie final sharedHttpClient = Client(); Future<Map<String, dynamic>> fetchNiftyData() async { final response = await sharedHttpClient.get( Uri.parse(optionChainUrl), headers: { 'Accept': 'application/json, text/plain, */*', 'Accept-Language': 'en-US,en;q=0.9', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Referer': 'https://www.nseindia.com/option-chain', }, ); // 前置校验响应状态 if (response.statusCode != 200) { throw Exception('请求失败,状态码:${response.statusCode}'); } // 校验返回内容类型,避免把HTML当成JSON解析 if (!response.headers['content-type']?.contains('application/json') ?? true) { throw Exception('接口返回非JSON内容,可能被反爬拦截'); } return jsonDecode(response.body) as Map<String, dynamic>; }
- 注意:不要每次请求都新建
Client实例,否则会话Cookie无法保存,每次请求都是无状态的裸连接,依然会被随机拦截。
2. 修复页面层的异常捕获逻辑
现有页面代码直接对异步请求结果做赋值,没有加任何异常捕获,一旦请求抛错就会触发未处理的UI层异常。修改后的页面逻辑需要补充加载态、错误态的展示,同时捕获所有请求异常:
class _NiftyScreenState extends State<NiftyScreen> { Map<String, dynamic>? niftyDetails; String? errorText; bool isLoading = true; @override void initState() { super.initState(); loadNiftyData(); } Future<void> loadNiftyData() async { try { final data = await fetchNiftyData(); setState(() { niftyDetails = data; isLoading = false; }); } catch (e) { setState(() { errorText = e.toString(); isLoading = false; }); } } @override Widget build(BuildContext context) { return Scaffold( body: isLoading ? const LinearProgressIndicator() : errorText != null ? Center(child: Text('加载失败:$errorText')) : ListView.builder( itemCount: niftyDetails!["filtered"]["data"].length, itemBuilder: (_, index) { return ListTile( title: Text( '${niftyDetails!["filtered"]["data"][index]["strikePrice"]}'), ); }, ), ); } }
API调用通用最佳实践
- 拿到响应后永远不要直接做JSON解析:先校验响应状态码、返回内容类型,确认是预期格式后再解析,避免模糊的格式化报错干扰问题定位。
- 所有异步网络请求必须加try-catch捕获全部异常,不要把未处理的异常抛到UI层导致页面崩溃。
- 对反爬严格的公开接口,复用HTTP客户端实例来维护会话状态,减少被识别为爬虫的概率。
- 对临时拦截的场景,加有限度的重试逻辑(间隔1-2秒重试1-2次即可),不要无限重试触发更严格的访问限制。
内容的提问来源于stack exchange,提问作者Manish Paul
相关产品推荐
相关产品推荐

