Evgenij LegotskojOct. 24, 2018, 1:38 a.m.

Django - Tutorial 038. Use BeatifulSoup 4 to clean up the published content from unwanted html tags

When developing a web site that adds the ability to write comments or publish articles that allow html layout, the mechanism for clearing unwanted html tags, in particular script and style tags, is important, since malicious scripts on a quality resource definitely should not be present. It will also be good to be able to clean up the style of the text, especially if the resource implies a uniform style. The discordance of screaming fonts is not needed by anyone, and adds problems with the layout.

To implement this mechanism, I use the Python package Beautiful Soup 4 and finally wrote one class, which essentially does everything I need. Removes unnecessary tags, adds necessary classes to tags, saves classes in tags, if you need to leave them during stripping, this is important for classes that are added at the stage of writing a comment, for example, when inserting a YouTube video or adding program code when the user selects which programming language should be represented in the program code block.

Content

Install BeautifulSoup 4

pip install beautifulsoup4

Program code

This example is presented in the form of a class, so that using the inheritance and redefinition of the cleaning method to form the necessary logic, and the program code of the module for cleaning html does not turn into a collection of heterogeneous, inconsistent functions.

# -*- coding: utf-8 -*-

import re

from bs4 import BeautifulSoup
from YourDjangoApp import settings


class ESoup:
    # initialization of the text clear object,
    # can use immediately receive additional tags for deletion so as not to override the class
    def __init__(self, text, tags_for_extracting=()):
        self.soup = BeautifulSoup(text, "lxml") if text else None
        self.tags_for_extracting = ('script', 'style',) + tags_for_extracting

    # Method to remove specified tags
    def __extract_tags(self, soup, tags=()):
        for tag in tags:
            for current_tag in soup.find_all(tag):
                current_tag.extract()
        return soup

    # Method for deleting attributes of all tags
    def __remove_attrs(self, soup):
        for tag in soup.find_all(True):
            tag.attrs = {}
        return soup

    # Method for deleting attributes of all tags except those listed in whitelist_tags
    def __remove_all_attrs_except(self, soup, whitelist_tags=()):
        for tag in soup.find_all(True):
            if tag.name not in whitelist_tags:
                tag.attrs = {}
        return soup

    # Remove all attributes from all tags except those listed in whitelist_tags
    # If the tag is in whitelist_tags, it will only delete those attributes that are not listed in whitelist_attrs
    # Also, this method saves to the tag the classes listed in whitelist_classes
    # regardless of whether it was listed in whitelist_tags or in whitelist_attrs.
    # I just have classes with a special position for tags
    def __remove_all_attrs_except_saving(self, soup, whitelist_tags=(), whitelist_attrs=(), whitelist_classes=()):
        for tag in soup.find_all(True):
            saved_classes = []
            if tag.has_attr('class'):
                classes = tag['class']
                for class_str in whitelist_classes:
                    if class_str in classes:
                        saved_classes.append(class_str)

            if tag.name not in whitelist_tags:
                tag.attrs = {}
            else:
                attrs = dict(tag.attrs)
                for attr in attrs:
                    if attr not in whitelist_attrs:
                        del tag.attrs[attr]

            if len(saved_classes) > 0:
                tag['class'] = ' '.join(saved_classes)

        return soup

    # Adds a nofollow relationship to the tag, checking the url of the src or img attribute
    # If the link leads to the internal pages of your site, then nofollow will not be added.
    def __add_rel_attr(self, soup, tag, attr):
        for tag in soup.find_all(tag):
            attr_content = tag.get(attr)
            if not attr_content.startswith(settings.SITE_URL) and not attr_content.startswith('/'):
                tag['rel'] = ['nofollow']
        return soup

    # Adds new classes to the tag, preserving those classes that already existed.
    def __add_class_attr(self, soup, tag, classes=()):
        for tag in soup.find_all(tag):
            saved_classes = []
            if tag.has_attr('class'):
                saved_classes.append(tag['class'])
            saved_classes.extend(list(classes))
            tag['class'] = ' '.join(saved_classes)
        return soup

    # The method that performs the cleaning, I propose to override it, if you need to change the logic for cleaning up the html code
    def clean(self):
        # if BeautifulSoup was created during initialization, then you can perform the cleanup
        if self.soup:
            # Remove all tags that we don’t like.
            soup = self.__extract_tags(soup=self.soup, tags=self.tags_for_extracting)
            # Remove all attributes from all tags except
            # src and href for tags img and a,
            # and also leave prettyprint class
            soup = self.__remove_all_attrs_except_saving(
                soup=soup,
                whitelist_tags=('img', 'a'),
                whitelist_attrs=('src', 'href',),
                whitelist_classes=('prettyprint',)
            )
            # add rel="nofollow" for external links
            soup = self.__add_rel_attr(soup=soup, tag='a', attr='href')
            soup = self.__add_rel_attr(soup=soup, tag='img', attr='src')
            # improve the appearance of images using the img-fluid class
            soup = self.__add_class_attr(soup=soup, tag='img', classes=('img-fluid',))
            # add the linenums class for pre tags
            soup = self.__add_class_attr(soup=soup, tag='pre', classes=('linenums',))
            # returning useful content, the fact is that BeautifulSoup 4 adds more html and body tags,
            # which I, for example, do not need
            return re.sub('<body>|</body>', '', soup.body.prettify())
        return ''

    # Static class method, something like Shortcut
    @staticmethod
    def clean_text(text, tags_for_extracting=()):
        soup = ESoup(text=text, tags_for_extracting=tags_for_extracting)
        return soup.clean()

