Сбор данных для процесса разработки приложений E2E ML.

Хорошо. Давайте просто приступим к делу!

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

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

TL;DR

Вот ссылка на код на GitHub:

Начиная

Мы будем извлекать списки квартир из https://www.propertypro.ng/ и сохранять добычу в файлах CSV для дальнейшего использования. Вот как выглядит сайт:

Нажимаем Аренда, вводим Лагос в строке поиска под Аренда, нажимаем раскрывающееся меню Тип, выберите Квартиры и нажмите кнопку Поиск. Новая страница должна выглядеть так:

Прокручивая вниз, мы должны увидеть что-то похожее на это:

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

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

Мы будем использовать библиотеку Python Requests для получения данных веб-страниц и BeautifulSoup для анализа HTML. Начнем с импорта необходимых библиотек.

import requests
import numpy as np
import pandas as pd
from bs4 import BeautifulSoup

Numpy и Pandas импортированы по привычке, LOL. Но в какой-то момент они должны сыграть свою роль.

Теперь давайте скопируем URL-адрес нашей текущей страницы на веб-сайте и передадим его функции get библиотеки requests. Затем мы передаем содержимое в BeautifulSoup:

r = requests.get('https://www.propertypro.ng/property-for-rent/flat-apartment/?search=Lagos&bedroom=&min_price=&max_price=')
soup = BeautifulSoup(r.content, 'html5lib')

Мы могли бы распечатать суп и проверить. Мы получим это;

Это HTML-код, из которого состоит веб-страница, и после проверки кажется, что он содержит все, что нам нужно. Теперь давайте исследуем элементы страницы и выясним, как анализировать данные страницы.

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

При проверке инструмента Inspect от RHS появляется стрелка, указывающая вверх в верхней левой части части экрана инструмента Inspect. Щелкните по нему, а затем щелкните по данным на странице. Обратите внимание на точку, выделенную синим цветом - она ​​должна выглядеть так:

Если щелкнуть значок раскрывающегося списка рядом с ним и продолжить изучение, мы заметим, что адрес заключен в тег h4, цена - в тег h3, заключенный в тег div, описание - в тег div и т. Д.

Вытаскивание всех индивидуальных характеристик

Давайте теперь приступим к созданию нашего кода, чтобы извлечь все индивидуальные характеристики каждого объявления о квартире:

listing_divs = soup.select('div[class=single-room-sale\ listings-property]')

В приведенном выше коде я использую select функцию BeautifulSoup для всех divs, у которых есть class с именем single-room-sale listings-property, и сохраняю их в списке под названием isting_divs. Давайте проверим количество элементов в list_divs с помощью функции len:

print("Number of apartment listings on a page:", len(listing_divs))

Вот результат:

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

listing_divs[0]

Это дает нам следующий результат:

Если вы посмотрите достаточно внимательно, вы сможете увидеть все особенности, которые мы хотим извлечь из каждого списка квартир. Мы начнем извлекать их по очереди:

listing_divs[0].select('h4')[0].text

Здесь мы пытаемся получить адрес, заключенный в тег h4. Результат находится в списке, поэтому нам пришлось извлечь его, вызвав первый индекс списка. Затем мы вызываем функцию text, чтобы получить строку из тега. Это дает нам следующий результат:

Это довольно просто! Затем давайте попробуем извлечь ценник:

listing_divs[0].select('h3[class*=listings-price]')[0].text.strip()

Цена заключена в тег h3 с class, часть названия которого - listings-price. Функция strip, прикрепленная к концу, предназначена для удаления начальных и конечных пробелов вокруг полученного значения. Вот результат:

Далее идет количество спален, ванных комнат и туалетов:

listing_divs[0].select('div[class*=fur-areea]')[0].text.strip().split('\n')

Все 3 функции расположены в теге div, где class часть его имени - fur-areea. Они находятся в отдельных тегах span, но как только мы вызовем функцию text в div, каждая строка в теге div будет извлечена как одна строка. Мы применили функцию strip для удаления начальных и конечных пробелов, затем мы разбили полученную строку на escape-символы новой строки в строке. Вот результат:

Последний объект, который нужно извлечь, - это данные описания.

