CoderCastrov logo
CoderCastrov
Парсинг веб-страниц

Комбинирование Playwright с Beautiful Soup для парсинга веб-сайтов

Комбинирование Playwright с Beautiful Soup для парсинга веб-сайтов
просмотров
7 мин чтение
#Парсинг веб-страниц

Парсинг данных с веб-сайтов, отрисовываемых с помощью JavaScript, с использованием библиотек Playwright и Beautiful Soup на языке Python

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

Мне это случалось несколько раз. Некоторые веб-сайты кажутся простыми для парсинга на первый взгляд, но на самом деле они отрисовываются с помощью JavaScript. Возьмем, например, страницу с бесконечной прокруткой или одностраничные приложения, которые могут менять представления без полной перезагрузки веб-страницы, а также всплывающие окна. Это только некоторые примеры, которые иногда скрыты, но достаточно, чтобы полностью изменить скрипт парсинга веб-страниц.

Безголовые браузеры, такие как Playwright, Selenium и Puppeteer, могут справиться с такими проблемами благодаря своей способности быть программированными для выполнения операций и взаимодействия с веб-страницами. Выбор безголового браузера зависит от предпочтений пользователя. В моем случае, Playwright как-то упрощает интеграцию с библиотеками парсинга веб-страниц, такими как Beautiful Soup. Он поддерживает Chromium, Firefox и WebKit, и предоставляет чистый и удобный для пользователя API, который можно использовать с TypeScript, JavaScript, Python, .NET, Java.

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


1 — Сценарий и настройка

Прежде всего, убедитесь, что у вас установлены необходимые библиотеки.

pip install playwright
pip install beautifulsoup4
playwright install

Beautiful Soup - это библиотека для парсинга веб-страниц, которая извлекает данные из файлов HTML и XML, и она принимает несколько парсеров.

Теперь мы готовы визуализировать основу скрипта.

if __name__ == '__main__':
    # оператор "with" для обработки исключений
    with sync_playwright() as p:
        # создает экземпляр браузера Chromium и запускает его
        browser = p.chromium.launch(headless=True)
        # создает новую страницу браузера (вкладку) внутри экземпляра браузера
        page = browser.new_page()

        try:
            # создает датафрейм для участков
            df_lands = parser(url=URL_LANDS, n_pages=34)
            # создает датафрейм для вилл
            df_villas = parser(url=URL_VILLAS, n_pages=95)
            # объединяет оба датафрейма
            df_data = pd.concat([df_villas, df_lands])
            # сохраняет все наборы данных в файлы .csv
            df_lands.to_csv(f"{PATH}/data/lands.csv")
            df_villas.to_csv(f"{PATH}/data/villas.csv")
            df_data.to_csv(f"{PATH}/data/kib_data.csv")

        finally:
            page.close()

Этот код размещается в самом конце файла .py, выражение if __name__ == ‘__main__’: проверяет, выполняется ли скрипт как основная программа. Прямо под ним используется оператор with, который используется для управления контекстом функций, таких как sync_playwright(), который используется для создания синхронного экземпляра Playwright, в данном случае с именем p. Затем создается экземпляр браузера Chromium (browser = p.chromium.launch(headless=True)). Затем создается новая страница браузера (page).

Конструкция try/finally служит для обеспечения выполнения операций независимо от возникновения исключения. Внутри нее основная функция (parser()) парсит два разных URL-адреса. Затем данные каждой ссылки объединяются и сохраняются в виде файлов .csv. Когда все операции завершены, закрывается экземпляр page.

2 — Основная функция

Функция scraper() содержит все остальные функции и отвечает за процесс парсинга веб-страниц.

def scraper(url, n_pages=90) -> pd.DataFrame:
    # список, в котором будут сохранены данные для каждой страницы веб-сайта
    details = []

    for website_page in range(n_pages):
        print('страница: ', website_page)
        property_links = obtain_links(url, website_page)
        for link in property_links:
            try:
                # создание объекта Beautiful Soup
                soup = change_currency_n_get_soup(link)

                # получение цены каждого объекта недвижимости
                prices = soup.select('.regular-price')

                # получение заголовков каждого объекта недвижимости
                titles = soup.select('.name')

                # получение кодов каждого объекта недвижимости
                codes = soup.select('.code')

            except Exception as error:
                print(error)
                time.sleep(60)
                continue

            try:
                type_sale, hold_years, description_items, colswidth20_item,\
                    date = get_shared_features(soup)

                if 'villas' in url:
                    price, year_built, land_size, building_size,\
                        pool, furnished, rooms = get_only_villas_features(
                            soup, description_items, prices)

                    detail = {
                        'Название': [
                            title.text.strip() for title in titles][0],
                        'Дата загрузки': date,
                        'Цена (IDR)': price,
                        'Код': [
                            code.text.strip() for code in codes][0],
                        'Местоположение': colswidth20_item[0].split('\n')[-1],
                        'Тип продажи': type_sale
                        .split(' ')[0],
                        'Срок аренды (лет)': hold_years,
                        'URL': link,
                        'Тип недвижимости': 'вилла',
                        'Год постройки': year_built,
                        'Спальни': rooms[3].split('\n')[1],
                        'Ванные комнаты': rooms[5],
                        'Площадь участка (сотки)': land_size,
                        'Площадь здания (кв. м)': building_size,
                        'Бассейн': pool,
                        'Меблировано': furnished.lower()
                    }

                    details.append(detail)

                else:
                    price = get_only_lands_features(prices)

                    detail = {
                        'Название': [
                            title.text.strip() for title in titles][0],
                        'Дата загрузки': date,
                        'Цена (IDR)': price,
                        'Код': [
                            code.text.strip() for code in codes][0],
                        'Местоположение': colswidth20_item[0].split('\n')[-1],
                        'Тип продажи': type_sale
                        .split(' ')[0],
                        'Срок аренды (лет)': hold_years,
                        'URL': link,
                        'Тип недвижимости': 'земля',
                        'Год постройки': None,
                        'Спальни': None,
                        'Ванные комнаты': None,
                        'Площадь участка (сотки)': description_items[3]
                        .split('\n')[1].strip(),
                        'Площадь здания (кв. м)': None,
                        'Бассейн': None,
                        'Меблировано': None
                    }

                    details.append(detail)

            except Exception as error:
                print(error)
            continue

    return pd.DataFrame(details)

