parse_saby/app/parse_saby.py

201 lines
9.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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: # Уовия ловит: 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()
# Ищем все ссылки <a> с 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()