内容简介:在学习或开发Flutter应用时,很多人会在app中硬编码很多假数据,用以调试界面,实际上我认为是完全没有必要的,Flutter使用Dart语言编程,而Dart语言作为一种全栈语言,其语法可以甩JavaScript几条街,我们是很有必要真正的将这种语言的能力发挥出来的。这里我就讲讲如何使用Dart语言编写爬虫获取数据,如何使用Dart语言编写编写简单服务器后端。首先我们花十分钟来编写一个简易爬虫。
在学习或开发Flutter应用时,很多人会在app中硬编码很多假数据,用以调试界面,实际上我认为是完全没有必要的,Flutter使用Dart语言编程,而Dart语言作为一种全栈语言,其语法可以甩JavaScript几条街,我们是很有必要真正的将这种语言的能力发挥出来的。
这里我就讲讲如何使用Dart语言编写爬虫获取数据,如何使用Dart语言编写编写简单服务器后端。
Dart 爬虫开发
首先我们花十分钟来编写一个简易爬虫。
环境准备
关于Dart 服务端SDK环境搭建,请阅读我的另一篇文章 Dart语言——45分钟快速入门(上)
我们完全手动创建一个 Dart
工程还是略显麻烦,因此我们需要安装一个脚手架,自动生成一个合乎规范的 Dart
工程项目,执行以下命令安装 stagehand
pub global activate stagehand 复制代码
完成安装后直接使用 stagehand
命令: stagehand -h
可能会报找不到错误,这时候我们有两种办法解决
-
配置环境变量
打开
cmd
命令行,输入如下命令echo %APPDATA%\Pub\Cache\bin 复制代码
这时可以看到,命令行输出了
stagehand
命令所在的路径,只需要将该路径加入到系统的Path
环境变量即可 -
使用
pub
工具调用除了配置环境变量,还可以使用
pub global run
去调用,由于我本机配置了各种各样的开发语言和工具,命令实在太多,我已经不太喜欢配置环境变量,这里就先使用该方式演示。执行以下命令可以查看一下帮助pub global run stagehand -h 复制代码
创建工程
新建一个文件夹 spider
, cd
到该目录下,运行以下命令,会在 spider
下自动生成一个命令行项目
pub global run stagehand console-full 复制代码
使用 vscode
打开该项目目录
编辑配置文件 pubspec.yaml
,避免不必要的下载,删除默认添加的 test
库依赖,配置如下依赖库
dependencies: http: ^0.12.0+2 html: ^0.14.0+2 复制代码
这里 http
库主要用于处理http请求,html库用于处理 html
内容的解析与提取,它们都是Dart官方提供的非标准库,GitHub链接如下
在项目下执行命令,下载依赖
pub get 复制代码
本文主要做Demo演示,不会对爬虫知识进行讲解。这里主要爬取了一个妹子图网站,大家可以根据自己的实际需要选择目标。如果对爬虫不太了解,请查找资料进行学习,也可以阅读本人的CSDN博客了解爬虫,这里默认大家都掌握爬虫技术。
编辑项目中lib/spider.dart
文件
import 'package:http/http.dart' as http; import 'package:html/parser.dart' show parse; import 'package:html/dom.dart'; import 'dart:convert'; import 'dart:io'; // 数据实体 class ItemEntity{ final String title; final String imgUrl; ItemEntity({this.title,this.imgUrl}); Map<String, dynamic> toJson(){ return { 'title': title, 'imgUrl': imgUrl, }; } } // 构造请求头 var header = { 'user-agent' : 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) '+ 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36', }; // 数据的请求 request_data() async{ var url = "https://www.mzitu.com/"; var response = await http.get(url,headers: header); if (response.statusCode == 200) { return response.body; } return '<html>error! status:${response.statusCode}</html>'; } // 数据的解析 html_parse() async{ var html = await request_data(); Document document = parse(html); // 这里使用css选择器语法提取数据 List<Element> images = document.querySelectorAll('#pins > li > a > img'); List<ItemEntity> data = []; if(images.isNotEmpty){ data = List.generate(images.length, (i){ return ItemEntity( title: images[i].attributes['alt'], imgUrl: images[i].attributes['data-original']); }); } return data; } // 数据的储存 void save_data() async{ var data = await html_parse(); var json_str = json.encode({'items':data}); // 将json写入文件中 await File('data.json').writeAsString(json_str,flush: true); } 复制代码
编辑项目下的 bin/main.dart
文件
import 'package:spider/spider.dart' as spider; main(List<String> arguments) { spider.save_data(); } 复制代码
从篇幅考虑。本文省略数据库相关操作,用一个 data.json
文件替代。以上代码中 save_data
函数即处理数据的持久化储存工作,对于小型爬虫而言,推荐使用 Sqlite3
作为数据库,大型爬虫推荐 MongoDB
数据库,我个人认为, MongoDB
是对爬虫最亲和的数据库。
运行以上程序,即可生成 data.json
文件
总结: 就我个人感觉,使用Dart语言写爬虫肯定是没有 Python 顺手高效的,Python在爬虫这块的 工具 过于强大、简洁、高效。
Dart 服务端
使用Dart语言的原生API开发HTTP服务器仍显得过于繁琐,因此我们需要一个HTTP服务器框架,这样我们就只需要关注业务逻辑的处理。如果大家使用过任何一款成熟的HTTP服务器框架,那么对于新框架上手就会易如反掌,因为绝大多数服务器框架的概念都是相同的,主要就是 ORM
、路由映射、模板渲染、中间件等等这些东西。
根据我所知的,目前可用的仍在维护的Dart的HTTP服务器框架主要有四个,依次按照star最多的从上到下来排序:
其中排第一的 aqueduct 是功能、文档、示例最完善的,因此我们就以此框架做演示
安装
pub global activate aqueduct 复制代码
创建项目
执行命令,生成项目 api_server
pub global run aqueduct create api_server 复制代码
最简示例——hello world
其中 bin/main.dart
下的入口文件可以不用修改,主要修改 lib/channel.dart
,删除多余注释,代码如下
import 'package:api_server/controller.dart'; import 'api_server.dart'; class ApiServerChannel extends ApplicationChannel { @override Future prepare() async { logger.onRecord.listen((rec) => print("$rec ${rec.error ?? ""} ${rec.stackTrace ?? ""}")); } @override Controller get entryPoint { final router = Router(); router .route("/") .linkFunction((request) async { return Response.ok('hello world!'); }); return router; } } 复制代码
简单说一下,这里有两个实现,其中 prepare()
方法一般用于预处理,例如连接数据库等,我们暂时用不到,不需理会。 entryPoint
方法是我们真正需要关注的方法,它的执行在 prepare()
方法之后,当有请求到来时,就会被回调。我们在该方法中注册路由,这里注册一个根路径,并设置一个响应请求的匿名回调方法。当我们打开浏览器访问 http://localhost:8888
时,它返回一个响应,即向浏览器打印一句 hello world!
cd
到项目根路径下,执行以下命令启动服务
dart bin/main.dart 复制代码
在浏览器访问 http://localhost:8888
,可以看输出 hello world!
实现后台API服务
Router
除了可以注册回调方法,还可以关联一个 Controller
用于处理来自客户端的请求。
在lib目录下新建 controller.dart
文件,自定义一个 Controller
。它需要继承自框架的 Controller
类,并实现一个 handle
方法。
import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:aqueduct/aqueduct.dart'; class ItemsController extends Controller { @override Future<RequestOrResponse> handle(Request request) async { final content = await File('asset/data.json').readAsString(); return Response.ok(json.decode(content)); } } 复制代码
在项目根路径下新建 asset
目录,将我们之前爬取的数据文件 data.json
拷贝进去。然后修改 entryPoint
方法,再注册一个新的 url
@override Controller get entryPoint { final router = Router(); router .route("/") .linkFunction((request) async { return Response.ok('hello world!'); }); // 注册一个新的url,并关联到我们自定义的Controller上 router .route('/api/all') .link(() => ItemsController()); return router; } 复制代码
重新启动服务器
dart bin/main.dart 复制代码
浏览器访问 http://localhost:8888/api/all
,成功获取数据
创建Flutter项目演示
Flutter环境准备这里就省略了。先创建一个Flutter 工程用于演示
代码结构如下
这里主要是三个文件list_dao.dart
、
item_model.dart
、
main.dart
首先配置依赖文件 pubspec.yaml
,主要用到了两个库 dio
和 flutter_staggered_grid_view
dependencies: flutter: sdk: flutter cupertino_icons: ^0.1.2 dio: 2.1.4 flutter_staggered_grid_view: "^0.2.7" 复制代码
下载依赖完成,编辑以下文件 list_dao.dart
import 'package:flutter_demo/model/item_model.dart'; import 'package:dio/dio.dart'; class ListDao { //这里配置自己的实际域名或IP地址 static const Host = 'http://192.168.1.102:8888'; static const header = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20100101 Firefox/23.0', "Referer": "https://www.mzitu.com" }; static Future<ItemModel> fetch() async { try { Response response = await Dio().get("$Host/api/all"); if (response.statusCode == 200) { return ItemModel.fromJson(response.data); } else { throw Exception("StatusCode: ${response.statusCode}"); } } catch (e) { print(e); return null; } } } 复制代码
实体类 item_model.dart
class ItemModel { List<Items> items; ItemModel({this.items}); ItemModel.fromJson(Map<String, dynamic> json) { if (json['items'] != null) { items = new List<Items>(); json['items'].forEach((v) { items.add(new Items.fromJson(v)); }); } } Map<String, dynamic> toJson() { final Map<String, dynamic> data = new Map<String, dynamic>(); if (this.items != null) { data['items'] = this.items.map((v) => v.toJson()).toList(); } return data; } } class Items { String title; String imgUrl; Items({this.title, this.imgUrl}); Items.fromJson(Map<String, dynamic> json) { title = json['title']; imgUrl = json['imgUrl']; } Map<String, dynamic> toJson() { final Map<String, dynamic> data = new Map<String, dynamic>(); data['title'] = this.title; data['imgUrl'] = this.imgUrl; return data; } } 复制代码
最后就是实际的UI代码了
import 'package:flutter/material.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'dao/list_dao.dart'; import 'model/item_model.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData( primarySwatch: Colors.pink, ), home: MyHomePage(), ); } } class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { Future<ItemModel> mFuture; @override void initState() { loadData(); super.initState(); } loadData() { mFuture = ListDao.fetch(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("美女图"), ), body: FutureBuilder( future: mFuture, builder: (ctx, snapshot) { switch (snapshot.connectionState) { case ConnectionState.none: case ConnectionState.active: case ConnectionState.waiting: return Center(child: CircularProgressIndicator()); case ConnectionState.done: if (snapshot.hasError) return Center(child: Text('Error: ${snapshot.error}')); return _buildList(snapshot.data); } return null; }), ); } // 创建GridView Widget _buildList(ItemModel data) { return Container( color: Color(0xfff5f6f7), padding: EdgeInsets.only(top: 12, left: 10, right: 10), child: StaggeredGridView.countBuilder( primary: false, crossAxisCount: 4, itemCount: data?.items == null ? 0 : data.items.length, itemBuilder: (ctx, i) { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8)), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ ClipRRect( // 处理圆角图片 borderRadius: BorderRadius.only( topLeft: Radius.circular(8), topRight: Radius.circular(8)), child: Image.network(data.items[i].imgUrl, headers: ListDao.header)), Padding( padding: const EdgeInsets.all(8.0), child: Text( data.items[i].title, style: TextStyle(fontSize: 16), ), ) ], ), ); }, staggeredTileBuilder: (int index) => StaggeredTile.fit(2), mainAxisSpacing: 10.0, crossAxisSpacing: 8.0, ), ); } } 复制代码
确认我们之前写的服务器后端已经启动,然后启动本机模拟器,运行起Flutter App
总结
我认为使用Dart语言开发服务端,并结合Nginx用于生成环境下,使Flutter开发人员真正承包整个项目的业务逻辑是非常可行的,这条路才是真正的全栈之路!Dart的语法优势是胜过JavaScript的,即使 Java 与之相比也显得冗余臃肿。至于是否好用,就待大家自行体会了。
关于Dart的服务端框架 aqueduct
,大家有兴趣可以查看官方文档深入学习,本文主要省略了数据库方面的处理,实际上数据库是独立的知识内容,与框架的关系不是太大。而且该框架也提供了一个ORM模块,大家直接查看文档学习Aqueduct ORM ,但目前似乎只支持PostgreSQL数据库,如果想要使用其他数据库,安装相应的驱动,链接如下
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。