CoderCastrov logo
CoderCastrov
Парсер

Парсинг веб-сайта Sewakost с использованием Python BeautifulSoup

Парсинг веб-сайта Sewakost с использованием Python BeautifulSoup
просмотров
11 мин чтение
#Парсер

Введение

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

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

В этой статье будет выполнен парсинг информации о комнатах в аренду в Южном Джакарте с веб-сайта sewakost.com с использованием BeautifulSoup. Результаты парсинга будут сохранены в файле .tsv (значения, разделенные табуляцией), а затем будет проведен дальнейший анализ. Серия статей будет следующей:

**Часть 0: Введение в проект и парсинг (Эта статья)**
Часть 1: Анализ количества и цены комнат в зависимости от удобств
Часть 2: Ценообразование на комнаты в зависимости от удобств

Отказ от ответственности:

Некоторые веб-сайты разрешают парсинг, а некоторые ограничивают доступ к парсингу на своем сайте. Чтобы узнать, разрешен ли парсинг веб-сайта, можно посмотреть файл "robots.txt" этого сайта. Файл можно получить, добавив "/robots.txt" к основному URL веб-сайта, который вы хотите спарсить. Чтобы узнать больше о правилах, указанных в файле robots.txt, можно обратиться к ссылке здесь.

В этом проекте в первом и втором правилах файла robots.txt sewakost.com указаны правила "User-agent: *" и "Allow: /", которые разрешают любому пользователю выполнять парсинг данных на sewakost.com, за исключением данных на URL, начинающихся с "Disallow", таких как sewakost.com/plugins, sewakost.com/libs и т.д. Подробности файла robots.txt на sewakost.com можно увидеть на рисунке ниже.


Roadmap и Предварительные требования

Мы будем использовать Python в качестве языка программирования и Vscode в качестве текстового редактора. Библиотекой, которую мы будем использовать, будет BeautifulSoup, которая упростит извлечение данных из HTML-документа. Ниже приведены шаги, которые мы будем выполнять:

Предварительные требования для понимания шагов в этой статье следующие:


Импорт библиотек

Мы будем импортировать библиотеки, которые будут использоваться в этом проекте.

import requests
import pandas as pd
from tqdm import tqdm
from bs4 import BeautifulSoup
import warnings
warnings.filterwarnings("ignore")
  • requests позволяет нам выполнять HTTP-запросы к веб-сайту, а затем возвращать объект ответа со всеми данными на этом веб-сайте (например, HTML).
  • pandas используется для преобразования данных в формате таблицы и экспорта в формате tsv-файла.
  • tqdm позволяет получать индикатор прогресса в процессе выполнения цикла.
  • BeautifulSoup (bs4) используется для извлечения и парсинга данных из HTML-файла, а затем преобразования их в объект BeautifulSoup, из которого мы можем получить доступ и сохранить необходимые данные.
  • warnings помогает скрыть предупреждающие сообщения из нашего кода.

Парсинг каждой страницы и получение информации о квартирах

Перед тем как начать парсинг, необходимо изучить структуру и содержимое HTML-тегов веб-сайта, который мы собираемся парсить. Мы будем парсить сайт sewakost.com, чтобы получить информацию о квартирах в Южной Джакарте (как показано на рисунке ниже). Вы можете получить доступ к этому веб-сайту, перейдя по этой ссылке и просмотреть список квартир в Южной Джакарте.

Это первая страница списка квартир в Южной Джакарте. Если посмотреть вниз страницы, можно увидеть, что мы можем выбрать следующую страницу (как показано на рисунке ниже).

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

На третьей странице URL будет выглядеть так:

И так далее. Мы будем использовать этот шаблон для доступа к каждой странице списка квартир в Южной Джакарте. Мы будем итерироваться по каждой странице, получать доступ к ней и преобразовывать ее в объект BeautifulSoup.

# Итерация по каждой странице, до 40 страниц (остановка перед 41 страницей)
for page_num in tqdm(range(1, 41)):
    # Получение доступа к URL каждой страницы
    url_sewakost = "https://www.sewakost.com/jakarta/selatan/index%s.html" % page_num
    # print(url_sewakost)
    r = requests.get(url_sewakost, verify=False)
    s = BeautifulSoup(r.content, "html.parser")

После преобразования содержимого страницы в объект BeautifulSoup, мы будем получать доступ к информации, содержащейся в каждой карточке списка квартир. Чтобы увидеть, какая информация содержится в каждой карточке квартиры, можно щелкнуть правой кнопкой мыши на любой карточке квартиры и выбрать "Inspect element" (Просмотреть элемент).

Из результатов "Inspect element" выше видно, что каждая карточка, содержащая информацию о квартире, находится внутри div с классом "main-column clearfix". Мы будем получать все div с этим классом, чтобы получить список всех квартир на странице.

# Получение доступа к карточкам с информацией о квартирах
find_kos = s.findAll("div", attrs = {"class": "main-column clearfix"})

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

