Оглавление
В этой части серии мы собираемся очистить содержимое веб-страницы, а затем обработать текст для отображения количества слов.
Обновления:
- 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-адреса.
- Часть первая : настройте локальную среду разработки, а затем разверните как промежуточную, так и производственную среду на Heroku.
- Часть вторая : настройте базу данных PostgreSQL вместе с SQLAlchemy и Alembic для обработки миграций.
- Часть третья: добавьте внутреннюю логику для очистки, а затем обработки количества слов с веб-страницы с помощью библиотек запросов, BeautifulSoup и Natural Language Toolkit (NLTK). ( текущий )
- Часть четвертая . Реализуйте очередь задач Redis для обработки текста.
- Часть пятая : Настройте Angular во внешнем интерфейсе для непрерывного опроса серверной части, чтобы узнать, обработан ли запрос.
- Часть шестая : Отправка на промежуточный сервер на Heroku - настройка Redis и подробное описание того, как запустить два процесса (веб и рабочий) на одном Dyno.
- Часть седьмая : Обновите интерфейс, чтобы сделать его более удобным для пользователя.
- Часть восьмая . Создайте настраиваемую директиву 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
- Здесь мы импортировали
requests
библиотеку, а такжеrequest
объект из Flask. Первый используется для отправки внешних HTTP-запросов GET для получения определенного URL-адреса, предоставленного пользователем, а второй используется для обработки запросов GET и POST в приложении Flask. - Затем мы добавили переменные для фиксации ошибок и результатов, которые передаются в шаблон.
- В самом представлении мы проверили, является ли запрос GET или POST-
- Если POST: мы взяли значение (URL) из формы и присвоили его
url
переменной. Затем мы добавили исключение для обработки любых ошибок и, при необходимости, добавили вerrors
список общее сообщение об ошибке . Наконец, мы отрисовали шаблон, включаяerrors
список иresults
словарь. - Если GET: мы просто визуализировали шаблон.
- Если POST: мы взяли значение (URL) из формы и присвоили его
Давайте проверим это:
$ 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' ]
Что происходит?
Обработка текста
- В нашем маршруте индекса мы использовали beautifulsoup для очистки текста, удалив HTML-теги, которые мы получили обратно из URL-адреса, а также nltk to-
- Обозначить необработанный текст (разбить текст на отдельные слова) и
- Превратите токены в текстовый объект nltk .
- Для правильной работы nltk вам необходимо загрузить правильные токенизаторы. Сначала создайте новый каталог
mkdir nltk_data
- затем запустите -python -m nltk.downloader
.Когда появится окно установки, обновите «Каталог загрузок», указав любой_абсолютный_путь_к_вашему_приложению / nltk_data / .Затем щелкните вкладку «Модели» и выберите «пункт» в столбце «Идентификатор». Щелкните "Загрузить". Дополнительную информацию можно найти в официальной документации .
Удаление знаков препинания, подсчет сырых слов
- Поскольку мы не хотим, чтобы знаки препинания учитывались в окончательных результатах, мы создали регулярное выражение, которое соответствует чему-либо, не входящему в стандартный алфавит.
- Затем, используя понимание списка , мы создали список слов без знаков препинания и цифр.
- Наконец, мы подсчитали, сколько раз каждое слово появлялось в списке, используя Counter .
Стоп-слова
Наш текущий вывод содержит много слов, которые мы, вероятно, не хотим считать, например, «я», «я», «тот» и так далее. Это так называемые стоп-слова.
- Со
stops
списком мы снова использовали понимание списка, чтобы создать окончательный список слов, которые не включают эти стоп-слова. - Затем мы создали словарь со словами (в качестве ключей) и связанными с ними счетчиками (в качестве значений).
- И, наконец, мы использовали метод сортировки, чтобы получить отсортированное представление нашего словаря. Теперь мы можем использовать отсортированные данные для отображения слов с наибольшим количеством в верхней части списка, что означает, что нам не придется выполнять такую сортировку в нашем шаблоне Jinja
Показать результаты
Давайте обновим 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
Протестируйте это на песочнице. Прокомментируйте, если у вас есть вопросы. Увидимся в следующий раз!