CoderCastrov logo
CoderCastrov
Парсер

Ежедневный мониторинг отзывов Google Maps на Python

Ежедневный мониторинг отзывов Google Maps на Python
просмотров
8 мин чтение
#Парсер

Расширенный парсинг для постепенного добавления самых свежих данных

В предыдущей статье я описал, как объединить две мощные библиотеки Python, Selenium и BeautifulSoup, чтобы создать автоматическое программное обеспечение, которое собирает отзывы с Google Maps.

Самая критическая проблема программного обеспечения заключается в ограничении, которое Google Maps накладывает на прокрутку страницы: после загрузки около 1200 отзывов в HTML API перестает отвечать. Это означает, что парсер может собрать максимум 1 тысячу отзывов для каждого предоставленного URL. В зависимости от применения или сценария анализа, это может быть небольшим набором данных.

В этой статье я объясню возможное обходное решение для решения проблемы, добавив еще два компонента: MongoDB и cron.


MongoDB

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

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

Мы будем использовать MongoDB для достижения одной основной цели: проверки, был ли уже собран отзыв.

Чтобы установить MongoDB, вы можете следовать пошаговому руководству на официальном сайте. Вам также нужно установить библиотеку Python для взаимодействия с ней:

conda install -c anaconda pymongo

cron

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

Шаблон файла выглядит следующим образом:

минута час день_месяца месяц день_недели имя_скрипта

Пример файла crontab:

0 4 * * * script.py 30 11 25 * * script2.py

script.py запланирован для запуска каждый день в 4:00 утра, а script2.py запланирован на 11:30 утра 25-го числа каждого месяца (* является заполнителем для "каждого").

Эта функция позволяет достичь другой цели: непрерывно проверять URL отзывов.

Эта команда поставляется вместе с установкой Linux и также включена в MAC OS. Набор команд, которые нам могут понадобиться, включает:

  • crontab -e: позволяет изменить файл crontab
  • crontab -l: показывает список уже запланированных скриптов
  • crontab file.txt: загружает file.txt как файл crontab, полезно для загрузки нескольких расписаний одновременно
  • grep CRON /var/log/syslog: проверяет журналы cron, в основном полезно для отладки

Примечание: Я обнаружил, что эта команда может сделать то же самое на компьютерах с Windows, но я не тестировал ее.


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

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

1. Уровень сохранения данных

Как уже упоминалось ранее, интеграция с MongoDB довольно проста, но нам нужно прояснить несколько терминов: в этом типе базы данных используются коллекции вместо таблиц и документы вместо строк, где каждый документ имеет структуру JSON. Кроме того, набор коллекций содержится внутри базы данных.

Для запуска приложения базы данных основная команда выглядит следующим образом:

mongod

Таким образом, по умолчанию Mongo работает на localhost:27017: для параметров команд и более продвинутых команд (например, для запуска в качестве службы) обратитесь к официальным руководствам.

Поскольку это база данных No-SQL, нам не обязательно определять фиксированную структуру перед началом работы. Если мы предоставляем допустимый документ, MongoDB автоматически сохраняет его в новой коллекции. Таким образом, код очень прост:

# определение MongoDB с использованием URL по умолчанию
client = MongoClient('mongodb://localhost:27017/')# установка соединения с базой данных        
collection = client['googlemaps']['review']

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

# вставка отзыва в коллекцию
collection.insert_one(r)

где r - это структура отзыва, которую мы получаем с помощью предыдущей реализации.

Если мы хотим проверить содержимое коллекции, мы можем использовать командную строку или, более интуитивно, использовать графический интерфейс пользователя (GUI) поверх нашей базы данных. Я использую бесплатный open-source инструмент Robo 3T, который позволяет легко просматривать данные:

2. Инкрементная логика

Инкрементная логика является основным изменением по сравнению с исходным кодом. Фактически, условие остановки в первой реализации (содержащейся в файле scraper.py в репозитории Github) проверяется после того, как было получено максимальное количество отзывов.

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

# проверка существования отзыва в коллекции
is_old_review = collection.find_one({'id_review': r['id_review']})

Примечание: поле, используемое для этой проверки, - это id_review, которое получается напрямую из Google Maps, а не _id, который, напротив, генерируется MongoDB и не имеет никакого отношения к самим данным.

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

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

