スクレイピングアプリ作成#2[作業メモ]

スクレイピングアプリの製作2日目
いつもは1~2時間ぐらいだが、今日は珍しく3時間ほど作業した。

それと途中でなんかアプリの方向性が決まった。

[目次1]作業内容

・スクレイピングのパッケージ比較
・HTML/CSSに関する勉強
・実際にアプリを作成

[目次2]メインじゃなけど勉強になったこと

・NullSafty
・名前が衝突した時の解決法
・非同期処理関連(asyncなど)


スクレイピングのパッケージ比較

結論、最初に調べていたuniversal_htmlを使った。
比較対象としてhtmlというパッケージも調べていたが、「DOM」などがよくわからず、サンプルも結局何が起こっているのかわからなかったので諦めた。他にはflutter_htmlなどがあるっぽい。

HTML/CSSに関する勉強

これを使って簡単に理解した。

HTML|コンテンツの内容(文字)を記述
CSS|Webページの見た目を整えるための言語

書き方もざっくり理解した。
・https://www.sejuku.net/blog/4
https://www.sejuku.net/blog/6026
・https://www.sejuku.net/blog/100547

スクレイピングはHTMLのタグを使ってデータを取れるっぽい
(いろんな方法がありそう)

実際にアプリを作成

作るアプリは自分のブログページをスクレイピングするアプリ。

ブログのタイトルを日付にしたせいで中身が良くわからず後から調べ直すのにも不便という問題があったのでそれを解決したい。

結局この記事を参考にした。編集したクラスの部分のみ記載

import 'package:flutter/material.dart';

import 'package:html/dom.dart' as dom;
import 'package:html/dom_parsing.dart';
import 'package:html/parser.dart';

import 'package:universal_html/controller.dart';

Future<void> main() async {
 runApp(const MyApp());

}

class MyApp extends StatelessWidget {
 const MyApp({Key? key}) : super(key: key);

 // This widget is the root of your application.
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     title: 'Flutter Demo',
     theme: ThemeData(
       primarySwatch: Colors.blue,
     ),
     home: const MyHomePage(title: 'Flutter Demo Home Page'),
   );
 }
}

class MyHomePage extends StatefulWidget {
 const MyHomePage({Key? key, required this.title}) : super(key: key);

 final String title;

 @override
 State<MyHomePage> createState() => _MyHomePageState();
}

//このクラスを結構いじった。
class _MyHomePageState extends State<MyHomePage> {
 
 //String型のリストを定義(_elementsをString型にするため)
 late List<String> _element = [];
 
 //コントローラーを定義
 final _controller = WindowController();

 String title = "--";

 @override
 void initState(){
   _updatepage();
   super.initState();
 }
 
 Future<void> _updatepage() async {
 
   //ここでhttpのページを指定して取得
   await _controller.openHttp(
     method: 'GET',
     uri: Uri.parse('https://note.com/n2_blog/n/n4ef8a03f93a5'),
   );
   final document = _controller.window!.document;
   
   //titleタグの内容を抜き取る
   final _topStorytitle = document.querySelectorAll("title").first.text;
   
   title = _topStorytitle ?? "--";
   print(_topStorytitle);
   
   //noteの見出しがh2タグだったのでh2タグをしてして抜き出す。
   final _elements = document.querySelectorAll("h2");
   //エレメントの内容チェックとstring型の配列に移し替え
   setState(() {
     for (final elem in _elements) {
       print(elem.text);
       if(elem.text != null) {
         _element.add(elem.text as String);
       }else{
         _element.add("non-text");
       }
     }
   });
 }

 @override
 Widget build(BuildContext context) {

   return Scaffold(
     appBar: AppBar(
       // Here we take the value from the MyHomePage object that was created by
       // the App.build method, and use it to set our appbar title.
       title: Text(title),
     ),
     body: Center(
       // Center is a layout widget. It takes a single child and positions it
       // in the middle of the parent.
       child: ListView.builder(
         itemCount: _element.length,
         itemBuilder: (BuildContext context, int index) {
           return Column(
             children: [
               ListTile(
                 leading: Icon(Icons.settings),
                 //表示部分
                 title: Text(_element[index]),
                 onTap: (){
                      //nothing
                 },
               ),
               Divider(),
             ],
           );
         },

       ),
     ),
     floatingActionButton: FloatingActionButton(
       onPressed: () async {
         //floatingボタンを押したら更新
         _updatepage();
       },
       tooltip: 'Increment',
       child: const Icon(Icons.add),
     ), // This trailing comma makes auto-formatting nicer for build methods.
   );
 }
}

アプリの画面

URLに12月6日の進捗日記を指定したので以下のようになった。

スクリーンショット 2021-12-11 22.48.48


NullSafty

このページを参考にした。
やっとnullSaftyがちゃんと使いこなせるようになった。(遅い)
nullかもしれない変数をnullを許容しない変数に入れるにはnullが絶対入らないようにif文でnullを排除すればいい。

また、その時に便利な「??」という演算子があったので上記のURLより引用

void main() {
 int value = 2;
 int? value2 = getNullableValue();
 value = value2 ?? 0;  // 例えば ?? でデフォルト値を与えてあげればOK
}

これでnullなら0を入れるという感じにnullを取り除くことができる。


名前が衝突した時の解決

import 'package:html/dom.dart'をつかしたら、Text()がimport 'package:flutter/material.dart'のものと衝突した。

この場合は、名前空間的な感じで呼び方をつけると良い。
参考: https://blog.dalt.me/1950

import 'package:html/dom.dart' as dom;

最終的には今回は必要ない知識だったけど、今後使いそう。

非同期処理関連(asyncなど)

今回もhtmlのページを取得する処理が非同期なため登場。
最初に画面に描画する変数を非同期にしてしまっていたためエラーが発生した。その場合は最初は適当な値をその変数に入れて表示をしておき、後から値を更新するのが良い。また、init()関数をFutureをつけて非同期にしたらこれもエラーが出た。細かい原因は良くわからなかったが今後注意していく。


明日の予定

もうちょっと使いやすくしたいなぁー
具体的には以下
・いろんな日の内容が一度に見れたらいい。
・クリックした時にブラウザでそのブログページを開く

この記事が気に入ったらサポートをしてみませんか?