在学习或开发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链接如下
-
http 库链接
-
html 库链接
在项目下执行命令,下载依赖
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 'error! status:${response.statusCode}';
}
// 数据的解析
html_parse() async{
var html = await request_data();
Document document = parse(html);
// 这里使用css选择器语法提取数据
List<Element> images = document.querySelectorAll('#pins > li > a > img');
List 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
- angel
- jaguar
- shelf
其中排第一的 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 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 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;
ItemModel({this.items});
ItemModel.fromJson(Map<String, dynamic> json) {
if (json['items'] != null) {
items = new List();
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 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: [
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数据库,如果想要使用其他数据库,安装相应的驱动,链接如下
- Mongo-dart MongoDB
- SQLJocky MySQL
- Dart Redis Redis
我的个人博客