Видите тему Обслуживаемая, обведенную фиолетовым цветом? Видите линию прямо под ним? Это то, что мы извлечем дальше, поскольку они, кажется, предоставляют важную информацию о квартире, которую мы могли бы не получить, просто глядя на цену и количество комнат. Начнем поиск темы:

listing_divs[7].select('div[class*=furnished-btn]')[0].text.replace('\n', ' ').strip()

Тема расположена в теге div с class, часть его имени - furnished-btn. Затем мы заменили каждый escape-символ новой строки одним пробелом и, наконец, удалили начальные и конечные пробелы. Вот результат:

Здесь у нас есть обслуживаемые и новостройки.

Очистив линию прямо под ней, мы получим:

listing_divs[7].select('div[class*=result-list-details]')[0].p.text.replace('Read more', '').replace('FOR RENT:', '').strip()

Здесь мы извлекаем из тега div значение class, часть имени которого - result-list-details. Затем мы получили тег p внутри тега div и извлекли его строку. Наконец, мы заменили Подробнее и В АРЕНДУ: пустыми строками и удалили лишние пробелы. И мы получили это:

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

Тестирование всего вместе

Посмотрите код ниже:

all_listings_data = []
for indv_listing in listing_divs:
    address = indv_listing.select('h4')[0].text
    price = indv_listing.select('h3[class*=listings-price]')[0].text.strip()
    listing_data = [address, price]
    utilities = indv_listing.select('div[class*=fur-areea]')[0].text.strip().split('\n')
    listing_data.extend(utilities)
    description = indv_listing.select('div[class*=result-list-details]')[0].p.text.replace('Read more', '').replace('FOR RENT:', '').strip()
    extra = indv_listing.select('div[class*=furnished-btn]')[0].text.replace('\n', ' ').strip()
    listing_data.append(extra + description)
    all_listings_data.append(listing_data)
    
pd.DataFrame(all_listings_data)

Теперь давайте рассмотрим все по порядку:

  • Я инициализировал пустой список с именем all_listings_data и прошел через listing_divs (помните, что listing_divs содержит 20 списков квартир).
  • Для каждого цикла я извлек адрес и цену и поместил их в список под названием listing_data. Я извлек количество комнат (кроватей, ванн и туалетов) и добавил их к listing_data. Я также извлек описание и тему в переменных description и extra соответственно, объединил две строки вместе и добавил их в listing_data. Затем я добавляю listing_data к all_listings_data.
  • Наконец, я преобразовал all_listings_data в Pandas DataFrame.

А теперь посмотрим на результат:

Большой! Наша работа почти закончена. Все, что осталось сделать, это написать динамическую функцию, которая будет получать списки квартир - с указанием названия города - и конвертировать их в Pandas DataFrame и сохранять локально в виде файла CSV.

Динамическая функция для получения данных о списках квартир

def parse_listing_data(location, max_price, rows_of_data=2000):
    all_listings_data = []
    page_num = 0
    
    while page_num < (rows_of_data / 20):
        url = "https://www.propertypro.ng/property-for-rent/flat-apartment/?search=" + location \
              + "&bedroom=&min_price=&max_price=" + str(max_price) + "&page=" + str(page_num)
        r = requests.get(url)
        soup = BeautifulSoup(r.content, 'html5lib')
        listing_divs = soup.select('div[class=single-room-sale\ listings-property]')
        if len(listing_divs) == 0:
            break 
            
        for indv_listing in listing_divs:
            address = indv_listing.select('h4')[0].text
            price = indv_listing.select('h3[class*=listings-price]')[0].text.strip()
            listing_data = [address, price]
            utilities = indv_listing.select('div[class*=fur-areea]')[0].text.strip().split('\n')
            listing_data.extend(utilities)
            description = indv_listing.select('div[class*=result-list-details]')[0].p.text.strip().replace('Read more', '').replace('FOR RENT: ', '')
            extra = indv_listing.select('div[class*=furnished-btn]')[0].text.strip().replace('\n', ' ')
            listing_data.append(extra + description)
            all_listings_data.append(listing_data)
        page_num += 1    
    listing_df = pd.DataFrame(all_listings_data, columns=['neighborhood', 'price', 'beds', 'baths', 'toilets', 'extra'])
    
    listing_df.to_csv("rent_listings_"+location+".csv", index=False)
    return listing_df