Using

So

soup = ESoup(text=text, tags_for_extracting=tags_for_extracting)
soup.clean()

Or so

ESoup.clean_text(text=text, tags_for_extracting=tags_for_extracting)
We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.
Support the author Donate

я думаю, что последний


    @staticmethod
    def clean_text(text, tags_for_extracting=()):
        soup = ESoup(text=text, tags_for_extracting=tags_for_extracting)
        return soup.clean()

есть смысл заменить на classmethod (при наследовании, старый вариант сломается, а с классом - нет):

    @classmethod
    def clean_text(cls, text, tags_for_extracting=()):
        soup = cls(text=text, tags_for_extracting=tags_for_extracting)
        return soup.clean()

Спасибо за информацию, не думал об этом.

Надо будет проверить на кошках.

Comments

Only authorized users can post comments.
Please, Log in or Sign up
Card image cap
Pulsum Via

Project for travelers from EVILEG.

Go
Fornex

Let me recommend you a great European Fornex hosting.

Fornex has proven itself to be a stable host over the years.

For Django projects I recommend VPS hosting

Following the link you will receive a 5% discount on shared hosting services, dedicated servers, VPS and VPN

View Hosting
Share on social networks
Donate

The EVILEG project has switched to a non-commercial basis and will develop solely on the enthusiasm of the site creator, the enthusiasm of users, donations and the hosting referral system

Thank you for your support

Available ways to support the project

PayPal

PatreonYandex.MoneyMore
DP

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:10points,
  • Rating points-10
VS

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:0points,
  • Rating points-10
VS

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:50points,
  • Rating points-4
Last comments
Ds

Android and QML - Adding Splash Screen

Интересен формат иконки, если это png, то как решается проблема scalability? не растягивается ли лого на китайфонах с 1280х2500? У меня просто сплеш скрин с градиентом и логотипом, и вот несколь…
p

Qt/C++ - Lesson 023. Moving QGraphicsItem on QGraphicsScene with mouse help

FIGURE Abdominopelvic regions. Zjuaqd https://newfasttadalafil.com/ - Cialis Cialis Recommendations for preparing children and adolescents for invasive cardiac procedures a statement…
KG

How to use nested forms in Django

Спасибо за полезную статью. Подскажите пожалуйста, что делать если нужно реализовать большее количество вложенных форм? Например если на модель Address ссылается fk другой модели, на котору…

Qt/C++ - Lesson 051. QMediaPlayer – simple audio player

Не думаю, QMediaPlayer в один поток проигрывает. Если вам нужно одновременное воспроизведение нескольких аудиоисточников, то вам нужна Bass audio library , насколько знаю, её обычно и…
AG

Qt/C++ - Lesson 051. QMediaPlayer – simple audio player

есть такая вообще возможность ?
Now discuss on the forum
AB

Sorting the added QML elements in the ListModel

I am writing an alarm clock in QML, I am required to sort the alarms in ascending order (depending on the date or time (if there are several alarms on the same day). I've done the sorting …

Изменение поведения QGroupBox при клике на его чекбокс

Я вынес виджеты вынес за пределы QGroupBox в итоге.

QSqlRelatipnalTabelModel Qt 4.8.1 как получить id внешней связи?

Есть еще принципиально другой вариант решить раз и навсегда вопрос с полей id внешней связи. Это форкнуть Qt 4.8.1 QSqlTableModel, то есть создать свою ветку развития. Например создадим кл…

Добавление AndroidManifest.xml в cmake

Добрый день. Как добавить AndroidManifest.xml в cmake? Это не работвет set(ANDROID_PACKAGE_SOURCE_DIR ${PROJECT_SOURCE_DIR}/android CACHE INTERNAL "")set(ANDROID_BUID_DIR ${CMAKE_C…
s

Событие wheelEvent для виджета QLineEdit

вот что получилось: gui.py from PyQt5 import QtCore, QtGui, QtWidgets class LineEdit(QtWidgets.QLineEdit): def wheelEvent(self, event): #print("_") delta = 1 if e…
About
Services
© EVILEG 2015-2022
Recommend hosting TIMEWEB