Несоответствие данных выводимых на экран с данными экспортируемыми в excel-файл

Есть Flask-проект:

from flask import Flask, request, render_template, send_file
import requests
# import csv
import pandas as pd
from io import BytesIO
import logging
import json
# import pandas as pd
# from io import BytesIO
# -*- coding: utf-8 -*-
import sys

sys.stdout.reconfigure(encoding='utf-8')

logging.basicConfig(level=logging.DEBUG)
app = Flask(__name__)


# Функция для поиска вакансий
def search_vacancies(keyword):
    BASE_URL = "https://api.hh.ru/vacancies"
    vacancies_found = []  # Список для хранения найденных вакансий
    per_page = 100
    page = 0

    while True:
        params = {
            'text': keyword,
            'page': page,
            'per_page': per_page,
            'only_with_salary': True  # Только вакансии с зарплатой
        }

        response = requests.get(BASE_URL, params=params)
        if response.status_code != 200:
            print("Ошибка при обращении к API")
            break

        lan = response.json()

        if not lan['items']:
            break

        for item in lan['items']:
            # vacancy_name = item['name']
            vacancy_name = item['name'].lower()  # Приводим название вакансии к нижнему регистру для сравнения
            # Проверяем, содержит ли название вакансии ключевое слово
            if keyword in vacancy_name:
                salary_from = item['salary']['from'] if item['salary'] else None
                salary_to = item['salary']['to'] if item['salary'] else None
                currency = item['salary']['currency'] if item['salary'] else None
                city = item['area']['name'] if 'area' in item else None
                link = item['alternate_url'] if 'alternate_url' in item else None
                discription = item['snippet']['responsibility'] if 'snippet' in item else None

                vacancies_found.append({
                    'name': vacancy_name,
                    'salary_from': salary_from,
                    'salary_to': salary_to,
                    'currency': currency,
                    'city': city,
                    'link': link,
                    'discription': discription
                })

        page += 1

    return vacancies_found


@app.route('/download', methods=['POST'])
def download():
    keyword = request.form['work_name']
    vacancies = search_vacancies(keyword)

    df = pd.DataFrame(vacancies)
    output = BytesIO()
    df.to_excel(output, index=False)
    output.seek(0)
    return send_file(output, as_attachment=True, download_name='vacancies.xlsx')


@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        keyword = request.form['keyword']
        vacancies = search_vacancies(keyword)
        print(len(vacancies))
        all_vacancies = (len(vacancies))
        if vacancies:
            # filename = save_to_json(vacancies)
            return render_template('index.html', vacancies=vacancies, download=True, all_vacancies=all_vacancies)
        else:
            return render_template('index.html', all_vacancies=0)
    return render_template('index.html')


if __name__ == '__main__':
    app.run(debug=True)
<h1>Парсер вакансий</h1>
<form method="POST">
    <label for="keyword">Введите вакансию для поиска:</label>
    <input type="text" id="keyword" style="margin-left: -2%;" name="keyword" required>
    <button type="submit">Искать</button>
</form>
{% if vacancies %}
<h2>Найденные вакансии: {{ all_vacancies }}</h2>
<form method="post" action="/download">
    <input type="hidden" name="work_name" value="{{ keyword }}">
    <input type="submit" value="Скачать в Excel">
</form>
<table>
    <tr>
        <th>Название</th>
        <th>Зарплата от</th>
        <th>Зарплата до</th>
        <th>Валюта</th>
        <th>Город</th>
        <th>Описание</th>
        <th>Ссылка</th>
    </tr>
    {% for vacancy in vacancies %}
    <tr>
        <td>{{ vacancy.name }}</td>
        <td>{{ vacancy.salary_from }}</td>
        <td>{{ vacancy.salary_to }}</td>
        <td>{{ vacancy.currency }}</td>
        <td>{{ vacancy.city }}</td>
        <td>{{ vacancy.discription }}</td>
        <td><a href="{{ vacancy.link }}" class="vacancy-link">{{ vacancy.link }}</a></td>
    </tr>
    {% endfor %}
</table>
{% elif message %}
<p>{{ message }}</p>
{% endif %}
<form method="post" action="/download">
    <input type="hidden" name="work_name" value="{{ keyword }}">
    <input type="submit" value="Скачать в Excel">
</form>
<!--{#</table>#}-->
</body>
</html>

Проблема в том, что результаты парсинга выводимых на экран не совпадают с данными экспортируемыми в excel-файл. Причём запрашиваю разные данные по ключевому слову и выводятся на экран данные соответствуюшие этому ключевому слову, а данные экспортируемые в excel-файл всегда одни и те же, хотя должны соответствовать данным на отображаемым на экране. Не могу понять почему так происходит, если кто знает подскажите в чём проблема.

Ссылка на проект: https://abram742.pythonanywhere.com/


Ответы (1 шт):

Автор решения: Vitalizzare

Относительно разницы между данными в браузере и Excel, есть два замечания.

Во-первых, вы не передали в render_template значение keyword, сохраняемое в форме "work_name". При сохранении в Excel-файл вы повторно запрашиваете данные, используя значение из "work_name" как keyword, но там ничего нет. То есть, в таблице каждый раз сохраняется результат запроса по пустой строке.

Во-вторых, после первого запроса данные могут измениться (вакансии удаляются или добавляются), и результат может не совпасть с экраном. Лучше сохранять данные в таблицу на стороне клиента, или хотя бы кешировать их на своём сервере, чтобы избежать повторных запросов к источнику данных.

Минимальные изменения, чтобы сделать приложение рабочим:

from flask import redirect

def download():
    keyword = request.form.get('work_name', '').strip()
    if keyword == '':    # ничего не делать если keyword не задан
        return redirect('/')   
    ...

def index():
    keyword = request.form.get('keyword', '').strip()
    if request.method == 'POST' and keyword:   # обновлять страницу только если задано ключевое слово
        vacancies = search_vacancies(keyword)
        ...
        if vacancies:
            return render_template('index.html', 
                                   vacancies=vacancies, 
                                   download=True, 
                                   all_vacancies=all_vacancies, 
                                   keyword=keyword)     # сохранить keyword на html-странице
        else:
            return render_template('index.html', 
                                   all_vacancies=0,     # даже если по запросу ничего нет
                                   keyword=keyword)     # сохранить keyword на html-странице
    return render_template('index.html')   # пустой keyword приемлем для стартовой страницы без каких-либо данных

Минимальное изменение, чтобы не повторять запрос при сохранении:

from functools import lru_cache

@lru_cache(maxsize=1)     # хранить в кеше только результат крайнего запроса
def search_vacancies(keyword):
    ...

Недостаток такого кеширования в том, что повторный запрос по тому же слову возвращает устаревший результат. Например, если клиент сделал запрос, свернул браузер, вернулся через сутки и повторил тот же поиск, то он получит прежний ответ. Стоит очищать кеш через значимое время.

→ Ссылка