CoderCastrov logo
CoderCastrov
Питон

Давайте разберем веб

Давайте разберем веб
просмотров
9 мин чтение
#Питон

Веб-разработка

Мы создаем небольшое веб-приложение (на Flask), которое предоставляет некоторые данные о пищевых привычках разных стран

Введение

Веб полон ценных данных (в основном общедоступных), которые могут быть использованы для исследований или других целей. Теперь, в эпоху искусственного интеллекта и машинного обучения, данные ценнее, чем когда-либо раньше! Но большинство этих данных предназначены для чтения людьми (то есть они представлены в виде HTML) и не доступны в форматах, которые легче читать компьютерам (например, XML, JSON или CSV).

Чтобы собрать эти данные для наших целей, мы используем парсеры, которые "парсят" эти веб-страницы, которые нас интересуют. Они просто получают страницу, парсят HTML и извлекают данные (тексты, изображения, ссылки и т. д.) из этого HTML. Мы определяем иерархии или пути (XPath) или просто используем CSS-селекторы, чтобы определить, какую часть HTML нас интересует.

В этом уроке мы создадим небольшое веб-приложение (на Flask), которое предоставляет некоторые данные о пищевых привычках разных стран в HTML, и напишем паука (или краулера, если хотите) с использованием фреймворка Scrapy.

Почему Scrapy? (Опционально, можно пропустить)

Scrapy - самый популярный фреймворк для написания веб-парсеров. Даже Google использует Scrapy для разбора веб-страниц. Кроме того, Scrapy очень масштабируем и имеет Twisted в своей основе. Twisted - это библиотека для работы с сетью, и она дает Scrapy преимущества так называемого "асинхронного ввода-вывода". Однако Scrapy не использует стандартную библиотеку асинхронного ввода-вывода. Они используют генераторы для достижения такого асинхронного поведения.

Scrapy также имеет удобную архитектуру потока данных, которая позволяет нам писать промежуточные обработчики, конвейеры и экспортеры для настройки поведения Scrapy.


Обзор

У нас есть страница с формой входа, и после входа мы видим некоторый контент. Сегодня мы собираемся создать скрипт, который будет автоматически выполнять вход и анализировать данные на главной странице.


Проект

Для этого проекта лучше иметь базовое представление о HTTP, но вам не нужно знать ничего, кроме этого заранее. Однако мы будем писать веб-приложение с использованием Flask, потому что оно небольшое, простое и отлично подходит для наших целей. Вот ссылка на наш финальный код. Требования к программному обеспечению для проекта следующие:


Установка

Сначала нам нужно создать папку (мы будем использовать термин "каталог" взаимозаменяемо) с названием Scraper. Затем создайте две подпапки с названиями webapp и scraper. В папке Scraper мы откроем терминал (или командную строку, если вы используете Windows) и напишем следующую команду:

pip install --user flask scrapy

Эта команда установит Flask и Scrapy для нас. Мы использовали параметр --user, чтобы нам не понадобились права администратора (не требуется, если вы используете virtualenv).


Веб-приложение

Для создания веб-приложения мы создаем файл с названием __init__.py и каталог с названием templates внутри каталога webapp.

Теперь мы помещаем следующий код внутрь __init__.py.

Теперь, чтобы запустить веб-приложение, мы пишем следующую команду:

python __init__.py

Затем мы создаем и заполняем файлы внутри каталога templates по одному.

Содержимое файла templates/base.html:

Содержимое файла templates/index.html:

Содержимое файла templates/login.html:

Содержимое файла templates/food_by_country.html:

Теперь дерево каталогов каталога webapp должно выглядеть следующим образом:

webapp/
├── __init__.py
└── templates
    ├── base.html
    ├── food_by_country.html
    ├── index.html
    └── login.html

Теперь в браузере мы переходим по адресу http://localhost:5000.

Входим, используя имя пользователя "admin" и пароль "admin" (без кавычек).

Затем, после входа в систему, нажмите на ссылку (этот шаг здесь для демонстрации того, как следовать ссылкам с помощью Scrapy), и теперь вы можете увидеть таблицу, состоящую из данных о еде.

