CoderCastrov logo
CoderCastrov
Парсер

Использование ChatGPT для разбора HTML: революционное изменение или устаревание регулярных выражений?

Использование ChatGPT для разбора HTML: революционное изменение или устаревание регулярных выражений?
просмотров
5 мин чтение
#Парсер

Спойлер: не можете дождаться кода? Вот он.

import openai  # для вызовов OpenAI API


openai.api_key = os.getenv("OPENAI_API_KEY")


def promt_generation(raw_html: str) -> str:
    promt = f"""    У меня есть фрагмент кода HTML. В нем содержится описание события.    Извлеките описание события из HTML в формате JSON.    Результативный JSON должен содержать поля: название события, ссылка на событие, место проведения, описание, время начала, время окончания        HTML: {raw_html}    JSON:    """
    return promt

@backoff.on_exception(backoff.expo, openai.error.Timeout)
def gpt_query(gpt_promt, verbose: bool = False):
    gpt_messages = [{"role": "system","content": "Вы - парсер HTML.",},{"role": "user","content": gpt_promt,},]
    openai_params = {'model':'gpt-3.5-turbo', 'max_tokens':1000,'temperature':0.0,'top_p':0.5,'frequency_penalty':0.5, 'messages':gpt_messages}
    response = openai.ChatCompletion.create(**openai_params)
    gpt_responce_raw = response.get("choices")[0].get("message").get("content").replace('\n', '')
    res = {'gpt_resp': validate_gpt_responce(gpt_responce_raw)}
    res.update(response.get("usage").to_dict())
    return res

def get_iterable_from_url(url: str) -> pd.DataFRame:
    resp = requests.get(url)
    dummy_scraper = BeautifulSoup(markup=resp.content, features="html.parser")
    if 'eater.com/' in url:
        scraper_params = {'name': 'section', 'class_': 'c-mapstack__card'}
    elif 'everout.com/' in url:
        scraper_params = {'name': 'div', 'class_': 'event-schedules'}
    else:
        RuntimeError('Valid scraper not found')
    page_blocks = dummy_scraper.find_all(**scraper_params)
    page_description = []
    for i in page_blocks:
        gpt_resp = gpt_query(promt_generation(i.text))
        gpt_resp['gpt_resp'].update({'source_url': url, 'raw_html': str(i)})
        page_description.append(gpt_resp['gpt_resp'])
    result_df = pd.json_normalize(page_description)
    return result_df

Теперь давайте посмотрим, что здесь происходит.


Допустим, мы решаем задачу создания сервиса для рекомендаций ресторанов, назовем такой сервис "FindMeWhereToEat". Одна из ключевых задач - сбор контента, необходимого для составления рекомендаций. В то время как обычный подход включает в себя создание парсеров, специфичных для веб-сайтов ресторанов, таких как Yelp, этот метод имеет недостаток: он требует частой доработки при изменении макета веб-сайта. При расширении нашего сервиса для включения большего количества источников время и усилия, затраченные на улучшение парсеров, также увеличиваются, что создает проблемы масштабируемости.

В этой статье я расскажу вам о создании более универсального парсера с использованием языковой модели на основе обучения (LLM). Этот подход предлагает решение, которое минимизирует затраты на масштабирование и поддержку, позволяя создать более эффективную и адаптируемую систему. Давайте подробнее рассмотрим этот подход и исследуем, как он может улучшить наш сервис рекомендаций ресторанов.


Декомпозиция задачи

Мы хотим создать систему, которая будет извлекать информацию о содержимом (рестораны) из HTML-страниц в структурированной форме (JSON): название, описание, часы работы и т.д.

Задача извлечения структурированной информации из HTML-кода с точки зрения NLP является задачей суммирования текста, мы хотим извлечь значимую часть большого текста. Чтобы обучить модель суммирования, нам нужно создать набор данных в виде соответствия полный_текст: суммированный_текст.

Чтобы обучить модель суммирования, нам нужно

  • собрать список сайтов, с которых мы будем извлекать обучающие примеры (данные о ресторанах)
  • посмотреть, какие ресурсы включены в вывод и собрать черный список - неинформативные (для нашей задачи) сайты (например, Reddit)
  • собрать обучающий набор из raw_html -> json
  • обучить модель и проверить качество суммирования

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


Источники данных

Сначала нам нужно получить список страниц, на которых мы будем обучать и проверять нашу модель.

Сбор этих сайтов можно автоматизировать

  • спросите у ChatGPT, Google Bard или Bing что-то вроде "Помогите мне найти список сайтов, на которых перечислены лучшие рестораны в Нью-Йорке"
  • попросите ChatGPT сгенерировать 15-20 поисковых запросов, семантически похожих на запрос из первого абзаца, и извлеките ссылки из результатов поиска с помощью библиотеки GoogleSerpAPI - такой подход увеличит полноту, то есть добавит сайты, о которых ChatGPT "не знает".

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

Обучающий набор данных

Тестовые данные для обучения модели резюмирования должны представлять собой набор данных типа raw_html -> json (структурированная информация).

Как собрать такие данные наилучшим образом? В идеале, мы должны реализовать сборщик данных, но давайте воспользуемся ChatGPT!

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

Еще один пример

Для извлечения информации из блока главное - извлечь сами блоки, а затем "подать" их на вход ChatGPT для структурирования информации в соответствии со следующим алгоритмом:

  • разделить страницу на блоки верхнего уровня для парсинга (как на картинке выше)
  • создать запрос для GPT, чтобы извлечь информацию из блока в формате JSON
  • запустить скрипт для получения описания деятельности в формате JSON с использованием созданного запроса
  • просмотреть случаи, когда парсинг не сработал, и добавить их в скрипт из пункта 3.

Пример HTML-фрагмента с необходимой информацией

<div class="c-mapstack__desktop-social">
<div class="c-social-buttons c-social-buttons--popover" data-cdata='{"entry_id":22663434,"services":["twitter","facebook","pocket","flipboard","email"],"base_url":"https://ny.eater.com/maps/best-romantic-restaurants-date-night-nyc"}' data-cid="site/social_buttons_list/popover-1689052966_9593_4783">
<h2 class="sr-only" id="heading-label--smgbvsiz">Share this story</h2>
<ul aria-labelledby="heading-label--smgbvsiz">
<li>
<a class="c-social-buttons__item c-social-buttons__facebook" data-analytics-social="facebook" href="https://www.facebook.com/sharer/sharer.php?text=The+Most+Romantic+Restaurants+in+NYC&amp;u=https%3A%2F%2Fny.eater.com%2Fmaps%2Fbest-romantic-restaurants-date-night-nyc">
<svg class="p-svg-icon c-social-buttons__svg" role="img"><use xlink:href="#icon-facebook"></use></svg>
<span class="sr-only">Share this on Facebook</span>
</a>
</li>
<li>
<div class="c-entry-content c-mapstack__content">
<p id="YnSnkk">When looking for a romantic restaurant or bar, words like “cozy,” “intimate,” and “low-lit” probably come to mind. But we’d argue that the food and bottles of colorful pet-nat are just as crucial to a memorable swoony evening as the decor. Below, we’ve rounded up a few of our favorite spots that are more than just a pretty indoor dining room — although we took that into account, too — for those special occasion dates.</p>
<p id="UsiWW1"><small><em>Health experts consider dining out to be a high-risk activity for the unvaccinated; it may pose a risk for the vaccinated, especially in areas with substantial COVID transmission.</em></small></p>
<p id="Agxajy"></p>
<p id="LfsNef"></p>
<a class="c-mapstack__content-read-more" data-analytics-link="read-more" data-read-more="now">Read More</a>
<div class="c-mapstack__disclaimer">
                If you buy something or book a reservation from an Eater link, Vox Media may earn a commission. See our <a href="https://eater.com/pages/eater-ethics-statement">ethics policy</a>.
              </div>

Запрос для ChatGPT (вы можете увидеть то же самое в коде на Python)

Вы - парсер HTML.
Я - фрагмент HTML-кода. Фрагмент содержит описание деятельности.
Извлеките описание мероприятия из HTML в формате JSON.
Результат JSON должен содержать поля: название мероприятия, ссылка на мероприятие, местоположение, описание, время начала, время окончания.

HTML: {raw_html}

JSON:

Результат резюмирования

{'event name': 'Le Rock',
 'link to event': 'https://www.google.com/maps/place/45+Rockefeller+Plaza,+New+York,+NY+10111',
 'location': '45 Rockefeller Plaza, New York, NY 10111',
 'description': 'Даже если вы не банкир или участник SNL, нарядиться для свидания в этом ресторане Art Deco в Rockefeller Center - это весело. Учитывая, насколько сложно забронировать место в этом популярном месте от команды Frenchette, вы впечатлите свою половинку, просто пригласив ее туда, не говоря уже о том, чтобы наслаждаться морепродуктами и роскошной десертной башней, пока вино льется рекой.',
 'time start': '',
 'time end': ''}

То есть ChatGPT эффективно обрабатывает парсинг HTML!

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

Обучение LLM для этой задачи будет рассмотрено в следующей статье этой серии.

Следите за обновлениями!

Бонус: https://gist.github.com/aleksandr-dzhumurat/3b4fdcdca8a0871ebd0c7a2338db93df