Автоматический веб-скрапинг с помощью Python и Celery

Автоматический веб-скрапинг с помощью Python и Celery
Это вторая часть руководства по созданию инструмента для скрапинга веб-страниц при помощи Python. Мы будем использовать интеграцию Celery и системы управления задачами.

В части 1 «Создание скрапера RSS-каналов при помощи Python» показано, как можно использовать Requests и Beautiful Soup.

В части 3 данной серии статей «Создание приложения для скрапинга веб-страниц при помощи Python, Celery и Django» я продемонстрирую, как интегрировать инструмент для скрапинга веб-страниц в приложения.

Автоматический веб-скрапинг при помощи Python и CeleryВ предыдущей статье я создал простую программу скрапинга RSS-каналов, которая извлекает информацию при помощи Requests и BeautifulSoup( смотрите код на GitHub). Сейчас мы будем использовать этот программный код как основу для создания системы управления задачами и запланированного скрапинга.

Следующим логическим шагом в скрапинге данных с сайтов, которые часто меняются(то есть RSS-канала, отображающего X элементов за раз), будет регулярный скрапинг. В предыдущем примере парсинга мы использовали командную строку для выполнения кода по команде. Однако это не масштабируемое решение. Чтобы автоматизировать его, мы добавим Celery для создания системы очереди задач с периодом выполнения.

Я буду использовать следующие инструменты:

  • Python 3.7+;
  • Requests;
  • BeautifulSoup 4;
  • Текстовый редактор(я использую Visual Studio Code);
  • Celery — распределенная очередь задач;
  • RabbitMQ — брокер сообщений.

Примечание. Все зависимости библиотеки перечислены в файлах requirements.txtи Pipfile / Pipfile.lock.

Краткое пояснение по Celery

Celery — это система управления задачами, она работает совместно с брокером сообщений для выполнения асинхронных задач.

Автоматический веб-скрапинг при помощи Python и CeleryВыше показано, что инициатор задач(наше приложение для скрапинга веб-страниц) передает информацию о задаче в очередь(Celery) для выполнения. Планировщик(Celery beat) выполняет их как задачи cron, без какой-либо дополнительной обработки или взаимодействий вне запуска приложения Celery.

Статьи

  1. Создание скрапера RSS-канала при помощи Python
  2. Автоматический парсинг веб-страниц при помощи Python и Celery(это руководство)
  3. Создание приложения для парсинга веб-страниц при помощи Python, Celery и Django

Краткое содержание проекта

Вот схема шагов, которые мы предпримем для создания окончательного проекта:

  1. Установка Celery и RabbitMQ — Celery управляет постановкой задач в очередь и их выполнением, а RabbitMQ обрабатывает сообщения.
  2. Начало работы с RabbitMQ и обработка логов.
  3. Создание доказательства концепции «Hello World» при помощи Celery, чтобы убедиться в том, что он работает.
  4. Регистрация возможности скрапинга py при помощи Celery.
  5. Дальнейшее развитие и управление задачами скрапинга.
  6. Создание и выполнение расписания для задач скрапинга.

Примечание. Введение в RabbitMQ и Celery довольно длинное, если у вас есть опыт работы с ними, я рекомендую сразу же перейти к шагу 4.

Приступаем к работе

Мы начнем с открытия каталога предыдущего проекта, в данном случае это web_scraping_example из предыдущей статьи. Если планируете, его можно клонировать с GitHub.

Примечание. Я использую Ubuntu, так что мои команды могут отличаться от. Кроме того, для краткости я пропустил повторение кода при помощи …

Кроме того, требования к проекту могут задавать для данной установки использование pip, как в примере, приведенном ниже.

$ pip install celery

Почему Celery & RabbitMQ?

Мы используем Celery и RabbitMQ, так как они довольно просты в параметру, тестировании и масштабировании в производственной среде. Хотя мы могли бы выполнять периодические задачи при помощи иных библиотек, или просто заданий cron, в целом, я хотел, чтобы в следующей статье данной серии мы основывались на этом.

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

Параметр RabbitMQ

Изменить и запустить сервер RabbitMQ в Ubuntu значительно проще, чем в операционной системе Windows. Я буду следовать официальному руководству по установке.

Ниже приведены команды установки для Debian, Ubuntu.

$ sudo apt-get update -y$ sudo apt-get install curl gnupg -y$ curl -fsSl https://github.com/rabbitmq/signing-keys/releases/download/2.0/rabbitmq-release-signing-key.asc | sudo apt-key add -$ sudo apt-get install apt-transport https$ sudo tee /etc/apt/sources.list.d/bintray.rabbitmq.list <<EOF$ deb https://dl.bintray.com/rabbitmq-erlang/debian bionic erlang $ deb https://dl.bintray.com/rabbitmq/debian bionic main$ EOF$ sudo apt-get update -y$ sudo apt-get install rabbitmq-server -y --fix-missing