Здесь много всего происходит, поэтому давайте рассмотрим код построчно:

  • Итак, мы создаем функцию с именем parse_listing_data и устанавливаем 3 аргумента, один из которых необязательный. Первое - это место, для которого мы хотим получить данные, второе - это потолок цен - очистка данных о квартирах с завышенной ценой не принесет особой пользы, в то время как последний параметр - это количество данных о списках квартир, которые мы хотели бы получить. .
  • Затем у нас есть пустой список all_listings_data и page_num, установленный на 0.
  • У нас есть цикл while с условием итерации, если page_num не превышает количество страниц, с которых мы хотим получить данные. Предположим, мы указали, что нам нужно только 200 строк данных, поскольку страница содержит 20 списков квартир, условие цикла гарантирует, что мы перебираем только 10 страниц данных.
  • Затем мы разбиваем URL-адрес, чтобы мы могли вписать location, max_price и page_num в URL-адрес, используя конкатенацию строк.
  • Следующая строка получает HTML-данные URL-адреса с помощью функции requests ’get, и мы передаем ее содержимое в BeautifulSoup для анализа HTML.
  • После этого мы выбираем все объявления о квартирах на странице. Мы также указываем, что если длина listing_divs равна нулю, что означает, что на этой странице нет списков квартир, цикл должен быть завершен.
  • Затем мы просматриваем каждое отдельное объявление и получаем его адрес, цену, количество (спальни, ванные комнаты, туалеты) и описание. Затем нужно добавить их в all_listing_data.
  • page_num увеличивается на 1, и цикл с итерацией снова page_num не меньше количества страниц, с которых мы хотим очистить данные.
  • Следующее, что нужно сделать, это преобразовать all_listing_data в Pandas DataFrame, указав при этом имена столбцов, а затем сохранить результат в CSV-файл, имя которого будет дополнено указанным именем местоположения.
  • Затем мы возвращаем DataFrame и все!

Использование функции для получения данных по разным городам

Наконец, давайте проверим функцию, передав ей необходимые аргументы. Начнем с Лагоса:

lagos_data = parse_listing_data('lagos', 2500000, 5000)
lagos_data

Здесь мы передаем lagos в качестве предпочтительного местоположения, 2,5 миллиона найр в качестве максимальной цены и 5000 в качестве количества строк, которое нам нужно. Вот наш результат:

Вот и все! CSV-файл, содержащий эти данные, был автоматически сохранен в нашем локальном хранилище (как мы указали в коде функции). Также обратите внимание, что некоторые квартиры оценены в долларах! Об этом позаботятся во время предварительной обработки.

Давайте попробуем то же самое для Ибадана:

ibadan_data = parse_listing_data('oyo', 3000000, 2000)
ibadan_data

Мы указали oyo, 3 миллиона найр в качестве максимальной цены квартиры и 2000 в качестве строк с данными, которые нам нужны.

Увы! В Ибадане доступно только 512 строк данных о квартирах.

Мы делаем то же самое для Абуджи, Огуна и Порт-Харкорта.

abuja_data = parse_listing_data('abuja', 4000000, 3000)
ogun_data = parse_listing_data('ogun', 2000000, 2000)
ph_data = parse_listing_data('rivers', 3000000, 2500)

Когда вы закончите, вы заметите, что в Абудже было получено около 700 строк данных. Ogun дал около 190 строк, в то время как Port Harcourt дал всего 100 строк данных.

И мы здесь закончили! Приветствую, если вы зашли так далеко!

Резюме

В этом руководстве мы систематически проходим весь процесс создания наборов данных со списками квартир, удаляя данные с веб-сайта №1 по ценам на недвижимость в Нигерии. Вот код на GitHub.

Следующим на очереди идет Data Wrangling, который включает в себя очистку данных до формата, с которым мы можем работать, и извлечение новых функций из существующих функций. Это должно занимать более 40% нашего общего времени разработки.

До свидания, ребята! Если у вас есть какие-либо вопросы, не стесняйтесь обращаться ко мне через комментарии здесь, в Twitter или через LinkedIn.