Как разобрать HTML с помощью Python?

задняя часть Python регулярное выражение HTML

如何用Python解析HTML?

С помощью нескольких простых скриптов вы можете легко очистить документы и другие большие HTML-файлы. Но сначала их нужно разобрать.

Как давний член группы документации Scribus, я постоянно в курсе последних обновлений исходного кода, чтобы документацию можно было обновлять и дополнять. Когда я недавно проверил Subversion на компьютере, который только что был обновлен до Fedora 27, я был поражен тем, сколько времени ушло на загрузку документации, состоящей из HTML-страниц и связанных изображений. Я боюсь, что документация проекта выглядит намного больше, чем сам проект, и подозреваю, что часть ее содержимого является документами-зомби — HTML-файлы, которые больше не используются, и изображения, недоступные в HTML.

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

Вот типичный тег изображения:

<img src="images/edit_shapes.png" ALT="Edit examples" ALIGN=left>

я правsrc=Часть между первым набором кавычек после того, как представляет интерес. После поиска некоторых решений я нашел один под названиемBeautifulSoupмодуль Python. Основная часть скрипта выглядит так:

soup = BeautifulSoup(all_text, 'html.parser')
match = soup.findAll("img")
if len(match) > 0:
    for m in match:
        imagelist.append(str(m))

мы можем использовать этоfindAllметод, чтобы выкопать теги изображения. Вот небольшая часть вывода:

<img src="images/pdf-form-ht3.png"/><img src="images/pdf-form-ht4.png"/><img src="images/pdf-form-ht5.png"/><img src="images/pdf-form-ht6.png"/><img align="middle" alt="GSview - Advanced Options Panel" src="images/gsadv1.png" title="GSview - Advanced Options Panel"/><img align="middle" alt="Scribus External Tools Preferences" src="images/gsadv2.png" title="Scribus External Tools Preferences"/>

Все идет нормально. Я думал, что следующий шаг поможет, но когда я попробовал некоторые строковые методы в скрипте, он вернул ошибку о токене вместо строки. Я сохраняю вывод в файл и помещаю его вKWriteредактировать в. Одна из приятных особенностей KWrite заключается в том, что вы можете использовать регулярные выражения (регулярные выражения) для выполнения операций «найти и заменить», поэтому я могу использовать\n<imgзаменять<img, чтобы вы могли видеть это более четко. Еще одна приятная особенность KWrite заключается в том, что если вы сделаете неразумный выбор с помощью регулярных выражений, вы можете отменить его.

Но я подумал, что должно быть что-то лучше этого, поэтому я обратился к регулярным выражениям, а точнее к Python.reмодуль. Соответствующая часть этого нового скрипта выглядит так:

match = re.findall(r'src="(.*)/>', all_text)
if len(match)>0:
    for m in match:
        imagelist.append(m)

Небольшая часть его вывода выглядит так:

images/cmcanvas.png" title="Context Menu for the document canvas" alt="Context Menu for the document canvas" /></td></tr></table><br images/eps-imp1.png" title="EPS preview in a file dialog" alt="EPS preview in a file dialog" images/eps-imp5.png" title="Colors imported from an EPS file" alt="Colors imported from an EPS file" images/eps-imp4.png" title="EPS font substitution" alt="EPS font substitution" images/eps-imp2.png" title="EPS import progress" alt="EPS import progress" images/eps-imp3.png" title="Bitmap conversion failure" alt="Bitmap conversion failure"

На первый взгляд, он похож на вывод выше, с дополнительным преимуществом удаления части изображения с метками, но загадочным образом перемежается метками таблиц и другими вещами. Я думаю, что это связано с этим регулярным выражениемsrc="(.*)/>, который называетсяжадный, это означает, что он не обязательно останавливается на встрече/>первый экземпляр . Я должен добавить, что я также пыталсяsrc="(.*)", это действительно не намного лучше, я не эксперт по регулярным выражениям (только что сделал это) и искал различные способы улучшить это, но безрезультатно.

Сделав кучу вещей, даже попробовал PerlHTML::Parserмодуль, и, в конечном счете, я пытаюсь сравнить это с некоторыми сценариями, которые я написал для Scribus, которые анализируют текстовое содержимое посимвольно, а затем выполняют некоторые действия. В конце концов, я наконец понял все эти методы без необходимости использования регулярных выражений или синтаксических анализаторов HTML. Вернемся к показанномуimgПримеры этикеток.

<img src="images/edit_shapes.png" ALT="Edit examples" ALIGN=left>

Я решил вернуться кsrc=этот кусок. Один из способов - подождатьsпоявляется, затем проверьте, является ли следующий символr, следующийc, будь то следующий=. Если да, то это совпадение! Тогда содержимое между двумя двойными кавычками — это то, что мне нужно. Проблема с этим подходом заключается в том, что структуры, подобные приведенным выше, необходимо постоянно идентифицировать. Один из способов посмотреть на строку, представляющую строку текста HTML:

for c in all_text:

Но эта логика слишком беспорядочна, чтобы продолжать сопоставлять предыдущуюc, и символ перед, символ перед, символ перед больше.

Наконец, я решил сосредоточиться на=и использовать метод индекса, чтобы я мог легко ссылаться на любой предыдущий или будущий символ в строке. Вот раздел поиска:

    index = 3
    while index < linelength:
        if (all_text[index] == '='):
            if (all_text[index-3] == 's') and (all_text[index-2] == 'r') and (all_text[index-1] == 'c'):
                imagefound(all_text, imagelist, index)
                index += 1
            else:
                index += 1
        else:
            index += 1

