Превод статьи Flask by Example - Обработка текста с помощью запросов, BeautifulSoup и NLTK.

Оглавление

 

В этой части серии мы собираемся очистить содержимое веб-страницы, а затем обработать текст для отображения количества слов.

Обновления:

  • 10.02.2020: Обновлен до версии Python 3.8.1, а также до последних версий запросов, BeautifulSoup и nltk.
  • 22.03.2016: Обновлен до версии Python 3.5.1, а также до последних версий запросов, BeautifulSoup и nltk.
  • 22.02.2015: Добавлена ​​поддержка Python 3.

Помните: вот что мы создаем - приложение Flask, которое вычисляет пары частотность слова на основе текста из заданного URL-адреса.

  1. Часть первая : настройте локальную среду разработки, а затем разверните как промежуточную, так и производственную среду на Heroku.
  2. Часть вторая : настройте базу данных PostgreSQL вместе с SQLAlchemy и Alembic для обработки миграций.
  3. Часть третья: добавьте внутреннюю логику для очистки, а затем обработки количества слов с веб-страницы с помощью библиотек запросов, BeautifulSoup и Natural Language Toolkit (NLTK). ( текущий )
  4. Часть четвертая . Реализуйте очередь задач Redis для обработки текста.
  5. Часть пятая : Настройте Angular во внешнем интерфейсе для непрерывного опроса серверной части, чтобы узнать, обработан ли запрос.
  6. Часть шестая : Отправка на промежуточный сервер на Heroku - настройка Redis и подробное описание того, как запустить два процесса (веб и рабочий) на одном Dyno.
  7. Часть седьмая : Обновите интерфейс, чтобы сделать его более удобным для пользователя.
  8. Часть восьмая . Создайте настраиваемую директиву Angular для отображения диаграммы частотного распределения с использованием JavaScript и D3.

Требования к установке

Используемые инструменты:

  • запросы ( 2.22.0 ) - библиотека для отправки HTTP-запросов
  • BeautifulSoup ( 4.8.2 ) - инструмент, используемый для парсинга и анализа документов из Интернета.
  • Natural Language Toolkit ( 3.4.5 ) - библиотека обработки естественного языка

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

$ cd flask-by-example
$ python -m pip install requests==2.22.0 beautifulsoup4==4.8.2 nltk==3.4.5
$ python -m pip freeze > requirements.txt

Рефакторинг индексного маршрута

Для начала давайте избавимся от части «hello world» в маршруте индекса в нашем файле app.py и настроим маршрут для рендеринга формы для приема URL-адресов. Сначала добавьте папку шаблонов для хранения наших шаблонов и добавьте в нее файл index.html .

$ mkdir templates
$ touch templates/index.html

Настройте очень простую HTML-страницу:

<!DOCTYPE html>
<html>
  <head>
    <title>Wordcount</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" media="screen">
    <style>
      .container {
        max-width: 1000px;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>Wordcount 3000</h1>
      <form role="form" method='POST' action='/'>
        <div class="form-group">
          <input type="text" name="url" class="form-control" id="url-box" placeholder="Enter URL..." style="max-width: 300px;" autofocus required>
        </div>
        <button type="submit" class="btn btn-default">Submit</button>
      </form>
      <br>
      {% for error in errors %}
        <h4>{{ error }}</h4>
      {% endfor %}
    </div>
    <script src="//code.jquery.com/jquery-2.2.1.min.js"></script>
    <script src="//netdna.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
  </body>
</html>

Мы использовали Bootstrap, чтобы добавить немного стиля, чтобы наша страница не была полностью отвратительной. Затем мы добавили форму с полем ввода текста, в которое пользователи могут вводить URL. Кроме того, мы использовали цикл Jinja for для перебора списка ошибок, отображая каждую из них.

 

Обновите app.py для обслуживания шаблона:

import os
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy


app = Flask(__name__)
app.config.from_object(os.environ['APP_SETTINGS'])
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

from models import Result


@app.route('/', methods=['GET', 'POST'])
def index():
    return render_template('index.html')


if __name__ == '__main__':
    app.run()

Почему оба метода HTTP methods=['GET', 'POST']? Что ж, в конечном итоге мы будем использовать один и тот же маршрут для запросов GET и POST - для обслуживания страницы index.html и обработки отправки формы, соответственно.

 

Запустите приложение, чтобы проверить его:

$ python manage.py runserver

Запросы

Теперь давайте воспользуемся библиотекой запросов, чтобы получить HTML-страницу из отправленного URL-адреса.

Измените свой индексный маршрут следующим образом:

@app.route('/', methods=['GET', 'POST'])
def index():
    errors = []
    results = {}
    if request.method == "POST":
        # get url that the user has entered
        try:
            url = request.form['url']
            r = requests.get(url)
            print(r.text)
        except:
            errors.append(
                "Unable to get URL. Please make sure it's valid and try again."
            )
    return render_template('index.html', errors=errors, results=results)

Не забудьте также обновить импорт:

import os
import requests
from flask import Flask, render_template, request
from flask_sqlalchemy import SQLAlchemy
  1. Здесь мы импортировали requestsбиблиотеку, а также requestобъект из Flask. Первый используется для отправки внешних HTTP-запросов GET для получения определенного URL-адреса, предоставленного пользователем, а второй используется для обработки запросов GET и POST в приложении Flask.
  2. Затем мы добавили переменные для фиксации ошибок и результатов, которые передаются в шаблон.
  3. В самом представлении мы проверили, является ли запрос GET или POST-
    • Если POST: мы взяли значение (URL) из формы и присвоили его urlпеременной. Затем мы добавили исключение для обработки любых ошибок и, при необходимости, добавили в errorsсписок общее сообщение об ошибке . Наконец, мы отрисовали шаблон, включая errorsсписок и resultsсловарь.
    • Если GET: мы просто визуализировали шаблон.

Давайте проверим это:

$ python manage.py runserver

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

 

Обработка множества

Имея в руках HTML, давайте посчитаем частоту слов, которые появляются на странице, и покажем их конечному пользователю. Обновите свой код в app.py до следующего, и мы рассмотрим, что происходит:

import os
import requests
import operator
import re
import nltk
from flask import Flask, render_template, request
from flask_sqlalchemy import SQLAlchemy
from stop_words import stops
from collections import Counter
from bs4 import BeautifulSoup


app = Flask(__name__)
app.config.from_object(os.environ['APP_SETTINGS'])
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
db = SQLAlchemy(app)

from models import Result


@app.route('/', methods=['GET', 'POST'])
def index():
    errors = []
    results = {}
    if request.method == "POST":
        # get url that the person has entered
        try:
            url = request.form['url']
            r = requests.get(url)
        except:
            errors.append(
                "Unable to get URL. Please make sure it's valid and try again."
            )
            return render_template('index.html', errors=errors)
        if r:
            # text processing
            raw = BeautifulSoup(r.text, 'html.parser').get_text()
            nltk.data.path.append('./nltk_data/')  # set the path
            tokens = nltk.word_tokenize(raw)
            text = nltk.Text(tokens)
            # remove punctuation, count raw words
            nonPunct = re.compile('.*[A-Za-z].*')
            raw_words = [w for w in text if nonPunct.match(w)]
            raw_word_count = Counter(raw_words)
            # stop words
            no_stop_words = [w for w in raw_words if w.lower() not in stops]
            no_stop_words_count = Counter(no_stop_words)
            # save the results
            results = sorted(
                no_stop_words_count.items(),
                key=operator.itemgetter(1),
                reverse=True
            )
            try:
                result = Result(
                    url=url,
                    result_all=raw_word_count,
                    result_no_stop_words=no_stop_words_count
                )
                db.session.add(result)
                db.session.commit()
            except:
                errors.append("Unable to add item to database.")
    return render_template('index.html', errors=errors, results=results)


if __name__ == '__main__':
    app.run()

Создайте новый файл с именем stop_words.py и добавьте следующий список:

stops = [
    'i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you',
    'your', 'yours', 'yourself', 'yourselves', 'he', 'him', 'his',
    'himself', 'she', 'her', 'hers', 'herself', 'it', 'its', 'itself',
    'they', 'them', 'their', 'theirs', 'themselves', 'what', 'which',
    'who', 'whom', 'this', 'that', 'these', 'those', 'am', 'is', 'are',
    'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'having',
    'do', 'does', 'did', 'doing', 'a', 'an', 'the', 'and', 'but', 'if',
    'or', 'because', 'as', 'until', 'while', 'of', 'at', 'by', 'for',
    'with', 'about', 'against', 'between', 'into', 'through', 'during',
    'before', 'after', 'above', 'below', 'to', 'from', 'up', 'down', 'in',
    'out', 'on', 'off', 'over', 'under', 'again', 'further', 'then',
    'once', 'here', 'there', 'when', 'where', 'why', 'how', 'all', 'any',
    'both', 'each', 'few', 'more', 'most', 'other', 'some', 'such', 'no',
    'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too', 'very', 's',
    't', 'can', 'will', 'just', 'don', 'should', 'now', 'id', 'var',
    'function', 'js', 'd', 'script', '\'script', 'fjs', 'document', 'r',
    'b', 'g', 'e', '\'s', 'c', 'f', 'h', 'l', 'k'
]

Что происходит?

Обработка текста

  1. В нашем маршруте индекса мы использовали beautifulsoup для очистки текста, удалив HTML-теги, которые мы получили обратно из URL-адреса, а также nltk to-
    • Обозначить необработанный текст (разбить текст на отдельные слова) и
    • Превратите токены в текстовый объект nltk .
  2. Для правильной работы nltk вам необходимо загрузить правильные токенизаторы. Сначала создайте новый каталог mkdir nltk_data- затем запустите - python -m nltk.downloader.Когда появится окно установки, обновите «Каталог загрузок», указав любой_абсолютный_путь_к_вашему_приложению / nltk_data / .Затем щелкните вкладку «Модели» и выберите «пункт» в столбце «Идентификатор». Щелкните "Загрузить". Дополнительную информацию можно найти в официальной документации .

Удаление знаков препинания, подсчет сырых слов

  1. Поскольку мы не хотим, чтобы знаки препинания учитывались в окончательных результатах, мы создали регулярное выражение, которое соответствует чему-либо, не входящему в стандартный алфавит.
  2. Затем, используя понимание списка , мы создали список слов без знаков препинания и цифр.
  3. Наконец, мы подсчитали, сколько раз каждое слово появлялось в списке, используя Counter .

Стоп-слова

Наш текущий вывод содержит много слов, которые мы, вероятно, не хотим считать, например, «я», «я», «тот» и так далее. Это так называемые стоп-слова.

  1. Со stopsсписком мы снова использовали понимание списка, чтобы создать окончательный список слов, которые не включают эти стоп-слова.
  2. Затем мы создали словарь со словами (в качестве ключей) и связанными с ними счетчиками (в качестве значений).
  3. И, наконец, мы использовали метод сортировки, чтобы получить отсортированное представление нашего словаря. Теперь мы можем использовать отсортированные данные для отображения слов с наибольшим количеством в верхней части списка, что означает, что нам не придется выполнять такую ​​сортировку в нашем шаблоне Jinja
Сохраните результатыНаконец, мы использовали try / except для сохранения результатов поиска и последующих подсчетов в базе данных.

Показать результаты

Давайте обновим index.html , чтобы отобразить результаты:

<!DOCTYPE html>
<html>
  <head>
    <title>Wordcount</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet" media="screen">
    <style>
      .container {
        max-width: 1000px;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="row">
        <div class="col-sm-5 col-sm-offset-1">
          <h1>Wordcount 3000</h1>
          <br>
          <form role="form" method="POST" action="/">
            <div class="form-group">
              <input type="text" name="url" class="form-control" id="url-box" placeholder="Enter URL..." style="max-width: 300px;">
            </div>
            <button type="submit" class="btn btn-default">Submit</button>
          </form>
          <br>
          {% for error in errors %}
            <h4>{{ error }}</h4>
          {% endfor %}
          <br>
        </div>
        <div class="col-sm-5 col-sm-offset-1">
          {% if results %}
            <h2>Frequencies</h2>
            <br>
            <div id="results">
              <table class="table table-striped" style="max-width: 300px;">
                <thead>
                  <tr>
                    <th>Word</th>
                    <th>Count</th>
                  </tr>
                </thead>
                {% for result in results%}
                  <tr>
                    <td>{{ result[0] }}</td>
                    <td>{{ result[1] }}</td>
                  </tr>
                {% endfor %}
              </table>
            </div>
          {% endif %}
        </div>
      </div>
    </div>
    <br><br>
    <script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
    <script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
  </body>
</html>

Здесь мы добавили ifоператор, чтобы увидеть, есть ли resultsчто-нибудь в нашем словаре, а затем добавили forцикл для перебораresults и отображения их в таблице. Запустите приложение, и вы сможете ввести URL-адрес и вернуть количество слов на странице.

$ python manage.py runserver

Что, если бы мы хотели отображать только первые десять ключевых слов?

results = sorted(
    no_stop_words_count.items(),
    key=operator.itemgetter(1),
    reverse=True
)[:10]

Резюме

Хорошо, отлично. Имея URL-адрес, мы можем подсчитать количество слов на странице. Если вы используете сайт без большого количества слов, например https://realpython.com , обработка должна происходить довольно быстро. Но что будет, если на сайте много слов? Например, попробуйте https://gutenberg.ca . Вы заметите, что это занимает больше времени.

Если у вас есть несколько пользователей, которые одновременно заходят на ваш сайт, чтобы узнать количество слов, и некоторые из них пытаются подсчитать более крупные страницы, это может стать проблемой. Или, возможно, вы решите изменить функциональность, чтобы, когда пользователь вводит URL-адрес, мы рекурсивно очищали весь веб-сайт и вычисляли частоту слов на основе каждой отдельной страницы. При достаточном трафике это значительно замедлит работу сайта.

Какое решение?

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

На данный момент зафиксируйте свой код, но перед тем, как перейти к Heroku, вы должны удалить все языковые токенизаторы, кроме английского, вместе с zip-файлом. Это значительно уменьшит размер коммита. Однако имейте в виду, что если вы обрабатываете неанглоязычный сайт, он будет обрабатывать только английские слова.

└── nltk_data
    └── tokenizers
        └── punkt
            ├── PY3
            │   └── english.pickle
            └── english.pickle
Переместите его в промежуточную среду только потому, что эта новая функция обработки текста завершена только наполовину:
$ git push stage master

Протестируйте это на песочнице. Прокомментируйте, если у вас есть вопросы. Увидимся в следующий раз!

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

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