Примечание: Мы обсудим Flask в отдельном учебнике.

Примечание: Мы оставляем веб-приложение работающим для парсера. Однако, если вы хотите остановить его, вы можете просто нажать Control-C.


Паук

Для создания проекта Scrapy мы пишем следующую команду:

scrapy startproject scraper

Затем мы переходим в каталог scraper. Теперь мы можем увидеть содержимое этой папки, используя команду ls (на Mac, Linux или BSD) или dir (на Windows). Как видно, есть файл с именем scrapy.cfg и каталог с именем scraper. В этом руководстве мы не будем говорить о файле cfg. Однако вы можете прочитать об этом в документации Scrapy. Теперь мы переходим в каталог scraper (теперь мы находимся в Scraper/scraper/scraper) и пишем команду:

scrapy genspider countryfood localhost:5000

Здесь вы можете изменить countryfood на любое другое имя, и он создаст файл Python с шаблоном по умолчанию с этим именем в папке spiders (расположение: Scraper/scraper/scraper/spiders). Паук будет парсить url localhost:5000, и у него будет ограничение на то, какие url он может парсить (ограничение домена).

Примечание: Scrapy дал нам предупреждение, и мы не будем его игнорировать. Мы разберемся с ним позже в этом руководстве.

После создания паука, дерево каталогов должно выглядеть следующим образом:

scraper/
├── scraper
│   ├── __init__.py
│   ├── items.py
│   ├── middlewares.py
│   ├── pipelines.py
│   ├── settings.py
│   └── spiders
│       ├── countryfood.py
│       ├── __init__.py
└── scrapy.cfg

Теперь мы изменяем код в countryfood.py следующим образом.

Здесь CountryfoodSpider - это наш парсер (каждый парсер в Scrapy - это класс). Scrapy запускает паука с url(ами), указанными в атрибуте класса start_urls. Для каждого элемента этого атрибута Scrapy вызывает метод parse паука и передает объект response.


Некоторые атрибуты

Также обратите внимание, что ранее в списке allowed_domains класса был элемент localhost:5000, но он был изменен на localhost, потому что номер порта не является частью домена. Поэтому, когда Scrapy фильтрует запросы по доменам, он может блокировать наши запросы.


Метод parse

Теперь давайте посмотрим внутрь метода parse. Как уже упоминалось ранее, это точка входа для любого паука Scrapy. Сначала мы проверяем, вошли ли мы в систему. Если нет, мы отправляем запрос POST (передавая строку 'POST' параметру method) на сервер с нашими учетными данными (url-кодированными, переданными с помощью параметра body). В любом случае мы вызываем функцию parse_index. Также важно установить заголовок content-type в application/x-www-form-urlencoded. В противном случае Flask просто проигнорирует тело запроса, поскольку не знает, какой тип данных находится внутри тела. Первый параметр этого метода будет рассмотрен позже в этом руководстве.

НО, мы видим, что есть ветвь else в функции, и внутри нее вызывается parse_index. Как же мы вызываем функцию, если не вошли в систему и получаем ответ после входа в систему? Для этого мы используем параметр callback для метода follow (строка 15) объекта response. Каждая функция обработчика ответа в Scrapy выполняется автономно. Чтобы передавать данные между ними, мы используем параметр cb_args. Функция follow возвращает запрос с установленным URL в абсолютный URL.


Генераторы и yield и yield from

Также обратите внимание, что мы здесь не использовали return. Scrapy использует генераторы для перекрестного выполнения кода. Поэтому мы должны использовать yield. Подробнее о генераторах Python можно прочитать здесь. Поскольку каждая вызываемая функция в пауке Scrapy возвращает генератор, как мы должны использовать yield from для передачи управления генератору?


Метод parse_index и селекторы.

Как уже упоминалось ранее, нам нужно нажать на ссылку, чтобы увидеть данные после входа в систему. Теперь, как мы можем найти эту ссылку?

