2015-02-18 3 views
5

Я работаю над массовым преобразованием нескольких HTML-файлов в XML с помощью BeautifulSoup в Python.Как заменить комментарии HTML пользовательскими <comment> элементами

Образец HTML-файл выглядит примерно так:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<!-- this is an HTML comment --> 
<!-- this is another HTML comment --> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
    <head> 
     ... 
     <!-- here is a comment inside the head tag --> 
    </head> 
    <body> 
     ... 
     <!-- Comment inside body tag --> 
     <!-- Another comment inside body tag --> 
     <!-- There could be many comments in each file and scattered, not just 1 in the head and three in the body. This is just a sample. --> 
    </body> 
</html> 
<!-- This comment is the last line of the file --> 

Я понял, как найти доктайп и заменить его с тегом <doctype>...</doctype>, но комментирование дает мне много разочарований. Я хочу заменить комментарии HTML <comment>...</comment>. В этом примере HTML я смог заменить первые два комментария HTML, но ничего внутри тега html и последнего комментария после закрытия html-тега я не был.

Вот мой код:

file = open ("sample.html", "r") 
soup = BeautifulSoup(file, "xml") 

for child in soup.children: 

    # This takes care of the first two HTML comments 
    if isinstance(child, bs4.Comment): 
     child.replace_with("<comment>" + child.strip() + "</comment>") 

    # This should find all nested HTML comments and replace. 
    # It looks like it works but the changes are not finalized 
    if isinstance(child, bs4.Tag): 
     re.sub("(<!--)|(&lt;!--)", "<comment>", child.text, flags=re.MULTILINE) 
     re.sub("(-->)|(--&gr;)", "</comment>", child.text, flags=re.MULTILINE) 

# The HTML comments should have been replaced but nothing changed. 
print (soup.prettify(formatter=None)) 

Это мой первый раз, используя BeautifulSoup. Как использовать BeautifulSoup для поиска и замены всех комментариев HTML с помощью тега <comment>?

Могу ли я преобразовать его в поток байтов через pickle, сериализуя его, применяя регулярное выражение, а затем повторно обработайте его до объекта BeautifulSoup? Будет ли это работать или просто вызвать больше проблем?

Я попытался использовать рассол на объекте дочернего тега, но десериализация завершилась неудачей с TypeError: __new__() missing 1 required positional argument: 'name'.

Затем я попытался протравить только текст тега, используя child.text, но десериализация не удалась из-за AttributeError: can't set attribute. В основном, child.text доступен только для чтения, что объясняет, почему регулярное выражение не работает. Итак, я не знаю, как изменить текст.

+0

не приведет к плохой сформированный 'xml' файл применяется только все изменения? – Birei

+0

Я не знаю, но 'Chilkat' имеет (не бесплатную) библиотеку python для преобразования HTML-to-XML, которая преобразует все комментарии HTML в' ' и файл XML выглядит хорошо. – user3621633

ответ

4

У вас есть несколько проблем:

  1. Вы не можете изменить child.text. это свойство только для чтения, которое просто вызывает get_text() за кулисами, а его результат - новая строка, не связанная с вашим документом.

  2. re.sub() не изменяет ничего на месте. Ваша линии

    re.sub("(<!--)|(&lt;!--)", "<comment>", child.text, flags=re.MULTILINE) 
    

    пришлось бы быть

    child.text = re.sub("(<!--)|(&lt;!--)", "<comment>", child.text, flags=re.MULTILINE) 
    

    ... но это не будет работать в любом случае, из-за пункт 1.

  3. Попытки изменить документ путем замены ломтей текста в нем с регулярным выражением - это неправильный способ использования BeautifulSoup. Вместо этого вам нужно найти узлы и заменить их на другие узлы.

Вот решение, которое работает:

import bs4 

with open("example.html") as f: 
    soup = bs4.BeautifulSoup(f) 

for comment in soup.find_all(text=lambda e: isinstance(e, bs4.Comment)): 
    tag = bs4.Tag(name="comment") 
    tag.string = comment.strip() 
    comment.replace_with(tag) 

Этот код начинается итерация результат вызова find_all(), воспользовавшись тем, что мы можем pass a function как text аргумента.В BeautifulSoup Comment является подклассом NavigableString, поэтому мы ищем его, как если бы это была строка, а lambda ... является просто сокращением, например.

def is_comment(e): 
    return isinstance(e, bs4.Comment) 

soup.find_all(text=is_comment) 

Затем мы создаем новый Tag с соответствующим именем, его содержание, чтобы быть раздели содержание оригинального комментария и замените комментарий тэгом мы только что создали.

Вот результат:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 

<comment>this is an HTML comment</comment> 
<comment>this is another HTML comment</comment> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
<head> 
     ... 
     <comment>here is a comment inside the head tag</comment> 
</head> 
<body> 
     ... 
     <comment>Comment inside body tag</comment> 
<comment>Another comment inside body tag</comment> 
<comment>There could be many comments in each file and scattered, not just 1 in the head and three in the body. This is just a sample.</comment> 
</body> 
</html> 
<comment>This comment is the last line of the file</comment> 
+0

Большое вам спасибо за помощь. Я просто читаю это сейчас. Если можете, объясните или уточните каждую строку в коде python, начиная с 'for'. Опять же, я все еще новичок в Beautiful Soup. Заранее спасибо! Я не совсем знаком с использованием лам. – user3621633

+0

@ user3621633 Я добавил объяснение, что делает код сейчас. –

+0

Это сработало! Спасибо. Единственное, что я хотел бы добавить, это то, что мне нужно было в формате XML. Поэтому, я думаю, мне нужно применить BeautifulSoup с xml перед записью в выходной файл. Если я использую xml в первый раз, комментарии HTML выходят как объекты HTML (то есть <), и я не уверен, что BeautifulSoup может работать с ними. – user3621633

Смежные вопросы