I am writing a telegram bot, where I want to store the information that the user will input in a excel spreadsheet. Two libraries the I am using are teleport and openpyxl. I am facing a problem that data is not writing in excel file.
Here is my code:
import telebot
import openpyxl
bot = telebot.TeleBot("TOKEN")
wb = openpyxl.load_workbook('results.xlsx')
ws = wb['Poll']
last_row = ws.max_row + 1
#bot.message_handler(content_types=['text'])
def handle_text(message):
if message.text == '/start':
bot.send_message(message.from_user.id, 'Hello Welcome')
user_id = str(message.from_user.id)
ws.cell(row = last_row, column = 1).value = user_id #here is where the problem is
wb.save('results.xlsx')
bot.polling(none_stop=True, timeout=60)
The code is not giving any mistakes, moreover, if I put ws.cell line before #bot.message_handle and give some value it works. It is just not working inside the decorator. Does anyone know how to fix this problem?
Thanks!
Your code currently has a fixed value for the row where you write data so you will always overwrite the same cells. Much better simply to append to the worksheet:
ws.append([user_id])
Related
everyone. I'm looking for a way to make notes in excel worksheet, using python. Found a way to add comments, but I need notes like on screenshot. Is there an easy way to add them using openpyxl or any other lib? screenshot of a note
Updating this because this post is the top google result
The openpyxl docs incorrectly refer to notes as comments. Just follow the instructions in the docs on how to add comments: https://openpyxl.readthedocs.io/en/latest/comments.html. There is one issue with their example code though in the docs, so use the below code instead:
from openpyxl import Workbook
from openpyxl.comments import Comment
wb = Workbook()
ws = wb.active
comment = Comment('This is the comment text', 'Comment Author')
ws["A1".comment] = comment
wb.save('your_file.xlsx')
I've been trying to accomplish the same thing. Apparently that "note" is the same as data validation as described in the docs.
So what you do is:
from openpyxl import load_workbook
from openpyxl.worksheet.datavalidation import DataValidation
wb = load_workbook('my_sheets.xlsx')
# Create 'note'
dv = DataValidation()
dv.errorTitle = 'Your note title'
dv.error = 'Your note body'
# Add 'note' to A1 in the active sheet
dv.add(wb.active['A1'])
# This is required also, or you won't see the note
wb.active.add_data_validation(dv)
wb.save('my_sheet_with_note.xlsx')
It also mentions prompts, which is something you can look into:
# Optionally set a custom prompt message
dv.promptTitle = 'List Selection'
dv.prompt = 'Please select from the list'
Edit: I updated the answer with a part of the code that solved my problem at the time:
def add_note(cell, prompt_title='', prompt=''):
p = {
'promptTitle': prompt_title[:32],
'prompt': prompt[:255],
}
dv = DataValidation(**p)
dv.add(cell)
cell.parent.add_data_validation(dv)
I'm filling out a web form which has input fields, dropdown menus, autocomplete fields and action buttons.
I'm pulling the data from an excel sheet using openpyxl. Initially it used to take between 3-4 seconds to populate these fields. After adding read_only=True to my readData function, it improved a bit but not as expected.
Does anyone has any suggestions on how I would be able to reduce the time it takes to populate each field? Any help is really appreciated. I'm leaving both the readData function as well as the populate_form which I use to fill out a text field as an example.
Cheers.
Method to read each cell:
workbook = openpyxl.load_workbook(file, read_only=True)
def readData(file, sheetName, row_num, column_num):
sheet = workbook.get_sheet_by_name(sheetName)
return sheet.cell(row=row_num, column=column_num).value
Method to populate input field:
def fill_out_form(driver, path, input_sel, row_num, column_num):
try:
wait_for_element(driver, "//input[#id='" + input_sel + "']", 5)
xls = readData(path, "Callcenter", row_num, column_num)
input_el = driver.find_element_by_xpath("//input[#id='" + input_sel + "']")
input_el.click()
if column_num == 9 or column_num == 40 or column_num == 67 or column_num == 121:
xls = datetime.strftime(xls,'%d/%m/%Y')
input_el.send_keys(xls)
input_el.send_keys(Keys.TAB)
loading_el = WebDriverWait(driver, 4).until(EC.presence_of_element_located((By.XPATH, "//*[#class='sk-attr js-sk-attr sk-attr--labeled sk-attr--mandatory sk-attr--infonnized sk-attr--error sk-textbox clearfix']")))
WebDriverWait(driver, 4).until(wait_not_spinning(loading_el))
except TimeoutException:
print("Loading took too much time!-Try again")
Unless your spreadsheet is huge I'm fairly certain the wait_for_element and WebDriverWait calls are taking the most time.
As was already suggested, try caching the spreadsheet(s) data using an efficient structure such as:
dict[file][sheet] = list[row][column]
Since it seems you only have one file you can load the data using:
def load_data(filename):
data = {}
workbook = openpyxl.load_workbook(filename, data_only=True, read_only=True, keep_vba=False)
for sheet_name in workbook.sheetnames:
data[sheet_name] = []
sheet = workbook[sheet_name]
for rows in sheet.iter_rows():
row_elements = []
for cell in rows:
try:
value = cell.value
except IndexError:
value = cell.internal_value
row_elements.append(value)
data[sheet_name].append(row_elements)
return data
In order to use it, you would call load_data(filename) once (when your application starts) and access the loaded data later on using xls_data instead of readData:
#application start
xls_data = load_data(filename)
....
# sheet_name->str, row_num->int, col_num->int
xls = xls_data[sheet_name][row_num][col_num]
The above will throw KeyError if the sheet name is invalid or IndexError for an invalid row,column combination.
Try implementing the readData method using the 'xlrd' library.
It does not provide rich API like openpyxl but I'm sure it'll run faster.
When you fill in a web form, in the end the data will be sent to a server with a POST request.
What I would recommend is to use e.g. wireshark to capture that POST request.
Analyse that request to see what exactly is sent to the server. Then you can create such a POST request using the requests module.
That means you don't have to deal with selenium at all.
And as the others have mentioned, read the excel file only once.
I'm having some trouble with PasteSpecial in python. Here's the sample code:
import win32com.client as win32com
from win32com.client import constants
xl = win32com.gencache.EnsureDispatch('Excel.Application')
xl.Visible = True
wb = xl.Workbooks.Add ()
Sheet1 = wb.Sheets("Sheet1")
# Fill in some summy formulas
for i in range(10):
Sheet1.Cells(i+1,1).Value = "=10*"+str(i+1)
Sheet1.Range("A1:A16").Copy()
Sheet1.Range("C1").Select()
Sheet1.PasteSpecial(Paste=constants.xlPasteValues)
I'm getting the following error:
TypeError: Paste() got an unexpected keyword argument 'Paste'
I know that paste is a keyword argument because of the MSDN here:
http://msdn.microsoft.com/en-us/library/office/ff839476(v=office.15).aspx
Any idea why it won't let me do this? Can't really find much on the web.
Edit for solution(s):
import win32com.client as win32com
from win32com.client import constants
xl = win32com.gencache.EnsureDispatch('Excel.Application')
xl.Visible = True
wb = xl.Workbooks.Add ()
Sheet1 = wb.Sheets("Sheet1")
# Fill in some summy formulas
for i in range(10):
Sheet1.Cells(i+1,1).Value = "=10*"+str(i+1)
Sheet1.Range("A1:A16").Copy()
Sheet1.Range("C1").PasteSpecial(Paste=constants.xlPasteValues)
# OR this I just found right after I posted this works as well:
xl.Selection.PasteSpecial(Paste=constants.xlPasteValues)
You can get value for xlPasteFormats by execute macro in Excel vb:
Sub Macro2()
Range("A7").Select
ActiveCell.FormulaR1C1 = xlPasteFormats
End Sub
The value for xlPasteFormats is -4122
In Python script you can use
xlSheet.Range("A7:H7").Copy()
xlSheet.Range("A%s:H%s"%(r,r)).PasteSpecial(Paste=-4122)
I don't work with python but to do a PasteSpecial in Excel-VBA, you have to mention the cell where you want to perform the pastespecial, so try like
Sheet1.Range("C1").PasteSpecial(Paste=constants.xlPasteValues)
If you want a simple paste then I guess this should work
Sheet1.Paste
This code fails with error: "AutoFilter method of Range class failed"
from win32com.client.gencache import EnsureDispatch
excel = EnsureDispatch('Excel.Application')
excel.Visible = 1
workbook = excel.Workbooks.Add()
sheet = workbook.ActiveSheet
sheet.Cells(1, 1).Value = 'Hello world'
sheet.Columns.AutoFilter()
This code also fails although it used to work:
from win32com.client import Dispatch
excel = Dispatch('Excel.Application')
excel.Visible = 1
workbook = excel.Workbooks.Add()
sheet = excel.ActiveSheet
sheet.Cells(1, 1).Value = 'Hello world'
sheet.Columns.AutoFilter()
Python uses win32com to communicate directly with Windows applications, and can work with (via EnsureDispatch) or without (via Dispatch) prior knowledge of the application's API. When you call EnsureDispatch, the API is fetched and written into win32com.gen_py., thereby permanently adding the application's API into your Python library.
Once you've initialised an application with EnsureDispatch, any time that a script uses Dispatch for that application, it will be given the pre-fetched API. This is good, because you can then make use of the predefined application constants (from win32com.client import constants).
However, sometimes previously working code will break. For example, in the following code, AutoFilter() will work without an argument as long as the Excel API has never previously been cached in the library...
# ExcelAutoFilterTest1
# Works unless you ever previously called EnsureDispatch('Excel.Application')
from win32com.client import Dispatch
excel = Dispatch('Excel.Application')
excel.Visible = 1
workbook = excel.Workbooks.Add()
sheet = workbook.ActiveSheet
sheet.Cells(1, 1).Value = 'Hello world'
sheet.Columns.AutoFilter()
The following code will always fail because now the Excel API has been fetched and written to win32com.gen_py.00020813-0000-0000-C000-000000000046x0x1x7 in your Python library, it will no longer accept AutoFilter() without an argument.
# ExcelAutoFilterTest2
# Always fails with error: AutoFilter method of Range class failed
from win32com.client.gencache import EnsureDispatch
excel = EnsureDispatch('Excel.Application')
excel.Visible = 1
workbook = excel.Workbooks.Add()
sheet = workbook.ActiveSheet
sheet.Cells(1, 1).Value = 'Hello world'
sheet.Columns.AutoFilter()
The following code always works because we're now providing the VisibleDropDown argument (1=on, 0=off).
# ExcelAutoFilterTest3
# Always succeeds
from win32com.client.gencache import EnsureDispatch
excel = EnsureDispatch('Excel.Application')
excel.Visible = 1
workbook = excel.Workbooks.Add()
sheet = workbook.ActiveSheet
sheet.Cells(1, 1).Value = 'Hello world'
sheet.Columns.AutoFilter(1)
This seems to be a bug, because the Excel API documentation claims that all arguments to AutoFilter are optional:
"If you omit all the arguments, this method simply toggles the display
of the AutoFilter drop-down arrows in the specified range."
I have to code a really annoying script that uses one excel file to update another, but because you cannot directly edit a xls file, nor insert row, I have had to improvise.
Now my question is:
Using the xlwt module for Python (using 2.7x), when you create a workbook and are working on it, how does one write to the same worksheet that was created in a different function? Can I just pass the workbook back and forth with its variable name? If so, how do I access the first worksheet I made, workbook[0]?
I have multiple functions that need to interact with this xlwt xls file I am making, so I just want to be sure I can pass it around different functions.
Thanks!
...yes
import xlwt
class MyWorkbook:
''' allow access to a workbooks sheets'''
def __init__(self,*args,**kwargs):
self.wb = xlwt.Workbook(*args,**kwargs)
self.sheets = []
def add_sheet(self,sheet_name):
self.sheets.append(self.wb.add_sheet(sheet_name))
return self.sheets[-1]
def GetSheetByIndex(self,n):
return self.sheets[n]
def save(self,fname_or_stream):
return self.wb.save(fname_or_stream)
def CreateWB():
''' return a MyWorkbook instance with 1 sheet'''
m= MyWorkbook()
m.add_sheet("first_sheet")
return m
def ModifySheet0(mwb):
'''uses instance of MyWorkbook and modifies sheet0'''
s = mwb.GetSheetByIndex(0)
s.write(0,0,"Hello World!")
def DoItAll()
'''passing around MyWorkbook'''
wb = CreateWB()
ModifySheet0(wb)
wb.save("somefile.xls")