Если вы используете Chrome (или даже Chromium) или Firefox, нажмите Control-Shift-C, и вы увидите появление окна в браузере. Затем перейдите на вкладку "Inspector". Или вы можете перейти туда непосредственно, щелкнув правой кнопкой мыши в любом месте страницы и выбрав пункт меню "Inspect element". Это покажет нам элементы и дерево HTML-документа. Мы видим, что ссылка (также известная как якорь или тег <a>) находится внутри тега <p>. Так что наш путь в основном состоит из p > a. Интерпретируйте это как "перейти к a из p". Теперь у нас есть два варианта. Мы можем использовать CSS-селекторы или XPath. XPath очень мощный и имеет много функциональных возможностей. Однако CSS-селекторы могут отлично справиться с задачей и намного проще. Поэтому в этом уроке мы будем использовать CSS-селекторы.

Чтобы выбрать тег <a> внутри тега <p>, мы можем просто использовать строку селектора p a. Это выберет все дочерние элементы якоря тега параграфа (как прямые, так и косвенные). Но нам нужны только прямые дочерние элементы. Поэтому мы используем p > a. Чтобы получить атрибут выбранного тега в CSS-селекторе, мы используем "псевдо-элементный селектор" с именем attr() и передаем имя внутри скобок атрибута, значение которого нас интересует. Мы используем двоеточие для указания, что мы используем "псевдо-элементный селектор". Таким образом, строка селектора будет "p > a::attr(href)", и теперь мы передаем ее в качестве первого аргумента метода css объекта response (который принимает CSS-селектор и возвращает все выбранные элементы, то есть selection). Чтобы получить первое значение из этого, мы используем метод get объекта selection. Есть также метод extract для объектов selection, но он вернет список всех значений из всех элементов выборки, что нам сейчас не нужно.

Надеюсь, теперь вы понимаете, что происходит в строке 15.

Затем, как обычно, мы возвращаем объект request с помощью response.follow с соответствующими значениями (URL и функция обратного вызова).


Метод parse_data

Теперь вы знаете большую часть Scrapy. Но как мы экспортируем данные? Для этого мы либо возвращаем словарь, либо элемент Scrapy. Но я предпочитаю использовать словарь для этого учебника.

Обратите внимание, что в цикле for мы перебираем объект selection. Объект selection в основном является списком, и его элементы также имеют метод css и могут использовать селекторы внутри своих дочерних элементов. Это то, что мы сделали в строке 35. Затем в словаре мы извлекли текст из всех выбранных элементов <td>, используя псевдо-селектор text и вызывая метод get для него. Однако в значении ключа food мы использовали генератор списка. Это потому, что в конце содержимого <td> был тег <br>, который даст пустую строку (потому что теги и тексты обрабатываются отдельно. Я рекомендую вам поиграть и поразмышлять с селекторами).

Запуск паука

Как уже упоминалось ранее, мы оставили веб-приложение работать. Теперь мы сначала перечислим все доступные пауки с помощью следующей команды:

scrapy list

Мы видим, что паук countryfood указан в списке. Теперь мы готовы к работе! Введите следующую команду, чтобы запустить паука:

scrapy crawl countryfood -L WARN -o -:jl

Здесь команда crawl указывает Scrapy начать работу паука. Опция -L устанавливает уровень журнала с отладки на WARN. Это полезно, потому что в противном случае Scrapy будет выводить много информации в журнал, включая извлеченные элементы. Опция -o указывает файл и формат вывода. Здесь -:jl - это значение, передаваемое для опции -o, которое говорит установить файл вывода в stdout и формат в JSON lines. Двоеточие разделяет эти два значения. Обычно мы используем JSON lines вместо JSON, потому что, если выводится большой объем данных, JSON не масштабируется хорошо.

Здесь находится конечный код для учебника.

Конечное дерево каталогов:

scraper/
├── scraper
│   ├── scraper
│   │   ├── __init__.py
│   │   ├── items.py
│   │   ├── middlewares.py
│   │   ├── pipelines.py
│   │   ├── settings.py
│   │   └── spiders
│   │       ├── countryfood.py
│   │       ├── __init__.py
│   └── scrapy.cfg
└── webapp
    ├── __init__.py
    └── templates
        ├── base.html
        ├── food_by_country.html
        ├── index.html
        └── login.html

Желаю удачи в вашем путешествии по парсингу!