[{"data":1,"prerenderedAt":1891},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-worksheet-does-not-exist-keyerror-in-openpyxl":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-worksheet-does-not-exist-keyerror-in-openpyxl":1882},{"id":4,"title":5,"body":6,"dateModified":1859,"datePublished":1859,"description":1860,"extension":1861,"faq":1862,"meta":1873,"navigation":202,"path":1874,"seo":1875,"slug":1878,"stem":1879,"type":1880,"__hash__":1881},"docs\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-worksheet-does-not-exist-keyerror-in-openpyxl\u002Findex.md","Fix \"Worksheet Does Not Exist\" KeyError in openpyxl",{"type":7,"value":8,"toc":1846},"minimark",[9,23,128,133,136,163,167,174,257,264,274,278,281,554,560,564,575,636,647,755,770,878,881,995,999,1002,1232,1247,1329,1333,1336,1568,1580,1584,1657,1661,1672,1720,1727,1731,1740,1744,1750,1763,1776,1789,1802,1806,1842],[10,11,12,16,17,22],"p",{},[13,14,15],"code",{},"KeyError: 'Worksheet Sales does not exist.'"," is maddening because the tab is visibly there when you open the workbook. The cause is almost always a character you cannot see: a trailing space, a non-breaking space pasted from a web page, or a difference in capitalisation that Excel treats loosely and Python does not. This guide shows how to see the real names, why the mismatch happens, and how to write a lookup that never breaks again — including the pandas variant of the same error. It is part of ",[18,19,21],"a",{"href":20},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002F","Troubleshooting Common Python Excel Errors",".",[24,25,33,34,33,38,33,42,33,49,33,56,33,61,33,70,33,76,33,80,33,85,33,88,33,92,33,94,33,97,33,105,33,110,33,113,33,116,33,122],"svg",{"viewBox":26,"role":27,"ariaLabelledBy":28,"xmlns":31,"style":32},"0 0 760 218","img",[29,30],"ws-t","ws-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  ",[35,36,37],"title",{"id":29},"What Excel shows on a tab versus what the file stores",[39,40,41],"desc",{"id":30},"Excel renders a trimmed tab label, so a sheet stored as space-Sales-space looks identical to Sales; openpyxl compares the stored string and the lookup misses.",[43,44],"rect",{"x":45,"y":45,"width":46,"height":47,"fill":48},"0","760","218","#ffffff",[50,51,55],"text",{"x":52,"y":53,"style":54},"196","28","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","What you see in Excel",[50,57,60],{"x":58,"y":53,"style":59},"566","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","What the file stores",[43,62],{"x":63,"y":64,"width":65,"height":66,"rx":67,"fill":68,"stroke":69},"34","44","322","48","9","#f0f2f5","var(--line,#cdd5e6)",[50,71,75],{"x":72,"y":73,"style":74},"195","74","font-size:13px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Sales",[43,77],{"x":78,"y":64,"width":65,"height":66,"rx":67,"fill":79,"stroke":69},"404","#fee8f2",[50,81,84],{"x":82,"y":73,"style":83},"565","font-size:13px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","'Sales '",[43,86],{"x":63,"y":87,"width":65,"height":66,"rx":67,"fill":68,"stroke":69},"102",[50,89,91],{"x":72,"y":90,"style":74},"132","Q3 Summary",[43,93],{"x":78,"y":87,"width":65,"height":66,"rx":67,"fill":79,"stroke":69},[50,95,96],{"x":82,"y":90,"style":83},"'Q3 Summary'",[98,99],"line",{"x1":100,"y1":101,"x2":102,"y2":101,"stroke":103,"style":104},"356","68","398","var(--brand,#5b5cf0)","stroke-width:2px",[106,107],"polygon",{"points":108,"fill":109},"398,68 388,63 388,73","#5b5cf0",[98,111],{"x1":100,"y1":112,"x2":102,"y2":112,"stroke":103,"style":104},"126",[106,114],{"points":115,"fill":109},"398,126 388,121 388,131",[43,117],{"x":63,"y":118,"width":119,"height":120,"rx":67,"fill":121,"stroke":69},"160","692","42","#fdefd8",[50,123,127],{"x":124,"y":125,"style":126},"380","186","font-size:12px;fill:var(--gold-ink,#7a4e06);text-anchor:middle","wb[\"Sales\"] misses on a trailing space; wb[\"Q3 Summary\"] misses on a non-breaking space",[129,130,132],"h2",{"id":131},"prerequisites","Prerequisites",[10,134,135],{},"Only openpyxl, and optionally pandas for the second half:",[137,138,143],"pre",{"className":139,"code":140,"language":141,"meta":142,"style":142},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl pandas\n","bash","",[13,144,145],{"__ignoreMap":142},[146,147,149,153,157,160],"span",{"class":98,"line":148},1,[146,150,152],{"class":151},"sMTad","pip",[146,154,156],{"class":155},"srMev"," install",[146,158,159],{"class":155}," openpyxl",[146,161,162],{"class":155}," pandas\n",[129,164,166],{"id":165},"step-1-print-the-names-exactly-as-stored","Step 1: print the names exactly as stored",[10,168,169,170,173],{},"The first move is always the same — list the sheets, and print their ",[13,171,172],{},"repr"," so invisible characters become visible:",[137,175,179],{"className":176,"code":177,"language":178,"meta":142,"style":142},"language-python shiki shiki-themes github-light github-dark-high-contrast","from openpyxl import load_workbook\n\nwb = load_workbook(\"sales.xlsx\")\nprint(wb.sheetnames)\nprint([repr(name) for name in wb.sheetnames])\n","python",[13,180,181,197,204,222,232],{"__ignoreMap":142},[146,182,183,187,191,194],{"class":98,"line":148},[146,184,186],{"class":185},"s-kum","from",[146,188,190],{"class":189},"skGVy"," openpyxl ",[146,192,193],{"class":185},"import",[146,195,196],{"class":189}," load_workbook\n",[146,198,200],{"class":98,"line":199},2,[146,201,203],{"emptyLinePlaceholder":202},true,"\n",[146,205,207,210,213,216,219],{"class":98,"line":206},3,[146,208,209],{"class":189},"wb ",[146,211,212],{"class":185},"=",[146,214,215],{"class":189}," load_workbook(",[146,217,218],{"class":155},"\"sales.xlsx\"",[146,220,221],{"class":189},")\n",[146,223,225,229],{"class":98,"line":224},4,[146,226,228],{"class":227},"sP0c6","print",[146,230,231],{"class":189},"(wb.sheetnames)\n",[146,233,235,237,240,242,245,248,251,254],{"class":98,"line":234},5,[146,236,228],{"class":227},[146,238,239],{"class":189},"([",[146,241,172],{"class":227},[146,243,244],{"class":189},"(name) ",[146,246,247],{"class":185},"for",[146,249,250],{"class":189}," name ",[146,252,253],{"class":185},"in",[146,255,256],{"class":189}," wb.sheetnames])\n",[137,258,262],{"className":259,"code":261,"language":50,"meta":142},[260],"language-text","['Sales ', 'Q3\\xa0Summary', 'Raw Data']\n[\"'Sales '\", \"'Q3\\\\xa0Summary'\", \"'Raw Data'\"]\n",[13,263,261],{"__ignoreMap":142},[10,265,266,267,269,270,273],{},"The plain list looks fine; the ",[13,268,172],{}," list shows a trailing space on the first sheet and ",[13,271,272],{},"\\xa0"," — a non-breaking space — in the second. Both are perfectly legal sheet names, and both defeat an exact lookup. Non-breaking spaces arrive whenever a name is copied from a web page, an email, or a PDF.",[129,275,277],{"id":276},"step-2-match-names-defensively","Step 2: match names defensively",[10,279,280],{},"Rather than fixing the file, fix the lookup. Comparing on a normalised form handles whitespace, case and Unicode variants at once:",[137,282,284],{"className":176,"code":283,"language":178,"meta":142,"style":142},"\"\"\"Find a worksheet by a human-typed name.\"\"\"\nimport unicodedata\n\nfrom openpyxl import load_workbook\n\ndef normalise(name: str) -> str:\n    \"\"\"Fold case, collapse every kind of space, and strip the ends.\"\"\"\n    text = unicodedata.normalize(\"NFKC\", name)   # turns \\xa0 into a plain space\n    return \" \".join(text.split()).casefold()\n\ndef get_sheet(wb, wanted: str):\n    target = normalise(wanted)\n    for name in wb.sheetnames:\n        if normalise(name) == target:\n            return wb[name]\n    raise KeyError(f\"{wanted!r} not found. Sheets: {[repr(n) for n in wb.sheetnames]}\")\n\nwb = load_workbook(\"sales.xlsx\")\nws = get_sheet(wb, \"q3 summary\")     # matches 'Q3\\xa0Summary'\nprint(ws.title, ws.max_row)\n",[13,285,286,291,298,302,312,316,340,346,367,379,384,400,411,424,439,448,508,513,526,546],{"__ignoreMap":142},[146,287,288],{"class":98,"line":148},[146,289,290],{"class":155},"\"\"\"Find a worksheet by a human-typed name.\"\"\"\n",[146,292,293,295],{"class":98,"line":199},[146,294,193],{"class":185},[146,296,297],{"class":189}," unicodedata\n",[146,299,300],{"class":98,"line":206},[146,301,203],{"emptyLinePlaceholder":202},[146,303,304,306,308,310],{"class":98,"line":224},[146,305,186],{"class":185},[146,307,190],{"class":189},[146,309,193],{"class":185},[146,311,196],{"class":189},[146,313,314],{"class":98,"line":234},[146,315,203],{"emptyLinePlaceholder":202},[146,317,319,322,326,329,332,335,337],{"class":98,"line":318},6,[146,320,321],{"class":185},"def",[146,323,325],{"class":324},"s_Opv"," normalise",[146,327,328],{"class":189},"(name: ",[146,330,331],{"class":227},"str",[146,333,334],{"class":189},") -> ",[146,336,331],{"class":227},[146,338,339],{"class":189},":\n",[146,341,343],{"class":98,"line":342},7,[146,344,345],{"class":155},"    \"\"\"Fold case, collapse every kind of space, and strip the ends.\"\"\"\n",[146,347,349,352,354,357,360,363],{"class":98,"line":348},8,[146,350,351],{"class":189},"    text ",[146,353,212],{"class":185},[146,355,356],{"class":189}," unicodedata.normalize(",[146,358,359],{"class":155},"\"NFKC\"",[146,361,362],{"class":189},", name)   ",[146,364,366],{"class":365},"s-wDw","# turns \\xa0 into a plain space\n",[146,368,370,373,376],{"class":98,"line":369},9,[146,371,372],{"class":185},"    return",[146,374,375],{"class":155}," \" \"",[146,377,378],{"class":189},".join(text.split()).casefold()\n",[146,380,382],{"class":98,"line":381},10,[146,383,203],{"emptyLinePlaceholder":202},[146,385,387,389,392,395,397],{"class":98,"line":386},11,[146,388,321],{"class":185},[146,390,391],{"class":324}," get_sheet",[146,393,394],{"class":189},"(wb, wanted: ",[146,396,331],{"class":227},[146,398,399],{"class":189},"):\n",[146,401,403,406,408],{"class":98,"line":402},12,[146,404,405],{"class":189},"    target ",[146,407,212],{"class":185},[146,409,410],{"class":189}," normalise(wanted)\n",[146,412,414,417,419,421],{"class":98,"line":413},13,[146,415,416],{"class":185},"    for",[146,418,250],{"class":189},[146,420,253],{"class":185},[146,422,423],{"class":189}," wb.sheetnames:\n",[146,425,427,430,433,436],{"class":98,"line":426},14,[146,428,429],{"class":185},"        if",[146,431,432],{"class":189}," normalise(name) ",[146,434,435],{"class":185},"==",[146,437,438],{"class":189}," target:\n",[146,440,442,445],{"class":98,"line":441},15,[146,443,444],{"class":185},"            return",[146,446,447],{"class":189}," wb[name]\n",[146,449,451,454,457,460,463,466,470,473,476,479,482,484,487,489,492,494,497,499,502,504,506],{"class":98,"line":450},16,[146,452,453],{"class":185},"    raise",[146,455,456],{"class":227}," KeyError",[146,458,459],{"class":189},"(",[146,461,462],{"class":185},"f",[146,464,465],{"class":155},"\"",[146,467,469],{"class":468},"sSjpA","{",[146,471,472],{"class":189},"wanted",[146,474,475],{"class":185},"!r",[146,477,478],{"class":468},"}",[146,480,481],{"class":155}," not found. Sheets: ",[146,483,469],{"class":468},[146,485,486],{"class":189},"[",[146,488,172],{"class":227},[146,490,491],{"class":189},"(n) ",[146,493,247],{"class":185},[146,495,496],{"class":189}," n ",[146,498,253],{"class":185},[146,500,501],{"class":189}," wb.sheetnames]",[146,503,478],{"class":468},[146,505,465],{"class":155},[146,507,221],{"class":189},[146,509,511],{"class":98,"line":510},17,[146,512,203],{"emptyLinePlaceholder":202},[146,514,516,518,520,522,524],{"class":98,"line":515},18,[146,517,209],{"class":189},[146,519,212],{"class":185},[146,521,215],{"class":189},[146,523,218],{"class":155},[146,525,221],{"class":189},[146,527,529,532,534,537,540,543],{"class":98,"line":528},19,[146,530,531],{"class":189},"ws ",[146,533,212],{"class":185},[146,535,536],{"class":189}," get_sheet(wb, ",[146,538,539],{"class":155},"\"q3 summary\"",[146,541,542],{"class":189},")     ",[146,544,545],{"class":365},"# matches 'Q3\\xa0Summary'\n",[146,547,549,551],{"class":98,"line":548},20,[146,550,228],{"class":227},[146,552,553],{"class":189},"(ws.title, ws.max_row)\n",[10,555,556,559],{},[13,557,558],{},"unicodedata.normalize(\"NFKC\", ...)"," is the piece that does the heavy lifting: it converts non-breaking spaces, full-width characters and other look-alikes into their plain equivalents, which is exactly the class of difference a person copying a name cannot see. The error message deliberately includes every available sheet name, so a failure in production tells you what the file actually contained.",[129,561,563],{"id":562},"step-3-know-when-to-use-position-instead","Step 3: know when to use position instead",[10,565,566,567,570,571,574],{},"Names are not the only handle. When a producer renames tabs each month — ",[13,568,569],{},"Jan 2026",", ",[13,572,573],{},"Feb 2026"," — but never reorders them, position is far more stable:",[137,576,578],{"className":176,"code":577,"language":178,"meta":142,"style":142},"first = wb.worksheets[0]        # first tab, whatever it is called\nactive = wb.active              # the sheet selected when the file was saved\nlast = wb.worksheets[-1]\nprint(first.title, active.title, last.title)\n",[13,579,580,598,611,629],{"__ignoreMap":142},[146,581,582,585,587,590,592,595],{"class":98,"line":148},[146,583,584],{"class":189},"first ",[146,586,212],{"class":185},[146,588,589],{"class":189}," wb.worksheets[",[146,591,45],{"class":227},[146,593,594],{"class":189},"]        ",[146,596,597],{"class":365},"# first tab, whatever it is called\n",[146,599,600,603,605,608],{"class":98,"line":199},[146,601,602],{"class":189},"active ",[146,604,212],{"class":185},[146,606,607],{"class":189}," wb.active              ",[146,609,610],{"class":365},"# the sheet selected when the file was saved\n",[146,612,613,616,618,620,623,626],{"class":98,"line":206},[146,614,615],{"class":189},"last ",[146,617,212],{"class":185},[146,619,589],{"class":189},[146,621,622],{"class":185},"-",[146,624,625],{"class":227},"1",[146,627,628],{"class":189},"]\n",[146,630,631,633],{"class":98,"line":224},[146,632,228],{"class":227},[146,634,635],{"class":189},"(first.title, active.title, last.title)\n",[10,637,638,639,642,643,646],{},"The equivalent in pandas is the ",[13,640,641],{},"sheet_name"," argument, which accepts a name, a zero-based index, a list, or ",[13,644,645],{},"None"," for every sheet:",[137,648,650],{"className":176,"code":649,"language":178,"meta":142,"style":142},"import pandas as pd\n\ndf_first = pd.read_excel(\"sales.xlsx\", sheet_name=0)        # by position\ndf_named = pd.read_excel(\"sales.xlsx\", sheet_name=\"Raw Data\")\nall_sheets = pd.read_excel(\"sales.xlsx\", sheet_name=None)   # dict of DataFrames\nprint(list(all_sheets))\n",[13,651,652,665,669,696,718,743],{"__ignoreMap":142},[146,653,654,656,659,662],{"class":98,"line":148},[146,655,193],{"class":185},[146,657,658],{"class":189}," pandas ",[146,660,661],{"class":185},"as",[146,663,664],{"class":189}," pd\n",[146,666,667],{"class":98,"line":199},[146,668,203],{"emptyLinePlaceholder":202},[146,670,671,674,676,679,681,683,686,688,690,693],{"class":98,"line":206},[146,672,673],{"class":189},"df_first ",[146,675,212],{"class":185},[146,677,678],{"class":189}," pd.read_excel(",[146,680,218],{"class":155},[146,682,570],{"class":189},[146,684,641],{"class":685},"sa561",[146,687,212],{"class":185},[146,689,45],{"class":227},[146,691,692],{"class":189},")        ",[146,694,695],{"class":365},"# by position\n",[146,697,698,701,703,705,707,709,711,713,716],{"class":98,"line":224},[146,699,700],{"class":189},"df_named ",[146,702,212],{"class":185},[146,704,678],{"class":189},[146,706,218],{"class":155},[146,708,570],{"class":189},[146,710,641],{"class":685},[146,712,212],{"class":185},[146,714,715],{"class":155},"\"Raw Data\"",[146,717,221],{"class":189},[146,719,720,723,725,727,729,731,733,735,737,740],{"class":98,"line":234},[146,721,722],{"class":189},"all_sheets ",[146,724,212],{"class":185},[146,726,678],{"class":189},[146,728,218],{"class":155},[146,730,570],{"class":189},[146,732,641],{"class":685},[146,734,212],{"class":185},[146,736,645],{"class":227},[146,738,739],{"class":189},")   ",[146,741,742],{"class":365},"# dict of DataFrames\n",[146,744,745,747,749,752],{"class":98,"line":318},[146,746,228],{"class":227},[146,748,459],{"class":189},[146,750,751],{"class":227},"list",[146,753,754],{"class":189},"(all_sheets))\n",[10,756,757,758,761,762,765,766,22],{},"Note the trap in that last form: ",[13,759,760],{},"sheet_name=None"," returns a dictionary, not a DataFrame, and ",[13,763,764],{},"sheet_name=0"," returns a DataFrame. Code that switches between them by configuration should normalise the result before using it. The broader pattern is covered in ",[18,767,769],{"href":768},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fread-all-sheets-from-an-excel-file-into-dataframes\u002F","Read all sheets from an Excel file into DataFrames",[24,771,33,776,33,779,33,782,33,785,33,790,33,795,33,801,33,806,33,810,33,814,33,818,33,823,33,826,33,830,33,833,33,837,33,841,33,844,33,848,33,851,33,855,33,860,33,863,33,867,33,870,33,873],{"viewBox":772,"role":27,"ariaLabelledBy":773,"xmlns":31,"style":32},"0 0 760 244",[774,775],"ws2-t","ws2-d",[35,777,778],{"id":774},"Four ways to address a worksheet, and when each is stable",[39,780,781],{"id":775},"Exact name is brittle, normalised name survives whitespace and case changes, position survives renames, and a pattern match survives dated tab names like Jan 2026.",[43,783],{"x":45,"y":45,"width":46,"height":784,"fill":48},"244",[50,786,789],{"x":124,"y":787,"style":788},"26","font-size:13px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","Pick the handle that survives how the producer edits the file",[43,791],{"x":792,"y":64,"width":793,"height":112,"rx":794,"fill":79,"stroke":69},"30","164","12",[50,796,800],{"x":797,"y":798,"style":799},"112","72","font-size:12.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","exact name",[50,802,805],{"x":797,"y":803,"style":804},"100","font-size:11.5px;fill:var(--text,#172033);text-anchor:middle","wb[\"Sales\"]",[50,807,809],{"x":797,"y":112,"style":808},"font-size:11.5px;fill:var(--muted,#5b6780);text-anchor:middle","breaks on a space,",[50,811,813],{"x":797,"y":812,"style":808},"146","a case change, a rename",[43,815],{"x":816,"y":64,"width":793,"height":112,"rx":794,"fill":817,"stroke":69},"206","#d9f4f1",[50,819,822],{"x":820,"y":798,"style":821},"288","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","normalised name",[50,824,825],{"x":820,"y":803,"style":804},"NFKC + casefold",[50,827,829],{"x":820,"y":112,"style":828},"font-size:11.5px;fill:var(--teal-ink,#0b6157);text-anchor:middle","survives whitespace",[50,831,832],{"x":820,"y":812,"style":828},"and capitalisation",[43,834],{"x":835,"y":64,"width":793,"height":112,"rx":794,"fill":836,"stroke":69},"382","#ebebfd",[50,838,840],{"x":839,"y":798,"style":59},"464","position",[50,842,843],{"x":839,"y":803,"style":804},"wb.worksheets[0]",[50,845,847],{"x":839,"y":112,"style":846},"font-size:11.5px;fill:var(--brand-strong,#4338ca);text-anchor:middle","survives any rename,",[50,849,850],{"x":839,"y":812,"style":808},"breaks on reordering",[43,852],{"x":853,"y":64,"width":854,"height":112,"rx":794,"fill":121,"stroke":69},"558","172",[50,856,859],{"x":857,"y":798,"style":858},"644","font-size:12.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","pattern match",[50,861,862],{"x":857,"y":803,"style":804},"startswith \u002F regex",[50,864,866],{"x":857,"y":112,"style":865},"font-size:11.5px;fill:var(--gold-ink,#7a4e06);text-anchor:middle","handles dated tabs",[50,868,869],{"x":857,"y":812,"style":808},"like \"Jan 2026\"",[43,871],{"x":792,"y":125,"width":872,"height":120,"rx":67,"fill":68,"stroke":69},"700",[50,874,877],{"x":124,"y":875,"style":876},"212","font-size:12px;fill:var(--text,#172033);text-anchor:middle","Whichever you choose, raise an error that lists the sheet names the file really had",[10,879,880],{},"For dated tabs, a pattern match beats both name and position:",[137,882,884],{"className":176,"code":883,"language":178,"meta":142,"style":142},"import re\n\nmonth_tab = next(\n    (n for n in wb.sheetnames if re.fullmatch(r\"[A-Z][a-z]{2} \\d{4}\", n.strip())),\n    None,\n)\nws = wb[month_tab] if month_tab else wb.active\n",[13,885,886,893,897,910,963,971,975],{"__ignoreMap":142},[146,887,888,890],{"class":98,"line":148},[146,889,193],{"class":185},[146,891,892],{"class":189}," re\n",[146,894,895],{"class":98,"line":199},[146,896,203],{"emptyLinePlaceholder":202},[146,898,899,902,904,907],{"class":98,"line":206},[146,900,901],{"class":189},"month_tab ",[146,903,212],{"class":185},[146,905,906],{"class":227}," next",[146,908,909],{"class":189},"(\n",[146,911,912,915,917,919,921,924,927,930,933,935,937,940,943,946,949,952,955,958,960],{"class":98,"line":224},[146,913,914],{"class":189},"    (n ",[146,916,247],{"class":185},[146,918,496],{"class":189},[146,920,253],{"class":185},[146,922,923],{"class":189}," wb.sheetnames ",[146,925,926],{"class":185},"if",[146,928,929],{"class":189}," re.fullmatch(",[146,931,932],{"class":185},"r",[146,934,465],{"class":155},[146,936,486],{"class":227},[146,938,939],{"class":468},"A-Z",[146,941,942],{"class":227},"][",[146,944,945],{"class":468},"a-z",[146,947,948],{"class":227},"]",[146,950,951],{"class":185},"{2}",[146,953,954],{"class":227}," \\d",[146,956,957],{"class":185},"{4}",[146,959,465],{"class":155},[146,961,962],{"class":189},", n.strip())),\n",[146,964,965,968],{"class":98,"line":234},[146,966,967],{"class":227},"    None",[146,969,970],{"class":189},",\n",[146,972,973],{"class":98,"line":318},[146,974,221],{"class":189},[146,976,977,979,981,984,986,989,992],{"class":98,"line":342},[146,978,531],{"class":189},[146,980,212],{"class":185},[146,982,983],{"class":189}," wb[month_tab] ",[146,985,926],{"class":185},[146,987,988],{"class":189}," month_tab ",[146,990,991],{"class":185},"else",[146,993,994],{"class":189}," wb.active\n",[129,996,998],{"id":997},"validate-the-whole-tab-set-before-you-read-anything","Validate the whole tab set before you read anything",[10,1000,1001],{},"In a reporting job the sheet lookup is rarely alone: the script wants three specific tabs, and discovering the third is missing after twenty seconds of parsing is wasted work. Check the full set up front and report every problem at once:",[137,1003,1005],{"className":176,"code":1004,"language":178,"meta":142,"style":142},"\"\"\"Assert the workbook has the tabs the report needs, before parsing.\"\"\"\nfrom openpyxl import load_workbook\n\nREQUIRED = {\"sales\", \"targets\", \"raw data\"}\n\nwb = load_workbook(\"monthly.xlsx\", read_only=True)\ntry:\n    present = {normalise(n) for n in wb.sheetnames}\nfinally:\n    wb.close()\n\nmissing = REQUIRED - present\nextra = present - REQUIRED\nif missing:\n    raise SystemExit(f\"monthly.xlsx is missing: {sorted(missing)}; it has {sorted(present)}\")\nif extra:\n    print(f\"note: extra sheets present, ignoring {sorted(extra)}\")\n",[13,1006,1007,1012,1022,1026,1053,1057,1080,1087,1106,1113,1118,1122,1138,1153,1160,1200,1207],{"__ignoreMap":142},[146,1008,1009],{"class":98,"line":148},[146,1010,1011],{"class":155},"\"\"\"Assert the workbook has the tabs the report needs, before parsing.\"\"\"\n",[146,1013,1014,1016,1018,1020],{"class":98,"line":199},[146,1015,186],{"class":185},[146,1017,190],{"class":189},[146,1019,193],{"class":185},[146,1021,196],{"class":189},[146,1023,1024],{"class":98,"line":206},[146,1025,203],{"emptyLinePlaceholder":202},[146,1027,1028,1031,1034,1037,1040,1042,1045,1047,1050],{"class":98,"line":224},[146,1029,1030],{"class":227},"REQUIRED",[146,1032,1033],{"class":185}," =",[146,1035,1036],{"class":189}," {",[146,1038,1039],{"class":155},"\"sales\"",[146,1041,570],{"class":189},[146,1043,1044],{"class":155},"\"targets\"",[146,1046,570],{"class":189},[146,1048,1049],{"class":155},"\"raw data\"",[146,1051,1052],{"class":189},"}\n",[146,1054,1055],{"class":98,"line":234},[146,1056,203],{"emptyLinePlaceholder":202},[146,1058,1059,1061,1063,1065,1068,1070,1073,1075,1078],{"class":98,"line":318},[146,1060,209],{"class":189},[146,1062,212],{"class":185},[146,1064,215],{"class":189},[146,1066,1067],{"class":155},"\"monthly.xlsx\"",[146,1069,570],{"class":189},[146,1071,1072],{"class":685},"read_only",[146,1074,212],{"class":185},[146,1076,1077],{"class":227},"True",[146,1079,221],{"class":189},[146,1081,1082,1085],{"class":98,"line":342},[146,1083,1084],{"class":185},"try",[146,1086,339],{"class":189},[146,1088,1089,1092,1094,1097,1099,1101,1103],{"class":98,"line":348},[146,1090,1091],{"class":189},"    present ",[146,1093,212],{"class":185},[146,1095,1096],{"class":189}," {normalise(n) ",[146,1098,247],{"class":185},[146,1100,496],{"class":189},[146,1102,253],{"class":185},[146,1104,1105],{"class":189}," wb.sheetnames}\n",[146,1107,1108,1111],{"class":98,"line":369},[146,1109,1110],{"class":185},"finally",[146,1112,339],{"class":189},[146,1114,1115],{"class":98,"line":381},[146,1116,1117],{"class":189},"    wb.close()\n",[146,1119,1120],{"class":98,"line":386},[146,1121,203],{"emptyLinePlaceholder":202},[146,1123,1124,1127,1129,1132,1135],{"class":98,"line":402},[146,1125,1126],{"class":189},"missing ",[146,1128,212],{"class":185},[146,1130,1131],{"class":227}," REQUIRED",[146,1133,1134],{"class":185}," -",[146,1136,1137],{"class":189}," present\n",[146,1139,1140,1143,1145,1148,1150],{"class":98,"line":413},[146,1141,1142],{"class":189},"extra ",[146,1144,212],{"class":185},[146,1146,1147],{"class":189}," present ",[146,1149,622],{"class":185},[146,1151,1152],{"class":227}," REQUIRED\n",[146,1154,1155,1157],{"class":98,"line":426},[146,1156,926],{"class":185},[146,1158,1159],{"class":189}," missing:\n",[146,1161,1162,1164,1167,1169,1171,1174,1176,1179,1182,1184,1187,1189,1191,1194,1196,1198],{"class":98,"line":441},[146,1163,453],{"class":185},[146,1165,1166],{"class":227}," SystemExit",[146,1168,459],{"class":189},[146,1170,462],{"class":185},[146,1172,1173],{"class":155},"\"monthly.xlsx is missing: ",[146,1175,469],{"class":468},[146,1177,1178],{"class":227},"sorted",[146,1180,1181],{"class":189},"(missing)",[146,1183,478],{"class":468},[146,1185,1186],{"class":155},"; it has ",[146,1188,469],{"class":468},[146,1190,1178],{"class":227},[146,1192,1193],{"class":189},"(present)",[146,1195,478],{"class":468},[146,1197,465],{"class":155},[146,1199,221],{"class":189},[146,1201,1202,1204],{"class":98,"line":450},[146,1203,926],{"class":185},[146,1205,1206],{"class":189}," extra:\n",[146,1208,1209,1212,1214,1216,1219,1221,1223,1226,1228,1230],{"class":98,"line":510},[146,1210,1211],{"class":227},"    print",[146,1213,459],{"class":189},[146,1215,462],{"class":185},[146,1217,1218],{"class":155},"\"note: extra sheets present, ignoring ",[146,1220,469],{"class":468},[146,1222,1178],{"class":227},[146,1224,1225],{"class":189},"(extra)",[146,1227,478],{"class":468},[146,1229,465],{"class":155},[146,1231,221],{"class":189},[10,1233,1234,1235,1238,1239,1242,1243,1246],{},"Reporting the extras as well as the misses catches the other half of the problem — a producer who renamed ",[13,1236,1237],{},"Targets"," to ",[13,1240,1241],{},"Targets 2026"," shows up as one missing and one extra tab, which is a much clearer signal than a ",[13,1244,1245],{},"KeyError"," on the name you expected.",[24,1248,33,1253,33,1256,33,1259,33,1262,33,1265,33,1268,33,1272,33,1275,33,1278,33,1282,33,1285,33,1288,33,1290,33,1293,33,1295,33,1298,33,1302,33,1306,33,1309,33,1312,33,1317,33,1320,33,1325],{"viewBox":1249,"role":27,"ariaLabelledBy":1250,"xmlns":31,"style":32},"0 0 760 214",[1251,1252],"ws3-t","ws3-d",[35,1254,1255],{"id":1251},"Comparing required tabs against the tabs the file has",[39,1257,1258],{"id":1252},"Set arithmetic between the required sheet names and the names present shows both what is missing and what arrived unexpectedly, which together identify a rename.",[43,1260],{"x":45,"y":45,"width":46,"height":1261,"fill":48},"214",[50,1263,1264],{"x":124,"y":787,"style":788},"A rename shows up as one miss plus one extra",[43,1266],{"x":792,"y":64,"width":1267,"height":90,"rx":794,"fill":836,"stroke":69},"200",[50,1269,1271],{"x":1270,"y":798,"style":59},"130","required",[50,1273,1274],{"x":1270,"y":87,"style":876},"sales",[50,1276,1277],{"x":1270,"y":112,"style":876},"targets",[50,1279,1281],{"x":1270,"y":1280,"style":876},"150","raw data",[43,1283],{"x":1284,"y":64,"width":1267,"height":90,"rx":794,"fill":817,"stroke":69},"280",[50,1286,1287],{"x":124,"y":798,"style":821},"present in file",[50,1289,1274],{"x":124,"y":87,"style":876},[50,1291,1292],{"x":124,"y":112,"style":876},"targets 2026",[50,1294,1281],{"x":124,"y":1280,"style":876},[43,1296],{"x":1297,"y":64,"width":1267,"height":90,"rx":794,"fill":121,"stroke":69},"530",[50,1299,1301],{"x":1300,"y":798,"style":858},"630","diagnosis",[50,1303,1305],{"x":1300,"y":87,"style":1304},"font-size:12px;fill:var(--accent-ink,#be185d);text-anchor:middle","missing: targets",[50,1307,1308],{"x":1300,"y":112,"style":126},"extra: targets 2026",[50,1310,1311],{"x":1300,"y":1280,"style":126},"so: a rename",[98,1313],{"x1":1314,"y1":1315,"x2":1316,"y2":1315,"stroke":103,"style":104},"230","110","276",[106,1318],{"points":1319,"fill":109},"276,110 266,105 266,115",[98,1321],{"x1":1322,"y1":1315,"x2":1323,"y2":1315,"stroke":1324,"style":104},"480","526","var(--teal,#0f9488)",[106,1326],{"points":1327,"fill":1328},"526,110 516,105 516,115","#0f766e",[129,1330,1332],{"id":1331},"clean-the-names-at-the-source-when-you-own-the-writer","Clean the names at the source when you own the writer",[10,1334,1335],{},"If your own code writes the workbook, the mismatch is preventable rather than survivable. Sanitise names on the way in, and the readers downstream never need the normalising lookup at all:",[137,1337,1339],{"className":176,"code":1338,"language":178,"meta":142,"style":142},"\"\"\"Write sheets with names that are safe to look up later.\"\"\"\nimport re\nimport unicodedata\n\nfrom openpyxl import Workbook\n\nINVALID = re.compile(r\"[\\\\\u002F?*\\[\\]:]\")\n\ndef clean_sheet_name(name: str) -> str:\n    text = unicodedata.normalize(\"NFKC\", name)\n    text = \" \".join(text.split())          # collapse and trim every kind of space\n    return INVALID.sub(\"-\", text)[:31] or \"Sheet\"\n\nwb = Workbook()\nwb.remove(wb.active)\nfor raw in (\"  Sales \", \"Q3\\u00a0Summary\", \"Raw \u002F Data\"):\n    wb.create_sheet(clean_sheet_name(raw))\n\nprint(wb.sheetnames)      # ['Sales', 'Q3 Summary', 'Raw - Data']\nwb.save(\"clean.xlsx\")\n",[13,1340,1341,1346,1352,1358,1362,1373,1377,1412,1416,1433,1446,1460,1488,1492,1501,1506,1539,1544,1548,1558],{"__ignoreMap":142},[146,1342,1343],{"class":98,"line":148},[146,1344,1345],{"class":155},"\"\"\"Write sheets with names that are safe to look up later.\"\"\"\n",[146,1347,1348,1350],{"class":98,"line":199},[146,1349,193],{"class":185},[146,1351,892],{"class":189},[146,1353,1354,1356],{"class":98,"line":206},[146,1355,193],{"class":185},[146,1357,297],{"class":189},[146,1359,1360],{"class":98,"line":224},[146,1361,203],{"emptyLinePlaceholder":202},[146,1363,1364,1366,1368,1370],{"class":98,"line":234},[146,1365,186],{"class":185},[146,1367,190],{"class":189},[146,1369,193],{"class":185},[146,1371,1372],{"class":189}," Workbook\n",[146,1374,1375],{"class":98,"line":318},[146,1376,203],{"emptyLinePlaceholder":202},[146,1378,1379,1382,1384,1387,1389,1391,1393,1397,1400,1403,1406,1408,1410],{"class":98,"line":342},[146,1380,1381],{"class":227},"INVALID",[146,1383,1033],{"class":185},[146,1385,1386],{"class":189}," re.compile(",[146,1388,932],{"class":185},[146,1390,465],{"class":155},[146,1392,486],{"class":227},[146,1394,1396],{"class":1395},"s_b0D","\\\\",[146,1398,1399],{"class":468},"\u002F?*",[146,1401,1402],{"class":1395},"\\[\\]",[146,1404,1405],{"class":468},":",[146,1407,948],{"class":227},[146,1409,465],{"class":155},[146,1411,221],{"class":189},[146,1413,1414],{"class":98,"line":348},[146,1415,203],{"emptyLinePlaceholder":202},[146,1417,1418,1420,1423,1425,1427,1429,1431],{"class":98,"line":369},[146,1419,321],{"class":185},[146,1421,1422],{"class":324}," clean_sheet_name",[146,1424,328],{"class":189},[146,1426,331],{"class":227},[146,1428,334],{"class":189},[146,1430,331],{"class":227},[146,1432,339],{"class":189},[146,1434,1435,1437,1439,1441,1443],{"class":98,"line":381},[146,1436,351],{"class":189},[146,1438,212],{"class":185},[146,1440,356],{"class":189},[146,1442,359],{"class":155},[146,1444,1445],{"class":189},", name)\n",[146,1447,1448,1450,1452,1454,1457],{"class":98,"line":386},[146,1449,351],{"class":189},[146,1451,212],{"class":185},[146,1453,375],{"class":155},[146,1455,1456],{"class":189},".join(text.split())          ",[146,1458,1459],{"class":365},"# collapse and trim every kind of space\n",[146,1461,1462,1464,1467,1470,1473,1476,1479,1482,1485],{"class":98,"line":402},[146,1463,372],{"class":185},[146,1465,1466],{"class":227}," INVALID",[146,1468,1469],{"class":189},".sub(",[146,1471,1472],{"class":155},"\"-\"",[146,1474,1475],{"class":189},", text)[:",[146,1477,1478],{"class":227},"31",[146,1480,1481],{"class":189},"] ",[146,1483,1484],{"class":185},"or",[146,1486,1487],{"class":155}," \"Sheet\"\n",[146,1489,1490],{"class":98,"line":413},[146,1491,203],{"emptyLinePlaceholder":202},[146,1493,1494,1496,1498],{"class":98,"line":426},[146,1495,209],{"class":189},[146,1497,212],{"class":185},[146,1499,1500],{"class":189}," Workbook()\n",[146,1502,1503],{"class":98,"line":441},[146,1504,1505],{"class":189},"wb.remove(wb.active)\n",[146,1507,1508,1510,1513,1515,1518,1521,1523,1526,1529,1532,1534,1537],{"class":98,"line":450},[146,1509,247],{"class":185},[146,1511,1512],{"class":189}," raw ",[146,1514,253],{"class":185},[146,1516,1517],{"class":189}," (",[146,1519,1520],{"class":155},"\"  Sales \"",[146,1522,570],{"class":189},[146,1524,1525],{"class":155},"\"Q3",[146,1527,1528],{"class":468},"\\u00a0",[146,1530,1531],{"class":155},"Summary\"",[146,1533,570],{"class":189},[146,1535,1536],{"class":155},"\"Raw \u002F Data\"",[146,1538,399],{"class":189},[146,1540,1541],{"class":98,"line":510},[146,1542,1543],{"class":189},"    wb.create_sheet(clean_sheet_name(raw))\n",[146,1545,1546],{"class":98,"line":515},[146,1547,203],{"emptyLinePlaceholder":202},[146,1549,1550,1552,1555],{"class":98,"line":528},[146,1551,228],{"class":227},[146,1553,1554],{"class":189},"(wb.sheetnames)      ",[146,1556,1557],{"class":365},"# ['Sales', 'Q3 Summary', 'Raw - Data']\n",[146,1559,1560,1563,1566],{"class":98,"line":548},[146,1561,1562],{"class":189},"wb.save(",[146,1564,1565],{"class":155},"\"clean.xlsx\"",[146,1567,221],{"class":189},[10,1569,1570,1571,1575,1576,22],{},"The same function is worth applying to any name derived from data — a region, a customer, a month label — because those are the names most likely to arrive with stray whitespace or a slash. It also protects against the two Excel rules that produce a repair prompt rather than an exception: the 31-character limit and the forbidden characters. Generating one sheet or file per group is exactly where that bites; see ",[18,1572,1574],{"href":1573},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-one-excel-report-per-region-in-a-loop\u002F","Generate one Excel report per region in a loop"," and ",[18,1577,1579],{"href":1578},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fsplit-one-excel-sheet-into-multiple-files-by-value\u002F","Split one Excel sheet into multiple files by value",[129,1581,1583],{"id":1582},"common-pitfalls-and-gotchas","Common pitfalls and gotchas",[1585,1586,1587,1605,1626,1640,1651],"ul",{},[1588,1589,1590,1596,1597,1600,1601,1604],"li",{},[1591,1592,1593],"strong",{},[13,1594,1595],{},"wb.get_sheet_by_name()"," was removed in openpyxl 3.x. Use ",[13,1598,1599],{},"wb[\"Name\"]"," — old tutorials still show the removed API and produce an ",[13,1602,1603],{},"AttributeError"," that looks unrelated.",[1588,1606,1607,1610,1611,1614,1615,1617,1618,1621,1622,1625],{},[1591,1608,1609],{},"Creating a sheet by accident."," ",[13,1612,1613],{},"wb.create_sheet(\"Sales\")"," when ",[13,1616,75],{}," already exists gives you ",[13,1619,1620],{},"Sales1",", and the next read finds the wrong tab. Check ",[13,1623,1624],{},"if \"Sales\" in wb.sheetnames"," first.",[1588,1627,1628,1631,1632,1635,1636,22],{},[1591,1629,1630],{},"Chart and formula references"," point at sheet names too. Renaming a sheet after writing a formula leaves ",[13,1633,1634],{},"#REF!"," in the workbook — see ",[18,1637,1639],{"href":1638},"\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",[1588,1641,1642,1645,1646,1575,1648,1650],{},[1591,1643,1644],{},"Case-only differences are legal."," Excel will not let you create ",[13,1647,1274],{},[13,1649,75],{}," in the same workbook through the UI, but a file produced by code can contain both, in which case a case-folded match becomes ambiguous. Prefer exact matching if you know that is possible.",[1588,1652,1653,1656],{},[1591,1654,1655],{},"A truly missing sheet."," Sometimes the sheet really is gone — the export changed. That is why the helper's error message lists what was there.",[129,1658,1660],{"id":1659},"performance-and-scale-notes","Performance and scale notes",[10,1662,1663,1664,1667,1668,1671],{},"Sheet lookup is free: ",[13,1665,1666],{},"wb.sheetnames"," is read from the workbook's relationship part, not by parsing sheets. What is not free is loading a large workbook just to discover which sheets it contains. Use ",[13,1669,1670],{},"read_only=True"," for an inspection pass and close the handle afterwards:",[137,1673,1675],{"className":176,"code":1674,"language":178,"meta":142,"style":142},"wb = load_workbook(\"huge.xlsx\", read_only=True)\ntry:\n    print(wb.sheetnames)\nfinally:\n    wb.close()\n",[13,1676,1677,1698,1704,1710,1716],{"__ignoreMap":142},[146,1678,1679,1681,1683,1685,1688,1690,1692,1694,1696],{"class":98,"line":148},[146,1680,209],{"class":189},[146,1682,212],{"class":185},[146,1684,215],{"class":189},[146,1686,1687],{"class":155},"\"huge.xlsx\"",[146,1689,570],{"class":189},[146,1691,1072],{"class":685},[146,1693,212],{"class":185},[146,1695,1077],{"class":227},[146,1697,221],{"class":189},[146,1699,1700,1702],{"class":98,"line":199},[146,1701,1084],{"class":185},[146,1703,339],{"class":189},[146,1705,1706,1708],{"class":98,"line":206},[146,1707,1211],{"class":227},[146,1709,231],{"class":189},[146,1711,1712,1714],{"class":98,"line":224},[146,1713,1110],{"class":185},[146,1715,339],{"class":189},[146,1717,1718],{"class":98,"line":234},[146,1719,1117],{"class":189},[10,1721,1722,1723,22],{},"In a batch that scans hundreds of files for a specific tab, that pattern keeps memory flat — the details are in ",[18,1724,1726],{"href":1725},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fspeed-up-openpyxl-with-read-only-mode\u002F","Speed up openpyxl with read-only mode",[129,1728,1730],{"id":1729},"conclusion","Conclusion",[10,1732,1733,1734,1736,1737,1739],{},"A worksheet ",[13,1735,1245],{}," is a string-comparison failure, not a missing sheet. Print the names with ",[13,1738,172],{}," to reveal invisible characters, then stop comparing raw strings: normalise with NFKC and case-folding, or address the sheet by position or pattern when names are volatile. Whatever you choose, make the error message list the names the file actually had, so the next failure is diagnosed from the log rather than from a rerun.",[129,1741,1743],{"id":1742},"frequently-asked-questions","Frequently asked questions",[10,1745,1746,1749],{},[1591,1747,1748],{},"The tab is right there in Excel — why can't openpyxl find it?","\nExcel displays a trimmed tab label, so a name with a leading space, a trailing space or a non-breaking space looks identical to a clean one. openpyxl compares the raw string, and those characters make the lookup miss.",[10,1751,1752,1755,1756,1758,1759,1762],{},[1591,1753,1754],{},"Does openpyxl see hidden sheets?","\nYes. Hidden and very-hidden sheets are in ",[13,1757,1666],{}," like any other, and you can read them normally. Check ",[13,1760,1761],{},"ws.sheet_state"," if you need to skip them deliberately.",[10,1764,1765,1768,1769,1772,1773,1775],{},[1591,1766,1767],{},"Why does pandas raise a different message for the same problem?","\npandas wraps the lookup and raises ",[13,1770,1771],{},"ValueError"," \"Worksheet named 'X' not found\", while openpyxl raises ",[13,1774,1245],{},". The cause is identical; only the exception type differs, so catch both if your code can take either path.",[10,1777,1778,1781,1782,1784,1785,1788],{},[1591,1779,1780],{},"Can I select a sheet by position instead of name?","\nYes. ",[13,1783,843],{}," is the first sheet in tab order and ",[13,1786,1787],{},"wb.active"," is whichever sheet was selected when the file was saved. Position is safer when producers rename tabs but never reorder them.",[10,1790,1791,1794,1795,1797,1798,1801],{},[1591,1792,1793],{},"How do I list every sheet name with its exact characters?","\nPrint the ",[13,1796,172],{}," of the list — ",[13,1799,1800],{},"print([repr(n) for n in wb.sheetnames])"," — so invisible characters and stray spaces appear as escape sequences instead of vanishing into the terminal output.",[129,1803,1805],{"id":1804},"related","Related",[1585,1807,1808,1816,1823,1830,1835],{},[1588,1809,1810,1610,1813,1815],{},[1591,1811,1812],{},"Up:",[18,1814,21],{"href":20}," — every error family in one triage map.",[1588,1817,1818,1822],{},[18,1819,1821],{"href":1820},"\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"," — the failure that happens one step earlier, before any sheet is addressed.",[1588,1824,1825,1829],{},[18,1826,1828],{"href":1827},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Frename-reorder-and-delete-excel-sheets-with-openpyxl\u002F","Rename, reorder and delete Excel sheets with openpyxl"," — how tab names change in the first place, and how to change them safely.",[1588,1831,1832,1834],{},[18,1833,769],{"href":768}," — reading every tab instead of guessing which one you need.",[1588,1836,1837,1841],{},[18,1838,1840],{"href":1839},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fiterate-over-rows-and-columns-with-openpyxl\u002F","Iterate over rows and columns with openpyxl"," — what to do with the worksheet once the lookup succeeds.",[1843,1844,1845],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}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 .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 .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}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}",{"title":142,"searchDepth":199,"depth":199,"links":1847},[1848,1849,1850,1851,1852,1853,1854,1855,1856,1857,1858],{"id":131,"depth":199,"text":132},{"id":165,"depth":199,"text":166},{"id":276,"depth":199,"text":277},{"id":562,"depth":199,"text":563},{"id":997,"depth":199,"text":998},{"id":1331,"depth":199,"text":1332},{"id":1582,"depth":199,"text":1583},{"id":1659,"depth":199,"text":1660},{"id":1729,"depth":199,"text":1730},{"id":1742,"depth":199,"text":1743},{"id":1804,"depth":199,"text":1805},"2026-08-27","Why openpyxl raises KeyError on a sheet you can see in Excel: hidden whitespace, case, renamed tabs and index confusion — plus a resilient sheet lookup helper.","md",[1863,1865,1867,1869,1871],{"q":1748,"a":1864},"Excel displays a trimmed tab label, so a name with a leading space, a trailing space or a non-breaking space looks identical to a clean one. openpyxl compares the raw string, and those characters make the lookup miss.",{"q":1754,"a":1866},"Yes. Hidden and very-hidden sheets are in wb.sheetnames like any other, and you can read them normally. Check ws.sheet_state if you need to skip them deliberately.",{"q":1767,"a":1868},"pandas wraps the lookup and raises ValueError \"Worksheet named 'X' not found\", while openpyxl raises KeyError. The cause is identical; only the exception type differs, so catch both if your code can take either path.",{"q":1780,"a":1870},"Yes. wb.worksheets[0] is the first sheet in tab order and wb.active is whichever sheet was selected when the file was saved. Position is safer when producers rename tabs but never reorder them.",{"q":1793,"a":1872},"Print the repr of the list — print([repr(n) for n in wb.sheetnames]) — so invisible characters and stray spaces appear as escape sequences instead of vanishing into the terminal output.",{},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-worksheet-does-not-exist-keyerror-in-openpyxl",{"title":1876,"description":1877},"Fix openpyxl Worksheet KeyError","Solve KeyError: Worksheet does not exist in openpyxl and Worksheet not found in pandas — normalise names, match case-insensitively, and fall back to sheet position.","fix-worksheet-does-not-exist-keyerror-in-openpyxl","getting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-worksheet-does-not-exist-keyerror-in-openpyxl\u002Findex","how-to","g-NwBzQk6Kjk_M2LM57UK3Smmc2Ih4svMOqipMgY46w",[1883,1887],{"title":1884,"path":1885,"stem":1886,"children":-1},"Fix \"openpyxl does not support the old .xls file format\"","\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-openpyxl-does-not-support-the-old-xls-format","getting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-openpyxl-does-not-support-the-old-xls-format\u002Findex",{"title":1888,"path":1889,"stem":1890,"children":-1},"Using openpyxl for Excel File Manipulation","\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation","getting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Findex",1788710155092]