# pyright: reportOptionalMemberAccess=false, reportAttributeAccessIssue=false, reportOperatorIssue=false # pyright: reportArgumentType=false, reportIndexIssue=false, reportCallIssue=false, reportGeneralTypeIssues=false # Подсветка синтаксиса отключена, т.к. тип данных везде кооректены и обрабатывается if. import requests from bs4 import BeautifulSoup import re import error_to_log def parse_html(url: str): """ Фунция принимает строку URL, выполняет запрос. Создает и возращает объект BeautifulSoup(HTML). """ # Запрос страницы response = requests.get(url) # Проверка статуса if not(200 <= response.status_code <= 299): # Повторный запрос response = requests.get(url) if not(200 <= response.status_code <= 299): print("Ошибка при запросе: ", response.status_code) return response.status_code # Создание обьекта BeautifulSoup(HTML страница) soup = BeautifulSoup(response.text, 'html.parser') return soup def parse_date_report(url: str): """ Функия ожидает list из URL ввида: url = https://formats.saby.ru/report/fns/128513 Заходит по URL, запоминает дату. Возвращает две строки ввида: 'date = 01.07.22' 'id_date = 5.01' """ # HTML report soup = parse_html(url) # Проверка на ошибку: if soup == int: return None # Вызовит ошибку, что бы пропустить данный url # Поиск в HTML строки ввида: 'Действующий формат (с 10.01.23) 5.01' div_element = soup.find('div', class_='controls-Dropdown__text') # Извлекаем текст из элемента text = div_element.get_text() # Парсим нужные данные. if '_' in text: # Уcловия ловит: text = 'Действующий формат 02-04-2025 _2025_001 regex = r'\w+\s\w+\s(\S+) (\S+)' date, id_date = re.search(regex, text).groups() return date, None, id_date else: # Все остальные regex = r'(\d{2}\D\d{2}\D\d{2})' \ r'(?:\D+(\d{1,2}\D\d{2}\D\d{2}))?.*?\)' \ r'\s*#?\s*(\d+(?:\D\d+)?)' match = re.search(regex, text) from_date = match.group(1) # Первая дата (обязательная) to_date = match.group(2) # Вторая дата (может быть None) version = match.group(3) # Число (обязательное) return from_date, to_date, version def parse_reports(soup:BeautifulSoup, # HTML объект report_title:str, # Строка ввида: 'report/fns' url_formats:str, # Строка ввида: 'https://formats.saby.ru' name_title:str): # Имя тайтла: 'fns' """ Достаются все необходимые данные, возвращаются в ввиде словаря: {106538: ('fns', 'НД по косвенным налогам', '01.08.23', '5.04')} """ result_dict_data = {} # Перебарает все URL, ищутся по тегу 'a' for link in soup.find_all('a'): try: # Ищет по тегу: href href = link.get('href') if f'{report_title}/' in href: # id report id = href.rstrip('/').split('/')[-1] # URL report url_report = f'{url_formats}{href}' link = soup.find('a', href=href) # Name report span = link.find('span', class_="ProxySbisRu__registry-BrowserItem_typeName") try: # Данные получены из url после парсинга from_date, to_date, version = parse_date_report(url_report) except Exception: continue # Может быть ошибка если url не доступен # Добавление всех данных в итоговый словарь result_dict_data.update({id: (name_title, span.text, from_date, to_date, version)}) except Exception as e: error_message = f"Ошибка при обработке отчета {report_title}: {e}" print(error_message) error_to_log.write_to_log(error_message) continue return result_dict_data def print_report_data(dict_name:dict, name_title:str): """ Вывод на стандартный поток вывода итоговых данных """ # Функция для красивого офорлмения файла def center_text(): """ Функия парсит сколько нужно подчеркивания, что бы центролизовать текст в строке с этими подчеркиваниями Возвращает две строки одна для начало блока записей тайтал, вторая конца блока. """ start_str = f'START_{name_title}' end_str = f'END_{name_title}' dash = 100 list_result = [] # сначал вернеться dash_start, потом end_start. for text in start_str, end_str: remaining_space = dash - len(text) left = remaining_space // 2 right = remaining_space - left # Чтобы учесть нечётную разницу list_result.append('_' * left + text + '_' * right) return list_result[:2] dash_start, dash_end = center_text() # Вывод с красивым офрмление в виде нижнего подчеркивания print(f'\n{dash_start}\n') for key, value in dict_name.items(): str_k_v = f'{key}: {value}\n' print(str_k_v, end='') print(f'{dash_end}\n') def search_title(url_format_report = 'https://formats.saby.ru/report'): """ Функция ищет все тайтлы на странице formats.saby.ru/report. Парамметры функции: url в котором будет происходить поиск Возвращает: Список URL-путей, например: ['/report/fns', '/report/example', ...] Исключения: ValueError: Если запрос к странице завершился с ошибкой (неверный статус). """ # Получаем HTML-cтраницу html = parse_html(url_format_report) # Проверяем, что html не является кодом ошибки (int) if isinstance(html, int): raise Exception(f'Ошибка при запросе {url_format_report}: {html}') # Множество в который будут заноситься тайтлы report_urls = set() # Ищем все ссылки с href, соответствующие шаблону /report/{title} for link in html.find_all('a', href=True): # Ищем по тегу: href href = link['href'] # Проверям что href содержит: /report/{title} if re.search(r'\/report\/(\w+)$', href): report_urls.add(href) return report_urls def process_reports_parse(url_formats = 'https://formats.saby.ru'): """ Функция пробегается по каждому тайтлу. Для всех записей(reports) выполняется запрос HTML страницы, которая парситься в объект BeautifulSoup(HTML страница). Из это обьекта достаются не обходимые данные Параметр функции: url_formats - используется для создание полных URL """ # Лист имеет вид: ['/report/fns', '/report/sfr'...] print(f'{' '*2}Получаем url список всех форматов отчетности в госорганы') set_title = search_title() dict_result = {} print(f"{' '*2}Обработка полученых фрматов") for report_title in set_title: try: # Получаем имя тайтла через парсинг name_title = report_title.rstrip('/').split('/')[-1] # URL на конкретный title url_title = f'{url_formats}{report_title}' # Объект HTML, одного(конкретного) title soup = parse_html(url_title) if isinstance(soup, int): error_message = f"Не удалось установить соедение c {url_title}" print({' '*4}, error_message) error_to_log.write_to_log(error_message) continue # Словарь с нужными данными dict_result_title = parse_reports(soup, report_title, url_formats, name_title) dict_result.update(dict_result_title) # Вывод на стандратный поток вывода print_report_data(dict_result_title, name_title) except Exception as e: print(f"Ошибка при обработке отчета {report_title}: {str(e)}") error_message = f"ERROR-PARSE_SABY: {e} DATA: {report_title}" error_to_log.write_to_log(error_message) continue return dict_result if __name__ == '__main__': # Можно запустить отдельно от всего проекта print("Запущен parse_saby.py") process_reports_parse()