Функция принимает только два параметра: количество страниц веб-сайта, которые нужно спарсить, и основной URL. Результатом является таблица данных, содержащая собранные данные с указанного диапазона страниц.

URL-адреса принадлежат одному и тому же веб-сайту, но один содержит информацию о виллах, а другой - о земельных участках. Нет общего URL-адреса, который бы включал оба типа недвижимости. Поэтому функция scraper() была создана для адаптации к каждому URL.

Подфункции включают:

  • **obtain_links()**: Возвращает список ссылок на объекты недвижимости с главной страницы веб-сайта.
  • **change_currency_n_get_soup()**: Мы более подробно рассмотрим эту функцию, так как она использует Playwright для автоматического выполнения кликов. Затем она возвращает экземпляр Beautiful Soup.
  • **get_shared_features()**: Она_ _парсит контент, организованный одинаковым образом для обоих URL-адресов.
  • **get_only_villas_features()**: Она_ _парсит контент, специфически организованный для вилл.
  • **get_only_lands_features()**: Она_ _парсит контент, специфически организованный для земельных участков.

В функции есть две основные итерации: одна перебирает страницы веб-сайта, а другая перебирает ссылки на каждый объект недвижимости. Вне циклов есть список details (в самом начале функции), который в основном содержит всю информацию. В этот список добавляются словари, созданные для каждого объекта недвижимости. Условный оператор if определяет, создавать ли словарь для земли или виллы в зависимости от входного URL-адреса.

3 — Использование Playwright для выполнения кликов

Для данного веб-сайта я хотел собрать цены на недвижимость в ETH (Ethereum), но он использует одностраничное приложение для изменения цен в соответствии с выбранной валютой. Поэтому мне пришлось автоматизировать два шага: первый - нажатие на класс, который вызывает выпадающее меню с разными валютами, а второй - поиск строки "ETH" и нажатие на HTML-класс, содержащий ее.

Шаги автоматизации были выполнены внутри функции **change_currency_n_get_soup()**.

def change_currency_n_get_soup(property_link) -> object:
    # перейти по новому URL, предоставленному ссылкой
    page.goto(property_link)

    # Нажать на выпадающее меню валюты
    page.click('.header-cur')

    # затем найти валюту ETH и нажать на нее
    page.locator('text=ETH').first.click()

    # вывести сообщение для подтверждения изменения валюты
    print('Валюта изменена!')

    html = page.inner_html('body')

    soup = BeautifulSoup(html, 'html.parser')

    return soup

Функция начинается с использования экземпляра Playwright (page), чтобы переместить браузер по ссылке на недвижимость. Затем тот же экземпляр нажимает на класс, который отображает выпадающее меню с несколькими валютами, и, наконец, находит нужную валюту и нажимает на нее.

Остальная часть функции в основном создает элемент Beautiful Soup с парсером HTML, чтобы начать парсинг.

4 — Примеры парсинга с использованием Beautiful Soup

Объект Beautiful Soup, созданный в функции change_currency_n_get_soup(), затем используется в других функциях, таких как get_shared_features().

def get_shared_features(soup):
    # получаем дату, используя изображение
    images = soup.select('figure')
    img_tags = images[0].find_all('img')
    img_src = img_tags[0].get('src')
    date = img_src.split('/')[-1].split('-property')[0]

    # получаем элементы внутри colswidth20
    colswidth20_items = soup.select('.colswidth20')
    colswidth20 = [
        colswidth20_item.text.strip()
        for colswidth20_item in colswidth20_items]

    # получаем элементы описания недвижимости
    property_description = soup.select(
        '.property-description-row.flexbox')
    description_items = []

    # список элементов, организованных по элементу "p"
    for desc_row in property_description:
        items = desc_row.find_all('p')
        for paragraph in items:
            description_items.append(
                paragraph.text.strip())

    return description_items, colswidth20, date

В задачах парсинга веб-страниц иногда данные могут быть неочевидными или даже не отображаться на сайте. Так было с переменной date. Я хотел получить дату загрузки каждого объекта недвижимости, но информация не отображалась. При более тщательном рассмотрении элементов HTML я понял, что могу извлечь дату загрузки из URL изображения объекта. Поэтому в первом блоке скрипта функции я использую экземпляр Beautiful Soup для выбора элемента figure.

description_items - это список, так же как и colswidth20. Первый содержит строки, извлеченные из раздела, содержащего ценную информацию о недвижимости, такую как площадь земли (в случае участков), местоположение, год постройки (в случае вилл), код недвижимости и другое. Каждая строка затем извлекается и очищается за пределами функции, то же самое касается colswidth20.

Заключение

Использование Playwright вместе с Beautiful Soup оказывается очень удобным способом создания скриптов для парсинга веб-страниц. Функции Playwright легко реализовать, нет необходимости устанавливать драйверы, как в случае с Selenium, что также упрощает интеграцию с приложениями. С другой стороны, Beautiful Soup является одной из самых широко используемых библиотек для парсинга, что обеспечивает множество доступных документационных ресурсов.

Простыми словами

Спасибо, что стали частью нашего сообщества! Перед тем, как уйти: