Сталкиваетесь ли Вы с необходимостью использовать внешние источники данных? Если да, то Вам пригодится алгоритм автоматизированного сбора информации с сайта – парсер. Разберём процесс создания такого алгоритма на примере сайта ЕФРСБ.           Перед Data Science специалистами регулярно встают задачи, для решения которых необходима информация из внешних источников, и часто её объёмы такие, что ручной поиск занимает непозволительно много времени. Автоматизированный сбор данных с сайта (парсинг) позволяет получить необходимые для задачи сведения, экономя время.           Одна из таких задач встала перед нашей командой: понадобились данные о процедуре признания физических лиц банкротами. Для этого был разработан алгоритм парсинга сайта Единого федерального реестра сведений о банкротстве (ЕФРСБ) с использованием библиотек requests и bs4. В настоящей статье предлагаю рассмотреть процесс создания этого парсера и познакомить Вас с решениями некоторых проблем, с которыми мы столкнулись.           Разработку алгоритма мы решили разбить на 2 части: Извлечение данных по одному клиенту с проработкой всех возможных сценариев (см. далее) Обёртка в цикл           Итак, мы имеем ФИО и дату рождения клиента:   и хотим узнать следующее: дата судебного решения о признании его банкротом (если клиент есть в реестре) и дата получения требований кредитора. Для этого необходимо: на странице DebtorSearch ввести в соответствующие поля ФИО (поле поиска по ДР отсутствует), перейти к результатам поиска, затем перейти к карте клиента. С карты клиента необходимо будет перейти к сообщениям: «сообщение о судебном акте» и «уведомление и получении требований кредитора».           Как осуществить каждый из этих переходов? Метод requests.get(), реализующий отправку GET запроса, имеет множество параметров, но нас будут интересовать 2: url и headers. Мы видим, что url страницы до ввода ФИО и нажатия «поиск» совпадает с url страницы после этих действий. Разница между этими страницами заключается в Request Headers (мы можем увидеть их при включённых DevTools). Обратим внимание на header cookie:           DebtorSearch до ввода ФИО             DebtorSearch после ввода ФИО и поиска   PersonPrivateCard             Мы видим, что в cookie заключена информация о введённом ФИО, т.е. мы можем перейти к этой странице следующим запросом (закодировав ФИО в URL с помощью библиотеки urllib):           В response_debtors_search.text теперь содержится текст html-страницы ответа, и в нём мы можем найти ссылку для перехода на карту клиента. Отметим здесь, что мы стараемся предусмотреть все возможные сценарии, т.е. должны предположить, что найдутся два и более клиента с одинаковыми ФИО. Мы знаем дату рождения клиента, поэтому будем открывать по очереди карты всех найденных по ФИО клиентов, пока не найдём клиента с нужной датой рождения. Ссылки извлечём с помощью библиотеки re:             Переход на карту первого клиента:             Имея текст html-страницы карты клиента, мы должны получить дату рождения и ссылки для перехода на сообщения о судебном акте и уведомлении требований кредитора. Здесь следует отметить, что существует 2 разных подхода к поиску данных в тексте ответа: один заключается в поиске паттернов в тексте (так мы искали ссылки для перехода к картам клиентов). Другой подход подразумевает использование библиотеки bs4: она позволяет создать вложенную структуру данных из текста html-страницы, которая повторяет структуру html-разметки:              В такой структуре можно осуществлять поиск не по фрагменту текста, а, например, по атрибутам html-тегов. Преимущество такого подхода в том, что он не привязан к паттерну в тексте, в котором может быть опечатка (и тогда поиск по паттерну не даст результата). С другой стороны, у него есть и существенный недостаток: нельзя быть уверенным, что атрибуты тегов (например, id) не менялись в течение эксплуатации сайта.           Мы извлечём дату из текста с помощью регулярного выражения (подразумевающего любой формат) из подстроки, начинающейся с искомого паттерна:           Теперь мы можем извлечь дату рождения с карты клиента:             Чтобы продемонстрировать возможности bs4, найдём ссылки для перехода на необходимые сообщения, перебирая все теги <a> (ссылки), пока не найдём ссылки с заданным текстом:             Отметим, что из логики процесса следовало, что нас интересует последнее по дате сообщение о судебном акте, т. е. links_for_message_1[-1], откуда возьмём дату принятия решения так же, как и дату рождения из карты клиента. Что касается второго сообщения, мы будем перебирать все уведомления, пока не найдём в тексте уведомления название компании-кредитора; в нём извлечём дату (опять же, аналогично дате рождения с карты клиента). Сам процесс перебора идентичен перебору карт клиентов до обнаружения нужной даты рождения – поэтому мы не будем приводить здесь этот код, а отметим последний важный факт для реализации запросов в цикле.           На сайте реестра установлено ограничение на количество запросов в единицу времени, и при превышении этого порога (точное значение неизвестно) ip-адрес блокируется на час. Решить эту проблему можно либо с помощью проксирования, либо добавив в циклы паузы – например, с помощью time.sleep(random.random() * 2).            В заключение отметим, что я показал основные инструменты, которыми осуществляется парсинг, и опустил описание таких тонкостей, как обработка исключений, создание чекпойнтов и т. д. Также отмечу, что в ходе работы над этим алгоритмом я не столкнулся с таким механизмом защиты, как captcha, и способы его обхода/избегания стали для меня дальнейшим направлением развития в теме парсинга.           Надеюсь, что описанные методы окажутся Вам полезными и           Спасибо за внимание! Профессиональное сообщество