parse_saby/app/parse_saby.py

191 lines
8.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.

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)
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: raise ValueError("Объект soup не должен быть None")
# Поиск в 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")
# Данные получены из url после парсинга
from_date, to_date, version = parse_date_report(url_report)
# Добавление всех данных в итоговый словарь
result_dict_data.update({id: (name_title, span.text, from_date, to_date, version)})
except Exception as e:
print(f"Ошибка при обработке отчета {report_title}: {str(e)}")
continue
return result_dict_data
def write_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):
error_message = f'Ошибка при запросе {url_format_report}: {html}'
raise Exception(error_message)
# Множество в который будут заноситься тайтлы
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'...]
set_title = search_title()
dict_result = {}
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):
print(f'Не удалось установить соедение c {url_title}')
continue
# Словарь с нужными данными
dict_result_title = parse_reports(soup, report_title, url_formats, name_title)
dict_result.update(dict_result_title)
# Вывод на стандратный поток вывода
write_report_data(dict_result, name_title)
except Exception as e:
print(f"Ошибка при обработке отчета {report_title}: {str(e)}")
continue
return dict_result
if __name__ == '__main__':
process_reports_parse()