Не открывается книжка Excel на Python Windows
Разрабатываю бота, который должен отсылать в ответ пользователю скриншот определенной области из excel файла.
Сейчас проблема в том, что он не может открыть книжку.
Стек: Python, win32com, PIL, gc.
Код получения скриншота:
import win32com.client as win32
import os
import time
from PIL import ImageGrab
import gc
def screenshot_excel_range(output_file, sheet_name, range_address=None):
file_path = output_file.replace(".png", ".xlsx")
print(f"Открываю файл: {file_path}")
abs_path = os.path.abspath(file_path)
print(f"Абсолютный путь: {abs_path}")
if not os.path.exists(file_path):
print(f"ОШИБКА: Файл не существует: {file_path}")
return
try:
excel = win32.Dispatch('Excel.Application')
excel.Visible = True
excel.DisplayAlerts = False
print("Excel запущен, пытаюсь открыть книгу...")
try:
workbooks = excel.Workbooks
workbook = workbooks.Open(r"{}".format(abs_path), ReadOnly=1)
print("Книга успешно открыта")
try:
sheet = workbook.Worksheets(sheet_name)
print(f"Лист '{sheet_name}' найден")
if range_address is None:
print("Используем видимую область")
excel.ActiveWindow.View = 1
sheet.Activate()
visible_range = excel.ActiveWindow.VisibleRange
rng = visible_range
else:
print(f"Используем указанный диапазон: {range_address}")
rng = sheet.Range(range_address)
print("Копирую изображение в буфер обмена...")
rng.CopyPicture(Appearance=1, Format=2)
time.sleep(2.0)
print("Получаю изображение из буфера обмена...")
img = ImageGrab.grabclipboard()
if img:
print(f"Сохраняю изображение в {output_file}")
img.save(output_file)
print(f"Изображение успешно сохранено в {output_file}")
else:
print("Не удалось получить изображение из буфера обмена.")
except Exception as e:
print(f"Ошибка при работе с листом: {e}")
finally:
print("Закрываю книгу...")
workbook.Close(SaveChanges=False)
print("Книга закрыта")
except Exception as e:
print(f"Ошибка при открытии книги: {e}")
finally:
print("Закрываю Excel...")
excel.Quit()
print("Excel закрыт")
except Exception as e:
print(f"Общая ошибка: {e}")
finally:
print("Очистка COM-объектов...")
gc.collect()
print("Готово")
Код хенделор:
import os
import asyncio
from datetime import datetime, timedelta
from aiogram import Router, types, F
from aiogram.filters import Command
from aiogram.types import InlineKeyboardMarkup
from aiogram.fsm.state import StatesGroup, State
from aiogram.fsm.context import FSMContext
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.utils.formatting import as_list, as_marked_section
from aiogram.types import BotCommand, BotCommandScopeDefault
from aiogram.types import FSInputFile
from assets.get_data import screenshot_excel_range
from assets.extract_excel_data import update_excel_file_for_roof_1, update_excel_file_for_roof_2, \
update_excel_file_for_fence_1, update_excel_file_for_fence_2
from assets.googleAPI import download_file
from create_bot import bot
router = Router()
# Укажите ID администратора, которому будут отправляться сообщения
# ADMIN_CHAT_ID = 1734794410
# Переменная для хранения количества пользователей за день
user_count_today = 0
sheet_name = "стоимость металической кровли "
class GetLenghtRoof(StatesGroup):
roof_length = State()
class GetLenghtRamp(StatesGroup):
ramp_length1 = State()
ramp_length2 = State()
class GetLenghtFence(StatesGroup):
fence_lenght = State()
class GetHightFence(StatesGroup):
fence_height = State()
class GetBeetwenPicket(StatesGroup):
beetwen_picket = State()
async def setup_bot_commands():
bot_commands = [
BotCommand(command="getroofcost", description="рассчитать стоимость кровли"),
BotCommand(command="getfencecost", description="рассчитать стоимость забора"),
]
await bot.set_my_commands(bot_commands, BotCommandScopeDefault())
def get_detailing() -> types.InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
builder.row(types.InlineKeyboardButton(text="Получить детализацию", callback_data="get_detailing"),
types.InlineKeyboardButton(text="Нет", callback_data="no"))
return builder
def type_roof_buttons() -> types.InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
builder.row(types.InlineKeyboardButton(text="Односкатная", callback_data="single_pitched"),
types.InlineKeyboardButton(text="Двухскатная", callback_data="double_pitched"))
return builder.as_markup()
def type_fence_buttons() -> types.InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
builder.row(types.InlineKeyboardButton(text="Из профнастила", callback_data="corrugated_sheet"),
types.InlineKeyboardButton(text="Из Штакетника", callback_data="picket_fence"))
return builder.as_markup()
@router.message(Command("start"))
async def send_welcome(message: types.Message):
photo = FSInputFile('assets/photo_start.jpg')
await message.answer_photo(photo=photo, caption="Привет! Я Матвей, что тебе рассчитать?")
@router.message(Command("getfencecost"))
async def fence_cost(message: types.Message):
await message.answer("Для начала выберите тип забора:", reply_markup=type_fence_buttons())
@router.message(Command("getroofcost"))
async def roof_cost(message: types.Message):
await message.answer("Для начала выберите тип кровли:", reply_markup=type_roof_buttons())
@router.callback_query(F.data == 'get_detailing')
async def get_detailing(call: types.CallbackQuery):
pass
@router.callback_query(F.data.in_(["corrugated_sheet", "picket_fence"]))
async def process_fence_type(call: types.CallbackQuery, state: FSMContext):
fence_type = call.data
await state.update_data(fence_type=fence_type)
await call.message.answer("Укажите длину забора в метрах в формате 00.00 ЧЕРЕЗ ТОЧКУ:\nнапример 12.3")
await state.set_state(GetLenghtFence.fence_lenght)
await call.answer()
@router.message(GetLenghtFence.fence_lenght)
async def process_fence_length(message: types.Message, state: FSMContext):
fence_lenght = message.text.strip()
if not fence_lenght:
await message.answer("Пожалуйста, введите корректное значение.")
return
await state.update_data(fence_lenght=fence_lenght)
await message.answer("Укажите высоту забора в метрах В ФОРМАТЕ 00.00 ЧЕРЕЗ ТОЧКУ:")
await state.set_state(GetHightFence.fence_height)
@router.message(GetHightFence.fence_height)
async def process_fence_height(message: types.Message, state: FSMContext):
fence_height = message.text.strip()
if not fence_height:
await message.answer("Пожалуйста, введите корректное значение.")
return
await state.update_data(fence_height=fence_height)
data = await state.get_data()
fence_type = data.get("fence_type")
if fence_type == "picket_fence":
await message.answer("Укажите расстояние между штакетниками в САНТИМЕТРАХ:")
await state.set_state(GetBeetwenPicket.beetwen_picket)
else:
# Если тип забора - профнастил, сразу считаем и отправляем результат
await calculate_and_send_fence_results(message, state)
@router.message(GetBeetwenPicket.beetwen_picket)
async def process_between_picket(message: types.Message, state: FSMContext):
between_picket = message.text.strip()
if not between_picket:
await message.answer("Пожалуйста, введите корректное значение.")
return
await state.update_data(between_picket=between_picket)
await calculate_and_send_fence_results(message, state)
@router.callback_query(F.data.in_(["single_pitched", "double_pitched"]))
async def single_pitched(call: types.CallbackQuery, state: FSMContext):
roof_type = call.data
await state.update_data(roof_type=roof_type)
await call.message.answer("Введите длину кровли по коньку в метрах В ФОРМАТЕ 00.00 ЧЕРЕЗ ТОЧКУ:")
await state.set_state(GetLenghtRoof.roof_length)
await call.answer()
@router.message(GetLenghtRoof.roof_length)
async def process_get_roof_length(message: types.Message, state: FSMContext):
roof_length = message.text.strip()
if not roof_length:
await message.answer("Пожалуйста, введите корректное значение.")
return
await state.update_data(roof_length=roof_length)
data = await state.get_data()
roof_type = data.get("roof_type")
if roof_type == "double_pitched":
await message.answer("Введите длину первого ската в метрах В ФОРМАТЕ 00.00 ЧЕРЕЗ ТОЧКУ:")
else:
await message.answer("Введите длину ската в метрах В ФОРМАТЕ 00.00 ЧЕРЕЗ ТОЧКУ:")
await state.set_state(GetLenghtRamp.ramp_length1)
@router.message(GetLenghtRamp.ramp_length1)
async def process_ramp_length1(message: types.Message, state: FSMContext):
ramp_length1 = message.text.strip()
if not ramp_length1:
await message.answer("Пожалуйста, введите корректное значение.")
return
await state.update_data(ramp_length1=ramp_length1)
data = await state.get_data()
roof_type = data.get("roof_type")
if roof_type == "double_pitched":
await message.answer("Введите длину второго ската в метрах В ФОРМАТЕ 00.00 ЧЕРЕЗ ТОЧКУ:")
await state.set_state(GetLenghtRamp.ramp_length2)
else:
await calculate_and_send_roof_results(message, state)
@router.message(GetLenghtRamp.ramp_length2)
async def process_ramp_length2(message: types.Message, state: FSMContext):
ramp_length2 = message.text.strip()
if not ramp_length2:
await message.answer("Пожалуйста, введите корректное значение.")
return
await state.update_data(ramp_length2=ramp_length2)
await calculate_and_send_roof_results(message, state)
async def calculate_and_send_roof_results(message: types.Message, state: FSMContext):
"""Функция выполняет расчет и отправку данных для кровли."""
data = await state.get_data()
roof_length = data.get("roof_length")
ramp_length1 = data.get("ramp_length1")
ramp_length2 = data.get("ramp_length2", None)
roof_type = data.get("roof_type")
user_id = message.from_user.id
global user_count_today
username = message.from_user.username or "Не указан"
local_file = None
output_file = None
try:
local_file = download_file(user_id)
base_name = os.path.splitext(os.path.basename(local_file))[0]
output_file = os.path.join(os.path.dirname(os.path.abspath(local_file)), base_name + ".png")
if roof_type == "double_pitched":
update_excel_file_for_roof_2(local_file, roof_length, ramp_length1, ramp_length2, sheet_name)
else:
update_excel_file_for_roof_1(local_file, roof_length, ramp_length1, sheet_name)
# Используем новую функцию для скриншота
# Можно настроить диапазон в зависимости от типа крыши или оставить None для автоматического определения
screenshot_excel_range(output_file, sheet_name, "A15:O31")
await message.answer_photo(photo=types.FSInputFile(output_file))
await message.answer(
"Отлично! Вот твой расчет. В стоимость включены саморезы, коньки и ветровые планки.\nХочешь получить детализацию заказа?",
reply_markup=get_detailing().as_markup())
user_count_today += 1
# Отправка данных администратору
report_text = f"""? Новый пользователь рассчитал кровлю:
Пользователь: {'@' + username if username else 'Не указан'}
Длина кровли по коньку: {roof_length} м
Первый скат: {ramp_length1} м
{f'Второй скат: {ramp_length2} м' if ramp_length2 else ''}"""
except Exception as e:
print(f"Ошибка: {e}")
await message.answer(
f"Произошла ошибка при расчете. Пожалуйста, попробуйте еще раз или обратитесь к администратору.")
finally:
# На Windows мы не удаляем файлы, так как они могут быть заблокированы Excel
pass
await state.clear()
async def calculate_and_send_fence_results(message: types.Message, state: FSMContext):
"""Функция выполняет расчет и отправку данных для забора."""
data = await state.get_data()
fence_type = data.get("fence_type")
fence_lenght = data.get("fence_lenght")
fence_height = data.get("fence_height")
between_picket = data.get("between_picket", None)
user_id = message.from_user.id
global user_count_today
username = message.from_user.username or "Не указан"
local_file = None
output_file = None
try:
local_file = download_file(user_id)
base_name = os.path.splitext(os.path.basename(local_file))[0]
output_file = os.path.join(os.path.dirname(os.path.abspath(local_file)), base_name + ".png")
if fence_type == "picket_fence":
update_excel_file_for_fence_2(local_file, fence_lenght, fence_height, between_picket, sheet_name)
else:
update_excel_file_for_fence_1(local_file, fence_lenght, fence_height, sheet_name)
# Используем функцию для скриншота с соответствующим диапазоном для забора
screenshot_excel_range(output_file, sheet_name, "A15:O31")
await message.answer_photo(photo=types.FSInputFile(output_file))
await message.answer(
"Отлично! Вот твой расчет стоимости забора.\nХочешь получить детализацию заказа?",
reply_markup=get_detailing().as_markup())
user_count_today += 1
# Отправка данных администратору
report_text = f"""? Новый пользователь рассчитал забор:
Пользователь: {'@' + username if username else 'Не указан'}
Тип забора: {"Штакетник" if fence_type == "picket_fence" else "Профнастил"}
Длина забора: {fence_lenght} м
Высота забора: {fence_height} м
{f'Расстояние между штакетниками: {between_picket} см' if between_picket else ''}"""
except Exception as e:
print(f"Ошибка: {e}")
await message.answer(
f"Произошла ошибка при расчете. Пожалуйста, попробуйте еще раз или обратитесь к администратору.")
finally:
# На Windows мы не удаляем файлы, так как они могут быть заблокированы Excel
pass
await state.clear()
async def send_daily_report():
"""Функция отправляет отчет администратору в 21:00 по МСК и сбрасывает счетчик."""
global user_count_today
while True:
now = datetime.now()
next_run = datetime(now.year, now.month, now.day, 21, 0, 0) # 21:00
if now > next_run:
next_run += timedelta(days=1)
wait_time = (next_run - now).total_seconds()
await asyncio.sleep(wait_time)
# Отправка отчета администратору
report_message = f"? Количество пользователей за сегодня: {user_count_today}"
# await router.bot.send_message(ADMIN_CHAT_ID, report_message)
# Сброс счетчика
user_count_today = 0
# Запуск задачи по отправке отчета
async def on_startup():
asyncio.create_task(send_daily_report())
Код подключения и скачивания файла с Google Таблиц:
import io
import uuid
from google.oauth2.service_account import Credentials
from googleapiclient.discovery import build
from googleapiclient.http import MediaIoBaseDownload
from decouple import config
SCOPES = ['https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/drive']
def download_file(user_id):
creds = Credentials.from_service_account_file(config('CREDENTIALS_FILE'), scopes=SCOPES)
service = build('drive', 'v3', credentials=creds)
# If this is a native Google Sheet you want to download as Excel: # You'll need to define this check
request = service.files().export_media(
fileId=config('CREDENTIALS_ID'), # Use a variable name that reflects what this ID represents
mimeType='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
)
# If this is already an Excel file in Drive:
fh = io.BytesIO()
downloader = MediaIoBaseDownload(fh, request)
done = False
while not done:
status, done = downloader.next_chunk()
fh.seek(0)
unique_filename = f"таблица_{user_id}_{uuid.uuid4().hex}.xlsx"
with open(unique_filename, "wb") as f:
f.write(fh.read())
return unique_filename
Код вставки данных в скачанную таблицу:
import os, gc
from openpyxl import load_workbook
def update_excel_file_for_roof_1(file, roof_lenght, ramp_lenght1, sheet_name):
filepath = os.path.abspath(file)
workbook = load_workbook(filepath)
try:
sheet = workbook[sheet_name]
sheet['C4'].value = roof_lenght
sheet['C5'].value = ramp_lenght1
workbook.save(filepath)
finally:
if hasattr(workbook, 'close'):
workbook.close()
gc.collect()
def update_excel_file_for_fence_1(file, roof_lenght, ramp_lenght1, sheet_name):
filepath = os.path.abspath(file)
workbook = load_workbook(filepath)
try:
sheet = workbook[sheet_name]
sheet['B3'].value = roof_lenght
sheet['C3'].value = ramp_lenght1
workbook.save(filepath)
finally:
if hasattr(workbook, 'close'):
workbook.close()
gc.collect()
def update_excel_file_for_fence_2(file, roof_lenght, ramp_lenght1, sheet_name):
filepath = os.path.abspath(file)
workbook = load_workbook(filepath)
try:
sheet = workbook[sheet_name]
sheet['B2'].value = roof_lenght
sheet['C2'].value = ramp_lenght1
sheet['D2'].value = ramp_lenght1
workbook.save(filepath)
finally:
if hasattr(workbook, 'close'):
workbook.close()
gc.collect()
def update_excel_file_for_roof_2(file, roof_lenght, ramp_lenght1, ramp_lenght2, sheet_name):
filepath = os.path.abspath(file)
workbook = load_workbook(filepath)
try:
sheet = workbook[sheet_name]
sheet['F4'].value = roof_lenght
sheet['F5'].value = ramp_lenght1
sheet['H5'].value = ramp_lenght2
workbook.save(filepath)
finally:
if hasattr(workbook, 'close'):
workbook.close()
gc.collect()
логи:
Открываю файл: C:\Users\Administrator\Desktop\Bot_Count_Roof_Cost\таблица_998992278_a951318df67b429faec3bf26486b9416.xlsx
Абсолютный путь: C:\Users\Administrator\Desktop\Bot_Count_Roof_Cost\таблица_998992278_a951318df67b429faec3bf26486b9416.xlsx
Excel запущен, пытаюсь открыть книгу...
Ошибка при открытии книги: (-2147352567, 'Exception occurred.', (0, 'Microsoft Excel', 'Open method of Workbooks class failed', 'xlmain11.chm', 0, -2146827284), None)
Закрываю Excel...
Excel закрыт
Очистка COM-объектов...
Готово
Может кто-то с таким сталкивался? Как исправить? Функционал отправки именно скриншота обязателен.
Ответы (1 шт):
Автор решения: S. Nick
→ Ссылка
Вы не показали как и откуда вы запускаете свой код.
Если вы сделаете это правильно, то у вас все получится.
q1609135.py
import win32com.client as win32
import os
import time
from PIL import ImageGrab
import gc
def screenshot_excel_range(output_file, sheet_name, range_address=None):
file_path = output_file.replace(".png", ".xlsx")
print(f"Открываю файл: {file_path}")
abs_path = os.path.abspath(file_path)
print(f"Абсолютный путь: {abs_path}")
if not os.path.exists(file_path):
print(f"ОШИБКА: Файл не существует: {file_path}")
return
try:
excel = win32.Dispatch('Excel.Application')
excel.Visible = True
excel.DisplayAlerts = False
print("Excel запущен, пытаюсь открыть книгу...")
try:
workbooks = excel.Workbooks
workbook = workbooks.Open(r"{}".format(abs_path), ReadOnly=1)
print("Книга успешно открыта")
try:
sheet = workbook.Worksheets(sheet_name)
print(f"Лист '{sheet_name}' найден")
if range_address is None:
print("Используем видимую область")
excel.ActiveWindow.View = 1
sheet.Activate()
visible_range = excel.ActiveWindow.VisibleRange
rng = visible_range
else:
print(f"Используем указанный диапазон: {range_address}")
rng = sheet.Range(range_address)
print("Копирую изображение в буфер обмена...")
rng.CopyPicture(Appearance=1, Format=2)
time.sleep(2.0)
print("Получаю изображение из буфера обмена...")
img = ImageGrab.grabclipboard()
if img:
print(f"Сохраняю изображение в {output_file}")
img.save(output_file)
print(f"Изображение успешно сохранено в {output_file}")
else:
print("Не удалось получить изображение из буфера обмена.")
except Exception as e:
print(f"Ошибка при работе с листом: {e}")
finally:
print("Закрываю книгу...")
workbook.Close(SaveChanges=False)
print("Книга закрыта")
except Exception as e:
print(f"Ошибка при открытии книги: {e}")
finally:
print("Закрываю Excel...")
excel.Quit()
print("Excel закрыт")
except Exception as e:
print(f"Общая ошибка: {e}")
finally:
print("Очистка COM-объектов...")
# gc.collect()
print("Готово")
screenshot_excel_range(output_file="Excel_1.png", sheet_name="Sheet1")