Когда я впервые установил RabbitMQ в виртуальной среде, он запустился автоматически. Чтобы проверить, что команда rabbitmq-server работает(ее мы будем использовать при работе с Celery), мне пришлось закрыть службу.

Я также заметил, что в разрешениях по умолчанию для установки указано Only root or rabbitmq should run rabbitmqctl shutdown, что мне показалось странным. Вместо того, чтобы решить эту проблему, я решил просто запустить sudo.

$ sudo rabbitmqctl shutdown

Далее я смог протестировать сервер, используя rabbitmq-server, я привожу команду и отображение ниже.

$ sudo rabbitmq-server

Автоматический веб-скрапинг при помощи Python и CeleryОтображение sudo rabbitmq-server в терминале

К сведению — вы можете без труда завершить команду rabbitmq-server, используя клавиатурную комбинацию Ctrl + C.

Для параметра RabbitMQ в операционной системе Windows требуются дополнительные действия. В официальной документации есть руководство по ручной установке.

Тестирование Celery с RabbitMQ

Прежде чем перейти к написанию кода проекта, я обычно начинаю с тестирования базовых примеров в стиле «Hello World», которые есть в пакетах и ​​фреймворках. Это дает мне общее представление о том, чего я могу ожидать, а также пару команд для терминала, которые необходимо добавить в набор инструментов для каждой конкретной технологии.

В данном случае работа с Celery будет сопровождаться их собственным подтверждением концепции «Hello World» в виде задачи, выполняющей базовое добавление. Оно доступно в официальной документации Celery. Я собираюсь вкратце проиллюстрировать его. Однако, если вам нужны пояснения или подробный обзор, пожалуйста, ознакомьтесь с официальной документаций.

Сейчас, когда у нас установлен и проверен брокер RabbitMQ, можно приступить к созданию файла tasks.py. Он будет содержать задачи, которые мы будем выполнять, будь то добавление, скрапинг веб-страниц или сохранение посетителей в базе данных. Сейчас я внесу изменения в каталог проекта.

$ touch tasks.py

Чтобы создать задачу добавления, мы будем импортировать Celery, и создавать возможность с флагом @app.task, позволяющую Celery workers приобретать задачу в системе очереди.

# tasks.pyfrom celery import Celeryapp = Celery('tasks') # определяем название приложения, которое будет использоваться во флаге@app.task # регистрация задачи для приложенияdef add(x, y): return x + y

Используя add данной задачи, мы можем начать тестирование исполнения. Здесь все может запутаться, поскольку на следующем шаге у меня будут одновременно открыты три терминала.

Я начну с краткого объяснения, далее углублюсь в программный код и предоставлю снимки экрана.

Пояснение

Чтобы завершить тест, мы будем выполнять задачу Celery при помощи командной строки, импортировав файл tasks.py и вызвав его. Чтобы задачи были получены в очередь, нам необходимо, чтобы Celery worker и сервисы RabbitMQ были активными. Сервер RabbitMQ будет действовать как брокер сообщений, в то время как Celery worker будет выполнять задачи.

Я буду обозначать каждый шаг номерами терминалов:

  1. RabbitMQ
  2. Celery worker
  3. Выполнение задачи

Терминал 1

Мы начнем с запуска сервера RabbitMQ в терминале №1.

# RabbitMQ $ sudo rabbitmq-server

Автоматический веб-скрапинг при помощи Python и CeleryЗапуск сервера RabbitMQ

Терминал 2

Впоследствии мы можем начать процесс Celery worker в терминале №2. Я добавил подробные параметра для worker, чтобы проиллюстрировать, как будет выглядеть результат.

Примечание: это надо выполнить из каталога проекта.

# Celery worker $ celery worker -A tasks -l INFO

Разберем приведенную выше команду:

  • celery — пакет, который мы вызываем.
  • worker — запуск процесса worker.
  • -A tasks — явно объявляем, что нам необходимо приложение
  • -l INFO- задает наличие подробных событий ведения журнала консоли(нам необходимо много деталей).

Чтобы проверить, правильно ли загружается worker, найдите в терминале строку concurrency: 4(prefork).

Кроме того, мы замечаем, что приложение [tasks] было импортировано вместе с регистрацией задач из файла tasks.py. worker зарегистрировал единственную задачу(1) tasks.add.

Автоматический веб-скрапинг при помощи Python и CeleryЗапуск Celery worker с подробной информацией

Терминал №3

