爬虫实战
环境准备
我们完全手动创建一个Dart工程还是略显麻烦,因此我们需要安装一个脚手架,自动生成一个合乎规范的Dart工程项目,执行以下命令安装(过时,参考升级到Dart 2.14)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
数据的获取与解析
网络请求
// 请求网络数据
Future<String> _requestData(String url) async {
var response = await http.get(url, headers: header);
if (response.statusCode == 200) {
return response.body;
}
return '<html>error! status:${response.statusCode}</html>';
}
解析标签
// 解析数据
List<ItemModel> _htmlParse(String html) {
Document document = parse(html);
List contents = <Element>[];
List images = <Element>[];
List<Element> parent = document.querySelectorAll('div.j-r-list-c');
for (var e in parent) {
var text = e.querySelectorAll("div.j-r-list-c-desc > a");
var image = e.querySelectorAll("div.j-r-list-c-img > a > img");
contents.add(text[0]);
images.add(image.isNotEmpty ? image[0] : Element.tag('img'));
}
/// 这里使用css选择器语法提取数据
// 文本
// List<Element> contents = document.querySelectorAll(
// 'div[class="j-r-list"] > ul > li > div > div[class="j-r-list-c-desc"] > a');
// // 图片
// List<Element> images = document.querySelectorAll(
// 'div[class="j-r-list"] > ul > li > div > div[class="j-r-list-c-img"] > a > img');
// 点赞
List<Element> likes = document.querySelectorAll(
'div.j-r-list-tool > div.j-r-list-tool-l > ul > li.j-r-list-tool-l-up > span');
// 点踩
List<Element> taps = document.querySelectorAll(
'div.j-r-list-tool > div.j-r-list-tool-l > ul > li.j-r-list-tool-l-down > span');
// 头像
List<Element> avatar =
document.querySelectorAll('div.j-list-user > div.u-img > a > img');
// 昵称
List<Element> nickNames =
document.querySelectorAll('div.j-list-user > div.u-txt > a');
// 日期
List<Element> date =
document.querySelectorAll('div.j-list-user > div.u-txt > span');
List data = List<ItemModel>();
for (var i = 0; i < contents.length; i++) {
ItemModel model = ItemModel();
model..avatarUrl = avatar[i].attributes['src']
..nickName = nickNames[i].text.trim()
..content = contents[i].text.trim()
..imgUrl = images[i].attributes['src'] ?? ''
..like = int.parse(likes[i].text)
..tap = int.parse(taps[i].text)
..date = date[i].text;
data.add(model);
}
return data;
}
这里使用的是css语法选择器进行数据提取。关于css选择器语法,可以参考本人一篇博客 链接地址。另外,在解析时需要注意导包的问题,会存在冲突
import 'package:http/http.dart' as http;
import 'package:html/parser.dart' show parse;
import 'package:html/dom.dart';
数据持久化
本课程分别使用sqlite3和PostgreSQL两种数据库演示。若缺乏数据库基础,或不了解sqlite3,可学习本人的一篇博客作为入门 链接地址
在Dart中使用sqlite3需要安装相应的第三方库,这里推荐使用 moor_ffi。PostgreSQL亦需要安装相应的驱动 postgresql2
sqlite3示例
import 'dart:ffi';
import 'dart:io';
import 'package:moor_ffi/open_helper.dart';
import 'package:moor_ffi/database.dart';
import 'model.dart';
const _createTable = '''
create table if not exists paragraph (
id integer primary key autoincrement,
nick_name text,
avatar text,
content text,
img_url text,
like_no integer,
tap_no integer,
date text
);
''';
// 配置sqlite3的加载路径
DynamicLibrary _openOnWin() {
final script = File(Platform.script.toFilePath()).parent;
return DynamicLibrary.open('${script.path}/sqlite3.dll');
}
class DbTools {
Database _db;
DbTools(){
open.overrideFor(OperatingSystem.windows, _openOnWin);
_db = Database.open('db/data.db');
_db.execute(_createTable);
}
}
PostgreSql 示例
关于PostgreSql数据库的安装参见下一节
class PostgresDb{
static const uri = 'postgres://postgres:123456@localhost:5432/test_db';
Connection _conn;
final Completer _completer = Completer();
PostgresDb(){
_init();
}
void _init() async{
_conn = await connect(uri);
await _conn.execute(_createTable2);
_completer.complete(true);
}
void saveAll(List<ItemModel> data) async{
// 等待初始化完成
await _completer.future;
for (var e in data) {
var value = [e.nickName,e.avatarUrl,e.content,e.imgUrl,e.like,e.tap,e.date];
await _conn.execute('insert into paragraph (nick_name,avatar,content,img_url,like_no,tap_no,date)'
'values (@0,@1,@2,@3,@4,@5,@6)',value);
}
}
void close()async{
await _completer.future;
_conn.close();
}
}
公众号“编程之路从0到1”