URL и заголовок

Мы можем видеть, что URL находится в теге a с классом "link-large". Этот тег a обернут в тег li, а тег li обернут в тег ul. Внутри этого тега находится информация о заголовке квартиры в атрибуте title тега и ссылка на квартиру в атрибуте href.

Таким образом, если объект квартиры называется row, то для доступа к заголовку и ссылке каждой квартиры можно использовать следующий код:

kos = {}
kos['url'] = row.ul.li.a['href']
kos['название'] = row.ul.li.a['title']

Цена

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

price = row.find("span", attrs = {"class": "price-tag"})
room['price'] = price.span.text

Полный код для итерации по каждой карточке

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

# Итерация по каждой странице
for page_num in tqdm(range(1, total_page+1)[:40]):
    # Получение URL каждой страницы
    url_sewakost = "https://www.sewakost.com/jakarta/selatan/index%s.html" % page_num
    # print(url_sewakost)
    r = requests.get(url_sewakost, verify=False)
    s = BeautifulSoup(r.content, "html.parser")
    
    # Получение карточек с информацией о комнатах
    find_rooms = s.findAll("div", attrs = {"class": "main-column clearfix"})
    # Получение информации о комнатах, если не удалось, то продолжаем итерацию
    for row in find_rooms:
        room = {}
        room['url'] = row.ul.li.a['href']
        room['name'] = row.ul.li.a['title']
        price = row.find("span", attrs = {"class": "price-tag"})
        room['price'] = price.span.text

Мы будем сохранять каждый словарь в список. Кроме того, во время итерации по каждой карточке мы также будем обрабатывать ошибки, чтобы процесс парсинга продолжался, даже если возникнет ошибка при получении данных из одной из карточек комнат. Таким образом, окончательный код для итерации по каждой странице и каждой карточке на каждой странице будет выглядеть следующим образом:

# Инициализация списка
sewakost = []

# Итерация по каждой странице
for page_num in tqdm(range(1, total_page+1)[:40]):
    # Получение URL каждой страницы
    url_sewakost = "https://www.sewakost.com/jakarta/selatan/index%s.html" % page_num
    # print(url_sewakost)
    r = requests.get(url_sewakost, verify=False)
    s = BeautifulSoup(r.content, "html.parser")
    
    # Получение карточек с информацией о комнатах
    find_rooms = s.findAll("div", attrs = {"class": "main-column clearfix"})
    # Получение информации о комнатах, если не удалось, то продолжаем итерацию
    for row in find_rooms:
        try:
          room = {}
          room['url'] = row.ul.li.a['href']
          room['name'] = row.ul.li.a['title']
          price = row.find("span", attrs = {"class": "price-tag"})
          room['price'] = price.span.text
          sewakost.append(room)
        except:
          continue

Если мы преобразуем список sewakost в датафрейм, то получим следующие данные:

Получение доступа к каждому URL-адресу квартиры

Мы получили URL-адреса, названия и цены для каждой квартиры в Южной Джакарте. Всего мы получили 958 квартирных данных для парсинга с 40 страниц. Теперь мы хотим получить дополнительную информацию для каждой квартиры, такую как тип квартиры, адрес, кондиционер, бесплатный Wi-Fi, внутренняя ванная комната, другие удобства в комнате и общие удобства. Давайте посмотрим, что произойдет, если мы откроем один из URL-адресов квартиры.

На этой информации о квартире мы хотим получить информацию о типе квартиры, адресе, кондиционере, бесплатном Wi-Fi, внутренней ванной комнате, других удобствах в комнате и общих удобствах. Давайте проведем инспекцию элемента, чтобы узнать, как извлечь эти данные с помощью BeautifulSoup.

Типы комнат

Типы комнат

Адрес

Адрес

Кондиционер

Кондиционер

Если мы обратим внимание на типы комнат, адрес, наличие кондиционера, бесплатный Wi-Fi и внутренний санузел, то мы увидим, что у них есть общий шаблон в идентификаторе div, который их оборачивает, и он всегда начинается с _df_field_{информация}_.

Кроме того, значение каждой информации всегда хранится в теге div с именем класса "value". Из-за этого общего шаблона мы будем итерироваться, чтобы получить значения этой информации.

# Итерация по каждому URL комнаты
for kos in tqdm(sewakost):
    req_kos = requests.get(kos['url'], verify = False)
    soup_kos = BeautifulSoup(req_kos.content, "html.parser")

    # Список информации, которую нужно получить
    informasi = ['jenis', 'address', 'aircon', 'free_wifi', 'kamar_mandi_dalam']

    # Получение информации и сохранение в соответствующем столбце
    for info in informasi:
      value = soup_kos.find("div", attrs={"id": "df_field_%s" % info}).find("div", attrs={"class":"value"})
      kos[info] = value.text.strip()