Я начинаю поиск с четвертого символа (индексация начинается с 0), поэтому я не получаю ошибку индексации ниже, и фактически перед четвертым символом в каждой строке не будет знака равенства. Первый тест должен увидеть, появляется ли строка=, если нет, то движемся вперед. Если мы видим знак равенства, то проверяем, совпадают ли первые три символа.s,rиc. Если все совпадает, вызываем функциюimagefound:

def imagefound(all_text, imagelist, index):
    end = 0
    index += 2
    newimage = ''
    while end == 0:
        if (all_text[index] != '"'):
            newimage = newimage + all_text[index]
            index += 1
        else:
            newimage = newimage + '\n'
            imagelist.append(newimage)
            end = 1
            return

Мы отправляем функции текущий индекс, который представляет=. Мы знаем, что следующим персонажем будет", поэтому пропускаем два символа и начинаем вызыватьnewimageСтрока управления добавляет символы, пока мы не найдем следующий", на данный момент мы завершили матч. Мы добавляем новую строку в строку (\n) добавить в списокimagelistв и возвращение (return), помните, что в остальной части этого HTML В строке может быть больше тегов изображений, поэтому мы сразу возвращаемся к циклу поиска.

Вот как теперь выглядит наш вывод:

images/text-frame-link.png
images/text-frame-unlink.png
images/gimpoptions1.png
images/gimpoptions3.png
images/gimpoptions2.png
images/fontpref3.png
images/font-subst.png
images/fontpref2.png
images/fontpref1.png
images/dtp-studio.png

Ах, намного чище, и это занимает всего несколько секунд. Я мог бы переместить индекс на 7 шагов вперед, чтобы сократитьimages/раздел, но я бы предпочел сохранить эту часть, чтобы убедиться, что я не отрезал первую букву имени файла изображения, которое легко успешно редактировать с помощью KWrite - вам даже не нужно регулярное выражение. После этого и сохранения файла следующим шагом будет запуск другого сценария, который я написал.sortlist.py:

#!/usr/bin/env python
# -*- coding: utf-8  -*-
# sortlist.py
 
import os
 
imagelist = []
for line in open('/tmp/imagelist_parse4.txt').xreadlines():
    imagelist.append(line)
 
imagelist.sort()
 
outfile = open('/tmp/imagelist_parse4_sorted.txt', 'w')
outfile.writelines(imagelist)
outfile.close()

Это читает содержимое файла, сохраняет в виде списка, сортирует его и сохраняет как другой файл. После этого я могу сделать следующее:

ls /home/gregp/development/Scribus15x/doc/en/images/*.png > '/tmp/actual_images.txt'

Затем мне нужно запустить этот файлsortlist.py,так какlsПорядок методов отличается от Python. Я мог бы запустить скрипт сравнения этих файлов, но я бы предпочел сделать это визуально. В конце концов мне удалось найти 42 изображения, на которые не было HTML-ссылок из документации.

Вот мой полный скрипт разбора:

#!/usr/bin/env python
# -*- coding: utf-8  -*-
# parseimg4.py
 
import os
 
def imagefound(all_text, imagelist, index):
    end = 0
    index += 2
    newimage = ''
    while end == 0:
        if (all_text[index] != '"'):
            newimage = newimage + all_text[index]
            index += 1
        else:
            newimage = newimage + '\n'
            imagelist.append(newimage)
            end = 1
            return
 
htmlnames = []
imagelist = []
tempstring = ''
filenames = os.listdir('/home/gregp/development/Scribus15x/doc/en/')
for name in filenames:
    if name.endswith('.html'):
        htmlnames.append(name)
#print htmlnames
for htmlfile in htmlnames:
    all_text = open('/home/gregp/development/Scribus15x/doc/en/' + htmlfile).read()
    linelength = len(all_text)
    index = 3
    while index < linelength:
        if (all_text[index] == '='):
            if (all_text[index-3] == 's') and (all_text[index-2] == 'r') and
(all_text[index-1] == 'c'):
                imagefound(all_text, imagelist, index)
                index += 1
            else:
                index += 1
        else:
            index += 1
 
outfile = open('/tmp/imagelist_parse4.txt', 'w')
outfile.writelines(imagelist)
outfile.close()
imageno = len(imagelist)
print str(imageno) + " images were found and saved"

имя сценарияparseimg4.py, что на самом деле не отражает количество сценариев, которые я написал подряд (как доработанных, так и переработанных, отброшенных и начатых заново). Обратите внимание, что я жестко запрограммировал эти каталоги и имена файлов, но их легко обобщить и позволить пользователю вводить эту информацию. Опять же, поскольку это рабочие сценарии, я отправляю вывод в/tmpкаталог, поэтому после перезагрузки системы они исчезнут.

Это не конец истории, потому что следующий вопрос: как HTML-файл Zombies? Любой неиспользуемый файл может быть указан изображением, не может найти предыдущий метод. у нас естьmenu.xmlфайлы в качестве оглавления для онлайн-руководства, но мне также необходимо учитывать, что некоторые из файлов, перечисленных в оглавлении (аннотация LCTT: оглавление — это сокращение от оглавления), могут ссылаться на файлы, которых нет в оглавлении, да Я нашел кое-что из этого документа.

В итоге могу сказать, что это более легкая задача, чем поиск изображений, и процесс ее разработки мне очень помог.