Создание приложения для парсинга веб-страниц с помощью Python, Celery и Django

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

Часть 1. Создание скрапера RSS-канала при помощи Python. В ней я рассказал о том, как можно без труда использовать Requests и Beautiful Soup.

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

Создание приложения для парсинга веб-страниц при помощи Python, Celery и Django

Предпосылки

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

Наш следующий шаг — объединить запланированные задачи парсинга в веб-приложение при помощи Django. Это предоставит нам доступ к базе данных, функция выводить данные на сайте и станет шагом к созданию приложения для «скрапинга». Цель этого проекта — создать что-то масштабируемое, похожее на агрегатор.

Эта статья не будет являться полным руководством по Django. Вместо этого я ориентирован на подход «Hello World» с последующим выведением извлеченного содержимого в веб-приложении.

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

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

Примечание. Все зависимости библиотеки перечислены в каталоге Pipfile/ Pipfile.lock.

Беглый взгляд на структуру

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

Создание приложения для парсинга веб-страниц при помощи Python, Celery и DjangoВыше продемонстрировано, что наше приложение Django отправляет задачи в очередь, выполняет, а далее сохраняет события в базе данных. Пока приложение Django работает, нам не необходимо будет выполнять какие-либо задачи по скрапингу веб-страниц.

Статьи

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

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

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

  1. Установить Django, фреймворк Python, который мы будем использовать для создания веб-приложения.
  2. Создать проект Django и запустить сервер.
  3. Создать приложение scraping для сбора данных.
  4. Изменить py и tasks.py продемонстрировав извлечение данных.
  5. Интегрировать данные с

Примечание. Если вы знакомы с Django, перейдите к шагу 4.

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

Мы начнем с создания виртуальной среды для проекта Django, а далее создадим стартовый проект. Этот программный код доступен в моем GitHub. Файл Piplock включает все зависимости проекта, виртуальная среда будет развернута со всеми необходимыми пакетами, чтобы сэкономить время.

$ mkdir django_web_scraping_example && cd django_web_scraping_example$ pipenv install requests bs4 lxml django celery

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

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

Создайте проект Django и запустите сервер

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

# django_web_scraping_example$ pipenv shell$ django-admin startproject django_web_scraping_example.$ python manage.py createsuperuser$ python manage.py makemigrations$ python manage.py migrate

Распаковав некоторые из приведенных выше команд, мы создадим экземпляр оболочки виртуальной среды для выполнения команд Django. Команда startproject создаст исходное приложение в каталоге, в котором мы находимся, используя ., и запустит иные стандартные команды: createsuperuser, makemigrations, migrate.

Сейчас мы запустим сервер, чтобы увидеть, что он работает.

Примечание. Убедитесь в том, что данные команды выполняются в оболочке pipenv.

$ python manage.py runserve

Перейдя к localhost:8000, мы видим, что сервер запущен.

Создание приложения для парсинга веб-страниц при помощи Python, Celery и DjangoПроект Django запущен на localhost: 8000.

Сейчас мы создадим URL-адрес urls.py, которому передадим будущее представление.

# urls.py from django.contrib import adminfrom django.urls import path, includefrom.views import HomePageView # newurlpatterns = [ path('', HomePageView.as_view(), name='home'), # homepage path('admin/', admin.site.urls),]

Выше приведено общее представление, импортируемое из файла views.py, который мы собираемся создать в основном каталоге проекта.

$ touch views.py

Сейчас мы можем составить представление:

# django_web_scraping_example/views.pyfrom django.shortcuts import renderfrom django.views import generic# Создаем представление здесь.class HomePageView(generic.ListView): template_name = 'home.html'

Далее мы создадим каталог шаблонов, базовый HTML-шаблон и шаблон домашней страницы:

$ mkdir templates && touch templates / base.html && touch templates / home.html

Чтобы зарегистрировать файлы шаблонов, мы добавим каталог templates в параметра Django:

# settings.pyTEMPLATES = [... 'DIRS': ['templates'], # new... ]

Сейчас мы добавим небольшой HTML-код, чтобы начать стилизацию:

# base.html{% load static %}<!DOCTYPE html><html> <head> <meta charset="utf-8">           <title>{% block title %}Django Web Scraping Example                     {% endblock title %}</title>    </head>    <body>        <div class="container">            {% block content %}            {% endblock content %}        </div>    </body></html>

Все содержимое, которое мы размещаем на страницах, попадет в контейнеры шаблона base.html, помеченные {% block %}.

