前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flutter 实现导出豆瓣书单功能

Flutter 实现导出豆瓣书单功能

原创
作者头像
三流之路
发布2024-09-02 16:28:08
880
发布2024-09-02 16:28:08
举报
文章被收录于专栏:Flutter

主要是之前 Python+Obsidian(Projects)导出豆瓣书单为了反爬,直接用自动化操作浏览器,但是这样不知道浏览器何时加载好,之前就不管如何等待 10 秒,本来就比爬虫慢,这样就更慢了,于是想到通过 Flutter 用内置的 WebView 加载,加载后再去获取 html。同时用来练手,实践适合桌面端的一些 API 和库。

第一步,获取到 html

Flutter 在桌面平台的 WebView 库不怎么多,许多也不怎么好用,最后选了一个 desktop_webview_window 算是可以用,它不是在 Widget 中加载,而是打开了一个新的窗口。所以我把它尺寸设成 0 了,不要让用户看到。

代码语言:dart
复制
webview ??= await WebviewWindow.create(  
  configuration: const CreateConfiguration(  
    windowHeight: 0,  
    windowWidth: 0,  
  ),
);

webview!.launch(url); // 加载 url

如何获取 html 呢?问 AI,说是执行一句 'document.documentElement.outerHTML',但现在问题是要知道何时已经加载好了,可以执行这句 js 了,网上查了点资料,说这个 webview!.isNavigating 可以用来判断,如果正在加载着还没结束,肯定还是正在 navigating 状态的。类型是 ValueListenable<bool>,由于我想把这个逻辑放在一个 package 里,不涉及 Widget,所以也就没放到 ValueListenableBuilder 里等值变成 fasle 再去执行 js,就直接监听

代码语言:dart
复制
listener = () async {  
  if (!webview!.isNavigating.value) { // fasle 表示加载完成  
    var html = await webview!.evaluateJavaScript('document.documentElement.outerHTML');  
    // 如果页面加载出错,返回 <html><head></head><body></body></html>    
    if (html == r'<html><head></head><body></body></html>') {  
      html = null;  
    }    
    callback(html); // Function(String?) callback 一个回调函数   
  }
};  
webview!.isNavigating.addListener(listener!);

如果加载失败,传一个非法的路径,好像也没什么特别的现象,于是自己加一个超时。

代码语言:dart
复制
Duration timeout = const Duration(seconds: 10)
timer = Timer(timeout, () {  
  callback(null);  
});
listener = () async {  
  if (!webview!.isNavigating.value) { // fasle 表示加载完成  
    // 如果超时那执行过了,就不用再回调了  
    if (timer?.isActive ?? false) {  
      timer?.cancel();   
      ...   
    }
  }
};

最后定义一个释放的方法供调用者在销毁页面时调用

代码语言:dart
复制
void dispose() {  
  if (listener != null) {  
    webview?.isNavigating.removeListener(listener!);  
  }  
  timer?.cancel();
}

解析 html

基本上就是仿着之前到 Python 代码写。比如豆列的解析

代码语言:dart
复制
var document = parse(html);
var name = document.querySelector('#content > h1 > span')?.text.trim();

var items = document.getElementsByClassName('doulist-item');
    for (var item in items) {
      var title = item.getElementsByClassName('title');
      if (title.isNotEmpty) {
        var tagA = title[0].getElementsByTagName('a');
        if (tagA.isNotEmpty) {
          var bookUrl = tagA[0].attributes['href']; // 拿到每本书的 url
          break;
        }
      }
    }

<span class="pl">出版年:</span> 2016-6<br/> 这种写法,之前在 Python 里通过 next_sibling 可以拿到,但是 Flutter 这 html 库没找到对应方法,有个 nextElementSibling 结果是 <html br>,各种属性用遍了,没找到能把 2016-6 给解析出来的,然后我就自己取子串了。

代码语言:dart
复制
for (var pl in document.getElementsByClassName('pl')) {
      final text = pl.text.trim();
      // outerHtml 就是 <span class="pl">出版年:</span>
      final outerHtml = pl.outerHtml;
      if (pl.text.contains('出版年')) {
	    int start = html.indexOf(outerHtml);
		int end = html.indexOf('<', start + outerHtml.length);
		// 拿到 2016
	    publishYear = html.substring(start + outerHtml.length, end).trim().substring(0,4);
      }
}

项目地址

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第一步,获取到 html
  • 解析 html
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档