Usuń HTML z ciągów znaków w Pythonie


269
from mechanize import Browser
br = Browser()
br.open('http://somewebpage')
html = br.response().readlines()
for line in html:
  print line

Podczas drukowania linii w pliku HTML próbuję znaleźć sposób, aby wyświetlić tylko zawartość każdego elementu HTML, a nie samo formatowanie. Jeśli znajdzie '<a href="whatever.com">some text</a>', wydrukuje tylko „trochę tekstu”, '<b>hello</b>'drukuje „cześć” itd. Jak by to zrobić?


16
Ważnym zagadnieniem jest sposób obsługi encji HTML (np &amp;.). Możesz albo 1) usunąć je wraz ze znacznikami (często niepożądane i niepotrzebne, ponieważ są one równoważne zwykłemu tekstowi), 2) pozostawić je bez zmian (odpowiednie rozwiązanie, jeśli rozebrany tekst wraca z powrotem do kontekstu HTML) lub 3 ) zdekoduj je do zwykłego tekstu (jeśli rozebrany tekst trafia do bazy danych lub do innego kontekstu innego niż HTML, lub jeśli Twoja platforma internetowa automatycznie wykonuje dla Ciebie znaki ucieczki HTML).
Søren Løvborg

2
dla @ SørenLøvborg punkt 2): stackoverflow.com/questions/753052/...
Robert

2
Górny odpowiedź tutaj, który był używany przez projekt Django do marca 2014 roku, został uznany jako niezabezpieczone przed cross-site scripting - zobacz ten link przykład sprawia, że dzięki. Polecam użycie Bleach.clean (), striptagów Markupsafe lub OSTATNICH tagów strip_tag.
rescdsk

Odpowiedzi:


419

Zawsze używałem tej funkcji do usuwania tagów HTML, ponieważ wymaga ona tylko stdlib w Pythonie:

W przypadku Python 3:

from io import StringIO
from html.parser import HTMLParser

class MLStripper(HTMLParser):
    def __init__(self):
        super().__init__()
        self.reset()
        self.strict = False
        self.convert_charrefs= True
        self.text = StringIO()
    def handle_data(self, d):
        self.text.write(d)
    def get_data(self):
        return self.text.getvalue()

def strip_tags(html):
    s = MLStripper()
    s.feed(html)
    return s.get_data()

W przypadku Python 2:

from HTMLParser import HTMLParser
from StringIO import StringIO

class MLStripper(HTMLParser):
    def __init__(self):
        self.reset()
        self.text = StringIO()
    def handle_data(self, d):
        self.text.write(d)
    def get_data(self):
        return self.text.getvalue()

def strip_tags(html):
    s = MLStripper()
    s.feed(html)
    return s.get_data()

3
Dwa lata później, w obliczu tego samego problemu, jest to o wiele bardziej eleganckie rozwiązanie. Jedyną zmianą, którą wprowadziłem, było zwrócenie self.fed jako listy, a nie dołączenie do niej, aby móc przejrzeć zawartość elementu.
skierowanie

47
Pamiętaj, że powoduje to usunięcie elementów HTML (np. &amp;) Oraz znaczników.
Søren Løvborg

30
@surya Jestem pewien, że to widziałeś
tkone

8
Dzięki za świetną odpowiedź. Jedną z rzeczy, na którą należy zwrócić uwagę, jeśli używacie nowszych wersji Pythona (3.2+), jest konieczność wywołania funkcji klasy nadrzędnej __init__. Zobacz tutaj: stackoverflow.com/questions/11061058/... .
pseudoramble

10
Aby zachować jednostki HTML (przekonwertowane na Unicode), dodałem dwie linie: parser = HTMLParser()i html = parser.unescape(html)na początku funkcji strip_tags.
James Doepp - pihentagyu

156

Nie myślałem zbyt wiele o przypadkach, w których będzie brakować, ale możesz zrobić prosty regex:

re.sub('<[^<]+?>', '', text)

Dla tych, którzy nie rozumieją wyrażenia regularnego, szuka łańcucha <...>, w którym wewnętrzna zawartość składa się z co najmniej jednego ( +) znaku, który nie jest <. Te ?środki, które będą pasować najmniejszy łańcuch można go znaleźć. Na przykład <p>Hello</p>, będzie pasował <'p>i </p>osobno z ?. Bez niego dopasuje cały ciąg <..Hello..>.

Jeśli nietag <pojawia się w html (np. 2 < 3), I tak powinien zostać zapisany jako sekwencja ucieczki, &...więc ^<może być niepotrzebny.


10
Jest to prawie dokładnie tak, jak robi to strip_tags Django .
Bluu