# home.html {% extends 'base.html'%}{% block content%} Hello World {% endblock content%}

После того, как мы закончили работу с шаблонами, пример «Hello World» завершен.

Создание приложения scraping для сбора данных

В этом разделе мы создадим наше приложение для скрапинга и модель данных. Они будут интегрированы в settings.py, и данные будут передаваться в основное приложение HomePageView.

$ python manage.py startapp scraping

В параметрах зарегистрируем приложение:

# settings.pyINSTALLED_APPS [...'scraping.apps.ScrapingConfig', # new]

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

# models.pyfrom django.db import models# Здесь создаем модель.class News(models.Model):    title = models.CharField(max_length=200)    link = models.CharField(max_length=2083, default="", unique=True)    published = models.DateTimeField()    created_at = models.DateTimeField(auto_now_add=True)    updated_at = models.DateTimeField(auto_now=True)    source = models.CharField(max_length=30, default="", blank=True, null=True)

Распаковывая приведенный выше программный код, давайте перечислим, что выполняет каждое поле ввода:

  • title- структурированные данные RSS;
  • link- ссылка на статью;
  • published- дата публикации статьи;
  • created_at- дата ввода данных, по умолчанию «теперь»;
  • updated_at- если данные обновлены, они будут обновляться и у нас;
  • source- любой веб-сайт, который мы решили скрапить.

После создания модели приложение Django не загружается, так как нам не хватает миграций(то есть построения таблиц).

$ python manage.py makemigrations $ python manage.py migrate

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

Параметр файла celery.py

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

Этот раздел будет основан на коде, который был описан в предыдущих статьях данной серии. Мы начнем с добавления файла celery.py для приложения Celery, а далее добавим задачи из базы кода автоматизированного скрапера веб-страниц при помощи Python и Celery.

$ touch django_web_scraping_example / celery.py

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

# celery.pyfrom __future__ import absolute_importimport osfrom celery import Celeryfrom celery.schedules import crontab # scheduler# параметра django по умолчаниюos.environ.setdefault('DJANGO_SETTINGS_MODULE','django_web_scraping_example.settings')app = Celery('django_web_scraping_example')app.conf.timezone = 'UTC'app.config_from_object("django.conf:settings", namespace="CELERY")app.autodiscover_tasks()

Это параметра по умолчанию из документации Celery, они демонстрируют, что приложение Celery будет использовать модуль параметров и автоматом обнаруживать задачи.

Вторая ключевая конфигурация перед созданием задач — это параметр settings.py из брокера сообщений(RabbitMQ) и Celery.

# settings.py# celeryCELERY_BROKER_URL = 'amqp://localhost:5672'CELERY_RESULT_BACKEND = 'amqp://localhost:5672'CELERY_ACCEPT_CONTENT = ['application/json']CELERY_TASK_SERIALIZER = 'json'CELERY_RESULT_SERIALIZER = 'json'CELERY_TIMEZONE = 'UTC'

Добавляем tasks.py

Задачи, изложенные в tasks.py, похожи на те, что были в моей предыдущей статье. Основные изменения это:

  • Функцию сохранения;
  • То как мы называем объекты.

Вместо того чтобы сохранять извлеченные данные в.txt файлы, они будут храниться как записи базы данных в СУБД, используемой по умолчанию ( SQLite ).

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

# scraping/tasks.py# скрапингimport requestsfrom bs4 import BeautifulSoupimport jsonfrom datetime import datetimeimport lxml# функцию скрапинга@shared_taskdef hackernews_rss():    article_list = []    try:        print('Starting the scraping tool')        # выполняем запрос, разбираем данные при помощи XML        # разбираем данные в BS4 r = requests.get('https://news.ycombinator.com/rss')        soup = BeautifulSoup(r.content, features='xml')        # выбираем из данных только "items", которые нам нужны articles = soup.findAll('item')            # для каждого "item" разбираем его в список for a in articles:            title = a.find('title').text link = a.find('link').text published_wrong = a.find('pubDate').text published = datetime.strptime(published_wrong, '%a, %d %b %Y %H:%M:%S %z')            # выводим(published, published_wrong) # проверяем корректность формата даты            # создаем объект "article" с данными            # из каждого "item"            article = {                'title': title,                'link': link,                'published': published,                'source': 'HackerNews RSS'            }            # добавляем "article_list" с каждым объектом "article"            article_list.append(article)            print('Finished scraping the articles')                # после цикла передаем сохраненный объект в файл .txt return save_function(article_list)    except Exception as e:        print('The scraping job failed. See exception:')        print(e)