# функция остановки для работы инкрементально
def stop(r, collection, min_date_review):        
    is_old = collection.find_one({'id_review': r['id_review']})        
    if is_old is None and r['timestamp'] >= min_date_review: 
        return False
    else: 
        return True

Поскольку Google Maps предоставляет только относительную дату, такую как "2 дня назад", "1 месяц назад", нам нужна дополнительная функция, которая сравнивает это значение с текущей датой и возвращает временную метку отзыва:

# функция разбора относительной даты
def parse_relative_date(string_date):
    curr_date = datetime.now()
    split_date = string_date.split(‘ ‘)
    n = split_date[0]
    delta = split_date[1]    if delta == ‘year’:
        return curr_date — timedelta(days=365)
    elif delta == ‘years’:
        return curr_date — timedelta(days=365 * int(n))
    elif delta == ‘month’:
        return curr_date — timedelta(days=30)
    elif delta == ‘months’:
        return curr_date — timedelta(days=30 * int(n))
    elif delta == ‘week’:
        return curr_date — timedelta(weeks=1)
    elif delta == ‘weeks’:
        return curr_date — timedelta(weeks=int(n))
    elif delta == ‘day’:
        return curr_date — timedelta(days=1)
    elif delta == ‘days’:
        return curr_date — timedelta(days=int(n))
    elif delta == ‘hour’:
        return curr_date — timedelta(hours=1)
    elif delta == ‘hours’:
        return curr_date — timedelta(hours=int(n))
    elif delta == ‘minute’:
        return curr_date — timedelta(minutes=1)
    elif delta == ‘minutes’:
        return curr_date — timedelta(minutes=int(n))
    elif delta == ‘moments’:
        return curr_date — timedelta(seconds=1)

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

min_date_review = datetime.strptime('2022-01-01', '%Y-%m-%d')
stop = False
offset = 0
n_new_reviews = 0
while not stop:
   rlist = scraper.get_reviews(offset)
   for r in rlist:
       # вычисляем дату отзыва и сравниваем с введенной минимальной датой отзыва
       r['timestamp'] = parse_relative_date(r['relative_date'])
       stop = stop(r, collection, min_date_review)
       if not stop:
           collection.insert_one(r)
           n_new_reviews += 1
       else:
           break
   offset += len(rlist)

Более разработанную версию можно найти на Github в скрипте monitor.py.

3. Периодическое выполнение

После того, как мы определили этот скрипт, мы можем запускать его вручную столько раз, сколько нам нужно, или мы можем запланировать его с помощью crontab:

  • В первом запуске для каждого предоставленного URL будут сохранены отзывы до тех пор, пока не будет найден отзыв, старше указанной min_date_review.
  • Во всех остальных запусках скрапер будет извлекать только самые свежие отзывы, которые еще не были сохранены.

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

0 7 * * * monitor.py

Что касается частоты запуска, мы можем настроить crontab на выполнение каждый день, каждый час или даже каждую минуту. Выбор зависит в основном от целевых URL-адресов: чем более известное место, тем чаще будут добавляться новые отзывы. Как уже было сказано в начале, за один проход можно получить около 1000 отзывов, прежде чем API перестанет отвечать, поэтому если определенное место получает 3000 отзывов в день, то его нужно запланировать каждые 8 часов, чтобы не пропустить никаких данных.

Как отказ от ответственности, я не знаю, может ли планирование с частотой в минуты привести к блокировке/черному списку со стороны Google: я тестировал этот код, запуская его несколько раз в день без каких-либо особых проблем.

Ограничения

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

Фактически, прошлые отзывы недоступны, если они выходят за пределы 1200 отзывов, поэтому рассмотрение настройки скрипта мониторинга в момент t0 позволяет получить до t0 около 1K отзывов на каждый URL-адрес, а затем максимум 1K отзывов на каждый URL-адрес добавляется каждый раз при выполнении кода.


Заключение

В этом руководстве я объяснил более сложное программное обеспечение для улучшения парсинга отзывов с добавлением уровня сохранения данных.

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

Если вы хотите посмотреть полный код, вот ссылка на Github: не стесняйтесь вносить улучшения или находить ошибки, или просто посмотрите его, чтобы проверить последние функции, благодаря также вкладу других участников.

Спасибо за прочтение этой статьи, любая обратная связь будет очень ценна!