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

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

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

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

Предпосылки

Ранее я создал простую программу для скрапинга 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.

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

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

Выше продемонстрировано, что наше приложение 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, мы видим, что сервер запущен.

Проект Django запущен на localhost: 8000.

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

# urls.py from django.contrib import admin
from 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 render
from 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.py
INSTALLED_APPS [
...
'scraping.apps.ScrapingConfig', # new
] 

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

# models.py
from 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.py
from __future__ import absolute_import
import os
from celery import Celery
from 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 requests
from bs4 import BeautifulSoup
import json
from datetime import datetime
import lxml
# функция скрапинга
@shared_task
def 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.py
from 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

Запуск службы RabbitMQ.

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

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

Запуск сервера Django.

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

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

Запуск службы Celery.

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

Данные HackerNews на главной странице.

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

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

Заключение

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

Что дальше?

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

Данная публикация является переводом статьи «Making a Web Scraping Application with Python, Celery, and Django» , подготовленная редакцией проекта.

Меню