- profanity-filter: A Python library for detecting and filtering profanity
- Features
- Caveats
- Usage
- Basics
- Deep analysis
- Multilingual analysis
- Using as a part of Spacy pipeline
- Customizations
- Console Executable
- RESTful web service
- Installation
- Basic installation
- Deep analysis
- Other language support
- Russian language support
- Фильтр нецензурной лексики за 5 минут
- Анализирование
- Сравнение
- Доработки
- Расстояние Левенштейна
- Небольшие проблемы
- Код фильтра
profanity-filter: A Python library for detecting and filtering profanity
profanity-filter is a universal library for detecting and filtering profanity. Support for English and Russian is included.
Features
- Full text or individual words censoring.
- Multilingual support, including profanity filtering in texts written in mixed languages.
- Deep analysis. The library detects not only the exact profane word matches but also derivative and distorted profane words using the Levenshtein automata, ignoring dictionary words, containing profane words as a part.
- Spacy component for using the library as a part of the pipeline.
- Explanation of decisions (attribute original_profane_word ).
- Partial word censoring.
- Extensibility support. New languages can be added by supplying dictionaries.
- RESTful web service.
Caveats
- Context-free. The library cannot detect using profane phrases consisted of decent words. Vice versa, the library cannot detect appropriate usage of a profane word.
Usage
Here are the basic examples of how to use the library. For more examples please see tests folder.
Basics
Deep analysis
Multilingual analysis
Using as a part of Spacy pipeline
Customizations
Console Executable
$ profanity_filter -h usage: profanity_filter TEXT -f PATH LANGUAGES OUTPUT_FILE filter console utility optional arguments: -h, --help show this message and -t TEXT, --text TEXT Test the given text profanity -f PATH, --file PATH Test the given file profanity -l LANGUAGES, --languages LANGUAGES Test profanity using specified languages separated -o OUTPUT_FILE, --output OUTPUT_FILE Write the censored output to a file --show Print the censored text
RESTful web service
$ uvicorn profanity_filter.web:app --reload INFO: Uvicorn running on http://127.0.0.1:8000 .
Go to the /docs for interactive documentation.
Installation
First two parts of installation instructions are designed for the users who want to filter English profanity. If you want to filter profanity in another language you still need to read it.
Basic installation
For minimal setup you need to install profanity-filter with is bundled with spacy and download spacy model for tokenization and lemmatization:
$ pip install profanity-filter $ $ python -m spacy download en
For more info about Spacy models read: https://spacy.io/usage/models/.
Deep analysis
To get deep analysis functionality install additional libraries and dictionary for your language.
Firstly, install hunspell and hunspell-devel packages with your system package manager.
$ sudo yum install hunspell
$ sudo zypper install hunspell hunspell-devel
$ pip install -U profanity-filter git+https://github.com/rominf/hunspell_serializable@49c00fabf94cacf9e6a23a0cd666aac10cb1d491#egg git+https://github.com/rominf/pyffs@6c805fbfd7771727138b169b32484b53c0b0fad1#egg $ profanity_filter/data $ wget https://cgit.freedesktop.org/libreoffice/dictionaries/plain/en/en_US.aff $ wget https://cgit.freedesktop.org/libreoffice/dictionaries/plain/en/en_US.dic $ mv en_US.aff en.aff $ mv en_US.dic en.dic
Other language support
Let’s take Russian for example on how to add new language support.
Russian language support
Firstly, we need to provide file profanity_filter/data/ru_badwords.txt which contains a newline separated list of profane words. For Russian it’s already present, so we skip file generation.
Next, we need to download the appropriate Spacy model. Unfortunately, Spacy model for Russian is not yet ready, so we will use an English model for tokenization. If you had not install Spacy model for English, it’s the right time to do so. As a consequence, even if you want to filter just Russian profanity, you need to specify English in ProfanityFilter constructor as shown in usage examples.
Next, we download dictionaries in Hunspell format for deep analysis from the site https://cgit.freedesktop.org/libreoffice/dictionaries/plain/:
> profanity_filter/data > wget https://cgit.freedesktop.org/libreoffice/dictionaries/plain/ru_RU/ru_RU.aff > wget https://cgit.freedesktop.org/libreoffice/dictionaries/plain/ru_RU/ru_RU.dic > mv ru_RU.aff ru.aff > mv ru_RU.dic ru.dic
Pymorphy2
For Russian and Ukrainian languages to achieve better results we suggest you to install pymorphy2 . To install pymorphy2 with Russian dictionary run:
$ pip install -U profanity-filter git+https://github.com/kmike/pymorphy2@ca1c13f6998ae2d835bdd5033c17197dcba84cf4#egg
You need to install polyglot package and it’s requirements for language detection. See https://polyglot.readthedocs.io/en/latest/Installation.html for more detailed instructions.
$ sudo yum install libicu-devel
$ sudo zypper install libicu-devel
$ pip install -U profanity-filter
$ pip install -U profanity-filterYou can always check will deep, morphological, and multilingual analyses work by inspecting the value of module variable AVAILABLE_ANALYSES . If you've followed all steps and installed support for all analyses you will see the following:
If something is not right, you can import dependencies yourself to see the import exceptions:
English profane word dictionary: https://github.com/areebbeigh/profanityfilter/ (author Areeb Beigh).
Фильтр нецензурной лексики за 5 минут
Для одного из моих проектов мне понадобилось сделать фильтр мата. Сегодня мы попытаемся сделать его за несколько минут. Ну что же, приступим.
Анализирование
Сперва я должен бы как-то разбивать текст на части, чтобы потом сравнивать его с нецензурной лексикой. Решение нашлось очень просто. Я составил список запрещенных слов и стал проходится циклом по введенному тексту, разбивая его на куски размером с каждое запрещенное слово.
#Фраза, которую будем проверять. phrase = input("Введите фразу для проверки: ") #Искомое слово. Пока что только одно. words = ["банан"] #Фрагменты, которые получатся после разбиения слова. fragments = [] #Проходимся по всем словам. for word in words: #Разбиваем слово на части, и проходимся по ним. for part in range(len(phrase)): #Вот сам наш фрагмент. fragment = phrase[part: part+len(word)] #Сохраняем его в наш список. fragments.append(fragment) #Выводим получившиеся фрагменты. print(fragments)
Запускаем наш файл и вводим фразу.
Введите фразу для проверки: Привет, я банан. ['Приве', 'ривет', 'ивет,', 'вет, ', 'ет, я', 'т, я ', ', я б', ' я ба', 'я бан', ' бана', 'банан', 'анан.', 'нан.', 'ан.', 'н.', '.']
Вот что у нас получилось. Смотрим на все фрагменты и видим среди них искомое слово «банан». Теперь нам осталось сравнить эти фрагменты с искомыми словами.
Сравнение
Для того, чтобы сравнивать фрагменты с искомыми словами, я решил просто использовать цикл.
#Проходимся по всем словам. for word in words: #Проходимся по всем фрагментам. for fragment in fragments: #Сравниваем фрагмент и искомое слово if word == fragment: #Если они равны, выводим надпись о их нахождении. print("Найдено", word)
Введите фразу для проверки: Привет, я банан. ['Приве', 'ривет', 'ивет,', 'вет, ', 'ет, я', 'т, я ', ', я б', ' я ба', 'я бан', ' бана', 'банан', 'анан.', 'нан.', 'ан.', 'н.', '.'] Найдено банан
Все, простейший фильтр нецензурной лексики готов!
Доработки
Простейший фильтр был готов, но я решил его чуточку дополнить.
Так как русский человек очень изобретателен, то он может поменять некоторые буквы на другой язык. Например, «бaнaн». Здесь место обычной «а», я поставил английскую. И теперь наш фильтр не будет распознавать это слово.
Введите фразу для проверки: Привет, я бaнaн. ['Приве', 'ривет', 'ивет,', 'вет, ', 'ет, я', 'т, я ', ', я б', ' я бa', 'я бaн', ' бaнa', 'бaнaн', 'aнaн.', 'нaн.', 'aн.', 'н.', '.']
Фильтр не вывел ничего, а значит не нашел слово. Поэтому мы должны создать дополнительный фильтр, который будет переводить буквы английского алфавита и похожие символы в русский текст.
В интернете я нашел вот такой список, который чуточку доработал.
Перед фильтрацией мы должны перевести весь текст в нижний регистр и убрать все пробелы, так как кто-нибудь может ввести искомые слова вот так: «БАНАН» или «б а н а н».
phrase = phrase.lower().replace(" ", "")
Теперь мы должны как-то сравнить этот список с нашим текстом. Для этого я создал вот такую функцию.
#Проходимся по нашему словарю. for key, value in d.items(): #Проходимся по каждой букве в значении словаря. То есть по вот этим спискам ['а', 'a', '@']. for letter in value: #Проходимся по каждой букве в нашей фразе. for phr in phrase: #Если буква совпадает с буквой в нашем списке. if letter == phr: #Заменяем эту букву на ключ словаря. phrase = phrase.replace(phr, key)
Что у нас получилось теперь.
Введите фразу для проверки: Привет, я б@н@н. ['Приве', 'ривет', 'ивет,', 'вет, ', 'ет, я', 'т, я ', ', я б', ' я ба', 'я бан', ' бана', 'банан', 'анан.', 'нан.', 'ан.', 'н.', '.'] Найдено банан
То, что у нас не находилось раньше теперь легко распознаётся.
Расстояние Левенштейна
Я понял, что если хоть чуточку изменить слово, то его уже невозможно найти.
Введите фразу для проверки: Я люблю бонан. ['Я люб', ' любл', 'люблю', 'юблю ', 'блю б', 'лю бо', 'ю бон', ' бона', 'бонан', 'онан.', 'нан.', 'ан.', 'н.', '.']
Наши опасения подтвердились, но решения этой проблемы есть расстояние Левенштейна.
В интернете я нашел функцию этого алгоритма для python. Вот как она выглядит.
def distance(a, b): "Calculates the Levenshtein distance between a and b." n, m = len(a), len(b) if n > m: # Make sure n
Теперь мы должны переписать функцию сравнения.
#Проходимся по всем словам. for word in words: #Проходимся по всем фрагментам. for fragment in fragments: #Если отличие этого фрагмента меньше или равно 25% этого слова, то считаем, что они равны. if distance(fragment, word)
Что у нас получилось теперь.
Введите фразу для проверки: Я люблю бонан. ['Я люб', ' любл', 'люблю', 'юблю ', 'блю б', 'лю бо', 'ю бон', ' бона', 'бонан', 'онан.', 'нан.', 'ан.', 'н.', '.'] Найдено банан
Небольшие проблемы
Наверное вы уже догадались, что если в списке для фильтрации будет больше одного слова, то он будет некорректно работать. Поэтому я переписал это всё в один цикл.
#Проходимся по всем словам. for word in words: #Разбиваем слово на части, и проходимся по ним. for part in range(len(phrase)): #Вот сам наш фрагмент. fragment = phrase[part: part+len(word)] #Если отличие этого фрагмента меньше или равно 25% этого слова, то считаем, что они равны. if distance(fragment, word)
Все, наш фильтр полностью готов.
Код фильтра
import string words = ["банан", "помидор"] print("Фильтруемые слова:", words) #Фраза, которую будем проверять. phrase = input("Введите фразу для проверки: ").lower().replace(" ", "") def distance(a, b): "Calculates the Levenshtein distance between a and b." n, m = len(a), len(b) if n > m: # Make sure n for key, value in d.items(): #Проходимся по каждой букве в значении словаря. То есть по вот этим спискам ['а', 'a', '@']. for letter in value: #Проходимся по каждой букве в нашей фразе. for phr in phrase: #Если буква совпадает с буквой в нашем списке. if letter == phr: #Заменяем эту букву на ключ словаря. phrase = phrase.replace(phr, key) #Проходимся по всем словам. for word in words: #Разбиваем слово на части, и проходимся по ним. for part in range(len(phrase)): #Вот сам наш фрагмент. fragment = phrase[part: part+len(word)] #Если отличие этого фрагмента меньше или равно 25% этого слова, то считаем, что они равны. if distance(fragment, word)