From 972524c0567edcf951dd8023f86dc95e419501b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9B=D0=B5=D0=B2?= Date: Mon, 25 Aug 2025 10:26:19 +0500 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B2=D0=B0=D1=8F=20=D1=82?= =?UTF-8?q?=D0=B5=D1=81=D1=82=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/main.py | 5 + app/parse_saby.py | 202 ++++++++++++++++++ .../working_database.py | 0 3 files changed, 207 insertions(+) create mode 100644 app/main.py create mode 100644 app/parse_saby.py rename working_database.py => app/working_database.py (100%) diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..b89d6d4 --- /dev/null +++ b/app/main.py @@ -0,0 +1,5 @@ +import working_database +import parse_saby + +working_database.connect_hvac() +parse_saby.process_reports_parse() \ No newline at end of file diff --git a/app/parse_saby.py b/app/parse_saby.py new file mode 100644 index 0000000..2e51c64 --- /dev/null +++ b/app/parse_saby.py @@ -0,0 +1,202 @@ +import requests +from bs4 import BeautifulSoup +import re + +def parse_html(url: str): + """ + Фунция принимает строку URL, выполняет запрос. + Создает и возращает объект BeautifulSoup(HTML). + """ + # Запрос страницы + response = requests.get(url) + # Проверка статуса в ответе на запрос + if not(200 <= response.status_code <= 299): + # Повторный запрос + response = requests.get(url) + 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) + # Если не удалось отправить запрос, ворзвращаем URL и код ошибки. + # Они будут занесы в файл. + if soup == int: + return url, soup + # Поиск в HTML строки ввида: 'Действующий формат (с 10.01.23) 5.01' + div_element = soup.find('div', class_='controls-Dropdown__text') + # Извлекаем текст из элемента + text = div_element.get_text() + # Парсим нужные данные + # Уловия ловит: text = 'Действующий формат 02-04-2025 _2025_001' + if '_' in text: + regex = r'\w+\s\w+\s(\S+) (\S+)' + date, id_date = re.search(regex, text).groups() + return date, id_date + # Все остальные + else: + regex = r'(\d{2}\D\d{2}\D\d{2})(?:(?:.+)?\))? #?(\d+(?:\D\d+)?)' + date, id_date = re.search(regex, text).groups() + return date, id_date + +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'): + # Ищет по тегу: 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") + # Данные получены из url после парсинга + date, id_date = parse_date_report(url_report) + # Добавление всех данных в итоговый словарь + result_dict_data.update({id: (name_title, span.text, date, id_date)}) + + return result_dict_data + +def write_report_data(filename, 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() + + #Конец блока + #Запись в файл с красивым офрмление в виде нижнего подчеркивания + with open(filename, 'a', encoding='utf-8') as f: + f.write(f'\n{dash_start}\n') + for key, value in dict_name.items(): + str_k_v = f'{key}: {value}\n' + f.write(str_k_v) + f.write(f'{dash_end}\n') + +def search_title(): + """ + Функция ищет все тайтлы на странице formats.saby.ru/report. + + Возвращает: + Список URL-путей, например: ['/report/fns', '/report/example', ...] + + Исключения: + ValueError: Если запрос к странице завершился с ошибкой (неверный статус). + """ + # url по которому в котором будет происходить поиск, + url_format_report = 'https://formats.saby.ru/report' + + # Получаем HTML-cтраницу + html = parse_html(url_format_report) + + # Проверяем, что html не является кодом ошибки (int) + if isinstance(html, int): + error_message = f'Ошибка при запросе {url_format_report}: {html}' + raise Exception(error_message) + + # < в который будут заноситься тайтлы + 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 + +# Имя файла в который будет записан результат кода +filename_save = 'ReportData.txt' +def clear_report_data_file(): + """ + Удаляем старые записи, что бы записать актульные + """ + with open(filename_save, 'w') as f: + pass + +def process_reports_parse(): + """ + Функция пробегается по каждому тайтлу. + Для всех записей(reports) выполняется запрос HTML страницы, + которая парситься в объект BeautifulSoup(HTML страница). + Из это обьекта достаются не обходимые данные, + которые записываются в текстовый файл. + """ + # Очищаем файл перед записью + clear_report_data_file() + + # Лист имеет вид: ['/report/fns', '/report/sfr'...] + list_title = search_title() + + for report_title in list_title: + try: + # используется для создание полных URL + url_formats = 'https://formats.saby.ru' + # Получаем имя тайтла через парсинг + name_title = report_title.rstrip('/').split('/')[-1] + + # URL на конкретный title + url_title = f'{url_formats}{report_title}' + + # Объект HTML, конкретного title + soup = parse_html(url_title) + + # Словарь с нужными данными + dict_result = parse_reports(soup, report_title, url_formats, name_title) + + # Запись данных в текстовый файл + write_report_data(filename_save, dict_result, name_title) + + except Exception as e: + print(f"Ошибка при обработке отчета {report_title}: {str(e)}") + continue + +if __name__ == '__main__': + process_reports_parse() + + diff --git a/working_database.py b/app/working_database.py similarity index 100% rename from working_database.py rename to app/working_database.py