1. 项目概述:自定义规则的番剧采集与在线观看程序
作为一名长期混迹二次元圈子的老码农,我最近用Flutter开发了一款支持自定义采集规则的番剧聚合应用。这个项目的核心价值在于:它不像传统视频APP那样受限于固定片源,而是允许用户通过编写简单的JSON规则,自由添加任何网站的番剧资源。想象一下,当B站没买某部新番版权时,你只需要花5分钟写条采集规则,就能把其他小站的资源整合到自己的追番列表里——这才是真正的"我的追番我做主"。
技术栈上选择了Flutter跨平台框架,配合Dart语言的强类型特性,实现了Android/iOS双端兼容。数据层采用Hive本地数据库存储收藏记录,网络请求通过Dio封装了智能重试机制,视频播放则基于改良版的chewie播放器组件,支持硬解和弹幕加载。最关键的采集引擎采用规则模板+JavaScript注入的方式,既能解析常规HTML页面,也能处理动态渲染的SPA站点。
开发过程中最大的坑:不同视频站的防盗链策略千奇百怪。经过两个月的实战,我总结出一套通用的反反爬方案,后文会具体分享如何通过请求头伪装、随机延迟和本地缓存绕过检测。
2. 核心架构设计
2.1 规则引擎实现原理
采集规则采用JSON Schema定义,每个规则包含三个核心模块:
{ "site": "某动漫网", "search": { "url": "https://example.com/search?kw=${keyword}", "list": "div.result-item", "fields": { "title": "h3::text", "url": "a::attr(href)", "cover": "img::attr(data-src)" } }, "detail": { "videoUrl": "script:contains('m3u8') | regex_extract('(http.*?\\.m3u8)')" } }- 页面导航:使用CSS选择器或XPath定位元素,支持正则表达式提取关键字段
- 动态执行:通过内置JavaScript引擎处理Ajax加载内容,示例代码:
final jsResult = await page.evaluate(''' JSON.parse(document.querySelector('script#__NEXT_DATA__').innerHTML) ''');- 智能缓存:对图片和视频链接自动生成MD5指纹,避免重复请求
2.2 播放器关键技术点
基于video_player和chewie二次开发的播放器核心特性:
- 预加载机制:在用户hover到番剧封面时,悄悄加载前30秒视频数据
- 弹幕协议:兼容B站弹幕格式(.xml)和AcFun弹幕格式(.json)
- 解码优化:
VideoPlayerController.network( videoUrl, formatHint: VideoFormat.hls, httpHeaders: {'referer': rule.siteDomain} );
2.3 性能优化方案
通过Flutter性能工具实测的优化手段:
| 优化点 | 提升效果 | 实现方式 |
|---|---|---|
| 图片缓存 | 减少70%流量 | cached_network_image+LRU策略 |
| 请求合并 | 降低50%延迟 | Dio拦截器合并相同域名请求 |
| 列表项复用 | 提升30%帧率 | ListView.builder+AutomaticKeepAlive |
| 隔离JavaScript执行 | 避免UI卡顿 | 通过compute函数在独立isolate运行 |
3. 完整实现步骤
3.1 开发环境准备
推荐使用FVM管理多版本Flutter环境:
fvm install 3.13.0 fvm use 3.13.0 flutter pub global activate fvm3.2 核心模块开发
采集服务实现流程:
- 规则验证阶段:检查JSON Schema有效性
- 页面加载阶段:通过UserAgent伪装成移动浏览器
- 数据提取阶段:优先尝试静态解析,失败时降级到动态渲染
- 结果处理阶段:自动补全相对URL,过滤广告资源
关键代码片段:
Future<AnimeItem> crawl(Rule rule) async { final response = await dio.get(rule.searchUrl, options: Options(extra: {'js': rule.needJsRender})); final doc = parse(response.data); final items = doc.querySelectorAll(rule.listSelector) .map((e) => AnimeItem( title: e.querySelector(rule.titleSelector)?.text, url: _absUrl(e, rule.urlSelector, rule.baseUrl) )).toList(); if (rule.needPaginate) { await _handlePagination(doc, rule); } return items; }3.3 反爬虫策略突破
实战中有效的几种方案:
请求头伪装套餐:
dio.interceptors.add(InterceptorsWrapper( onRequest: (opt) { opt.headers.addAll({ 'sec-ch-ua': '"Chromium";v="116"', 'x-requested-with': 'com.anime.app' }); } ));动态延迟算法:
void randomDelay() async { await Future.delayed(Duration( milliseconds: 1000 + Random().nextInt(3000) )); }IP轮换方案:通过配置多个反向代理服务器自动切换出口IP
4. 典型问题排查指南
4.1 常见错误代码表
| 错误码 | 可能原因 | 解决方案 |
|---|---|---|
| ERR_001 | 规则CSS选择器失效 | 使用浏览器开发者工具验证元素 |
| ERR_002 | 网站启用Cloudflare防护 | 尝试添加cf_clearancecookie |
| ERR_003 | 视频格式不支持 | 在规则中强制指定formatHint |
| ERR_004 | 跨域资源加载被阻 | 配置CORS代理服务器 |
4.2 调试技巧
- 实时规则测试:在App内嵌的WebView中执行
document.querySelectorAll('你的选择器')验证规则 - 网络请求捕获:使用Charles设置手机代理,观察实际请求参数
- 性能分析:通过Flutter DevTools的Timeline视图检查JS执行耗时
5. 进阶开发建议
对于想深度定制的小伙伴,可以尝试这些方向:
- 规则市场:搭建一个用户共享采集规则的平台
- 智能识别:用机器学习自动生成CSS选择器路径
- 离线缓存:实现番剧整集下载功能(注意版权风险)
- 插件系统:允许用户编写Dart插件扩展功能
我在实际开发中发现,很多小众动漫站的视频其实是引用的大站CDN资源。通过分析Referer和Origin策略,可以找到直接访问源站的方法,这比破解各种奇怪的加密参数要高效得多。另外推荐使用flutter_distributor打包发布,能省去很多配置不同平台签名文件的麻烦。