Как рекурсивно собрать названия школ и адреса сайтов
Задача сбора данных из иерархической структуры веб-страниц - типичная головоломка для веб-скрапинга. Допустим, у вас есть каталог школ, где навигация идёт от региона к городу, затем к району и только потом к списку школ с адресами их сайтов. Вручную перебирать все уровни вложенности неэффективно. В этой статье мы разберём рекурсивный подход на Python с использованием библиотек requests и BeautifulSoup, который избавит от кучи вложенных циклов и позволит гибко обрабатывать любую глубину иерархии.
Понимание структуры данных
Прежде чем писать код, нужно чётко определить типы страниц. В вашем случае есть два вида:
- Страница-навигатор - содержит ссылки на города, районы или регионы (например, список
внутри).- Целевая страница - содержит таблицу со школами, где каждая строка включает название школы и ссылку на её веб-сайт (ссылки внутри
в таблице).Конечная цель - добраться до целевых страниц и извлечь данные. Иерархия может быть разной длины: от двух уровней (город -> школы) до четырёх (регион -> область -> район -> школы).
Рекурсивный алгоритм обхода
Рекурсия идеально подходит для таких задач, так как глубина иерархии заранее неизвестна. Основная идея: функция проверяет, является ли текущая страница целевой (содержит таблицу школ). Если да - парсит данные. Если нет - собирает все ссылки со страницы и рекурсивно вызывает себя для каждой из них.
import requests from bs4 import BeautifulSoup def parse_schools(url, visited=None): if visited is None: visited = set() if url in visited: return [] visited.add(url) response = requests.get(url) soup = BeautifulSoup(response.text, 'html.parser') # Проверяем, есть ли на странице таблица со школами table = soup.find('table') if table and table.find_all('a'): # Извлекаем названия школ и ссылки из таблицы schools = [] for row in table.find_all('tr'): cells = row.find_all('td') if len(cells) >= 2: name = cells[0].get_text(strip=True) link = cells[1].find('a') if link and link.get('href'): schools.append({ 'name': name, 'url': link['href'] }) return schools else: # Иначе собираем все ссылки-навигаторы (внутри div) links = soup.select('div a[href]') all_schools = [] for link in links: href = link['href'] if not href.startswith('http'): href = url.rstrip('/') + '/' + href.lstrip('/') all_schools.extend(parse_schools(href, visited)) return all_schoolsКлючевые моменты реализации
Избежание повторного посещения
Используйте множество
visited, чтобы не попасть в бесконечный цикл при наличии перекрёстных ссылок. Каждый новый URL проверяется перед обработкой.Обработка относительных ссылок
Ссылки на страницах часто бывают относительными. Функция должна превращать их в абсолютные, иначе рекурсия сломается. Пример:
href = url.rstrip('/') + '/' + href.lstrip('/').Гибкость селекторов
В примере используется
soup.select('div a[href]')для навигационных страниц. Если структура отличается, адаптируйте селектор под конкретный сайт. Для целевых страниц - поиск таблицы с ссылками.Оптимизация и обработка ошибок
В реальном проекте добавьте задержки между запросами (time.sleep), чтобы не перегружать сервер. Также обрабатывайте исключения (например, ошибки соединения или неверный статус ответа). Для больших объёмов данных рассмотрите использование асинхронных запросов или очередей.
Заключение
Рекурсивный парсинг избавляет от необходимости писать вложенные циклы под каждый уровень иерархии. Главное - правильно определить условие остановки (целевая страница) и обрабатывать ссылки. Этот подход универсален и легко адаптируется под любую структуру каталога школ или аналогичных справочников.
Часто задаваемые вопросы
Используйте множество visited для хранения уже посещённых URL. Перед обработкой нового адреса проверяйте его наличие в этом множестве. Это предотвращает повторный обход одних и тех же страниц.
Преобразуйте относительные ссылки в абсолютные, используя базовый URL текущей страницы. Например, комбинируйте href с помощью url.rstrip('/') + '/' + href.lstrip('/'). Иначе рекурсия не сможет корректно переходить по ним.
Проверьте наличие характерных элементов: обычно это таблица (тег <table>), содержащая ссылки <a> в ячейках. Можно также искать определённый CSS-класс или текстовый маркер, например 'Школы' или 'Учебные заведения'.
Основные: requests для HTTP-запросов и BeautifulSoup для парсинга HTML. Для асинхронного обхода можно добавить aiohttp. Также полезны time (задержки) и urllib.parse (работа с URL).
Оберните requests.get в try-except, обрабатывая исключения ConnectionError и Timeout. При ошибке можно пропустить URL или повторить запрос с задержкой. Также проверяйте статус ответа (response.status_code == 200) перед парсингом.
- Целевая страница - содержит таблицу со школами, где каждая строка включает название школы и ссылку на её веб-сайт (ссылки внутри