Коротко — не держите пользователя в "пустом" браузере пока сервер парсит. Делайте быстрый initial response, показывающий спиннер, и либо:
- делайте запрос на парсинг через AJAX (fetch / jQuery) и по его окончании прячете спиннер и вставляете результат; либо
- для продвинутого контроля прогресса — запускайте задачу в фоне (Celery/RQ) и опрашивайте статус (polling) или используйте WebSocket/SSE для пуша прогресса.
Ниже — простая и надёжная реализация через AJAX (подходит для ~5 секунд).
1) Django view (возвращает JSON или HTML-фрагмент)
views.py
```
import json
from django.http import JsonResponse
from django.template.loader import render_to_string
from django.views.decorators.http import require_POST
@require_POST
def parse_view(request):
data = json.loads(request.body.decode())
# пример парсинга, который занимает ~5s
result = do_parsing(data.get('param')) # ваша функция
# можно вернуть JSON-структуру или готовый HTML-фрагмент
html = render_to_string('partials/result.html', {'items': result})
return JsonResponse({'html': html})
```
urls.py
```
from django.urls import path
from . import views
urlpatterns = [
path('parse/', views.parse_view, name='parse'),
]
```
2) Шаблон с кнопкой, спиннером и контейнером для результата
template.html
```
<html>
<head>
<!-- Подключите Bootstrap CSS если хотите готовый спиннер -->
</head>
<body>
<form id="parseForm">
<input name="param" id="param" value="..." />
<button type="submit">Запустить парсинг</button>
</form>
<!-- Спиннер (скрыт по умолчанию) -->
<div id="spinner" style="display:none;">
<!-- Пример простого CSS-спиннера / или Bootstrap:
<div class="spinner-border" role="status"><span class="sr-only">Loading...</span></div>
-->
Загрузка...
</div>
<!-- Контейнер для результата -->
<div id="result"></div>
<script>
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
document.getElementById('parseForm').addEventListener('submit', function(e){
e.preventDefault();
const param = document.getElementById('param').value;
document.getElementById('spinner').style.display = 'block';
fetch('/parse/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken
},
body: JSON.stringify({param: param})
}).then(resp => {
if (!resp.ok) throw new Error('Network error');
return resp.json();
}).then(data => {
document.getElementById('spinner').style.display = 'none';
document.getElementById('result').innerHTML = data.html;
}).catch(err => {
document.getElementById('spinner').style.display = 'none';
alert('Ошибка: ' + err);
});
});
</script>
</body>
</html>
```
Пояснения и советы
- CSRF: при POST из JS не забудьте отправлять X-CSRFToken (пример функции getCookie выше).
- Возвращать HTML-фрагмент удобно — сервер рендерит шаблон с данными, клиент вставляет его в страницу. Можно также вернуть JSON и рендерить на клиенте.
- Для простого 5-секундного процесса такой синхронный AJAX-запрос — вполне нормален. Но если операции могут длиться дольше или число одновременных запросов растёт, лучше запускать задачу в фоне (Celery) и:
- вернуть task_id сразу,
- опрашивать статус (/task-status/<id>/) пока не готов,
- или использовать WebSocket / Server-Sent Events для моментального пуша прогресса.
- Если нужно показывать процент выполнения — либо парсер должен отдавать прогресс (через фоновые задачи или SSE/WebSocket), либо делайте многократные этапы с обновлением прогресса на сервере.
Альтернатива — StreamingHttpResponse: сервер частями отправляет HTML (например сначала страница со спиннером), затем через flush присылает результаты. Это сложнее и чаще не нужно.
Если нужно, могу дать пример с Celery + polling или с WebSocket (Django Channels) для отображения процента.