Далее мы можем начать выполнение теста в терминале №3. Я буду выполнять цикл, чтобы проиллюстрировать, что служба worker перехватывает пару задач. Мы добьемся этого, введя add из файла tasks.py, а далее выполнив цикл for. Примечание. После строки add.delay(i, i) вам необходимо будет использовать клавиатурную комбинацию Ctrl + Enter длявыполнения команды.

$ python>>> from tasks import add # pulling in add from tasks.py>>> for i in range(1000):... add.delay(i, i) # delay calls the task

Сейчас вы должны увидеть большой блок вывода в терминале № 3(выполнение задачи Celery). Это продемонстрирует, что worker получает результат задачи от терминала №2.

Автоматический веб-скрапинг при помощи Python и CeleryЗапуск выполнения задачи Celery

Терминал 2

Если мы проверим Celery worker в терминале № 2, процесс, выполняющий задачу add, мы увидим, что он перехватывает каждое из выполнений задачи.

Автоматический веб-скрапинг при помощи Python и CeleryCelery worker, получающий и выполняющий задачи

Сейчас мы успешно доказали, что Celery и RabbitMQ установлены правильно. Это помогает заложить основу для иных задач, которые мы будем реализовывать(например, скрапинг веб-страниц), демонстрируя, как взаимодействуют Celery, Celery worker и RabbitMQ.

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

Создание tasks.py при помощи Celery

Приведенный выше пример помог проверить процесс, который мы будем использовать для выполнения задач при помощи Celery, а также продемонстрировал, как задачи регистрируются при помощи Celery worker.

Основываясь на приведенном выше примере, мы начнем с создания задач скрапинга. Теперь я собираюсь отказаться от файла scraping.py, поскольку он будет просто скопирован в файл tasks.py для простоты.

Я начну с удаления из примера возможности def add(x, y) и копирования зависимостей(Requests и BeautifulSoup) вместе с самими возможностями.

Примечание: я буду использовать те же возможности, но в файле tasks.py.

# tasks.pyfrom celery import Celeryimport requests # ввод данныхfrom bs4 import BeautifulSoup # парсинг xmlimport json # экспорт в файлыapp = Celery('tasks')# функцию сохраненияdef save_function(article_list): with open('articles.txt', 'w' as outfile: json.dump(article_list, outfile)# функцию скрапингаdef hackernews_rss(): article_list = [] try: # выполняем запрос, разбираем данные при помощи XML       # разбираем данные в BS4 r = requests.get('https://news.ycombinator.com/rss')        soup = BeautifulSoup(r.content, features='xml')        # выбираем только "item", которые нам нужны из данных articles = soup.findAll('item')        # для каждого "item" разбираем его в список for a in articles:            title = a.find('title').text link = a.find('link').text published = a.find('pubDate').text            # создаем объект "article" с данными            # из каждого "item"            article = {                'title': title,                'link': link,                'published': published                }            # добавляем "article_list" с каждым объектом "article"            article_list.append(article)        # после цикла вносим сохраненные объекты в файл.txt return save_function(article_list)    except Exception as e:        print('The scraping job failed. See exception: ')        print(e)

Упомянутые выше возможности парсинга веб-страниц сейчас доступны в файле tasks.py вместе с их зависимостями. Следующим шагом будет регистрация задач в приложении Celery, для этого просто размещаем @app.task над каждой функцией.

# tasks.py... # то же, что и выше@app.taskdef save_function(article_list):   ...@app.taskdef hackernews_rss():

Дальнейшее развитие возможностей скрапинга

Хотя возможности скрапинга, которые мы определили, доказали эффективность для извлечения данных из RSS-канала, у нас все ещё есть функции для улучшения. Ниже я в общих чертах обрисовал, что мы будем изменять в наборе инструментов для скрапинга, прежде чем автоматизируем его.

Улучшения

  1. Сохранение результатов в файлах .json с отметками даты и времени.
  2. Добавление даты и времени created_at для каждой статьи.
  3. Добавление строки source, если мы хотим извлекать данные и с иных веб-сайтов.

Упомянутые выше изменения невелики, поскольку мы выполнили основную часть работы в рамках первой статьи о реализации. Хотя это несущественное изменение, .json будет читать удобнее, чем .txt. Два дополнительных столбца также помогут сделать его более «масштабируемым» при добавлении иных каналов, а также при последующем анализе данных.

Давайте начнем с save_function: обновим ее для вывода файла .json и добавим временную метку, чтобы улучшить качество при обращении к ранее извлеченным данным.

# tasks.pyfrom datetime import datetime # for time stamps... def save_function(articles_list):    # временная метка и имя файла timestamp = datetime.now().strftime('%Y%m%d-%H%M%S')    filename = 'articles-{}.json'.format(timestamp)    # создаем файл статьи с временной меткой with open(filename, 'w').format(timestamp) as outfile:        json.dump(article_list, outfile)

