Python Regex для файла HTML с многострочными элементами

Проблема

Я преобразовал PDF-файл в HTML-файл с помощью встроенных инструментов Adobe, но я изо всех сил пытаюсь написать функции регулярного выражения для захвата различных данных:

  • Festival_name
  • Контактное лицо
  • Почта для связи
  • Контактный телефон
  • заметки
  • Festival_months.

ОСНОВНАЯ ПРОБЛЕМА: многие элементы HTML вместе с текстом между ними занимают несколько строк, поэтому регулярное выражение часто пропускается.

(Примечание: HTML-документ содержит список музыкальных фестивалей и их соответствующие детали)

Что я пробовал

Мой текущий код работает следующим образом:

  1. Откройте файл HTML и пропустите каждую строку
  2. Используйте регулярное выражение для поиска тегов со встроенным стилем (style = 'цвет: # FF2500)
  3. Поместите совпадения в словарь с именем FestivalBlock {} в ключ с именем город
  4. Начните другое регулярное выражение из текущей строки, ища Festival_name
  5. Добавить текущий FestivalBlock в словарь FestivalList {} под ключом фестивали

HTML-снимок экрана и отрывок:

Python Regex для файла HTML с многострочными элементами

<h1 style='margin-top:7.3pt;margin-right:0in;margin-bottom:0in;margin-left:
61.0pt;margin-bottom:.0001pt'><a name=ALABAMA></a><a name = "_bookmark0"></a>ALABAMA</h1>

<p class=MsoBodyText style='margin-top:.2pt'><b style='mso-bidi-font-weight:
normal'><span style='font-size:35.0pt;mso-bidi-font-size:11.0pt'><o:p>&nbsp;</o:p></span></b></p>

<h5 style='margin-left:61.0pt'><a name=Alabaster></a><a name = "_bookmark1"></a><span
style='color:#FF2500'>Alabaster</span></h5>

<p class=MsoBodyText style='margin-top:.15pt'><b style='mso-bidi-font-weight:
normal'><i style='mso-bidi-font-style:normal'><span style='font-size:12.5pt;
mso-bidi-font-size:11.0pt'><o:p>&nbsp;</o:p></span></i></b></p>

<p class=MsoNormal style='margin-top:0in;margin-right:73.45pt;margin-bottom:
0in;margin-left:61.0pt;margin-bottom:.0001pt;line-height:101%'><b
style='mso-bidi-font-weight:normal'><span style='font-size:14.0pt;mso-bidi-font-size:
11.0pt;line-height:101%'>Takeoff Fest </span></b><a
href = "mailto:[email protected]"><span style='font-size:12.0pt;
mso-bidi-font-size:11.0pt;line-height:101%;color:blue'>[email protected]</span></a><a
href = "mailto:[email protected]"><span style='color:blue'>m</span></a><span
style='color:blue'> </span>Festival Month(s): <i style='mso-bidi-font-style:
normal'>June<o:p></o:p></i></p>

<p class=MsoBodyText style='margin-top:.05pt'><i style='mso-bidi-font-style:
normal'><span style='font-size:13.5pt;mso-bidi-font-size:11.0pt'><o:p>&nbsp;</o:p></span></i></p>

<h5 style='margin-left:61.0pt'><a name=Albertville></a><a name = "_bookmark2"></a><span
style='color:#FF2500'>Albertville</span></h5>

<p class=MsoBodyText style='margin-top:.4pt'><b style='mso-bidi-font-weight:
normal'><i style='mso-bidi-font-style:normal'><span style='font-size:12.5pt;
mso-bidi-font-size:11.0pt'><o:p>&nbsp;</o:p></span></i></b></p>

<h6 style='margin-top:0in;margin-right:34.05pt;margin-bottom:0in;margin-left:
61.0pt;margin-bottom:.0001pt;line-height:105%'>Albertville Main Street Music
Festival</h6>

<p class=MsoBodyText style='margin-top:.2pt;margin-right:73.45pt;margin-bottom:
0in;margin-left:61.0pt;margin-bottom:.0001pt'><a
href = "mailto:[email protected]"><span style='color:windowtext;
text-decoration:none;text-underline:none'>[email protected]</span></a>
Festival Month(s):<i style='mso-bidi-font-style:normal'>August<o:p></o:p></i></p>

<p class=MsoBodyText style='margin-top:.3pt'><i style='mso-bidi-font-style:
normal'><span style='font-size:12.5pt;mso-bidi-font-size:11.0pt'><o:p>&nbsp;</o:p></span></i></p>

<h6 style='margin-left:61.0pt'>Main Street Music Festival</h6>

<p class=MsoNormal style='margin-top:.2pt;margin-right:-.95pt;margin-bottom:
0in;margin-left:61.0pt;margin-bottom:.0001pt;line-height:100%'><a
href = "http://www.mainstreetmusicfestival.com/e%20ntertainment.html"><span
style='font-size:10.0pt;mso-bidi-font-size:11.0pt;line-height:100%;color:blue'>http://www.mainstreetmusicfestival.com/e</span></a><span
style='font-size:10.0pt;mso-bidi-font-size:11.0pt;line-height:100%;color:blue'>
</span><a href = "http://www.mainstreetmusicfestival.com/e%20ntertainment.html"><span
style='font-size:10.0pt;mso-bidi-font-size:11.0pt;line-height:100%;color:blue'>ntertainment.html</span></a><span
style='font-size:10.0pt;mso-bidi-font-size:11.0pt;line-height:100%;color:blue'>
</span><a href = "mailto:[email protected]"><span
style='color:windowtext;text-decoration:none;text-underline:none'>[email protected]</span></a>
<b style='mso-bidi-font-weight:normal'><i style='mso-bidi-font-style:normal'><span
style='font-size:12.0pt;mso-bidi-font-size:11.0pt;line-height:100%'>Note: </span></i></b><i
style='mso-bidi-font-style:normal'><span style='font-size:12.0pt;mso-bidi-font-size:
11.0pt;line-height:100%'>Submissions open through June (online only).<o:p></o:p></span></i></p>

<p class=MsoNormal style='margin-left:61.0pt;line-height:13.2pt;mso-line-height-rule:
exactly'>Festival Month(s): <i style='mso-bidi-font-style:normal'>August<o:p></o:p></i></p>

<p class=MsoBodyText style='margin-top:.35pt'><i style='mso-bidi-font-style:
normal'><span style='font-size:13.5pt;mso-bidi-font-size:11.0pt'><o:p>&nbsp;</o:p></span></i></p>

<h5 style='margin-left:61.0pt'><a name = "Alexander_City"></a><a name = "_bookmark3"></a><span
style='color:#FF2500'>Alexander City</span></h5>

<p class=MsoBodyText style='margin-top:.4pt'><b style='mso-bidi-font-weight:
normal'><i style='mso-bidi-font-style:normal'><span style='font-size:12.5pt;
mso-bidi-font-size:11.0pt'><o:p>&nbsp;</o:p></span></i></b></p>

<p class=MsoNormal style='margin-top:0in;margin-right:34.05pt;margin-bottom:
0in;margin-left:61.0pt;margin-bottom:.0001pt;line-height:100%'><b
style='mso-bidi-font-weight:normal'><span style='font-size:14.0pt;mso-bidi-font-size:
11.0pt;line-height:100%'>Alexander City Jazz Festival </span></b><a
href = "http://www.alexcityjazzfest.com/get-involved.html256-397-1019"><span
style='font-size:12.0pt;mso-bidi-font-size:11.0pt;line-height:100%;color:blue'>http://www.alexcityjazzfest.com/get-</span></a><span
style='font-size:12.0pt;mso-bidi-font-size:11.0pt;line-height:100%;color:blue'>
</span><a href = "http://www.alexcityjazzfest.com/get-involved.html256-397-1019"><span
style='font-size:12.0pt;mso-bidi-font-size:11.0pt;line-height:100%;color:blue'>involved.html256-397-1019</span></a><span
style='font-size:12.0pt;mso-bidi-font-size:11.0pt;line-height:100%;color:blue'>
</span><a href = "mailto:[email protected]"><span style='color:blue'>[email protected]</span></a></p>

<p class=MsoNormal style='margin-left:61.0pt;line-height:13.65pt;mso-line-height-rule:
exactly'><b style='mso-bidi-font-weight:normal'><i style='mso-bidi-font-style:
normal'><span style='font-size:12.0pt;mso-bidi-font-size:11.0pt'>Note: </span></i></b><i
style='mso-bidi-font-style:normal'><span style='font-size:12.0pt;mso-bidi-font-size:
11.0pt'>Online submission.<o:p></o:p></span></i></p>

<p class=MsoBodyText style='margin-top:.3pt;margin-right:0in;margin-bottom:
0in;margin-left:61.0pt;margin-bottom:.0001pt'>Festival Month(s): <i
style='mso-bidi-font-style:normal'>June<o:p></o:p></i></p>

Код Python:

import re
from re import search
import json
from collections import defaultdict
import os

f = open("./data/fairs-and-festivals-min.htm", "r", encoding = "utf-8")

# Variables ...
cityList = {}
festivalList = {
    "festivals": []
}
file_pos = 0

for line in f:
    # [0] Prepare dictionary for festival data ...
    festivalBlock = {
        "city": "",
        "festivals": []
    }

    # [1] Find City w/ red text ...
    cityData = re.search(r"'color:(#FF2500)'", line)

    # Match was found ...
    if (cityData):

    # [2] Add city name to list ...

        # Strip extra HTML from city name ...
        city = re.findall(r"style='color:#FF2500'>(.*?)</span>", line)
        city = " ".join(city) # Convert city from list to string ...
        festivalBlock["city"] = city

        # if cityData.group(1) not in cityList:
        #     cityList[cityData.group(1)] = [city]
        # else:
        #     cityList[cityData.group(1)].append(city)


        """
            Festival Blocks - DATA STRUCTURE:

            "state": "Arizona",
            "city": "Alabaster",
            "festivals": [
                            {
                                "festival_name": "Hopkins Raspberry Festival",
                                "contact_name": "Lou Jean Gleason",
                                "contact_email": "[email protected]",
                                "contact_phone": "952-931-0878",
                                "notes": "Note: Online submission.",
                                "festival_months": "Festival Month(s): July"
                            },
                            {
                                "festival_name": "Hopkins Raspberry Festival",
                                "contact_name": "Lou Jean Gleason",
                                "contact_email": "[email protected]",
                                "contact_phone": "952-931-0878",
                                "notes": "Note: Online submission.",
                                "festival_months": "Festival Month(s): July"
                            }
                        ]
        """

    # [3] Find first festival in this city ... 

        # TODO: Look for multiple festivals within cities ...
        # Get Festival name (span or h6) | STOP AFTER FIRST MATCH ...
        festivalCount = 0 # iterator
        for cityLine in f:

            """ DEBUGGING CODE BELOW ... """
            # if (festivalCount >= 3):
            #     continue

            # Span version | Pattern 'line-height:xxx%'>festival_name</span>'
            festivalName = re.findall(r"line-height:([+-]?([0-9]*[.])?[0-9]+)%'>(.*?)</span>", cityLine) 
            if (festivalName):
                festival = defaultdict(list)
                festival["festival_name"] = festivalName[0][2]
                festivalBlock["festivals"].append(festival)
                festivalCount += 1
                break
            else:
                regex = re.compile(r">(.*?)</h6>", re.DOTALL)
                festivalName = regex.findall(cityLine)

                if (festivalName): # Festival name wrapped in h6 | Ignore empty strings
                    print(festivalName)
                    unwantedPhrase = "Note"
                    if not unwantedPhrase in festivalName:
                    # if unwantedPhrase in festivalName: # Ignore unwanted text/phrases (i.e. Note:)
                        festival = defaultdict(list)
                        festival["festival_name"] = festivalName
                        festivalBlock["festivals"].append(festival)
                        
                        festivalCount += 1
                        break
                    break


    # [4] Append block to list of festivals ...
        festivalList["festivals"].append([festivalBlock])

        
        # [Z] Iterate file line position...
        file_pos += len(line)
    else:

        # [Z] Iterate file line position...
        file_pos += len(line)
        continue

    
                
# Print festivals to json file 
output = open("./data/festivals.json", "w")
output.write(json.dumps(festivalList))
output.close()

Помимо Python, я пробовал украшение HTML-документ, минификация HTML-документ и даже использование jQuery.

Мое исследование

Я пробовал библиотеки, такие как Py2PDF, PyQuery и html2str, но регулярные выражения кажутся наиболее практичным решением; учитывая, как плохо отформатирован HTML-документ.

Вопрос

Как я могу гарантировать, что мои методы регулярного выражения не пропускают элементы HTML, выходящие за пределы одной строки?

Совет дня: используйте библиотеку Beautiful Soup, регулярное выражение нет, для анализа содержимого HTML.

Tim Biegeleisen 30.03.2021 08:31

@TimBiegeleisen, спасибо! Будет ли он анализировать CSS и / или атрибуты? В HTML-документе нет согласованных классов или идентификаторов, поэтому я должен полагаться на встроенные стили как на отличия.

Shoko 30.03.2021 08:40

Он возвращает вам атрибуты. Вам решать, что делать со значениями атрибутов.

Tim Roberts 30.03.2021 08:45
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
3
34
0

Другие вопросы по теме