Python: получить открытие и закрытие тегов HTML - PullRequest
0 голосов
/ 24 января 2019

Вопрос:

Как найти текст для всех открывающих и закрывающих тегов HTML с помощью python (3.6).Это должен быть точный текст с пробелами и потенциально недопустимым html:

# input
html = """<p>This <a href="book"> book </a  > will help you</p attr="e">"""

# desired output
output = ['<p>', '<a href="book">', '</a  >', '</p attr="e">']

Попытка решения:

Видимо, это невозможно в Beautifulsoup, этот вопрос: Как получить открывающий и закрывающий тег в красивом супе из строки HTML? ссылки на html.parser

Реализовать собственный синтаксический анализатор легко.Вы можете использовать self.get_starttag_text(), чтобы получить текст, соответствующий последнему открытому тегу.Но по какой-то причине не существует аналогичного метода get_endtag_text().

, который означает, что мой парсер выдает такой вывод:

class MyHTMLParser(HTMLParser):
    def __init__(self):
        super().__init__()
        self.tags = []

    def reset_stored_tags(self):
        self.tags = []
    def handle_starttag(self, tag, attrs):
        self.tags.append(self.get_starttag_text())

    def handle_endtag(self, tag):
        self.tags.append(self.get_endtag_text())

    def handle_startendtag(self, data):
        self.tags.append(self.get_starttag_text())
# input
input_doc = """<p>This <a href="book"> book </a> will help you</p>"""

parser = MyHTMLParser()
parser.feed(input_doc)

print(parser.tags)
# ['<p>', '<a href="book">', '<a href="book">', '<a href="book">']

Аргумент tag handle_endtag простострока "a" или "p", а не какой-либо пользовательский тип данных, который может предоставить весь тег.

Ответы [ 2 ]

0 голосов
/ 12 февраля 2019

В то время как ответ от @ Ajax1234 содержит хороший python + beautifulsoup, я обнаружил, что он очень нестабилен. Главным образом потому, что мне нужна точная строка тега html. Каждый тег, найденный методом, должен присутствовать в HTML-тексте. Это приводит к следующим проблемам:

  • Он анализирует имена тегов и атрибуты из HTML и подключает их вместе, чтобы сформировать строку тега yield f'<{_d.name}>' if not _attrs else f'<{_d.name} {_attrs}>'. Это избавляет от лишних пробелов в теге: <p > становится <p>

  • Всегда генерирует закрывающий тег, даже если в разметке его нет

  • Сбой для атрибутов, которые являются списками: <p class="a b"> становится <p class="[a, b]">

Проблема пробелов может быть частично решена путем очистки HTML перед его обработкой. Я использовал отбеливатель , но это может быть слишком агрессивным. В частности, вы должны указать список принятых тегов, прежде чем использовать его.

Лучший подход - это тонкая оболочка вокруг html.parser.HTMLParser . Это то, что я уже начал в своем вопросе, разница здесь в том, что я автоматически добавляю генерирующий закрывающий тег.

from html.parser import HTMLParser

class MyHTMLParser(HTMLParser):
    def __init__(self):
        super().__init__()
        self.tags = []

    def handle_starttag(self, tag, attrs):
        self.tags.append(self.get_starttag_text())

    def handle_endtag(self, tag):
        self.tags.append(f"</{tag}>")

parser = MyHTMLParser();
parser.feed("""<p > Argh, whitespace and p is not closed </a>""")
parser.tags # ['<p >', '</a>']

Это решило проблемы, упомянутые выше, но у него есть один недостаток, он не смотрит на фактический текст для закрывающего тега. Если в закрывающем теге есть дополнительные аргументы или пробелы, при синтаксическом анализе они не будут отображаться.

0 голосов
/ 24 января 2019

Вы можете использовать рекурсию и перебирать атрибут soup.contents:

from bs4 import BeautifulSoup as soup

html = """<p>This <a href="book"> book </a> will help you</p>"""

def attrs(_d):
  if _d.name != '[document]':
    _attrs = ' '.join(f'{a}="{b}"' for a, b in getattr(_d, 'attrs', {}).items())
    yield f'<{_d.name}>' if not _attrs else f'<{_d.name} {_attrs}>'
  for i in _d.contents:
    if not isinstance(i, str):
       yield from attrs(i)
  if _d.name != '[document]':
    yield f'</{_d.name}>'

print(list(attrs(soup(html, 'html.parser'))))

Выход:

['<p>', '<a href="book">', '</a>', '</p>']

Редактировать: для недопустимого HTML вы можете использовать re:

import re
html = """<p>This <a href="book"> book </a  > will help you</p attr="e">"""
new_results = re.findall('\<[a-zA-Z]+.*?\>|\</[a-zA-Z]+.*?\>', html)

Выход:

['<p>', '<a href="book">', '</a  >', '</p attr="e">']
...