Я использую возможность datetime.now().strftime(...) для создания метки даты и времени, используя .format(timestamp) .

Переходя к двум изменениям в возможности hackernews_rss() — мы добавим некоторую информацию об источнике и отметку времени created_at . Это два простых изменения, которые помогут нам оставаться в курсе, если мы добавим дополнительные возможности скрапинга.

# tasks.py...def hackernews_rss():    ...        for a in articles:            ...            article = {                ...                'created_at': str(datetime.now()),                'source': 'HackerNews RSS'                }        ...

Указанные выше изменения иллюстрируют добавление столбцов created_at и source . К сведению — если вы опустите переход str() , вы не сможете его выполнить из-за Object of type datetime is not JSON serializable .

Планирование задач при помощи Celery

Сейчас мы будем использовать функции Celery по планированию задач, опираясь на то, что beat_schedule поставляется из коробки. Это может нам регистрировать задачи на определенное время при помощи агента планирования.

Отличное описание примеров планирования можно найти в официальной документации . Я также включил пару дополнительных примеров расписания в файле tasks.py в репозитории GitHub .

Я собираюсь выполнять задачу скрапинга каждую минуту, поскольку это продемонстрирует, как Celery worker взаимодействует с запланированными задачами. Это не приведет к разнице в данных, так как используемый нами RSS-канал не обновляется ежеминутно.

Моя цель в данной демонстрации — показать выходные файлы статей и простое расписание задач.

Создание расписания

# tasks.py... from celery.schedules import crontab # scheduler# выполнение запланированных задачapp.conf.beat_schedule = {    # выполняется каждую минуту    'scraping-task-one-min': {        'task': 'tasks.hackernews_rss',        'schedule': crontab()    }}...

Приведенная выше конфигурация будет регистрировать расписание задач в самом приложении Celery. После запуска мы сможем вызвать расписание Celery, чтобы дважды проверить, что он поставил в очередь.

Выполняем задачи

Сейчас, когда расписание создано, пришло время включить сервер RabbitMQ и запустить процессы Celery worker.

В этом примере мы будем использовать две вкладки терминала:

  1. Сервер RabbitMQ
  2. Celery worker

Терминал 1

Чтобы запустить сервер RabbitMQ (наш брокер сообщений), мы будем использовать ту же команду, что и раньше.

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

$ sudo rabbitmqctl shutdown$ sudo rabbitmq-server

Сейчас вы должны видеть результат, аналогичный предыдущему (смотрите скриншот экрана, приведенный ниже).

Автоматический веб-скрапинг при помощи Python и CeleryЗапуск сервера RabbitMQ

После запуска сервера RabbitMQ мы можем начать с терминала №2.

Терминал 2

Мы изменим команду, поскольку сейчас она будет включать в себя обозначение -Bдля того, какие вызовы worker выполняет расписание.

$ celery -A tasks worker -B -l INFO

Отображение консоли будет иллюстрировать запуск приложения и (в зависимости от того, какое у вас расписание) будет отображать информацию о выполнении задачи.

На скриншоте ниже:

  1. Зарегистрированы [tasks].
  2. Начинается расписание
  3. Наш worker MainProcess получает задачу Received task: tasks.hackernews_rss.
  4. Запускается ForkPoolWorker и выполняет задачу, а далее возвращает результат.

Автоматический веб-скрапинг при помощи Python и CeleryВыполнение запланированной задачи

К сведению — мы можем остановить выполнение запланированной задачи при помощи клавиатурной комбинации Ctrl + C, поскольку это будет работать бесконечно.

Сейчас, когда мы успешно выполнили задачу save_function(), созданный ранее файл вывел файл .json.

Автоматический веб-скрапинг при помощи Python и CeleryОтображение .json файла задачи

Заключение

Мы успешно расширили наш простой инструмент для скрапинга веб-страниц, чтобы создать расписание. Это гарантирует, что нам больше не необходимо вручную выполнять задачи скрапинга, и мы можем просто «включить и оставить». После планировки задач, проект сможет скрапить веб-сайты на предмет наличия данных, которые изменяются по заданному расписанию (скажем, каждые 15 минут), и каждый раз возвращать новые данные.

Поэтому же нам делать дальше?

В третьей части данной серии статей я продемонстрирую приложение Django с интеграцией Celery и скрапингом веб-страниц. Это будет отличный пример веб-приложения, которое извлекает данные на веб-сайт и заполняет его информацией. Нашим конечным продуктом будет агрегатор новостей, который будет извлекать информацию сразу же из нескольких RSS-каналов.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *