[{"data":1,"prerenderedAt":2655},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors":2646},{"id":4,"title":5,"body":6,"dateModified":2623,"datePublished":2623,"description":2624,"extension":2625,"faq":2626,"meta":2637,"navigation":270,"path":2638,"seo":2639,"slug":2642,"stem":2643,"type":2644,"__hash__":2645},"docs\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Findex.md","Troubleshooting Common Python Excel Errors",{"type":7,"value":8,"toc":2608},"minimark",[9,24,200,205,228,231,570,586,590,599,660,663,861,869,873,883,1024,1027,1185,1205,1209,1219,1222,1389,1424,1428,1431,1460,1463,1600,1607,1611,1623,1626,1658,1662,1665,2156,2167,2171,2341,2344,2348,2351,2381,2388,2395,2398,2461,2465,2491,2495,2507,2527,2533,2539,2545,2549,2552,2556,2604],[10,11,12,13,17,18,23],"p",{},"Every Python developer who automates Excel meets the same short list of errors, usually at the worst moment: a scheduled job dies at 06:00 with ",[14,15,16],"code",{},"zipfile.BadZipFile",", or a workbook your script wrote yesterday now greets a colleague with \"we found a problem with some content\". The messages look cryptic because the exception is raised several layers below the code you wrote — in a zip reader, an XML parser, or an engine you never named. This guide is a triage map for those failures: what each message actually means, how to confirm the cause in one line, and which fix to apply. It sits inside ",[19,20,22],"a",{"href":21},"\u002Fgetting-started-with-python-excel-automation\u002F","Getting Started with Python Excel Automation"," because the errors here are the ones that interrupt the very first script you write, long before performance or formatting matter.",[25,26,34,35,34,39,34,43,34,50,34,57,34,66,34,72,34,77,34,81,34,85,34,89,34,92,34,95,34,99,34,103,34,106,34,109,34,114,34,118,34,121,34,124,34,132,34,137,34,141,34,144,34,148,34,151,34,156,34,161,34,165,34,167,34,170,34,173,34,175,34,178,34,181,34,183,34,186,34,189,34,194,34,196,34,198],"svg",{"viewBox":27,"role":28,"ariaLabelledBy":29,"xmlns":32,"style":33},"0 0 760 268","img",[30,31],"tce-t","tce-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:760px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[36,37,38],"title",{"id":30},"The four layers between your code and an Excel file",[40,41,42],"desc",{"id":31},"A read passes through the file system, a zip container, an XML parser and finally the library API. Each layer raises its own family of errors, so the layer that fails tells you which fix applies.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","268","#ffffff",[51,52,56],"text",{"x":53,"y":54,"style":55},"380","26","font-size:13px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","Where the exception comes from",[44,58],{"x":59,"y":60,"width":61,"height":62,"rx":63,"fill":64,"stroke":65},"24","46","164","86","12","#f0f2f5","var(--line,#cdd5e6)",[51,67,71],{"x":68,"y":69,"style":70},"106","76","font-size:13px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","file system",[51,73,76],{"x":68,"y":74,"style":75},"98","font-size:11.5px;fill:var(--muted,#5b6780);text-anchor:middle","path, locks,",[51,78,80],{"x":68,"y":79,"style":75},"116","permissions",[44,82],{"x":83,"y":60,"width":61,"height":62,"rx":63,"fill":84,"stroke":65},"212","#ebebfd",[51,86,88],{"x":87,"y":69,"style":70},"294","zip container",[51,90,91],{"x":87,"y":74,"style":75},"an .xlsx is a zip",[51,93,94],{"x":87,"y":79,"style":75},"of XML parts",[44,96],{"x":97,"y":60,"width":61,"height":62,"rx":63,"fill":98,"stroke":65},"400","#d9f4f1",[51,100,102],{"x":101,"y":69,"style":70},"482","XML parser",[51,104,105],{"x":101,"y":74,"style":75},"sheets, styles,",[51,107,108],{"x":101,"y":79,"style":75},"shared strings",[44,110],{"x":111,"y":60,"width":112,"height":62,"rx":63,"fill":113,"stroke":65},"588","148","#fdefd8",[51,115,117],{"x":116,"y":69,"style":70},"662","library API",[51,119,120],{"x":116,"y":74,"style":75},"your sheet and",[51,122,123],{"x":116,"y":79,"style":75},"cell lookups",[125,126],"line",{"x1":127,"y1":128,"x2":129,"y2":128,"stroke":130,"style":131},"188","89","208","var(--brand,#5b5cf0)","stroke-width:2px",[133,134],"polygon",{"points":135,"fill":136},"208,89 198,84 198,94","#5b5cf0",[125,138],{"x1":139,"y1":128,"x2":140,"y2":128,"stroke":130,"style":131},"376","396",[133,142],{"points":143,"fill":136},"396,89 386,84 386,94",[125,145],{"x1":146,"y1":128,"x2":147,"y2":128,"stroke":130,"style":131},"564","584",[133,149],{"points":150,"fill":136},"584,89 574,84 574,94",[44,152],{"x":59,"y":153,"width":61,"height":69,"rx":154,"fill":155,"stroke":65},"160","10","#fee8f2",[51,157,160],{"x":68,"y":158,"style":159},"186","font-size:11.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","FileNotFoundError",[51,162,164],{"x":68,"y":163,"style":159},"206","PermissionError",[44,166],{"x":83,"y":153,"width":61,"height":69,"rx":154,"fill":155,"stroke":65},[51,168,169],{"x":87,"y":158,"style":159},"BadZipFile",[51,171,172],{"x":87,"y":163,"style":159},"InvalidFileException",[44,174],{"x":97,"y":153,"width":61,"height":69,"rx":154,"fill":155,"stroke":65},[51,176,177],{"x":101,"y":158,"style":159},"ValueError: no engine",[51,179,180],{"x":101,"y":163,"style":159},"XML syntax errors",[44,182],{"x":111,"y":153,"width":112,"height":69,"rx":154,"fill":155,"stroke":65},[51,184,185],{"x":116,"y":158,"style":159},"KeyError",[51,187,188],{"x":116,"y":163,"style":159},"TypeError on None",[125,190],{"x1":68,"y1":191,"x2":68,"y2":192,"stroke":65,"style":193},"132","156","stroke-width:1.5px",[125,195],{"x1":87,"y1":191,"x2":87,"y2":192,"stroke":65,"style":193},[125,197],{"x1":101,"y1":191,"x2":101,"y2":192,"stroke":65,"style":193},[125,199],{"x1":116,"y1":191,"x2":116,"y2":192,"stroke":65,"style":193},[201,202,204],"h2",{"id":203},"read-the-traceback-from-the-bottom-up-then-identify-the-file","Read the traceback from the bottom up, then identify the file",[10,206,207,208,211,212,215,216,219,220,223,224,227],{},"Python prints the exception last, and that final line is the only part written for you rather than for the library author. Two pieces of information settle most cases: the exception type, and which module raised it. ",[14,209,210],{},"zipfile"," means the container never opened. ",[14,213,214],{},"openpyxl.utils.exceptions"," means the container opened but the contents were not what openpyxl expected. ",[14,217,218],{},"pandas.io.excel"," means pandas could not pick an engine. ",[14,221,222],{},"xml.etree"," or ",[14,225,226],{},"lxml"," means a part inside the workbook is malformed.",[10,229,230],{},"Before changing any code, confirm what the file actually is. Extensions lie constantly, because export tools rename files without converting them:",[232,233,238],"pre",{"className":234,"code":235,"language":236,"meta":237,"style":237},"language-python shiki shiki-themes github-light github-dark-high-contrast","\"\"\"Identify a spreadsheet by its magic bytes, not its extension.\"\"\"\nfrom pathlib import Path\n\nSIGNATURES = {\n    b\"PK\\x03\\x04\": \"Zip container — .xlsx, .xlsm, .xlsb or .ods\",\n    b\"\\xd0\\xcf\\x11\\xe0\": \"OLE2 compound file — legacy .xls (or an encrypted workbook)\",\n    b\"\u003C?xml\": \"XML text — a SpreadsheetML file, not a real .xlsx\",\n    b\"\u003Chtml\": \"HTML text — an export renamed to .xls\",\n    b\"\\xef\\xbb\\xbf\": \"UTF-8 text with a byte-order mark — almost certainly CSV\",\n}\n\ndef identify(path: str) -> str:\n    head = Path(path).read_bytes()[:8]\n    for magic, label in SIGNATURES.items():\n        if head.startswith(magic):\n            return label\n    if head[:1].isalpha() or head[:1] in (b'\"', b\",\"):\n        return \"Plain text — CSV\u002FTSV with a spreadsheet extension\"\n    return f\"Unrecognised: {head!r}\"\n\nprint(identify(\"report.xlsx\"))\n","python","",[14,239,240,248,265,272,285,310,329,344,359,378,384,389,413,431,449,458,467,514,523,550,555],{"__ignoreMap":237},[241,242,244],"span",{"class":125,"line":243},1,[241,245,247],{"class":246},"srMev","\"\"\"Identify a spreadsheet by its magic bytes, not its extension.\"\"\"\n",[241,249,251,255,259,262],{"class":125,"line":250},2,[241,252,254],{"class":253},"s-kum","from",[241,256,258],{"class":257},"skGVy"," pathlib ",[241,260,261],{"class":253},"import",[241,263,264],{"class":257}," Path\n",[241,266,268],{"class":125,"line":267},3,[241,269,271],{"emptyLinePlaceholder":270},true,"\n",[241,273,275,279,282],{"class":125,"line":274},4,[241,276,278],{"class":277},"sP0c6","SIGNATURES",[241,280,281],{"class":253}," =",[241,283,284],{"class":257}," {\n",[241,286,288,291,294,298,301,304,307],{"class":125,"line":287},5,[241,289,290],{"class":253},"    b",[241,292,293],{"class":246},"\"PK",[241,295,297],{"class":296},"sSjpA","\\x03\\x04",[241,299,300],{"class":246},"\"",[241,302,303],{"class":257},": ",[241,305,306],{"class":246},"\"Zip container — .xlsx, .xlsm, .xlsb or .ods\"",[241,308,309],{"class":257},",\n",[241,311,313,315,317,320,322,324,327],{"class":125,"line":312},6,[241,314,290],{"class":253},[241,316,300],{"class":246},[241,318,319],{"class":296},"\\xd0\\xcf\\x11\\xe0",[241,321,300],{"class":246},[241,323,303],{"class":257},[241,325,326],{"class":246},"\"OLE2 compound file — legacy .xls (or an encrypted workbook)\"",[241,328,309],{"class":257},[241,330,332,334,337,339,342],{"class":125,"line":331},7,[241,333,290],{"class":253},[241,335,336],{"class":246},"\"\u003C?xml\"",[241,338,303],{"class":257},[241,340,341],{"class":246},"\"XML text — a SpreadsheetML file, not a real .xlsx\"",[241,343,309],{"class":257},[241,345,347,349,352,354,357],{"class":125,"line":346},8,[241,348,290],{"class":253},[241,350,351],{"class":246},"\"\u003Chtml\"",[241,353,303],{"class":257},[241,355,356],{"class":246},"\"HTML text — an export renamed to .xls\"",[241,358,309],{"class":257},[241,360,362,364,366,369,371,373,376],{"class":125,"line":361},9,[241,363,290],{"class":253},[241,365,300],{"class":246},[241,367,368],{"class":296},"\\xef\\xbb\\xbf",[241,370,300],{"class":246},[241,372,303],{"class":257},[241,374,375],{"class":246},"\"UTF-8 text with a byte-order mark — almost certainly CSV\"",[241,377,309],{"class":257},[241,379,381],{"class":125,"line":380},10,[241,382,383],{"class":257},"}\n",[241,385,387],{"class":125,"line":386},11,[241,388,271],{"emptyLinePlaceholder":270},[241,390,392,395,399,402,405,408,410],{"class":125,"line":391},12,[241,393,394],{"class":253},"def",[241,396,398],{"class":397},"s_Opv"," identify",[241,400,401],{"class":257},"(path: ",[241,403,404],{"class":277},"str",[241,406,407],{"class":257},") -> ",[241,409,404],{"class":277},[241,411,412],{"class":257},":\n",[241,414,416,419,422,425,428],{"class":125,"line":415},13,[241,417,418],{"class":257},"    head ",[241,420,421],{"class":253},"=",[241,423,424],{"class":257}," Path(path).read_bytes()[:",[241,426,427],{"class":277},"8",[241,429,430],{"class":257},"]\n",[241,432,434,437,440,443,446],{"class":125,"line":433},14,[241,435,436],{"class":253},"    for",[241,438,439],{"class":257}," magic, label ",[241,441,442],{"class":253},"in",[241,444,445],{"class":277}," SIGNATURES",[241,447,448],{"class":257},".items():\n",[241,450,452,455],{"class":125,"line":451},15,[241,453,454],{"class":253},"        if",[241,456,457],{"class":257}," head.startswith(magic):\n",[241,459,461,464],{"class":125,"line":460},16,[241,462,463],{"class":253},"            return",[241,465,466],{"class":257}," label\n",[241,468,470,473,476,479,482,485,487,489,492,494,497,500,503,506,508,511],{"class":125,"line":469},17,[241,471,472],{"class":253},"    if",[241,474,475],{"class":257}," head[:",[241,477,478],{"class":277},"1",[241,480,481],{"class":257},"].isalpha() ",[241,483,484],{"class":253},"or",[241,486,475],{"class":257},[241,488,478],{"class":277},[241,490,491],{"class":257},"] ",[241,493,442],{"class":253},[241,495,496],{"class":257}," (",[241,498,499],{"class":253},"b",[241,501,502],{"class":246},"'\"'",[241,504,505],{"class":257},", ",[241,507,499],{"class":253},[241,509,510],{"class":246},"\",\"",[241,512,513],{"class":257},"):\n",[241,515,517,520],{"class":125,"line":516},18,[241,518,519],{"class":253},"        return",[241,521,522],{"class":246}," \"Plain text — CSV\u002FTSV with a spreadsheet extension\"\n",[241,524,526,529,532,535,538,541,544,547],{"class":125,"line":525},19,[241,527,528],{"class":253},"    return",[241,530,531],{"class":253}," f",[241,533,534],{"class":246},"\"Unrecognised: ",[241,536,537],{"class":296},"{",[241,539,540],{"class":257},"head",[241,542,543],{"class":253},"!r",[241,545,546],{"class":296},"}",[241,548,549],{"class":246},"\"\n",[241,551,553],{"class":125,"line":552},20,[241,554,271],{"emptyLinePlaceholder":270},[241,556,558,561,564,567],{"class":125,"line":557},21,[241,559,560],{"class":277},"print",[241,562,563],{"class":257},"(identify(",[241,565,566],{"class":246},"\"report.xlsx\"",[241,568,569],{"class":257},"))\n",[10,571,572,573,576,577,580,581,585],{},"Run that against the failing file first. A surprising share of \"openpyxl is broken\" reports end here: the file is a CSV, an HTML table, or a legacy ",[14,574,575],{},".xls"," wearing an ",[14,578,579],{},".xlsx"," extension. The follow-up guide ",[19,582,584],{"href":583},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-file-format-cannot-be-determined-in-pandas\u002F","Fix \"Excel file format cannot be determined\" in pandas"," works through that specific message end to end.",[201,587,589],{"id":588},"when-the-container-will-not-open-at-all","When the container will not open at all",[10,591,592,595,596,598],{},[14,593,594],{},"zipfile.BadZipFile: File is not a zip file"," is the single most common Python Excel error, and it has nothing to do with Excel. An ",[14,597,579],{}," is a zip archive; when the bytes on disk are not a zip, the read fails before any spreadsheet logic runs. Three causes account for nearly all of it:",[600,601,602,618],"table",{},[603,604,605],"thead",{},[606,607,608,612,615],"tr",{},[609,610,611],"th",{},"Symptom",[609,613,614],{},"Real cause",[609,616,617],{},"Fix",[619,620,621,638,649],"tbody",{},[606,622,623,627,635],{},[624,625,626],"td",{},"File opens in Excel, fails in Python",[624,628,629,630,632,633],{},"It is a legacy ",[14,631,575],{}," or an HTML export renamed ",[14,634,579],{},[624,636,637],{},"Convert it, or read it with the right engine",[606,639,640,643,646],{},[624,641,642],{},"File is much smaller than expected",[624,644,645],{},"A truncated or failed download",[624,647,648],{},"Re-fetch and verify the byte length or checksum",[606,650,651,654,657],{},[624,652,653],{},"File works sometimes, fails on a schedule",[624,655,656],{},"The job read it mid-write, while a sync client was still copying",[624,658,659],{},"Wait for a stable size, or write to a temp name and rename",[10,661,662],{},"The third case is the nastiest because it is intermittent. A file appears on a network share the instant its first byte lands, so a cron job that fires on a timer can open a half-written workbook. Guard the read:",[232,664,666],{"className":234,"code":665,"language":236,"meta":237,"style":237},"\"\"\"Only read a file once its size has stopped changing.\"\"\"\nimport time\nfrom pathlib import Path\n\ndef wait_until_stable(path: str, checks: int = 3, delay: float = 2.0) -> Path:\n    p = Path(path)\n    last = -1\n    stable = 0\n    while stable \u003C checks:\n        size = p.stat().st_size\n        stable = stable + 1 if size == last else 0\n        last = size\n        time.sleep(delay)\n    return p\n\nwb_path = wait_until_stable(\"\u002Fmnt\u002Fshare\u002Fincoming\u002Fsales.xlsx\")\n",[14,667,668,673,680,690,694,730,740,753,763,777,787,819,829,834,841,845],{"__ignoreMap":237},[241,669,670],{"class":125,"line":243},[241,671,672],{"class":246},"\"\"\"Only read a file once its size has stopped changing.\"\"\"\n",[241,674,675,677],{"class":125,"line":250},[241,676,261],{"class":253},[241,678,679],{"class":257}," time\n",[241,681,682,684,686,688],{"class":125,"line":267},[241,683,254],{"class":253},[241,685,258],{"class":257},[241,687,261],{"class":253},[241,689,264],{"class":257},[241,691,692],{"class":125,"line":274},[241,693,271],{"emptyLinePlaceholder":270},[241,695,696,698,701,703,705,708,711,713,716,719,722,724,727],{"class":125,"line":287},[241,697,394],{"class":253},[241,699,700],{"class":397}," wait_until_stable",[241,702,401],{"class":257},[241,704,404],{"class":277},[241,706,707],{"class":257},", checks: ",[241,709,710],{"class":277},"int",[241,712,281],{"class":253},[241,714,715],{"class":277}," 3",[241,717,718],{"class":257},", delay: ",[241,720,721],{"class":277},"float",[241,723,281],{"class":253},[241,725,726],{"class":277}," 2.0",[241,728,729],{"class":257},") -> Path:\n",[241,731,732,735,737],{"class":125,"line":312},[241,733,734],{"class":257},"    p ",[241,736,421],{"class":253},[241,738,739],{"class":257}," Path(path)\n",[241,741,742,745,747,750],{"class":125,"line":331},[241,743,744],{"class":257},"    last ",[241,746,421],{"class":253},[241,748,749],{"class":253}," -",[241,751,752],{"class":277},"1\n",[241,754,755,758,760],{"class":125,"line":346},[241,756,757],{"class":257},"    stable ",[241,759,421],{"class":253},[241,761,762],{"class":277}," 0\n",[241,764,765,768,771,774],{"class":125,"line":361},[241,766,767],{"class":253},"    while",[241,769,770],{"class":257}," stable ",[241,772,773],{"class":253},"\u003C",[241,775,776],{"class":257}," checks:\n",[241,778,779,782,784],{"class":125,"line":380},[241,780,781],{"class":257},"        size ",[241,783,421],{"class":253},[241,785,786],{"class":257}," p.stat().st_size\n",[241,788,789,792,794,796,799,802,805,808,811,814,817],{"class":125,"line":386},[241,790,791],{"class":257},"        stable ",[241,793,421],{"class":253},[241,795,770],{"class":257},[241,797,798],{"class":253},"+",[241,800,801],{"class":277}," 1",[241,803,804],{"class":253}," if",[241,806,807],{"class":257}," size ",[241,809,810],{"class":253},"==",[241,812,813],{"class":257}," last ",[241,815,816],{"class":253},"else",[241,818,762],{"class":277},[241,820,821,824,826],{"class":125,"line":391},[241,822,823],{"class":257},"        last ",[241,825,421],{"class":253},[241,827,828],{"class":257}," size\n",[241,830,831],{"class":125,"line":415},[241,832,833],{"class":257},"        time.sleep(delay)\n",[241,835,836,838],{"class":125,"line":433},[241,837,528],{"class":253},[241,839,840],{"class":257}," p\n",[241,842,843],{"class":125,"line":451},[241,844,271],{"emptyLinePlaceholder":270},[241,846,847,850,852,855,858],{"class":125,"line":460},[241,848,849],{"class":257},"wb_path ",[241,851,421],{"class":253},[241,853,854],{"class":257}," wait_until_stable(",[241,856,857],{"class":246},"\"\u002Fmnt\u002Fshare\u002Fincoming\u002Fsales.xlsx\"",[241,859,860],{"class":257},")\n",[10,862,863,864,868],{},"The full breakdown, including how to salvage data from a partially damaged archive, is in ",[19,865,867],{"href":866},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-badzipfile-error-when-reading-excel-in-python\u002F","Fix BadZipFile when reading an Excel file in Python",".",[201,870,872],{"id":871},"when-the-engine-is-wrong-for-the-format","When the engine is wrong for the format",[10,874,875,876,505,879,882],{},"pandas does not read Excel itself; it delegates to an engine, and each engine handles exactly one family of formats. Choosing the wrong one produces ",[14,877,878],{},"ValueError: Excel file format cannot be determined",[14,880,881],{},"openpyxl does not support the old .xls file format",", or a missing-dependency error naming a package you have never installed.",[25,884,34,889,34,892,34,895,34,898,34,901,34,905,34,911,34,914,34,919,34,923,34,926,34,929,34,933,34,937,34,941,34,945,34,950,34,952,34,956,34,958,34,962,34,964,34,966,34,968,34,970,34,973,34,976,34,978,34,980,34,982,34,985,34,987,34,990,34,992,34,995,34,998,34,1003,34,1005,34,1008,34,1010,34,1012,34,1014,34,1016,34,1018,34,1020],{"viewBox":885,"role":28,"ariaLabelledBy":886,"xmlns":32,"style":33},"0 0 760 244",[887,888],"tce2-t","tce2-d",[36,890,891],{"id":887},"Which engine reads which spreadsheet format",[40,893,894],{"id":888},"openpyxl reads xlsx and xlsm, xlrd reads only legacy xls, pyxlsb reads xlsb, odfpy reads ods, and python-calamine reads all of them; picking an engine outside its column raises a format error.",[44,896],{"x":46,"y":46,"width":47,"height":897,"fill":49},"244",[51,899,900],{"x":53,"y":54,"style":55},"Engine coverage by file format",[44,902],{"x":59,"y":903,"width":904,"height":903,"rx":154,"fill":64,"stroke":65},"44","150",[51,906,910],{"x":907,"y":908,"style":909},"99","72","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","engine",[44,912],{"x":913,"y":903,"width":191,"height":903,"rx":154,"fill":84,"stroke":65},"182",[51,915,918],{"x":916,"y":908,"style":917},"248","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle",".xlsx \u002F .xlsm",[44,920],{"x":921,"y":903,"width":922,"height":903,"rx":154,"fill":84,"stroke":65},"322","120",[51,924,575],{"x":925,"y":908,"style":917},"382",[44,927],{"x":928,"y":903,"width":922,"height":903,"rx":154,"fill":84,"stroke":65},"450",[51,930,932],{"x":931,"y":908,"style":917},"510",".xlsb",[44,934],{"x":935,"y":903,"width":936,"height":903,"rx":154,"fill":84,"stroke":65},"578","158",[51,938,940],{"x":939,"y":908,"style":917},"657",".ods",[44,942],{"x":59,"y":943,"width":904,"height":944,"rx":427,"fill":98,"stroke":65},"96","34",[51,946,949],{"x":907,"y":947,"style":948},"118","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","openpyxl",[44,951],{"x":913,"y":943,"width":191,"height":944,"rx":427,"fill":98,"stroke":65},[51,953,955],{"x":916,"y":947,"style":954},"font-size:12px;fill:var(--teal-ink,#0b6157);text-anchor:middle","yes",[44,957],{"x":921,"y":943,"width":922,"height":944,"rx":427,"fill":155,"stroke":65},[51,959,961],{"x":925,"y":947,"style":960},"font-size:12px;fill:var(--accent-ink,#be185d);text-anchor:middle","no",[44,963],{"x":928,"y":943,"width":922,"height":944,"rx":427,"fill":155,"stroke":65},[51,965,961],{"x":931,"y":947,"style":960},[44,967],{"x":935,"y":943,"width":936,"height":944,"rx":427,"fill":155,"stroke":65},[51,969,961],{"x":939,"y":947,"style":960},[44,971],{"x":59,"y":972,"width":904,"height":944,"rx":427,"fill":98,"stroke":65},"138",[51,974,975],{"x":907,"y":153,"style":948},"xlrd \u002F pyxlsb \u002F odfpy",[44,977],{"x":913,"y":972,"width":191,"height":944,"rx":427,"fill":155,"stroke":65},[51,979,961],{"x":916,"y":153,"style":960},[44,981],{"x":921,"y":972,"width":922,"height":944,"rx":427,"fill":98,"stroke":65},[51,983,984],{"x":925,"y":153,"style":954},"xlrd",[44,986],{"x":928,"y":972,"width":922,"height":944,"rx":427,"fill":98,"stroke":65},[51,988,989],{"x":931,"y":153,"style":954},"pyxlsb",[44,991],{"x":935,"y":972,"width":936,"height":944,"rx":427,"fill":98,"stroke":65},[51,993,994],{"x":939,"y":153,"style":954},"odfpy",[44,996],{"x":59,"y":997,"width":904,"height":944,"rx":427,"fill":113,"stroke":65},"180",[51,999,1002],{"x":907,"y":1000,"style":1001},"202","font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","calamine",[44,1004],{"x":913,"y":997,"width":191,"height":944,"rx":427,"fill":113,"stroke":65},[51,1006,955],{"x":916,"y":1000,"style":1007},"font-size:12px;fill:var(--gold-ink,#7a4e06);text-anchor:middle",[44,1009],{"x":921,"y":997,"width":922,"height":944,"rx":427,"fill":113,"stroke":65},[51,1011,955],{"x":925,"y":1000,"style":1007},[44,1013],{"x":928,"y":997,"width":922,"height":944,"rx":427,"fill":113,"stroke":65},[51,1015,955],{"x":931,"y":1000,"style":1007},[44,1017],{"x":935,"y":997,"width":936,"height":944,"rx":427,"fill":113,"stroke":65},[51,1019,955],{"x":939,"y":1000,"style":1007},[51,1021,1023],{"x":53,"y":1022,"style":75},"232","pandas picks an engine from the extension — override it with engine= when the extension is wrong",[10,1025,1026],{},"The practical rule: never let pandas guess when you already know. Pass the engine explicitly, and install it in the same requirements file as pandas so a fresh environment cannot drift:",[232,1028,1030],{"className":234,"code":1029,"language":236,"meta":237,"style":237},"import pandas as pd\n\ndf_modern = pd.read_excel(\"sales.xlsx\", engine=\"openpyxl\")   # pip install openpyxl\ndf_legacy = pd.read_excel(\"sales.xls\", engine=\"xlrd\")        # pip install xlrd\ndf_binary = pd.read_excel(\"sales.xlsb\", engine=\"pyxlsb\")     # pip install pyxlsb\ndf_open   = pd.read_excel(\"sales.ods\", engine=\"odf\")         # pip install odfpy\ndf_fast   = pd.read_excel(\"sales.xlsx\", engine=\"calamine\")   # pip install python-calamine\n",[14,1031,1032,1045,1049,1079,1106,1133,1160],{"__ignoreMap":237},[241,1033,1034,1036,1039,1042],{"class":125,"line":243},[241,1035,261],{"class":253},[241,1037,1038],{"class":257}," pandas ",[241,1040,1041],{"class":253},"as",[241,1043,1044],{"class":257}," pd\n",[241,1046,1047],{"class":125,"line":250},[241,1048,271],{"emptyLinePlaceholder":270},[241,1050,1051,1054,1056,1059,1062,1064,1067,1069,1072,1075],{"class":125,"line":267},[241,1052,1053],{"class":257},"df_modern ",[241,1055,421],{"class":253},[241,1057,1058],{"class":257}," pd.read_excel(",[241,1060,1061],{"class":246},"\"sales.xlsx\"",[241,1063,505],{"class":257},[241,1065,910],{"class":1066},"sa561",[241,1068,421],{"class":253},[241,1070,1071],{"class":246},"\"openpyxl\"",[241,1073,1074],{"class":257},")   ",[241,1076,1078],{"class":1077},"s-wDw","# pip install openpyxl\n",[241,1080,1081,1084,1086,1088,1091,1093,1095,1097,1100,1103],{"class":125,"line":274},[241,1082,1083],{"class":257},"df_legacy ",[241,1085,421],{"class":253},[241,1087,1058],{"class":257},[241,1089,1090],{"class":246},"\"sales.xls\"",[241,1092,505],{"class":257},[241,1094,910],{"class":1066},[241,1096,421],{"class":253},[241,1098,1099],{"class":246},"\"xlrd\"",[241,1101,1102],{"class":257},")        ",[241,1104,1105],{"class":1077},"# pip install xlrd\n",[241,1107,1108,1111,1113,1115,1118,1120,1122,1124,1127,1130],{"class":125,"line":287},[241,1109,1110],{"class":257},"df_binary ",[241,1112,421],{"class":253},[241,1114,1058],{"class":257},[241,1116,1117],{"class":246},"\"sales.xlsb\"",[241,1119,505],{"class":257},[241,1121,910],{"class":1066},[241,1123,421],{"class":253},[241,1125,1126],{"class":246},"\"pyxlsb\"",[241,1128,1129],{"class":257},")     ",[241,1131,1132],{"class":1077},"# pip install pyxlsb\n",[241,1134,1135,1138,1140,1142,1145,1147,1149,1151,1154,1157],{"class":125,"line":312},[241,1136,1137],{"class":257},"df_open   ",[241,1139,421],{"class":253},[241,1141,1058],{"class":257},[241,1143,1144],{"class":246},"\"sales.ods\"",[241,1146,505],{"class":257},[241,1148,910],{"class":1066},[241,1150,421],{"class":253},[241,1152,1153],{"class":246},"\"odf\"",[241,1155,1156],{"class":257},")         ",[241,1158,1159],{"class":1077},"# pip install odfpy\n",[241,1161,1162,1165,1167,1169,1171,1173,1175,1177,1180,1182],{"class":125,"line":331},[241,1163,1164],{"class":257},"df_fast   ",[241,1166,421],{"class":253},[241,1168,1058],{"class":257},[241,1170,1061],{"class":246},[241,1172,505],{"class":257},[241,1174,910],{"class":1066},[241,1176,421],{"class":253},[241,1178,1179],{"class":246},"\"calamine\"",[241,1181,1074],{"class":257},[241,1183,1184],{"class":1077},"# pip install python-calamine\n",[10,1186,1187,1188,1190,1191,1194,1195,1199,1200,1204],{},"Note that xlrd dropped ",[14,1189,579],{}," support in version 2.0, which is why an old tutorial's ",[14,1192,1193],{},"engine=\"xlrd\""," on a modern workbook now fails. The dedicated pages ",[19,1196,1198],{"href":1197},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-openpyxl-does-not-support-the-old-xls-format\u002F","Fix \"openpyxl does not support the old .xls format\""," and ",[19,1201,1203],{"href":1202},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002F","Handling Excel File Formats and Conversions"," cover conversion rather than merely reading.",[201,1206,1208],{"id":1207},"when-the-sheet-or-cell-is-not-where-you-think","When the sheet or cell is not where you think",[10,1210,1211,1212,1215,1216,868],{},"Once the file opens, the next class of failure is a lookup that misses. ",[14,1213,1214],{},"KeyError: 'Worksheet Sheet1 does not exist.'"," is thrown by openpyxl when the sheet name differs by a character you cannot see — a trailing space, a non-breaking space pasted from a web page, or different capitalisation. pandas raises its own ",[14,1217,1218],{},"ValueError: Worksheet named 'Sheet1' not found",[10,1220,1221],{},"Never hardcode a sheet name without a fallback. Normalise instead:",[232,1223,1225],{"className":234,"code":1224,"language":236,"meta":237,"style":237},"from openpyxl import load_workbook\n\nwb = load_workbook(\"sales.xlsx\")\nprint(wb.sheetnames)  # ['  Q3 Summary ', 'Raw Data']\n\ndef get_sheet(wb, wanted: str):\n    \"\"\"Match a sheet name ignoring case and surrounding whitespace.\"\"\"\n    target = wanted.strip().casefold()\n    for name in wb.sheetnames:\n        if name.strip().casefold() == target:\n            return wb[name]\n    raise KeyError(f\"{wanted!r} not in {wb.sheetnames}\")\n\nws = get_sheet(wb, \"q3 summary\")\n",[14,1226,1227,1239,1243,1257,1267,1271,1285,1290,1300,1312,1324,1331,1370,1374],{"__ignoreMap":237},[241,1228,1229,1231,1234,1236],{"class":125,"line":243},[241,1230,254],{"class":253},[241,1232,1233],{"class":257}," openpyxl ",[241,1235,261],{"class":253},[241,1237,1238],{"class":257}," load_workbook\n",[241,1240,1241],{"class":125,"line":250},[241,1242,271],{"emptyLinePlaceholder":270},[241,1244,1245,1248,1250,1253,1255],{"class":125,"line":267},[241,1246,1247],{"class":257},"wb ",[241,1249,421],{"class":253},[241,1251,1252],{"class":257}," load_workbook(",[241,1254,1061],{"class":246},[241,1256,860],{"class":257},[241,1258,1259,1261,1264],{"class":125,"line":274},[241,1260,560],{"class":277},[241,1262,1263],{"class":257},"(wb.sheetnames)  ",[241,1265,1266],{"class":1077},"# ['  Q3 Summary ', 'Raw Data']\n",[241,1268,1269],{"class":125,"line":287},[241,1270,271],{"emptyLinePlaceholder":270},[241,1272,1273,1275,1278,1281,1283],{"class":125,"line":312},[241,1274,394],{"class":253},[241,1276,1277],{"class":397}," get_sheet",[241,1279,1280],{"class":257},"(wb, wanted: ",[241,1282,404],{"class":277},[241,1284,513],{"class":257},[241,1286,1287],{"class":125,"line":331},[241,1288,1289],{"class":246},"    \"\"\"Match a sheet name ignoring case and surrounding whitespace.\"\"\"\n",[241,1291,1292,1295,1297],{"class":125,"line":346},[241,1293,1294],{"class":257},"    target ",[241,1296,421],{"class":253},[241,1298,1299],{"class":257}," wanted.strip().casefold()\n",[241,1301,1302,1304,1307,1309],{"class":125,"line":361},[241,1303,436],{"class":253},[241,1305,1306],{"class":257}," name ",[241,1308,442],{"class":253},[241,1310,1311],{"class":257}," wb.sheetnames:\n",[241,1313,1314,1316,1319,1321],{"class":125,"line":380},[241,1315,454],{"class":253},[241,1317,1318],{"class":257}," name.strip().casefold() ",[241,1320,810],{"class":253},[241,1322,1323],{"class":257}," target:\n",[241,1325,1326,1328],{"class":125,"line":386},[241,1327,463],{"class":253},[241,1329,1330],{"class":257}," wb[name]\n",[241,1332,1333,1336,1339,1342,1345,1347,1349,1352,1354,1356,1359,1361,1364,1366,1368],{"class":125,"line":391},[241,1334,1335],{"class":253},"    raise",[241,1337,1338],{"class":277}," KeyError",[241,1340,1341],{"class":257},"(",[241,1343,1344],{"class":253},"f",[241,1346,300],{"class":246},[241,1348,537],{"class":296},[241,1350,1351],{"class":257},"wanted",[241,1353,543],{"class":253},[241,1355,546],{"class":296},[241,1357,1358],{"class":246}," not in ",[241,1360,537],{"class":296},[241,1362,1363],{"class":257},"wb.sheetnames",[241,1365,546],{"class":296},[241,1367,300],{"class":246},[241,1369,860],{"class":257},[241,1371,1372],{"class":125,"line":415},[241,1373,271],{"emptyLinePlaceholder":270},[241,1375,1376,1379,1381,1384,1387],{"class":125,"line":433},[241,1377,1378],{"class":257},"ws ",[241,1380,421],{"class":253},[241,1382,1383],{"class":257}," get_sheet(wb, ",[241,1385,1386],{"class":246},"\"q3 summary\"",[241,1388,860],{"class":257},[10,1390,1391,1392,1395,1396,1399,1400,1402,1403,1406,1407,1410,1411,1415,1416,1418,1419,1423],{},"Its close relative is ",[14,1393,1394],{},"TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'",", which appears when you sum a range that contains empty cells — openpyxl returns ",[14,1397,1398],{},"None",", not ",[14,1401,46],{},", for a blank. And if the value you expect is a formula string like ",[14,1404,1405],{},"'=SUM(B2:B9)'"," rather than a number, you loaded the workbook without ",[14,1408,1409],{},"data_only=True","; ",[19,1412,1414],{"href":1413},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fread-formula-results-with-openpyxl-data-only\u002F","Read formula results with openpyxl data_only"," explains why that flag returns ",[14,1417,1398],{}," on a file Excel has never opened. ",[19,1420,1422],{"href":1421},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-worksheet-does-not-exist-keyerror-in-openpyxl\u002F","Fix \"Worksheet does not exist\" KeyError in openpyxl"," walks the whole lookup path.",[201,1425,1427],{"id":1426},"when-excel-repairs-the-file-your-script-wrote","When Excel repairs the file your script wrote",[10,1429,1430],{},"The most alarming failure produces no Python exception at all. The script finishes, the file exists, and Excel greets the recipient with \"We found a problem with some content in report.xlsx\". Excel is strict about a handful of rules that openpyxl and xlsxwriter will happily let you break:",[1432,1433,1434,1441,1444,1447,1453],"ul",{},[1435,1436,1437,1438,868],"li",{},"A sheet name longer than 31 characters, or containing ",[14,1439,1440],{},"\\ \u002F ? * [ ]",[1435,1442,1443],{},"A defined name, table name or chart reference pointing at a sheet you later deleted or renamed.",[1435,1445,1446],{},"Two Excel tables with the same name in one workbook, or a table whose range no longer holds data.",[1435,1448,1449,1450,1452],{},"A string written into a cell that starts with ",[14,1451,421],{}," but is not a valid formula.",[1435,1454,1455,1456,1459],{},"A ",[14,1457,1458],{},"datetime"," carrying a timezone, which the file format cannot represent.",[10,1461,1462],{},"A five-line guard catches the sheet-name class before it ships:",[232,1464,1466],{"className":234,"code":1465,"language":236,"meta":237,"style":237},"import re\n\nINVALID = re.compile(r\"[\\\\\u002F?*\\[\\]:]\")\n\ndef safe_sheet_name(name: str) -> str:\n    \"\"\"Return a name Excel will accept: trimmed, sanitised, at most 31 chars.\"\"\"\n    cleaned = INVALID.sub(\"-\", name).strip() or \"Sheet\"\n    return cleaned[:31]\n\nprint(safe_sheet_name(\"Q3 2026 \u002F EMEA — regional breakdown\"))  # 'Q3 2026 - EMEA — regional break'\n",[14,1467,1468,1475,1479,1517,1521,1539,1544,1568,1580,1584],{"__ignoreMap":237},[241,1469,1470,1472],{"class":125,"line":243},[241,1471,261],{"class":253},[241,1473,1474],{"class":257}," re\n",[241,1476,1477],{"class":125,"line":250},[241,1478,271],{"emptyLinePlaceholder":270},[241,1480,1481,1484,1486,1489,1492,1494,1497,1501,1504,1507,1510,1513,1515],{"class":125,"line":267},[241,1482,1483],{"class":277},"INVALID",[241,1485,281],{"class":253},[241,1487,1488],{"class":257}," re.compile(",[241,1490,1491],{"class":253},"r",[241,1493,300],{"class":246},[241,1495,1496],{"class":277},"[",[241,1498,1500],{"class":1499},"s_b0D","\\\\",[241,1502,1503],{"class":296},"\u002F?*",[241,1505,1506],{"class":1499},"\\[\\]",[241,1508,1509],{"class":296},":",[241,1511,1512],{"class":277},"]",[241,1514,300],{"class":246},[241,1516,860],{"class":257},[241,1518,1519],{"class":125,"line":274},[241,1520,271],{"emptyLinePlaceholder":270},[241,1522,1523,1525,1528,1531,1533,1535,1537],{"class":125,"line":287},[241,1524,394],{"class":253},[241,1526,1527],{"class":397}," safe_sheet_name",[241,1529,1530],{"class":257},"(name: ",[241,1532,404],{"class":277},[241,1534,407],{"class":257},[241,1536,404],{"class":277},[241,1538,412],{"class":257},[241,1540,1541],{"class":125,"line":312},[241,1542,1543],{"class":246},"    \"\"\"Return a name Excel will accept: trimmed, sanitised, at most 31 chars.\"\"\"\n",[241,1545,1546,1549,1551,1554,1557,1560,1563,1565],{"class":125,"line":331},[241,1547,1548],{"class":257},"    cleaned ",[241,1550,421],{"class":253},[241,1552,1553],{"class":277}," INVALID",[241,1555,1556],{"class":257},".sub(",[241,1558,1559],{"class":246},"\"-\"",[241,1561,1562],{"class":257},", name).strip() ",[241,1564,484],{"class":253},[241,1566,1567],{"class":246}," \"Sheet\"\n",[241,1569,1570,1572,1575,1578],{"class":125,"line":346},[241,1571,528],{"class":253},[241,1573,1574],{"class":257}," cleaned[:",[241,1576,1577],{"class":277},"31",[241,1579,430],{"class":257},[241,1581,1582],{"class":125,"line":361},[241,1583,271],{"emptyLinePlaceholder":270},[241,1585,1586,1588,1591,1594,1597],{"class":125,"line":380},[241,1587,560],{"class":277},[241,1589,1590],{"class":257},"(safe_sheet_name(",[241,1592,1593],{"class":246},"\"Q3 2026 \u002F EMEA — regional breakdown\"",[241,1595,1596],{"class":257},"))  ",[241,1598,1599],{"class":1077},"# 'Q3 2026 - EMEA — regional break'\n",[10,1601,1602,1603,868],{},"The full checklist, including how to diff a repaired file against your original to find what Excel stripped, is in ",[19,1604,1606],{"href":1605},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-repaired-content-after-writing-with-python\u002F","Fix \"Excel found unreadable content\" after writing with Python",[201,1608,1610],{"id":1609},"when-writing-fails-locks-paths-and-encodings","When writing fails: locks, paths and encodings",[10,1612,1613,1614,1617,1618,1622],{},"Writing has a smaller error surface, dominated by one message on Windows: ",[14,1615,1616],{},"PermissionError: [Errno 13] Permission denied",". In practice it means the target workbook is open in Excel, which holds an exclusive lock, and the fix is either to close it or to write somewhere else and swap the file in. ",[19,1619,1621],{"href":1620},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fhandle-permission-denied-when-writing-excel-in-python\u002F","Handle \"Permission denied\" when writing Excel in Python"," covers the atomic-write pattern that makes a scheduled job immune to a colleague's open window.",[10,1624,1625],{},"Two more writing faults are worth pre-empting:",[1432,1627,1628,1641],{},[1435,1629,1630,1636,1637,1640],{},[1631,1632,1633,1635],"strong",{},[14,1634,160],{}," on save"," — the directory does not exist. ",[14,1638,1639],{},"Path(out).parent.mkdir(parents=True, exist_ok=True)"," before saving.",[1435,1642,1643,1649,1650,1653,1654,1657],{},[1631,1644,1645,1648],{},[14,1646,1647],{},"IllegalCharacterError"," from openpyxl"," — a string contains a control character (often ",[14,1651,1652],{},"\\x00"," or a vertical tab) that XML forbids. Strip them with ",[14,1655,1656],{},"re.sub(r\"[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f]\", \"\", value)"," as data comes in from a database or scraped source.",[201,1659,1661],{"id":1660},"a-defensive-reader-you-can-reuse-everywhere","A defensive reader you can reuse everywhere",[10,1663,1664],{},"Most of the diagnostics above collapse into one helper that every job in a codebase can call. It resolves the path, refuses a file that is not the format it claims, picks the engine deliberately, and re-raises every failure with the filename attached so a 06:00 log line is actionable without a rerun.",[232,1666,1668],{"className":234,"code":1667,"language":236,"meta":237,"style":237},"\"\"\"open_workbook — one entry point that fails loudly and legibly.\"\"\"\nfrom pathlib import Path\n\nimport pandas as pd\n\nENGINES = {\".xlsx\": \"openpyxl\", \".xlsm\": \"openpyxl\", \".xls\": \"xlrd\",\n           \".xlsb\": \"pyxlsb\", \".ods\": \"odf\"}\n\nclass SpreadsheetError(RuntimeError):\n    \"\"\"Raised with the offending path so logs never say just 'BadZipFile'.\"\"\"\n\ndef read_sheet(path: str, sheet=0) -> pd.DataFrame:\n    p = Path(path).expanduser().resolve()\n    if not p.is_file():\n        raise SpreadsheetError(f\"{p} does not exist (cwd={Path.cwd()})\")\n    if p.stat().st_size == 0:\n        raise SpreadsheetError(f\"{p} is zero bytes — upstream write failed\")\n\n    head = p.read_bytes()[:4]\n    suffix = p.suffix.lower()\n    if suffix in (\".xlsx\", \".xlsm\") and head[:2] != b\"PK\":\n        raise SpreadsheetError(f\"{p} is named {suffix} but is not a zip container\")\n    if suffix == \".xls\" and head != b\"\\xd0\\xcf\\x11\\xe0\":\n        raise SpreadsheetError(f\"{p} is named .xls but is not an OLE2 file\")\n\n    try:\n        return pd.read_excel(p, sheet_name=sheet, engine=ENGINES.get(suffix))\n    except Exception as exc:  # noqa: BLE001 — re-raised with context below\n        raise SpreadsheetError(f\"{p}: {type(exc).__name__}: {exc}\") from exc\n",[14,1669,1670,1675,1685,1689,1699,1703,1740,1760,1764,1780,1785,1789,1810,1819,1829,1862,1876,1897,1901,1915,1925,1966,1998,2028,2050,2055,2063,2088,2106],{"__ignoreMap":237},[241,1671,1672],{"class":125,"line":243},[241,1673,1674],{"class":246},"\"\"\"open_workbook — one entry point that fails loudly and legibly.\"\"\"\n",[241,1676,1677,1679,1681,1683],{"class":125,"line":250},[241,1678,254],{"class":253},[241,1680,258],{"class":257},[241,1682,261],{"class":253},[241,1684,264],{"class":257},[241,1686,1687],{"class":125,"line":267},[241,1688,271],{"emptyLinePlaceholder":270},[241,1690,1691,1693,1695,1697],{"class":125,"line":274},[241,1692,261],{"class":253},[241,1694,1038],{"class":257},[241,1696,1041],{"class":253},[241,1698,1044],{"class":257},[241,1700,1701],{"class":125,"line":287},[241,1702,271],{"emptyLinePlaceholder":270},[241,1704,1705,1708,1710,1713,1716,1718,1720,1722,1725,1727,1729,1731,1734,1736,1738],{"class":125,"line":312},[241,1706,1707],{"class":277},"ENGINES",[241,1709,281],{"class":253},[241,1711,1712],{"class":257}," {",[241,1714,1715],{"class":246},"\".xlsx\"",[241,1717,303],{"class":257},[241,1719,1071],{"class":246},[241,1721,505],{"class":257},[241,1723,1724],{"class":246},"\".xlsm\"",[241,1726,303],{"class":257},[241,1728,1071],{"class":246},[241,1730,505],{"class":257},[241,1732,1733],{"class":246},"\".xls\"",[241,1735,303],{"class":257},[241,1737,1099],{"class":246},[241,1739,309],{"class":257},[241,1741,1742,1745,1747,1749,1751,1754,1756,1758],{"class":125,"line":331},[241,1743,1744],{"class":246},"           \".xlsb\"",[241,1746,303],{"class":257},[241,1748,1126],{"class":246},[241,1750,505],{"class":257},[241,1752,1753],{"class":246},"\".ods\"",[241,1755,303],{"class":257},[241,1757,1153],{"class":246},[241,1759,383],{"class":257},[241,1761,1762],{"class":125,"line":346},[241,1763,271],{"emptyLinePlaceholder":270},[241,1765,1766,1769,1773,1775,1778],{"class":125,"line":361},[241,1767,1768],{"class":253},"class",[241,1770,1772],{"class":1771},"sMTad"," SpreadsheetError",[241,1774,1341],{"class":257},[241,1776,1777],{"class":277},"RuntimeError",[241,1779,513],{"class":257},[241,1781,1782],{"class":125,"line":380},[241,1783,1784],{"class":246},"    \"\"\"Raised with the offending path so logs never say just 'BadZipFile'.\"\"\"\n",[241,1786,1787],{"class":125,"line":386},[241,1788,271],{"emptyLinePlaceholder":270},[241,1790,1791,1793,1796,1798,1800,1803,1805,1807],{"class":125,"line":391},[241,1792,394],{"class":253},[241,1794,1795],{"class":397}," read_sheet",[241,1797,401],{"class":257},[241,1799,404],{"class":277},[241,1801,1802],{"class":257},", sheet",[241,1804,421],{"class":253},[241,1806,46],{"class":277},[241,1808,1809],{"class":257},") -> pd.DataFrame:\n",[241,1811,1812,1814,1816],{"class":125,"line":415},[241,1813,734],{"class":257},[241,1815,421],{"class":253},[241,1817,1818],{"class":257}," Path(path).expanduser().resolve()\n",[241,1820,1821,1823,1826],{"class":125,"line":433},[241,1822,472],{"class":253},[241,1824,1825],{"class":253}," not",[241,1827,1828],{"class":257}," p.is_file():\n",[241,1830,1831,1834,1837,1839,1841,1843,1845,1847,1850,1852,1855,1857,1860],{"class":125,"line":451},[241,1832,1833],{"class":253},"        raise",[241,1835,1836],{"class":257}," SpreadsheetError(",[241,1838,1344],{"class":253},[241,1840,300],{"class":246},[241,1842,537],{"class":296},[241,1844,10],{"class":257},[241,1846,546],{"class":296},[241,1848,1849],{"class":246}," does not exist (cwd=",[241,1851,537],{"class":296},[241,1853,1854],{"class":257},"Path.cwd()",[241,1856,546],{"class":296},[241,1858,1859],{"class":246},")\"",[241,1861,860],{"class":257},[241,1863,1864,1866,1869,1871,1874],{"class":125,"line":460},[241,1865,472],{"class":253},[241,1867,1868],{"class":257}," p.stat().st_size ",[241,1870,810],{"class":253},[241,1872,1873],{"class":277}," 0",[241,1875,412],{"class":257},[241,1877,1878,1880,1882,1884,1886,1888,1890,1892,1895],{"class":125,"line":469},[241,1879,1833],{"class":253},[241,1881,1836],{"class":257},[241,1883,1344],{"class":253},[241,1885,300],{"class":246},[241,1887,537],{"class":296},[241,1889,10],{"class":257},[241,1891,546],{"class":296},[241,1893,1894],{"class":246}," is zero bytes — upstream write failed\"",[241,1896,860],{"class":257},[241,1898,1899],{"class":125,"line":516},[241,1900,271],{"emptyLinePlaceholder":270},[241,1902,1903,1905,1907,1910,1913],{"class":125,"line":525},[241,1904,418],{"class":257},[241,1906,421],{"class":253},[241,1908,1909],{"class":257}," p.read_bytes()[:",[241,1911,1912],{"class":277},"4",[241,1914,430],{"class":257},[241,1916,1917,1920,1922],{"class":125,"line":552},[241,1918,1919],{"class":257},"    suffix ",[241,1921,421],{"class":253},[241,1923,1924],{"class":257}," p.suffix.lower()\n",[241,1926,1927,1929,1932,1934,1936,1938,1940,1942,1945,1948,1950,1953,1955,1958,1961,1964],{"class":125,"line":557},[241,1928,472],{"class":253},[241,1930,1931],{"class":257}," suffix ",[241,1933,442],{"class":253},[241,1935,496],{"class":257},[241,1937,1715],{"class":246},[241,1939,505],{"class":257},[241,1941,1724],{"class":246},[241,1943,1944],{"class":257},") ",[241,1946,1947],{"class":253},"and",[241,1949,475],{"class":257},[241,1951,1952],{"class":277},"2",[241,1954,491],{"class":257},[241,1956,1957],{"class":253},"!=",[241,1959,1960],{"class":253}," b",[241,1962,1963],{"class":246},"\"PK\"",[241,1965,412],{"class":257},[241,1967,1969,1971,1973,1975,1977,1979,1981,1983,1986,1988,1991,1993,1996],{"class":125,"line":1968},22,[241,1970,1833],{"class":253},[241,1972,1836],{"class":257},[241,1974,1344],{"class":253},[241,1976,300],{"class":246},[241,1978,537],{"class":296},[241,1980,10],{"class":257},[241,1982,546],{"class":296},[241,1984,1985],{"class":246}," is named ",[241,1987,537],{"class":296},[241,1989,1990],{"class":257},"suffix",[241,1992,546],{"class":296},[241,1994,1995],{"class":246}," but is not a zip container\"",[241,1997,860],{"class":257},[241,1999,2001,2003,2005,2007,2010,2013,2016,2018,2020,2022,2024,2026],{"class":125,"line":2000},23,[241,2002,472],{"class":253},[241,2004,1931],{"class":257},[241,2006,810],{"class":253},[241,2008,2009],{"class":246}," \".xls\"",[241,2011,2012],{"class":253}," and",[241,2014,2015],{"class":257}," head ",[241,2017,1957],{"class":253},[241,2019,1960],{"class":253},[241,2021,300],{"class":246},[241,2023,319],{"class":296},[241,2025,300],{"class":246},[241,2027,412],{"class":257},[241,2029,2031,2033,2035,2037,2039,2041,2043,2045,2048],{"class":125,"line":2030},24,[241,2032,1833],{"class":253},[241,2034,1836],{"class":257},[241,2036,1344],{"class":253},[241,2038,300],{"class":246},[241,2040,537],{"class":296},[241,2042,10],{"class":257},[241,2044,546],{"class":296},[241,2046,2047],{"class":246}," is named .xls but is not an OLE2 file\"",[241,2049,860],{"class":257},[241,2051,2053],{"class":125,"line":2052},25,[241,2054,271],{"emptyLinePlaceholder":270},[241,2056,2058,2061],{"class":125,"line":2057},26,[241,2059,2060],{"class":253},"    try",[241,2062,412],{"class":257},[241,2064,2066,2068,2071,2074,2076,2079,2081,2083,2085],{"class":125,"line":2065},27,[241,2067,519],{"class":253},[241,2069,2070],{"class":257}," pd.read_excel(p, ",[241,2072,2073],{"class":1066},"sheet_name",[241,2075,421],{"class":253},[241,2077,2078],{"class":257},"sheet, ",[241,2080,910],{"class":1066},[241,2082,421],{"class":253},[241,2084,1707],{"class":277},[241,2086,2087],{"class":257},".get(suffix))\n",[241,2089,2091,2094,2097,2100,2103],{"class":125,"line":2090},28,[241,2092,2093],{"class":253},"    except",[241,2095,2096],{"class":277}," Exception",[241,2098,2099],{"class":253}," as",[241,2101,2102],{"class":257}," exc:  ",[241,2104,2105],{"class":1077},"# noqa: BLE001 — re-raised with context below\n",[241,2107,2109,2111,2113,2115,2117,2119,2121,2123,2125,2127,2130,2133,2136,2138,2140,2142,2145,2147,2149,2151,2153],{"class":125,"line":2108},29,[241,2110,1833],{"class":253},[241,2112,1836],{"class":257},[241,2114,1344],{"class":253},[241,2116,300],{"class":246},[241,2118,537],{"class":296},[241,2120,10],{"class":257},[241,2122,546],{"class":296},[241,2124,303],{"class":246},[241,2126,537],{"class":296},[241,2128,2129],{"class":277},"type",[241,2131,2132],{"class":257},"(exc).",[241,2134,2135],{"class":277},"__name__",[241,2137,546],{"class":296},[241,2139,303],{"class":246},[241,2141,537],{"class":296},[241,2143,2144],{"class":257},"exc",[241,2146,546],{"class":296},[241,2148,300],{"class":246},[241,2150,1944],{"class":257},[241,2152,254],{"class":253},[241,2154,2155],{"class":257}," exc\n",[10,2157,2158,2159,2163,2164,2166],{},"Two details matter more than they look. Resolving the path and printing the working directory turns the most common deployment failure — a relative path that meant something different under cron — into a one-line diagnosis. And checking the magic bytes ",[2160,2161,2162],"em",{},"before"," handing the file to pandas converts an obscure ",[14,2165,169],{}," deep in a zip reader into a sentence naming the file and the mismatch.",[201,2168,2170],{"id":2169},"decide-which-fix-applies-in-one-pass","Decide which fix applies in one pass",[25,2172,34,2177,34,2180,34,2183,34,2186,34,2189,34,2193,34,2199,34,2203,34,2205,34,2208,34,2211,34,2214,34,2218,34,2221,34,2224,34,2228,34,2231,34,2235,34,2238,34,2241,34,2244,34,2248,34,2251,34,2256,34,2260,34,2262,34,2265,34,2267,34,2270,34,2272,34,2275,34,2280,34,2283,34,2286,34,2289,34,2291,34,2296,34,2300,34,2304,34,2306,34,2309,34,2312,34,2315,34,2317,34,2320,34,2323,34,2326,34,2328,34,2331,34,2334,34,2337],{"viewBox":2173,"role":28,"ariaLabelledBy":2174,"xmlns":32,"style":33},"0 0 760 282",[2175,2176],"tce3-t","tce3-d",[36,2178,2179],{"id":2175},"Triage route from a failing read to the right fix",[40,2181,2182],{"id":2176},"Check whether the file exists, then whether its magic bytes match its extension, then whether the engine matches the format, then whether the sheet name resolves; each no leads to a specific fix.",[44,2184],{"x":46,"y":46,"width":47,"height":2185,"fill":49},"282",[51,2187,2188],{"x":53,"y":54,"style":55},"Four questions, in order",[44,2190],{"x":2191,"y":60,"width":61,"height":2192,"rx":154,"fill":84,"stroke":65},"30","58",[51,2194,2198],{"x":2195,"y":2196,"style":2197},"112","70","font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","1. Does the path",[51,2200,2202],{"x":2195,"y":2201,"style":2197},"90","resolve to a file?",[44,2204],{"x":83,"y":60,"width":61,"height":2192,"rx":154,"fill":84,"stroke":65},[51,2206,2207],{"x":87,"y":2196,"style":2197},"2. Do the bytes match",[51,2209,2210],{"x":87,"y":2201,"style":2197},"the extension?",[44,2212],{"x":2213,"y":60,"width":61,"height":2192,"rx":154,"fill":84,"stroke":65},"394",[51,2215,2217],{"x":2216,"y":2196,"style":2197},"476","3. Is the engine right",[51,2219,2220],{"x":2216,"y":2201,"style":2197},"for that format?",[44,2222],{"x":2223,"y":60,"width":153,"height":2192,"rx":154,"fill":84,"stroke":65},"576",[51,2225,2227],{"x":2226,"y":2196,"style":2197},"656","4. Does the sheet",[51,2229,2230],{"x":2226,"y":2201,"style":2197},"name resolve?",[125,2232],{"x1":2233,"y1":2234,"x2":129,"y2":2234,"stroke":130,"style":131},"194","75",[133,2236],{"points":2237,"fill":136},"208,75 198,70 198,80",[125,2239],{"x1":139,"y1":2234,"x2":2240,"y2":2234,"stroke":130,"style":131},"390",[133,2242],{"points":2243,"fill":136},"390,75 380,70 380,80",[125,2245],{"x1":2246,"y1":2234,"x2":2247,"y2":2234,"stroke":130,"style":131},"558","572",[133,2249],{"points":2250,"fill":136},"572,75 562,70 562,80",[125,2252],{"x1":2195,"y1":2253,"x2":2195,"y2":2254,"stroke":2255,"style":131},"104","152","var(--accent,#f43f8f)",[133,2257],{"points":2258,"fill":2259},"112,152 107,142 117,142","#be185d",[125,2261],{"x1":87,"y1":2253,"x2":87,"y2":2254,"stroke":2255,"style":131},[133,2263],{"points":2264,"fill":2259},"294,152 289,142 299,142",[125,2266],{"x1":2216,"y1":2253,"x2":2216,"y2":2254,"stroke":2255,"style":131},[133,2268],{"points":2269,"fill":2259},"476,152 471,142 481,142",[125,2271],{"x1":2226,"y1":2253,"x2":2226,"y2":2254,"stroke":2255,"style":131},[133,2273],{"points":2274,"fill":2259},"656,152 651,142 661,142",[51,2276,961],{"x":2277,"y":2278,"style":2279},"128","130","font-size:11px;fill:var(--muted,#5b6780);text-anchor:start",[51,2281,961],{"x":2282,"y":2278,"style":2279},"310",[51,2284,961],{"x":2285,"y":2278,"style":2279},"492",[51,2287,961],{"x":2288,"y":2278,"style":2279},"672",[44,2290],{"x":2191,"y":192,"width":61,"height":943,"rx":154,"fill":113,"stroke":65},[51,2292,2295],{"x":2195,"y":2293,"style":2294},"184","font-size:11.5px;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Use an absolute",[51,2297,2299],{"x":2195,"y":2298,"style":2294},"204","path and wait for",[51,2301,2303],{"x":2195,"y":2302,"style":2294},"224","a stable size",[44,2305],{"x":83,"y":192,"width":61,"height":943,"rx":154,"fill":113,"stroke":65},[51,2307,2308],{"x":87,"y":2293,"style":2294},"Convert the file",[51,2310,2311],{"x":87,"y":2298,"style":2294},"or re-download it;",[51,2313,2314],{"x":87,"y":2302,"style":2294},"stop trusting names",[44,2316],{"x":2213,"y":192,"width":61,"height":943,"rx":154,"fill":113,"stroke":65},[51,2318,2319],{"x":2216,"y":2293,"style":2294},"Pass engine=",[51,2321,2322],{"x":2216,"y":2298,"style":2294},"explicitly and pin",[51,2324,2325],{"x":2216,"y":2302,"style":2294},"the dependency",[44,2327],{"x":2223,"y":192,"width":153,"height":943,"rx":154,"fill":113,"stroke":65},[51,2329,2330],{"x":2226,"y":2293,"style":2294},"Match names case-",[51,2332,2333],{"x":2226,"y":2298,"style":2294},"and space-insensitively",[51,2335,2336],{"x":2226,"y":2302,"style":2294},"against sheetnames",[51,2338,2340],{"x":53,"y":2339,"style":75},"272","Each question is one line of code — run them in order and the failure names itself",[10,2342,2343],{},"Work the questions in that order and you will rarely need a debugger. The order matters because each answer changes what the next one means: an engine argument cannot help a file that is really a CSV, and a sheet-name fix cannot help a workbook that never opened. In a scheduled job, encode the same order as guard clauses so the log records which question failed rather than a stack trace from three libraries down.",[201,2345,2347],{"id":2346},"pin-the-libraries-so-a-fixed-error-cannot-come-back","Pin the libraries so a fixed error cannot come back",[10,2349,2350],{},"A large share of \"it worked last month\" reports are version drift rather than data problems. Three changes in the ecosystem break old code on upgrade, and all three surface as errors from this page:",[1432,2352,2353,2365,2375],{},[1435,2354,2355,2358,2359,2361,2362,2364],{},[1631,2356,2357],{},"xlrd 2.0"," removed ",[14,2360,579],{}," support entirely, so any tutorial code passing ",[14,2363,1193],{}," to a modern workbook now raises a format error.",[1435,2366,2367,2370,2371,2374],{},[1631,2368,2369],{},"pandas 2.x"," made the engine lookup stricter and turned several silent fallbacks into explicit ",[14,2372,2373],{},"ValueError","s — code that used to guess right by luck now fails loudly.",[1435,2376,2377,2380],{},[1631,2378,2379],{},"openpyxl 3.1"," tightened validation of styles and defined names, so a workbook written by an older release can raise a warning or drop a stale name on re-save.",[10,2382,2383,2384,2387],{},"Record exact versions in ",[14,2385,2386],{},"requirements.txt"," (or the lock file of whichever tool you use) and install the engines alongside pandas rather than relying on whatever the base image happens to carry:",[232,2389,2393],{"className":2390,"code":2392,"language":51,"meta":237},[2391],"language-text","pandas==2.2.3\nopenpyxl==3.1.5\nxlrd==2.0.1\npython-calamine==0.3.1\n",[14,2394,2392],{"__ignoreMap":237},[10,2396,2397],{},"Then make the versions visible when something does break. Logging them at start-up costs one line and removes an entire round of guesswork when a job fails only on the server:",[232,2399,2401],{"className":234,"code":2400,"language":236,"meta":237,"style":237},"import openpyxl\nimport pandas as pd\n\nprint(f\"pandas {pd.__version__}, openpyxl {openpyxl.__version__}\")\n",[14,2402,2403,2410,2420,2424],{"__ignoreMap":237},[241,2404,2405,2407],{"class":125,"line":243},[241,2406,261],{"class":253},[241,2408,2409],{"class":257}," openpyxl\n",[241,2411,2412,2414,2416,2418],{"class":125,"line":250},[241,2413,261],{"class":253},[241,2415,1038],{"class":257},[241,2417,1041],{"class":253},[241,2419,1044],{"class":257},[241,2421,2422],{"class":125,"line":267},[241,2423,271],{"emptyLinePlaceholder":270},[241,2425,2426,2428,2430,2432,2435,2437,2440,2443,2445,2448,2450,2453,2455,2457,2459],{"class":125,"line":274},[241,2427,560],{"class":277},[241,2429,1341],{"class":257},[241,2431,1344],{"class":253},[241,2433,2434],{"class":246},"\"pandas ",[241,2436,537],{"class":296},[241,2438,2439],{"class":257},"pd.",[241,2441,2442],{"class":277},"__version__",[241,2444,546],{"class":296},[241,2446,2447],{"class":246},", openpyxl ",[241,2449,537],{"class":296},[241,2451,2452],{"class":257},"openpyxl.",[241,2454,2442],{"class":277},[241,2456,546],{"class":296},[241,2458,300],{"class":246},[241,2460,860],{"class":257},[201,2462,2464],{"id":2463},"key-takeaways","Key takeaways",[1432,2466,2467,2470,2475,2482,2485,2488],{},[1435,2468,2469],{},"Identify the file by its first bytes before debugging the code; extensions are unreliable and Excel's silent repairs hide the truth.",[1435,2471,2472,2474],{},[14,2473,169],{}," is a container problem — a wrong format, a truncated download, or a read that raced a write.",[1435,2476,2477,2478,2481],{},"pandas delegates to an engine per format; pass ",[14,2479,2480],{},"engine="," explicitly and pin the dependency so environments cannot drift.",[1435,2483,2484],{},"Sheet lookups fail on invisible whitespace and case; match names defensively rather than hardcoding them.",[1435,2486,2487],{},"A workbook Excel offers to repair is usually breaking one of a few hard rules — sheet names, duplicate table names, stale defined names, or timezone-aware datetimes.",[1435,2489,2490],{},"Wrap every failure with the filename before re-raising, so a scheduled job's log tells you which input broke without a rerun.",[201,2492,2494],{"id":2493},"frequently-asked-questions","Frequently asked questions",[10,2496,2497,2500,2501,2503,2504,2506],{},[1631,2498,2499],{},"Why does the same file open fine in Excel but fail in Python?","\nExcel repairs quietly. It will open an ",[14,2502,575],{}," file named ",[14,2505,579],{},", tolerate a truncated download, and silently fix a damaged zip entry. Python libraries do none of that — they read the bytes literally. When Excel opens a file that openpyxl rejects, the file is almost always not the format its extension claims.",[10,2508,2509,2512,2513,2515,2516,2519,2520,2522,2523,2526],{},[1631,2510,2511],{},"What is the fastest way to tell what a mystery file really is?","\nRead the first bytes. A modern ",[14,2514,579],{}," starts with ",[14,2517,2518],{},"PK"," (a zip container), a legacy ",[14,2521,575],{}," starts with the OLE2 signature ",[14,2524,2525],{},"D0 CF 11 E0",", and anything starting with a letter, a quote or a byte-order mark is text — CSV, TSV or HTML saved with a spreadsheet extension.",[10,2528,2529,2532],{},[1631,2530,2531],{},"Do I need Excel installed to fix these errors?","\nNo. Every fix on this page is pure Python and runs on a headless server. You only need Excel itself when a workbook must recalculate formulas or run macros, which is the xlwings case, not a file-reading case.",[10,2534,2535,2538],{},[1631,2536,2537],{},"Why does my script work locally and fail on the server?","\nThe three usual causes are a different library version, a relative path resolving against a different working directory, and a file that is still being written by an upload or sync client when the job starts. Pin versions, use absolute paths, and check the file size is stable before reading.",[10,2540,2541,2544],{},[1631,2542,2543],{},"Should I catch these exceptions or let them crash the job?","\nCatch the ones you can act on — a missing sheet, a locked output path — and re-raise with the filename attached. Let genuinely unexpected exceptions propagate so the scheduler records a failure rather than writing an empty report.",[201,2546,2548],{"id":2547},"conclusion","Conclusion",[10,2550,2551],{},"Python Excel errors feel opaque because they surface several layers below your code, but each layer has a small, memorable vocabulary. Establish the file's true format first, then read the exception's module to decide whether you are fighting the container, the engine, the parser or your own lookup. The five guides below take each family in turn, with the one-line diagnostic and the durable fix for every message.",[201,2553,2555],{"id":2554},"related","Related",[1432,2557,2558,2567,2572,2577,2582,2587,2592],{},[1435,2559,2560,2563,2564,2566],{},[1631,2561,2562],{},"Up:"," ",[19,2565,22],{"href":21}," — the section this troubleshooting map belongs to, from first read to first automated report.",[1435,2568,2569,2571],{},[19,2570,867],{"href":866}," — the container-level failure and how to salvage the data.",[1435,2573,2574,2576],{},[19,2575,1198],{"href":1197}," — reading and converting legacy workbooks.",[1435,2578,2579,2581],{},[19,2580,584],{"href":583}," — engine selection when the extension is wrong or missing.",[1435,2583,2584,2586],{},[19,2585,1422],{"href":1421}," — sheet lookups that miss on whitespace, case or index.",[1435,2588,2589,2591],{},[19,2590,1606],{"href":1605}," — the rules Excel enforces on files your script produces.",[1435,2593,2594,2563,2597,1199,2601,2603],{},[1631,2595,2596],{},"Sibling topics:",[19,2598,2600],{"href":2599},"\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002F","Reading Excel Files with pandas",[19,2602,1203],{"href":1202}," — the normal paths these errors interrupt.",[2605,2606,2607],"style",{},"html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .s_b0D, html code.shiki .s_b0D{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#72F088;--shiki-dark-font-weight:bold}html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}",{"title":237,"searchDepth":250,"depth":250,"links":2609},[2610,2611,2612,2613,2614,2615,2616,2617,2618,2619,2620,2621,2622],{"id":203,"depth":250,"text":204},{"id":588,"depth":250,"text":589},{"id":871,"depth":250,"text":872},{"id":1207,"depth":250,"text":1208},{"id":1426,"depth":250,"text":1427},{"id":1609,"depth":250,"text":1610},{"id":1660,"depth":250,"text":1661},{"id":2169,"depth":250,"text":2170},{"id":2346,"depth":250,"text":2347},{"id":2463,"depth":250,"text":2464},{"id":2493,"depth":250,"text":2494},{"id":2547,"depth":250,"text":2548},{"id":2554,"depth":250,"text":2555},"2026-08-27","Decode the errors Python throws at Excel files: BadZipFile, InvalidFileException, engine errors, missing worksheets, corrupt output and locked files — with fixes.","md",[2627,2629,2631,2633,2635],{"q":2499,"a":2628},"Excel repairs quietly. It will open an .xls file named .xlsx, tolerate a truncated download, and silently fix a damaged zip entry. Python libraries do none of that — they read the bytes literally. When Excel opens a file that openpyxl rejects, the file is almost always not the format its extension claims.",{"q":2511,"a":2630},"Read the first bytes. A modern .xlsx starts with PK (a zip container), a legacy .xls starts with the OLE2 signature D0 CF 11 E0, and anything starting with a letter, a quote or a byte-order mark is text — CSV, TSV or HTML saved with a spreadsheet extension.",{"q":2531,"a":2632},"No. Every fix on this page is pure Python and runs on a headless server. You only need Excel itself when a workbook must recalculate formulas or run macros, which is the xlwings case, not a file-reading case.",{"q":2537,"a":2634},"The three usual causes are a different library version, a relative path resolving against a different working directory, and a file that is still being written by an upload or sync client when the job starts. Pin versions, use absolute paths, and check the file size is stable before reading.",{"q":2543,"a":2636},"Catch the ones you can act on — a missing sheet, a locked output path — and re-raise with the filename attached. Let genuinely unexpected exceptions propagate so the scheduler records a failure rather than writing an empty report.",{},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors",{"title":2640,"description":2641},"Fix Common Python Excel Errors","A triage guide to Python Excel errors: BadZipFile, openpyxl does not support xls, missing worksheet KeyError, unreadable output, permission denied and encoding faults.","troubleshooting-common-python-excel-errors","getting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Findex","guide","EAZl5_vRvN14sBqTTzC5coSpfT1b1sAjryh9GiwlczU",[2647,2651],{"title":2648,"path":2649,"stem":2650,"children":-1},"Skip Rows and Set the Header When Reading Excel with pandas","\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fskip-rows-and-set-header-when-reading-excel-with-pandas","getting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fskip-rows-and-set-header-when-reading-excel-with-pandas\u002Findex",{"title":2652,"path":2653,"stem":2654,"children":-1},"Fix BadZipFile When Reading an Excel File in Python","\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-badzipfile-error-when-reading-excel-in-python","getting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-badzipfile-error-when-reading-excel-in-python\u002Findex",1788710151645]