Объяснение шагов кода выше следующее:

  • Итерация по строкам в датафрейме sewakost и получение URL каждой комнаты, затем преобразование HTML-страницы комнаты в объект BeautifulSoup.
  • Создание списка информации, содержащего шаблон после df_field_{информация}.
  • Итерация по каждой информации в списке и поиск соответствующего div по его идентификатору. Например, для типа комнат будет искаться div с идентификатором df_field_jenis, а для кондиционера - df_field_aircon.
soup_kos.find("div", attrs={"id": "df_field_%s" % info})
  • После получения тега div с информацией мы снова ищем div с именем класса "value", который содержит значение искомой информации, и сохраняем его в переменную "value".
value = soup_kos.find("div", attrs={"id": "df_field_%s" % info}).find("div", attrs={"class":"value"})
  • Затем мы получаем значение, обращаясь к атрибуту .text объекта value и используя функцию strip(), чтобы удалить все пробелы в полученном тексте. Значение сохраняется в соответствующем столбце информации в датафрейме kos.

После этого мы хотим получить каждое удобство, как внутри комнаты, так и общие удобства в комнате. Давайте проанализируем элемент для этих удобств.

Удобства в комнате

Общие удобства

Если мы внимательно посмотрим, как на удобства в комнате, так и на общие удобства, они обернуты тегом div с именем id "df_field_fasilitas_kamar" для комнаты и "df_field_fasilitas_kost" для общих удобств. Затем значения каждой информации хранятся в атрибуте title тега li внутри div с классом "value".

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

# Получение дополнительной информации о удобствах в комнате и общих удобствах
kos['fasilitas_kamar'], kos['fasilitas_kost'] = [], []
fasilitas = ['fasilitas_kamar', 'fasilitas_kost']

# Сбор удобств -> сохранение в столбце
for f in fasilitas:
  list_f = soup_kos.find("div", attrs={"id": "df_field_%s" % f}).find("div", attrs={"class":"value"})
  for each_f in list_f.findAll("li"):
    kos[f].append(each_f['title'])

Объяснение этапов кода выше следующее:

  • Первая строка создает новый столбец с пустыми значениями в dataframe kos.
  • Создание списка удобств в соответствии с шаблоном после id тега div, т.е. df_field_{fasilitas}.
  • Итерация по списку удобств и поиск div с id df_field_fasilitas_kamar и df_field_fasilitas_kost.
  • Затем снова ищем тег div внутри него с именем класса "value" и сохраняем его в переменной list_f.
  • В объекте list_f мы ищем все теги li и итерируем каждый тег li.
  • Каждый объект each_f, который стал объектом, будет получать его атрибут title, то есть значение удобства, которое хотим получить. Как получить его: each_f[‘title’], а затем его значение добавляется в соответствующий столбец удобств в dataframe kos.

Полный код для получения всей информации о типе, адресе, кондиционере, Wi-Fi, ванной комнате, дополнительных удобствах в комнате и общих удобствах выглядит следующим образом (с дополнительной обработкой ошибок try except):

# Итерация по каждому URL-адресу квартиры
for kos in tqdm(sewakost):
  try:
    req_kos = requests.get(kos['url'], verify = False)
    soup_kos = BeautifulSoup(req_kos.content, "html.parser")

    # Список информации, которую нужно получить
    informasi = ['jenis', 'address', 'aircon', 'free_wifi', 'kamar_mandi_dalam']

    # Получение информации -> сохранение в соответствующем столбце
    for info in informasi:
      value = soup_kos.find("div", attrs={"id": "df_field_%s" % info}).find("div", attrs={"class":"value"})
      kos[info] = value.text.strip()
    
    # Получение информации о дополнительных удобствах в комнате и общих удобствах
    kos['fasilitas_kamar'], kos['fasilitas_kost'] = [], []
    fasilitas = ['fasilitas_kamar', 'fasilitas_kost']

    # Сбор удобств -> сохранение в соответствующем столбце
    for f in fasilitas:
      list_f = soup_kos.find("div", attrs={"id": "df_field_%s" % f}).find("div", attrs={"class":"value"})
      for each_f in list_f.findAll("li"):
        kos[f].append(each_f['title'])

  except:
    pass

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

Экспорт датасета

После получения всей информации о каждом жилье, результатом будет следующий датафрейм:

Здесь мы извлекли данные о жилье, такие как URL, название, цена, тип, адрес и удобства внутри жилья. Колонки "удобства в комнате" и "удобства в жилье" все еще представлены в виде списка, содержащего запятые. Поэтому, если мы хотим сохранить этот датафрейм для дальнейшего анализа, лучше сохранить его в формате .tsv (значения, разделенные табуляцией).

pd.DataFrame(sewakost).to_csv('sewakost_40pages.tsv', sep="\t", index=False)

В следующей статье будет проведен анализ и визуализация датасета жилья в Южной Джакарте. Полный код можно найти по ссылке github.

Надеюсь, это будет полезно! Удачи!