10
Zauważ, że pozostawia to elementy HTML (np. &amp;) Niezmienione w danych wyjściowych.
Søren Løvborg

35
Nadal można oszukać tę metodę za pomocą czegoś takiego: <skrypt <skrypt>> alert („Cześć!”) <</script> / script>

19
NIE Rób tego w ten sposób! Jak mówi Julio Garcia, NIE JEST BEZPIECZNY!
rescdsk

18
Ludzie, nie mylcie usuwania HTML i odkażania HTML. Tak, w przypadku zepsutych lub złośliwych danych wejściowych odpowiedź może generować dane wyjściowe ze znacznikami HTML. Nadal jest to całkowicie poprawne podejście do usuwania tagów HTML. Jednak usuwanie tagów HTML nie jest prawidłowym zamiennikiem prawidłowego odkażania HTML. Zasada nie jest trudna: za każdym razem , gdy wstawiasz ciąg tekstowy do wyjścia HTML, zawsze powinieneś go unikać (używając cgi.escape(s, True)), nawet jeśli wiesz, że nie zawiera HTML (np. Ponieważ usunąłeś treść HTML) . Jednak nie o to pytał OP.
Søren Løvborg,

76

Możesz użyć get_text()funkcji BeautifulSoup .

from bs4 import BeautifulSoup

html_str = '''
<td><a href="http://www.fakewebsite.com">Please can you strip me?</a>
<br/><a href="http://www.fakewebsite.com">I am waiting....</a>
</td>
'''
soup = BeautifulSoup(html_str)

print(soup.get_text()) 
#or via attribute of Soup Object: print(soup.text)

Wskazane jest jawne określenie analizatora składni , na przykład jako BeautifulSoup(html_str, features="html.parser"), aby dane wyjściowe były odtwarzalne.


32

Krótka wersja!

import re, cgi
tag_re = re.compile(r'(<!--.*?-->|<[^>]*>)')

# Remove well-formed tags, fixing mistakes by legitimate users
no_tags = tag_re.sub('', user_input)

# Clean up anything else by escaping
ready_for_web = cgi.escape(no_tags)

Źródło Regex: MarkupSafe . Ich wersja obsługuje również encje HTML, podczas gdy ta szybka nie.

Dlaczego nie mogę po prostu usunąć tagów i zostawić go?

To jedna rzecz, aby powstrzymać ludzi przed <i>italicizing</i>rzeczami, nie pozostawiając ipływających. Ale to kolejna rzecz, aby wziąć arbitralny wkład i uczynić go całkowicie nieszkodliwym. Większość technik na tej stronie pozostawia niezmienione niezamknięte komentarze ( <!--) i nawiasy kątowe, które nie są częścią tagów ( blah <<<><blah). Wersja HTMLParser może nawet pozostawić pełne tagi, jeśli znajdują się w niezamkniętym komentarzu.

Co jeśli twój szablon jest {{ firstname }} {{ lastname }}? firstname = '<a'i lastname = 'href="http://evil.com/">'zostaną przepuszczone przez wszystkie narzędzia do usuwania tagów na tej stronie (z wyjątkiem @Medeiros!), ponieważ same nie są kompletnymi tagami. Usunięcie zwykłych tagów HTML nie wystarczy.

Django strip_tags, ulepszona (patrz następny nagłówek) wersja najważniejszej odpowiedzi na to pytanie, daje następujące ostrzeżenie:

Absolutnie NIE ma gwarancji, że powstały ciąg będzie bezpieczny w HTML. Dlatego NIGDY nie zaznaczaj bezpiecznie wyniku strip_tagspołączenia bez ucieczki przed nim, na przykład za pomocą escape().

Postępuj zgodnie z ich radami!

Aby usunąć tagi za pomocą HTMLParser, musisz uruchomić go wiele razy.

Łatwo jest ominąć najwyższą odpowiedź na to pytanie.

Spójrz na ten ciąg ( źródło i dyskusja ):

<img<!-- --> src=x onerror=alert(1);//><!-- -->

Gdy HTMLParser widzi go po raz pierwszy, nie może stwierdzić, że <img...>jest to tag. Wygląda na zepsuty, więc HTMLParser się go nie pozbywa. To tylko usuwa <!-- comments -->, pozostawiając cię z

<img src=x onerror=alert(1);//>

Problem ten został ujawniony projektowi Django w marcu 2014 roku. Ich stary strip_tagsbył zasadniczo taki sam jak najlepsza odpowiedź na to pytanie. Ich nowa wersja zasadniczo uruchamia go w pętli, dopóki ponowne uruchomienie nie zmieni łańcucha:

# _strip_once runs HTMLParser once, pulling out just the text of all the nodes.

def strip_tags(value):
    """Returns the given HTML with all tags stripped."""
    # Note: in typical case this loop executes _strip_once once. Loop condition
    # is redundant, but helps to reduce number of executions of _strip_once.
    while '<' in value and '>' in value:
        new_value = _strip_once(value)
        if len(new_value) >= len(value):
            # _strip_once was not able to detect more tags
            break
        value = new_value
    return value

Oczywiście nic z tego nie stanowi problemu, jeśli zawsze unikniesz wyniku strip_tags().

Aktualizacja 19 marca 2015 r . : Wystąpił błąd w wersjach Django przed 1.4.20, 1.6.11, 1.7.7 i 1.8c1. Te wersje mogą wejść w nieskończoną pętlę w funkcji strip_tags (). Naprawiona wersja została odtworzona powyżej. Więcej informacji tutaj .

Dobre rzeczy do skopiowania lub użycia

Mój przykładowy kod nie obsługuje encji HTML - robią to wersje spakowane Django i MarkupSafe.

Mój przykładowy kod został pobrany z doskonałej biblioteki MarkupSafe w celu zapobiegania skryptom krzyżowym. Jest wygodny i szybki (dzięki przyspieszeniom języka C do natywnej wersji Pythona). Jest zawarty w Google App Engine i jest używany przez Jinja2 (wersja 2.7 i nowsze) , Mako, Pylony i inne. Działa łatwo z szablonami Django z Django 1.7.

Strip_tags Django i inne narzędzia HTML z najnowszej wersji są dobre, ale uważam je za mniej wygodne niż MarkupSafe. Są dość samodzielne, możesz skopiować to, czego potrzebujesz z tego pliku .

Jeśli chcesz usunąć prawie wszystkie znaczniki, biblioteka Bleach jest dobra. Możesz wymusić egzekwowanie reguł takich jak „moi użytkownicy mogą pisać kursywą, ale nie mogą tworzyć ramek iframe”.

Poznaj właściwości swojego narzędzia do usuwania tagów! Uruchom na nim testy Fuzz! Oto kod, którego użyłem do badania tej odpowiedzi.

zawstydzona uwaga - samo pytanie dotyczy drukowania na konsoli, ale jest to najlepszy wynik Google dla „python strip html from string”, dlatego właśnie ta odpowiedź dotyczy 99% internetu.


Mój przykładowy kod „alternatywnej ostatniej linii” nie obsługuje encji HTML - jak źle to jest?
rescdsk

Analizuję tylko niewielką część HTML bez specjalnych znaczników, a Twoja krótka wersja działa bardzo dobrze. Dzięki za udostępnienie!
tbolender

31

Potrzebowałem sposobu, aby rozebrać tagi i zdekodować encje HTML na zwykły tekst. Poniższe rozwiązanie oparte jest na odpowiedzi Eloffa (której nie mogłem użyć, ponieważ usuwa byty).

from HTMLParser import HTMLParser
import htmlentitydefs

class HTMLTextExtractor(HTMLParser):
    def __init__(self):
        HTMLParser.__init__(self)
        self.result = [ ]

    def handle_data(self, d):
        self.result.append(d)

    def handle_charref(self, number):
        codepoint = int(number[1:], 16) if number[0] in (u'x', u'X') else int(number)
        self.result.append(unichr(codepoint))

    def handle_entityref(self, name):
        codepoint = htmlentitydefs.name2codepoint[name]
        self.result.append(unichr(codepoint))

    def get_text(self):
        return u''.join(self.result)

def html_to_text(html):
    s = HTMLTextExtractor()
    s.feed(html)
    return s.get_text()

Szybki test:

html = u'<a href="#">Demo <em>(&not; \u0394&#x03b7;&#956;&#x03CE;)</em></a>'
print repr(html_to_text(html))

Wynik:

u'Demo (\xac \u0394\u03b7\u03bc\u03ce)'

Obsługa błędów:

  • Nieprawidłowa struktura HTML może powodować błąd HTMLParseError .
  • Niepoprawne nazwane jednostki HTML (takie jak &#apos;, które są poprawne w XML i XHTML, ale nie zwykły HTML) spowodują ValueErrorwyjątek.
  • Numeryczne jednostki HTML określające punkty kodowe poza zakresem Unicode akceptowanym przez Python (takie jak, w niektórych systemach, znaki spoza Podstawowej płaszczyzny wielojęzycznej ) spowodują ValueErrorwyjątek.

Uwaga dotycząca bezpieczeństwa: nie należy mylić rozbiórki HTML (konwersja HTML na zwykły tekst) z odkażaniem HTML (konwersja zwykłego tekstu na HTML). Ta odpowiedź usunie HTML i zdekoduje jednostki na zwykły tekst - co nie sprawia, że ​​wynik jest bezpieczny w użyciu w kontekście HTML.

Przykład: &lt;script&gt;alert("Hello");&lt;/script&gt;zostanie przekonwertowany na <script>alert("Hello");</script>, co jest w 100% poprawnym zachowaniem, ale oczywiście niewystarczające, jeśli wynikowy zwykły tekst zostanie wstawiony w niezmienionej postaci na stronie HTML.

Zasada nie jest trudna: za każdym razem , gdy wstawiasz ciąg tekstowy do wyjścia HTML, zawsze powinieneś go unikać (używając cgi.escape(s, True)), nawet jeśli wiesz, że nie zawiera HTML (np. Ponieważ usunąłeś treść HTML) .

(Jednak OP poprosił o wydrukowanie wyniku na konsoli, w którym to przypadku nie jest wymagane ucieczka HTML).

Wersja Python 3.4+: (z doctest!)

import html.parser

class HTMLTextExtractor(html.parser.HTMLParser):
    def __init__(self):
        super(HTMLTextExtractor, self).__init__()
        self.result = [ ]

    def handle_data(self, d):
        self.result.append(d)

    def get_text(self):
        return ''.join(self.result)

def html_to_text(html):
    """Converts HTML to plain text (stripping tags and converting entities).
    >>> html_to_text('<a href="#">Demo<!--...--> <em>(&not; \u0394&#x03b7;&#956;&#x03CE;)</em></a>')
    'Demo (\xac \u0394\u03b7\u03bc\u03ce)'

    "Plain text" doesn't mean result can safely be used as-is in HTML.
    >>> html_to_text('&lt;script&gt;alert("Hello");&lt;/script&gt;')
    '<script>alert("Hello");</script>'

    Always use html.escape to sanitize text before using in an HTML context!

    HTMLParser will do its best to make sense of invalid HTML.
    >>> html_to_text('x < y &lt z <!--b')
    'x < y < z '

    Unrecognized named entities are included as-is. '&apos;' is recognized,
    despite being XML only.
    >>> html_to_text('&nosuchentity; &apos; ')
    "&nosuchentity; ' "
    """
    s = HTMLTextExtractor()
    s.feed(html)
    return s.get_text()

Zauważ, że HTMLParser poprawił się w Pythonie 3 (co oznacza mniej kodu i lepszą obsługę błędów).


18

Jest na to prosty sposób:

def remove_html_markup(s):
    tag = False
    quote = False
    out = ""

    for c in s:
            if c == '<' and not quote:
                tag = True
            elif c == '>' and not quote:
                tag = False
            elif (c == '"' or c == "'") and tag:
                quote = not quote
            elif not tag:
                out = out + c

    return out

Pomysł wyjaśniono tutaj: http://youtu.be/2tu9LTDujbw

Możesz zobaczyć, jak działa tutaj: http://youtu.be/HPkNPcYed9M?t=35s

PS - Jeśli jesteś zainteresowany klasą (o inteligentnym debugowaniu za pomocą Pythona), dam ci link: http://www.udacity.com/overview/Course/cs259/CourseRev/1 . Jest wolne!

Nie ma za co! :)


2
Zastanawiam się, dlaczego ta odpowiedź została właśnie odrzucona. To prosty sposób na rozwiązanie problemu bez lib. Po prostu czysty python i działa tak, jak pokazują linki.
Medeiros,

2
Prawdopodobnie ludzie wolą biblioteki, aby zapewnić im bezpieczeństwo. Przetestowałem twój kod i zdałem i zawsze wolę mały kod, który rozumiem, niż używanie lib i zakładanie, że jest w porządku, dopóki nie pojawi się błąd. Dla mnie tego właśnie szukałem i jeszcze raz dziękuję. Jeśli chodzi o opinie negatywne, nie wchodź w taki sposób myślenia. Ludzie tutaj powinni dbać o jakość, a nie o głosy. Ostatnio SO stało się miejscem, w którym każdy chce punktów, a nie wiedzy.
Jimmy Kane

2
Problem z tym rozwiązaniem polega na obsłudze błędów. Na przykład jeśli podasz <b class="o'>x</b>jako dane wyjściowe funkcji wejściowych x. Ale tak naprawdę to wejście jest nieprawidłowe. Myślę, że dlatego ludzie wolą biblioteki lib.
laltin

1
Działa również z tym wejściem. Właśnie przetestowane. Po prostu uświadom sobie, że w tych bibliotekach znajdziesz podobny kod. Wiem, to nie jest bardzo pytoniczne. Wygląda jak kod C lub Java. Myślę, że jest wydajny i można go łatwo przenieść na inny język.
Medeiros

1
Prosty, Pythoniczny i wydaje się działać równie dobrze lub lepiej niż jakakolwiek inna omawiana metoda. Możliwe, że nie będzie działać dla niektórych źle sformułowanych HTML, ale nie da się tego przezwyciężyć.
denson

16

Jeśli chcesz zachować encje HTML (tj. &amp;), Dodałem metodę „handle_entityref” do odpowiedzi Eloffa .

from HTMLParser import HTMLParser

class MLStripper(HTMLParser):
    def __init__(self):
        self.reset()
        self.fed = []
    def handle_data(self, d):
        self.fed.append(d)
    def handle_entityref(self, name):
        self.fed.append('&%s;' % name)
    def get_data(self):
        return ''.join(self.fed)

def html_to_text(html):
    s = MLStripper()
    s.feed(html)
    return s.get_data()

13

Jeśli chcesz usunąć wszystkie tagi HTML, najłatwiejszym sposobem, jaki znalazłem, jest użycie BeautifulSoup:

from bs4 import BeautifulSoup  # Or from BeautifulSoup import BeautifulSoup

def stripHtmlTags(htmlTxt):
    if htmlTxt is None:
            return None
        else:
            return ''.join(BeautifulSoup(htmlTxt).findAll(text=True)) 

Próbowałem kodu akceptowanej odpowiedzi, ale otrzymywałem komunikat „RuntimeError: przekroczona maksymalna głębokość rekurencji”, co nie zdarzyło się w przypadku powyższego bloku kodu.


1
Właśnie wypróbowałem twoją metodę, ponieważ wydaje się czystsza, działała, no cóż ... nie usunęła tagów wejściowych!
kustomrtr

Uważam, że to prosta aplikacja z BeautifulSoup ma problem z whitespaces: ''.join(BeautifulSoup('<em>he</em>llo<br>world').find_all(text=True)). Tutaj wyjściem jest „helloworld”, podczas gdy prawdopodobnie chcesz, aby był to „hello world”. ' '.join(BeautifulSoup('<em>he</em>llo<br>world').find_all(text=True))nie pomaga, ponieważ staje się „on llo world”.
Finn Årup Nielsen

@kustomrtr, przepraszam za moją niewiedzę, co mogę włożyć w samowystarczalność? NameError: nazwa „self” nie jest zdefiniowana
Ian_De_Oliveira

@Ian_De_Oliveira Możesz go usunąć, zakładałem, że jest w klasie, ale nie jest potrzebny. Zredagowałem również odpowiedź, aby ją usunąć
Vasilis

@Ian_De_Oliveira Możesz go usunąć, zakładałem, że jest w klasie, ale nie jest potrzebny. Zredagowałem również odpowiedź, aby ją usunąć
Vasilis

10

Oto proste rozwiązanie, które usuwa tagi HTML i dekoduje encje HTML w oparciu o niezwykle szybką lxmlbibliotekę:

from lxml import html

def strip_html(s):
    return str(html.fromstring(s).text_content())

strip_html('Ein <a href="">sch&ouml;ner</a> Text.')  # Output: Ein schöner Text.

3
Od 2020 r. Był to najszybszy i najlepszy sposób na usunięcie zawartości HTML. Plus bonus obsługi dekodowania. Idealne do wykrywania języka!
dfabiano

text_content()zwraca, lxml.etree._ElementUnicodeResultwięc być może będziesz musiał najpierw rzucić go na string
Suzana

1
@Suzana Dobry punkt. Wydaje się, że jest automatycznie rzutowany strna operacje łańcuchowe, takie jak +i indeksowanie []. W każdym razie dodano obsadę dla dobrego pomiaru.
Robin Dinse

9

Lxml.html rozwiązanie -na (lxml jest natywna biblioteka, a więc znacznie szybciej niż w jakimkolwiek czystym roztworze Pythona).

from lxml import html
from lxml.html.clean import clean_html

tree = html.fromstring("""<span class="item-summary">
                            Detailed answers to any questions you might have
                        </span>""")

print(clean_html(tree).strip())

# >>> Detailed answers to any questions you might have

Zobacz także http://lxml.de/lxmlhtml.html#cleaning-up-html, aby dowiedzieć się, co dokładnie robi lxml.cleaner.

Jeśli potrzebujesz większej kontroli nad tym, co dokładnie jest dezynfekowane przed konwersją na tekst, możesz użyć lxml Cleaner jawnie, przekazując odpowiednie opcje w konstruktorze, np .:

cleaner = Cleaner(page_structure=True,
                  meta=True,
                  embedded=True,
                  links=True,
                  style=True,
                  processing_instructions=True,
                  inline_style=True,
                  scripts=True,
                  javascript=True,
                  comments=True,
                  frames=True,
                  forms=True,
                  annoying_tags=True,
                  remove_unknown_tags=True,
                  safe_attrs_only=True,
                  safe_attrs=frozenset(['src','color', 'href', 'title', 'class', 'name', 'id']),
                  remove_tags=('span', 'font', 'div')
                  )
sanitized_html = cleaner.clean_html(unsafe_html)

1
Mam AttributeError: 'HTMLElement' obiekt ma atrybut 'strip'
Aris

7

Pakiet Pięknej Zupy robi to natychmiast.

from bs4 import BeautifulSoup

soup = BeautifulSoup(html)
text = soup.get_text()
print(text)

3
Z kolejki recenzji: Czy mogę prosić o dodanie dodatkowego kontekstu wokół odpowiedzi. Odpowiedzi zawierające tylko kod są trudne do zrozumienia. Pomoże zarówno pytającemu, jak i przyszłym czytelnikom, czy możesz dodać więcej informacji w swoim poście.
help-info.de

2

Oto moje rozwiązanie dla Pythona 3.

import html
import re

def html_to_txt(html_text):
    ## unescape html
    txt = html.unescape(html_text)
    tags = re.findall("<[^>]+>",txt)
    print("found tags: ")
    print(tags)
    for tag in tags:
        txt=txt.replace(tag,'')
    return txt

Nie jestem pewien, czy jest idealny, ale rozwiązałem mój przypadek użycia i wydaje się prosty.


2

Możesz użyć innego parsera HTML ( takiego jak lxml lub Beautiful Soup ) - takiego, który oferuje funkcje do wyodrębnienia samego tekstu. Lub możesz uruchomić wyrażenie regularne na łańcuchu linii, który usuwa tagi. Zobacz dokumentację Python, aby uzyskać więcej.


1
link amk nie działa. Masz alternatywę?

2
Witryna Python ma teraz dobre instrukcje, oto regex - instrukcje: docs.python.org/howto/regex
Jason Coon

5
W lxml:lxml.html.fromstring(s).text_content()
Bluu

1
Przykład Bluu z lxml dekoduje encje HTML (np. &amp;) Na tekst.
Søren Løvborg

1

Z powodzeniem wykorzystałem odpowiedź Eloffa dla Pythona 3.1 [wielkie dzięki!].

Uaktualniłem do Python 3.2.3 i napotkałem błędy.

Rozwiązaniem udostępnionym tutaj dzięki respondentowi Thomasowi K jest wstawienie super().__init__()do następującego kodu:

def __init__(self):
    self.reset()
    self.fed = []

... aby wyglądało to tak:

def __init__(self):
    super().__init__()
    self.reset()
    self.fed = []

... i będzie działać dla Python 3.2.3.

Ponownie, dzięki Thomasowi K za poprawkę i za oryginalny kod Eloffa podany powyżej!


1

Możesz napisać własną funkcję:

def StripTags(text):
     finished = 0
     while not finished:
         finished = 1
         start = text.find("<")
         if start >= 0:
             stop = text[start:].find(">")
             if stop >= 0:
                 text = text[:start] + text[start+stop+1:]
                 finished = 0
     return text

1
Czy dołączenie do ciągów tworzy nową kopię ciągu?
Jeremy L,

1
@Nerdling - Tak, co może prowadzić do dość imponującej nieefektywności często używanych funkcji (lub, w tym przypadku, rzadko używanych funkcji, które działają na duże plamy tekstu). Szczegółowe informacje na tej stronie. : D
Jeremy Sandell,

Czy testuje z podanymi ciągami? Nie.
Jimmy Kane

1

Wszystkie rozwiązania z parserem HTML są łamliwe, jeśli działają tylko raz:

html_to_text('<<b>script>alert("hacked")<</b>/script>

prowadzi do:

<script>alert("hacked")</script>

co zamierzasz zapobiec. jeśli używasz parsera HTML, policz tagi, aż zero zostanie zastąpione:

from HTMLParser import HTMLParser

class MLStripper(HTMLParser):
    def __init__(self):
        self.reset()
        self.fed = []
        self.containstags = False

    def handle_starttag(self, tag, attrs):
       self.containstags = True

    def handle_data(self, d):
        self.fed.append(d)

    def has_tags(self):
        return self.containstags

    def get_data(self):
        return ''.join(self.fed)

def strip_tags(html):
    must_filtered = True
    while ( must_filtered ):
        s = MLStripper()
        s.feed(html)
        html = s.get_data()
        must_filtered = s.has_tags()
    return html

1
Jeśli wywołasz wywoływaną funkcję html_to_texti osadzisz tekst wyprowadzany z tej funkcji w html bez zmiany znaczenia tego tekstu, to brak ucieczki jest zagrożeniem bezpieczeństwa, a nie html_to_textfunkcja. html_to_textFunkcja nigdy nie obiecał, wyjście będzie tekst. A wstawianie tekstu do html bez ucieczki stanowi potencjalną lukę w zabezpieczeniach niezależnie od tego, czy tekst został pobrany, html_to_text czy z innego źródła.
kasperd

Masz rację w przypadku braku ucieczki, ale pytaniem było usunięcie html z danego ciągu, aby nie uciekać z danego ciągu. Jeśli wcześniejsze odpowiedzi budują nowy HTML ze swoimi rozwiązaniami w wyniku usunięcia niektórych HTML, to korzystanie z tych rozwiązań jest niebezpieczne.
Falk Nisius

1

Jest to szybka poprawka i może być jeszcze bardziej zoptymalizowana, ale będzie działać dobrze. Ten kod zastąpi wszystkie niepuste tagi „” i usuwa wszystkie tagi html z podanego tekstu wejściowego. Możesz uruchomić go za pomocą pliku wyjściowego ./file.py

    #!/usr/bin/python
import sys

def replace(strng,replaceText):
    rpl = 0
    while rpl > -1:
        rpl = strng.find(replaceText)
        if rpl != -1:
            strng = strng[0:rpl] + strng[rpl + len(replaceText):]
    return strng


lessThanPos = -1
count = 0
listOf = []

try:
    #write File
    writeto = open(sys.argv[2],'w')

    #read file and store it in list
    f = open(sys.argv[1],'r')
    for readLine in f.readlines():
        listOf.append(readLine)         
    f.close()

    #remove all tags  
    for line in listOf:
        count = 0;  
        lessThanPos = -1  
        lineTemp =  line

            for char in lineTemp:

            if char == "<":
                lessThanPos = count
            if char == ">":
                if lessThanPos > -1:
                    if line[lessThanPos:count + 1] != '<>':
                        lineTemp = replace(lineTemp,line[lessThanPos:count + 1])
                        lessThanPos = -1
            count = count + 1
        lineTemp = lineTemp.replace("&lt","<")
        lineTemp = lineTemp.replace("&gt",">")                  
        writeto.write(lineTemp)  
    writeto.close() 
    print "Write To --- >" , sys.argv[2]
except:
    print "Help: invalid arguments or exception"
    print "Usage : ",sys.argv[0]," inputfile outputfile"

1

Adaptacja 3 w pytaniu odpowiedzi søren-løvborg

from html.parser import HTMLParser
from html.entities import html5

class HTMLTextExtractor(HTMLParser):
    """ Adaption of http://stackoverflow.com/a/7778368/196732 """
    def __init__(self):
        super().__init__()
        self.result = []

    def handle_data(self, d):
        self.result.append(d)

    def handle_charref(self, number):
        codepoint = int(number[1:], 16) if number[0] in (u'x', u'X') else int(number)
        self.result.append(unichr(codepoint))

    def handle_entityref(self, name):
        if name in html5:
            self.result.append(unichr(html5[name]))

    def get_text(self):
        return u''.join(self.result)

def html_to_text(html):
    s = HTMLTextExtractor()
    s.feed(html)
    return s.get_text()

1

Do jednego projektu potrzebowałem więc rozebrać HTML, ale także css i js. Dlatego zrobiłem odmianę odpowiedzi Eloffa:

class MLStripper(HTMLParser):
    def __init__(self):
        self.reset()
        self.strict = False
        self.convert_charrefs= True
        self.fed = []
        self.css = False
    def handle_starttag(self, tag, attrs):
        if tag == "style" or tag=="script":
            self.css = True
    def handle_endtag(self, tag):
        if tag=="style" or tag=="script":
            self.css=False
    def handle_data(self, d):
        if not self.css:
            self.fed.append(d)
    def get_data(self):
        return ''.join(self.fed)

def strip_tags(html):
    s = MLStripper()
    s.feed(html)
    return s.get_data()

1

Oto rozwiązanie podobne do obecnie akceptowanej odpowiedzi ( https://stackoverflow.com/a/925630/95989 ), z tym wyjątkiem, że korzysta HTMLParserbezpośrednio z klasy wewnętrznej (tj. Bez podklasy), dzięki czemu jest znacznie bardziej zwięzły:

def strip_html (tekst):
    części = []                                                                      
    parser = HTMLParser ()                                                           
    parser.handle_data = parts.append                                               
    parser.feed (tekst)                                                               
    powrót '' .join (części)

0

Analizuję readmesy Github i stwierdzam, że następujące działania naprawdę działają dobrze:

import re
import lxml.html

def strip_markdown(x):
    links_sub = re.sub(r'\[(.+)\]\([^\)]+\)', r'\1', x)
    bold_sub = re.sub(r'\*\*([^*]+)\*\*', r'\1', links_sub)
    emph_sub = re.sub(r'\*([^*]+)\*', r'\1', bold_sub)
    return emph_sub

def strip_html(x):
    return lxml.html.fromstring(x).text_content() if x else ''

I wtedy

readme = """<img src="https://raw.githubusercontent.com/kootenpv/sky/master/resources/skylogo.png" />

            sky is a web scraping framework, implemented with the latest python versions in mind (3.4+). 
            It uses the asynchronous `asyncio` framework, as well as many popular modules 
            and extensions.

            Most importantly, it aims for **next generation** web crawling where machine intelligence 
            is used to speed up the development/maintainance/reliability of crawling.

            It mainly does this by considering the user to be interested in content 
            from *domains*, not just a collection of *single pages*
            ([templating approach](#templating-approach))."""

strip_markdown(strip_html(readme))

Usuwa wszystkie obniżki i HTML poprawnie.


0

Używając BeautifulSoup, html2text lub kodu z @Eloff, przez większość czasu pozostaje trochę elementów HTML, kod javascript ...

Możesz więc użyć kombinacji tych bibliotek i usunąć formatowanie przeceny (Python 3):

import re
import html2text
from bs4 import BeautifulSoup
def html2Text(html):
    def removeMarkdown(text):
        for current in ["^[ #*]{2,30}", "^[ ]{0,30}\d\\\.", "^[ ]{0,30}\d\."]:
            markdown = re.compile(current, flags=re.MULTILINE)
            text = markdown.sub(" ", text)
        return text
    def removeAngular(text):
        angular = re.compile("[{][|].{2,40}[|][}]|[{][*].{2,40}[*][}]|[{][{].{2,40}[}][}]|\[\[.{2,40}\]\]")
        text = angular.sub(" ", text)
        return text
    h = html2text.HTML2Text()
    h.images_to_alt = True
    h.ignore_links = True
    h.ignore_emphasis = False
    h.skip_internal_links = True
    text = h.handle(html)
    soup = BeautifulSoup(text, "html.parser")
    text = soup.text
    text = removeAngular(text)
    text = removeMarkdown(text)
    return text

Działa dla mnie dobrze, ale można go oczywiście ulepszyć ...


0

Prosty kod !. Spowoduje to usunięcie wszelkiego rodzaju tagów i treści w nim zawartych.

def rm(s):
    start=False
    end=False
    s=' '+s
    for i in range(len(s)-1):
        if i<len(s):
            if start!=False:
                if s[i]=='>':
                    end=i
                    s=s[:start]+s[end+1:]
                    start=end=False
            else:
                if s[i]=='<':
                    start=i
    if s.count('<')>0:
        self.rm(s)
    else:
        s=s.replace('&nbsp;', ' ')
        return s

Ale nie da pełnego wyniku, jeśli tekst zawiera symbole <> .


0
# This is a regex solution.
import re
def removeHtml(html):
  if not html: return html
  # Remove comments first
  innerText = re.compile('<!--[\s\S]*?-->').sub('',html)
  while innerText.find('>')>=0: # Loop through nested Tags
    text = re.compile('<[^<>]+?>').sub('',innerText)
    if text == innerText:
      break
    innerText = text

  return innerText.strip()

-2

Ta metoda działa dla mnie bezbłędnie i nie wymaga żadnych dodatkowych instalacji:

import re
import htmlentitydefs

def convertentity(m):
    if m.group(1)=='#':
        try:
            return unichr(int(m.group(2)))
        except ValueError:
            return '&#%s;' % m.group(2)
        try:
            return htmlentitydefs.entitydefs[m.group(2)]
        except KeyError:
            return '&%s;' % m.group(2)

def converthtml(s):
    return re.sub(r'&(#?)(.+?);',convertentity,s)

html =  converthtml(html)
html.replace("&nbsp;", " ") ## Get rid of the remnants of certain formatting(subscript,superscript,etc).

3
To dekoduje jednostki HTML na zwykły tekst, ale oczywiście nie usuwa żadnych tagów, co było pierwotnym pytaniem. (Również drugi blok try-wyjątkiem musi zostać obniżony, aby kod mógł wykonać tyle samo).
Søren Løvborg
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.