Приведенная выше функцию делает следующее:

  • Отправляет запрос к RSS-каналу, получает перечисленные элементы, а далее возвращает данные XML.
  • Разделяет данные XML на «item» при помощи findAll(‘item’), далее выполняет парсинг данных при помощи библиотеки LXML
  • Очищает данные в формате JSON, уделяя особое внимание формату даты, взятому из itemкаждой статьи. Это будет важно для сохранения статей в базе данных.
  • Проверяет, чтобы даты были указаны в формате, приемлемом для базы данных.
  • Добавляет статью в список элементов.
  • Вызывает save_function(), используя список статей

Если задача скрапинга не удалась, мы получим информацию от Exception.

Далее мы начнем рассматривать возможность save_function(), которая была реализована в предыдущей статье. Она была адаптирована для использования модели News, созданной в приложении scraping.

# scraping/tasks.py@shared_task(serializer='json')def save_function(article_list):    print('starting')    new_count = 0 for article in article_list:        try:            News.objects.create(                title = article['title'],                link = article['link'],                published = article['published'],                source = article['source']            )            new_count += 1 except Exception as e:            print('failed at latest_article is none')            print(e)            break return print('finished')

save_function() принимает article_list , переданный функцией скрапинга, а далее пытается сохранить каждый объект article в базе данных. Если вы хотели бы увидеть лучшую версию save_function() , которая распознает самую последнюю сохраненную статью и останавливает сохранение, вы найдете ее на моем GitHub .

Отправка данных в HomePageView

Сейчас, когда celery.py и tasks.py созданы, мы можем интегрировать данные в HomePageView , чтобы вывести их по URL-адресу.

Для начала откроем views.py в корне проекта и добавим в него модель News . Это сможет нам вызывать теги объектов article в шаблонах Django.

# django_web_scraping_example/views.pyfrom scraping.models import News # вводим News в представлениеclass HomePageView(generic.ListView):    template_name = 'home.html'    context_object_name = 'articles'     # назначаем список объектов "News" объекту "articles"    # передаем объекты "News", как набор запросов для представления списка def get_queryset(self):        return News.objects.all()

Готовый проект

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

Мы пройдем три этапа, чтобы запустить наш проект:

  1. Запустим службу брокера RabbitMQ.
  2. Запустим сервер Django.
  3. Включим задачи Celery.

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

Терминал №1 — RabbitMQ

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

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

$ sudo rabbitmqctl-shutdown$ sudo rabbitmq-server # start server

Создание приложения для парсинга веб-страниц при помощи Python, Celery и DjangoЗапуск службы RabbitMQ.

Терминал №2 — Django

  • Django запустить просто, мы начнем с команды runserver. Если вы используетеPipenv, выполните команду в оболочке:
$ pipenv shell$ python manage.py runserver

Создание приложения для парсинга веб-страниц при помощи Python, Celery и DjangoЗапуск сервера Django.

Терминал №3 — Celery

  • Сейчас, когда проект запущен, мы включим задачи Celery.
$ celery -A django_web_scraping_example worker -B -l INFO

Создание приложения для парсинга веб-страниц при помощи Python, Celery и DjangoЗапуск службы Celery.

Когда перечисленные выше службы будут запущены, мы сможем проверить результат скрапинга на главной странице (у меня это 127.0.0.1:8000).

Создание приложения для парсинга веб-страниц при помощи Python, Celery и DjangoДанные HackerNews на главной странице.

Выше приведена таблица извлеченных данных, которые были возвращены созданными задачами Celery. Если мы посмотрим на отображение задач, то увидим, что они не работают, так как данные не соответствуют уникальному ограничению (то есть это дубликат, и новых публикаций нет).

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

Заключение

Мы успешно интегрировали библиотеки скрапинга веб-страниц Python, Django, Celery, RabbitMQ и для создания программы чтения RSS-каналов. В приведенном выше примере представлен обзор агрегирования данных в формате веб-приложения, аналогичном популярным веб-сайтам (к примеру, Feedly ).

Что дальше?

  • Задействуйте иные сайты или новостные ленты.
  • Используйте полную версию save_function(), чтобы не пытаться сохранять каждый отдельный объект при каждом скрапинге (меньше обращений к базе данных)!
  • Создайте собственный RSS-канал с агрегированными данными.

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

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