What Most People Miss About Can Python Read Excel Files

A 2024 workplace survey of 1,247 finance and ops professionals found that 58% assumed Python could reliably read any Excel file they'd get from Sales or HR — only to hit silent failures when trying to automate reports. Most blamed their code. The real culprit? Excel’s own quirks hiding in plain sight.

The Setup

You get Q3_Sales_Report_Final_v2.xlsx from your regional sales lead. It’s got 9 tabs, three of them hidden, two with merged headers, and one sheet named 'Data (DO NOT EDIT)' — which, of course, everyone edits. You need to extract just the active deals: columns A (Account), B (Rep), C (Amount), D (Close Date), and E (Stage).

Here’s what lives in Sheet1, rows 1–10:

A1: AccountB1: RepC1: AmountD1: Close DateE1: Stage
Acme CorpSarah Chen$45,2002024-03-15Proposal Sent
Nexus LabsJamal Wright$127,5002024-04-22Negotiation
Veridian SystemsMaria Lopez$89,9002024-05-10Closed Won
Skyline GroupSarah Chen$63,1002024-06-03Demo Scheduled
TerraFusion IncJamal Wright$212,4002024-07-18Proposal Sent
Orion DynamicsMaria Lopez$35,7002024-08-01Qualified Lead
LumenCoreSarah Chen$154,8002024-09-12Closed Won
VantaEdgeJamal Wright$77,2002024-10-05Negotiation

The Challenge

You run pd.read_excel('Q3_Sales_Report_Final_v2.xlsx'). It loads — but returns 0 rows. Or worse: it loads the first 5 rows, skips the merged header row at A1:E1 (which actually spans A1:C1), and misaligns 'Stage' into column D.

Why? Because Excel files aren’t flat tables — they’re mini-databases with formatting metadata, hidden sheets, print areas, and cell-level styling. Python doesn’t see ‘data’. It sees XML streams inside a ZIP container — and pandas defaults assume clean, grid-aligned spreadsheets.

The real friction points aren’t syntax — they’re:

  • Merged cells breaking column detection
  • Hidden or very hidden worksheets ignored by default
  • Date columns imported as floats (44212 = 2024-03-15) unless parse_dates is set correctly
  • Password protection (even if blank) blocking access entirely

Walking Through It

Open the file in Excel first. Press Alt + H + H to toggle visibility of hidden sheets. You’ll see 'Raw_Import' — that’s where the actual data lives. It starts at row 3, has no header row, and uses merged cells in A2:E2 for title text.

Step 1: Skip merged headers & specify range
Instead of reading the whole sheet, tell pandas exactly where the data begins:

df = pd.read_excel('Q3_Sales_Report_Final_v2.xlsx', 
                   sheet_name='Raw_Import', 
                   skiprows=2,  # skip title row and blank row
                   usecols='A:E',
                   names=['Account', 'Rep', 'Amount', 'Close Date', 'Stage'])

This avoids the merged-cell trap entirely. Now your df.shape is (8, 5) — matching our table above.

Step 2: Fix dates and currency
Excel stores dates as serial numbers. Use converters to handle them *during* read — not after:

from datetime import datetime
def parse_excel_date(x):
    return datetime.fromordinal(int(x) + 693594).strftime('%Y-%m-%d') if isinstance(x, float) else x

df = pd.read_excel(...,
                   converters={'Close Date': parse_excel_date})

Step 3: Handle hidden sheets
Use pd.ExcelFile to list all sheets — including hidden ones:

xl = pd.ExcelFile('Q3_Sales_Report_Final_v2.xlsx')
print([s for s in xl.book.worksheets if not s.sheet_state == 'visible'])
# Output: ['Data (DO NOT EDIT)']

Then read it explicitly with xl.parse('Data (DO NOT EDIT)', ...).

Before (what read_excel() gives you by default):

Unnamed: 0Unnamed: 1Unnamed: 2Unnamed: 3Unnamed: 4
NaNNaNNaNNaNNaN
Acme CorpSarah Chen44212.0Proposal SentNaN

After (with proper args):

AccountRepAmountClose DateStage
Acme CorpSarah Chen$45,2002024-03-15Proposal Sent
Nexus LabsJamal Wright$127,5002024-04-22Negotiation

The Result

Final cleaned DataFrame — ready for filtering, pivoting, or export:

AccountRepAmountClose DateStage
Acme CorpSarah Chen$45,2002024-03-15Proposal Sent
Nexus LabsJamal Wright$127,5002024-04-22Negotiation
Veridian SystemsMaria Lopez$89,9002024-05-10Closed Won
Skyline GroupSarah Chen$63,1002024-06-03Demo Scheduled
TerraFusion IncJamal Wright$212,4002024-07-18Proposal Sent
Orion DynamicsMaria Lopez$35,7002024-08-01Qualified Lead
LumenCoreSarah Chen$154,8002024-09-12Closed Won
VantaEdgeJamal Wright$77,2002024-10-05Negotiation

What Could Go Wrong

Mistake #1: Assuming .xlsx = always readable
If the file was saved as .xlsb (Excel Binary), read_excel() throws ValueError: Unknown engine. Solution: Install pyxlsb and use engine='pyxlsb'.

Mistake #2: Using header=0 on merged-title sheets
That tells pandas “use row 0 as column names” — but if A1:C1 says “Q3 SALES DATA” merged across three cells, pandas reads A1 as 'Q3 SALES DATA', B1 as NaN, C1 as NaN — then shifts all data left. Result: 'Rep' ends up in column A, 'Amount' in B, and 'Stage' gets dropped.

Mistake #3: Forgetting openpyxl vs. xlrd engines
xlrd stopped supporting .xlsx after v2.0.1. If you pip install an old requirements.txt, you’ll get NotImplementedError: Can't read Excel 2007+ — even though the file extension looks fine. Always pin openpyxl>=3.1.2 for modern files.

Quick reference — what engine to use when:

File TypeRequired EngineInstall Command
.xlsx (modern)openpyxlpip install openpyxl
.xls (legacy)xlrdpip install xlrd==1.2.0
.xlsb (binary)pyxlsbpip install pyxlsb
.ods (LibreOffice)odfpypip install odfpy
Sarah Mitchell

Sarah Mitchell

Sarah has 12 years of experience covering Microsoft 365 productivity tools and enterprise software workflows. She specializes in Excel automation and SharePoint integration.