В данной статье будет рассмотрено, как использовать Flutter для извлечения данных с веб-сайтов с помощью техники, известной как веб-скрапинг. Пожалуйста, помните, что некоторые веб-сайты запрещают скрапинг в своих условиях использования. При использовании скрапинга убедитесь, что вы соблюдаете авторские права и условия использования.

Почему я решил описать web scraping?

Я начинающий flutter-разработчик, выполняю мелкие задачи, чтобы поднять уровень своих знаний. Недавно меня попросили получить данные товаров для мобильного приложения. API-запросов не было для получения данных, заказчик попросил использовать web scraping для решения данной проблемы. Хочу поделиться своим решением проблемы. Да, возможно, решение не самое лучшее, поэтому жду замечаний по моему решению.

Подготовка

Перед тем как начать, убедитесь, что у вас установлен Flutter и вы знакомы с основами языка программирования Dart. Необходимо иметь:

  1. Dart SDK version: 2.19.6

  2. Flutter 3.7.12

Для выполнения HTTP-запросов и парсинга HTML мы будем использовать пакеты http и html соответственно. Добавьте их в файл pubspec.yaml:

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  http: ^0.13.3
  html: ^0.15.0

Работа с web scraping

  1. Выполним GET-запрос к веб-сайту, с которого мы хотим извлечь данные. Я использую пакет http для этой цели:

    fetchProducts
    import 'package:http/http.dart' as http;
    
    void fetchProducts(String url) async {
      final response = await http.get(Uri.parse(url));
      if (response.statusCode == 200) {
            
      }
    }

  2. Далее, используем пакет html для парсинга HTML-контента, полученного из запроса:

    parser
    import 'package:html/parser.dart' as parser;
    import 'package:http/http.dart' as http;
    
    void fetchProducts(String url) async {
      final response = await http.get(Uri.parse(url));
      if (response.statusCode == 200) {
        final document = parser.parse(response.body);
      }
    }

  3. Создадим модель Product:

    Product
    class Product {
      String name;
      String model;
      String description;
      String imageUrl;
    
      Product({
        required this.name,
        required this.model,
        required this.description,
        required this.imageUrl,
      });
    }

  4. Извлечение данных:

    imageUrl
    // Получим все картинки товаров
    final imageElement = document.querySelector('.product-image-link img');
    final imageUrl = imageElement?.attributes['data-src'] ?? '';

  5. Перепишем функцию, получим все необходимые данные для товара из карточек и запушим в массив productList:

    Product.dart
    import 'package:html/parser.dart' as parser;
    import 'package:http/http.dart' as http;
    
    class Product {
      String name;
      String model;
      String description;
      String imageUrl;
    
      Product({
        required this.name,
        required this.model,
        required this.description,
        required this.imageUrl,
      });
    }
    
    Future<List<Product>> fetchProducts(String url, String cardWithDot) async {
      List<Product> productList = [];
      try {
        final response = await http.get(Uri.parse(url));
        if (response.statusCode == 200) {
          final document = parser.parse(response.body);
          final cards = document.querySelectorAll(cardWithDot);
    
          for (final card in cards) {
            final name = card.querySelector('.wd-entities-title')?.text ?? '';
            final model = card.querySelector('.price')?.text ?? '';
            final description =
                card.querySelector('.phone-description')?.text ?? '';
            final imageElement = card.querySelector('.product-image-link img');
            final imageUrl = imageElement?.attributes['data-src'] ?? '';
    
            final product = Product(
              name: name,
              model: model,
              description: description,
              imageUrl: imageUrl,
            );
            productList.add(product);
          }
        }
      } catch (e) {
        // Handle error
        print('Error $e');
      }
      return productList;
    }
    

Применение данных в приложении

После извлечения данных вы можете использовать их в вашем приложении.

  1. Как нам обработать состояния асинхронной операции? Для этого во Flutter есть специальный виджет FutureBuilder. В future: пишем наш метод fetchProducts, в котором мы передаем url, а также класс карточки:

    FutureBuilder
    return FutureBuilder<List<Product>>(
      future: fetchProducts('https://ga.com.tm/', '.product-grid-item'),
      ...
    )

  2. В builder делаем проверки для построения данных и само отображение данных:

    builder
    if (snapshot.hasError) {
      return Center(child: Text('Error: ${snapshot.error}'));
    } else {
      final productList = snapshot.data!;
      return Column(children: [
        SizedBox(
          width: MediaQuery.of(context).size.width,
          child: const Padding(
            padding: EdgeInsets.only(top: 8, left: 20),
            child: Text(
              'Новинки',
              style: TextStyle(
                fontSize: 25,
                fontWeight: FontWeight.w600,
                letterSpacing: 1,
                wordSpacing: 2,
              ),
            ),
          ),
        ),
        SizedBox(
          width: MediaQuery.of(context).size.width,
          child: GridView.builder(
              physics: const NeverScrollableScrollPhysics(),
              shrinkWrap: true,
              padding: const EdgeInsets.only(
                  left: 10, right: 10, top: 10, bottom: 10),
              gridDelegate:
                  const SliverGridDelegateWithFixedCrossAxisCount(
                      crossAxisCount: 2,
                      mainAxisSpacing: 12,
                      crossAxisSpacing: 12,
                      mainAxisExtent: 290),
              itemCount: productList.length,
              itemBuilder: (context, index) {
                final product = productList[index];
                return InkWell(
                  splashColor: Colors.transparent,
                  highlightColor: Colors.transparent,
                  onTap: () => {
                    //
                  },
                  child: Container(
                    decoration: BoxDecoration(
                        borderRadius: BorderRadius.circular(12),
                        color: Theme.of(context).primaryColor),
                    child: Column(children: [
                      Container(
                        height: 175,
                        decoration: BoxDecoration(
                            borderRadius: const BorderRadius.only(
                                topLeft: Radius.circular(12),
                                topRight: Radius.circular(12)),
                            color: Colors.white,
                            image: DecorationImage(
                                image: NetworkImage(
                                    product.imageUrl),
                                fit: BoxFit.cover)),
                      ),
                      //title
                      Padding(
                        padding: const EdgeInsets.only(
                            left: 5, top: 5, right: 5),
                        child: SizedBox(
                          height: 60,
                          width:
                              MediaQuery.of(context).size.width,
                          child: Text(
                            product.name,
                            //phoneDataList[index].name,
                            textAlign: TextAlign.start,
                            overflow: TextOverflow.ellipsis,
                            maxLines: 3,
                            style: const TextStyle(
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                        ),
                      ),
                    ]),
                  ),
                );
              }),
        ),
      ]);
    }

Как это выглядит?

Market
Market

Заключение

В этой статье мы рассмотрели, как использовать Flutter для веб-скрапинга и получения данных с веб-сайтов. С помощью пакетов http и html, вы можете выполнять HTTP-запросы, парсить HTML и извлекать нужную информацию. Надеюсь это статья пригодиться начинающим flutter-разработчикам, а также жду замечаний, если они имеются.

github

Комментарии (2)


  1. Chalokian
    11.08.2023 15:03
    +2

    А зачем для парсинга flutter?


    1. glebanya13 Автор
      11.08.2023 15:03

      Я расписал подход, как получить данные с помощью web scraping. Я считаю, что правильнее получать данные по API и работать с ними.