[{"data":1,"prerenderedAt":2729},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-excel-sheets-whose-names-change-with-pandas":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-excel-sheets-whose-names-change-with-pandas":2694,"ancestors:\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-excel-sheets-whose-names-change-with-pandas":2703,"children:\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-excel-sheets-whose-names-change-with-pandas":2708,"related:\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-excel-sheets-whose-names-change-with-pandas":2709},{"id":4,"title":5,"body":6,"dateModified":2658,"datePublished":2658,"description":2659,"extension":2660,"faq":2661,"keyword":2670,"kind":2671,"meta":2672,"navigation":374,"path":2682,"seo":2683,"slug":2686,"stem":2687,"topics":2688,"type":2692,"__hash__":2693},"docs\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-excel-sheets-whose-names-change-with-pandas\u002Findex.md","Read Excel Sheets Whose Names Change with pandas",{"type":7,"value":8,"toc":2644},"minimark",[9,37,311,316,343,346,647,651,661,734,749,753,756,1039,1049,1212,1216,1219,1500,1526,1530,1537,1666,1674,1678,1685,1916,1924,1928,1931,2344,2484,2487,2491,2551,2555,2565,2569,2579,2583,2589,2595,2601,2607,2611,2640],[10,11,12,13,17,18,21,22,25,26,30,31,36],"p",{},"Hard-coding ",[14,15,16],"code",{},"sheet_name=\"Sales\""," works until the file arrives with a tab called ",[14,19,20],{},"Sales Mar 2026",", or ",[14,23,24],{},"sales ",", or with a new summary sheet inserted in front of the one you wanted. Monthly exports, hand-maintained trackers and files assembled by several people all drift this way. The fix is to stop naming the sheet in the call and instead ",[27,28,29],"em",{},"find"," it: list the tabs, then choose by a rule you control. This guide, part of ",[32,33,35],"a",{"href":34},"\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002F","Reading Excel Files with pandas",", walks through the rules that hold up — pattern, date, visibility and content — and how to fail loudly when none of them matches.",[38,39,47,48,47,52,47,56,47,64,47,74,47,79,47,90,47,97,47,103,47,106,47,112,47,116,47,120,47,124,47,128,47,132,47,137,47,141,47,145,47,147,47,150,47,152,47,155,47,158,47,162,47,164,47,168,47,170,47,173,47,175,47,178,47,181,47,185,47,187,47,190,47,192,47,195,47,197,47,200,47,203,47,207,47,209,47,212,47,214,47,217,47,219,47,222,47,227,47,233,47,237,47,241,47,245,47,253,47,257,47,261,47,265,47,271,47,276,47,285,47,293,47,299,47,303,47,307],"svg",{"viewBox":40,"role":41,"ariaLabelledBy":42,"xmlns":45,"style":46},"111 0 538 222","img",[43,44],"names-tabs-t","names-tabs-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:538px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[49,50,51],"title",{"id":43},"A workbook whose target sheet has a changing name",[53,54,55],"desc",{"id":44},"A workbook window with four tabs. The first is a hidden lookup sheet, then two monthly sales sheets named with their month, then a notes sheet. The wanted sheet is the latest month, which is neither the first tab nor a fixed name.",[57,58],"rect",{"x":59,"y":60,"width":61,"height":62,"fill":63},"111","0","538","222","#ffffff",[57,65],{"x":66,"y":67,"width":68,"height":69,"rx":70,"fill":71,"stroke":72,"style":73},"127.5","16.0","215.0","30.0","8","#e7ebef","var(--line,#cdd5e6)","stroke-width:1.5px",[57,75],{"x":66,"y":76,"width":68,"height":77,"rx":60,"fill":71,"stroke":72,"style":78},"38.0","8.0","stroke-width:0px",[80,81,83],"g",{"transform":82},"translate(137.5,23.0) scale(0.667)",[84,85],"path",{"d":86,"fill":87,"stroke":88,"style":89},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6","none","var(--teal-ink,#0b6157)","stroke-width:2.70px;stroke-linecap:round;stroke-linejoin:round",[91,92,96],"text",{"x":93,"y":94,"style":95},"159.5","36.0","font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:start","sales_export.xlsx",[57,98],{"x":66,"y":99,"width":100,"height":101,"rx":60,"fill":71,"stroke":72,"style":102},"46.0","32.0","24.0","stroke-width:1px",[57,104],{"x":93,"y":99,"width":105,"height":101,"rx":60,"fill":71,"stroke":72,"style":102},"63.6",[91,107,111],{"x":108,"y":109,"style":110},"191.3","63.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[57,113],{"x":114,"y":99,"width":115,"height":101,"rx":60,"fill":71,"stroke":72,"style":102},"223.1","48.6",[91,117,119],{"x":118,"y":109,"style":110},"247.4","B",[57,121],{"x":122,"y":99,"width":123,"height":101,"rx":60,"fill":71,"stroke":72,"style":102},"271.7","70.9",[91,125,127],{"x":126,"y":109,"style":110},"307.1","C",[57,129],{"x":66,"y":130,"width":100,"height":131,"rx":60,"fill":71,"stroke":72,"style":102},"70.0","28.0",[91,133,136],{"x":134,"y":135,"style":110},"143.5","89.0","1",[57,138],{"x":93,"y":130,"width":105,"height":131,"rx":60,"fill":139,"stroke":140,"style":73},"#f0f4ff","var(--brand,#5b5cf0)",[91,142,144],{"x":108,"y":135,"style":143},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","region",[57,146],{"x":114,"y":130,"width":115,"height":131,"rx":60,"fill":139,"stroke":140,"style":73},[91,148,149],{"x":118,"y":135,"style":143},"rep",[57,151],{"x":122,"y":130,"width":123,"height":131,"rx":60,"fill":139,"stroke":140,"style":73},[91,153,154],{"x":126,"y":135,"style":143},"amount",[57,156],{"x":66,"y":157,"width":100,"height":131,"rx":60,"fill":71,"stroke":72,"style":102},"98.0",[91,159,161],{"x":134,"y":160,"style":110},"117.0","2",[57,163],{"x":93,"y":157,"width":105,"height":131,"rx":60,"fill":63,"stroke":72,"style":102},[91,165,167],{"x":108,"y":160,"style":166},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","North",[57,169],{"x":114,"y":157,"width":115,"height":131,"rx":60,"fill":63,"stroke":72,"style":102},[91,171,172],{"x":118,"y":160,"style":166},"Ana",[57,174],{"x":122,"y":157,"width":123,"height":131,"rx":60,"fill":63,"stroke":72,"style":102},[91,176,177],{"x":126,"y":160,"style":166},"4,100",[57,179],{"x":66,"y":180,"width":100,"height":131,"rx":60,"fill":71,"stroke":72,"style":102},"126.0",[91,182,184],{"x":134,"y":183,"style":110},"145.0","3",[57,186],{"x":93,"y":180,"width":105,"height":131,"rx":60,"fill":63,"stroke":72,"style":102},[91,188,189],{"x":108,"y":183,"style":166},"South",[57,191],{"x":114,"y":180,"width":115,"height":131,"rx":60,"fill":63,"stroke":72,"style":102},[91,193,194],{"x":118,"y":183,"style":166},"Ben",[57,196],{"x":122,"y":180,"width":123,"height":131,"rx":60,"fill":63,"stroke":72,"style":102},[91,198,199],{"x":126,"y":183,"style":166},"3,820",[57,201],{"x":66,"y":202,"width":100,"height":131,"rx":60,"fill":71,"stroke":72,"style":102},"154.0",[91,204,206],{"x":134,"y":205,"style":110},"173.0","4",[57,208],{"x":93,"y":202,"width":105,"height":131,"rx":60,"fill":63,"stroke":72,"style":102},[91,210,211],{"x":108,"y":205,"style":166},"East",[57,213],{"x":114,"y":202,"width":115,"height":131,"rx":60,"fill":63,"stroke":72,"style":102},[91,215,216],{"x":118,"y":205,"style":166},"Cy",[57,218],{"x":122,"y":202,"width":123,"height":131,"rx":60,"fill":63,"stroke":72,"style":102},[91,220,221],{"x":126,"y":205,"style":166},"2,950",[57,223],{"x":224,"y":225,"width":226,"height":101,"rx":60,"fill":71,"stroke":72,"style":102},"135.5","182.0","124.1",[91,228,232],{"x":229,"y":230,"style":231},"197.5","198.5","font-size:11.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","Lookups (hidden)",[57,234],{"x":235,"y":225,"width":236,"height":101,"rx":60,"fill":71,"stroke":72,"style":102},"261.5","113.1",[91,238,240],{"x":239,"y":230,"style":231},"318.1","Sales Feb 2026",[57,242],{"x":243,"y":225,"width":244,"height":101,"rx":60,"fill":63,"stroke":72,"style":102},"376.6","116.3",[246,247],"line",{"x1":248,"y1":249,"x2":250,"y2":249,"stroke":251,"style":252},"378.6","205.0","490.9","var(--teal,#0f9488)","stroke-width:3px",[91,254,20],{"x":255,"y":230,"style":256},"434.7","font-size:11.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[57,258],{"x":259,"y":225,"width":260,"height":101,"rx":60,"fill":71,"stroke":72,"style":102},"494.9","57.7",[91,262,264],{"x":263,"y":230,"style":231},"523.7","Notes",[246,266],{"x1":267,"y1":268,"x2":269,"y2":268,"stroke":140,"style":270},"350.5","111.0","382.5","stroke-width:2px",[272,273],"polygon",{"points":274,"fill":275},"386.5,111.0 377.5,116.0 377.5,106.0","#5b5cf0",[57,277],{"x":278,"y":279,"width":280,"height":281,"rx":282,"fill":283,"stroke":284,"style":270},"392.5","47.0","240.0","128.0","12","#fee8f2","var(--accent,#f43f8f)",[80,286,288],{"transform":287},"translate(500.5,60.0) scale(1.000)",[84,289],{"d":290,"fill":87,"stroke":291,"style":292},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--accent-ink,#be185d)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[91,294,298],{"x":295,"y":296,"style":297},"512.5","106.5","font-size:13.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","sheet_name=0 returns",[91,300,302],{"x":295,"y":301,"style":297},"124.0","Lookups",[91,304,306],{"x":295,"y":134,"style":305},"font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","hidden sheets still count",[91,308,310],{"x":295,"y":309,"style":305},"161.0","the name changes monthly",[312,313,315],"h2",{"id":314},"prerequisites","Prerequisites",[317,318,323],"pre",{"className":319,"code":320,"language":321,"meta":322,"style":322},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,324,325],{"__ignoreMap":322},[326,327,329,333,337,340],"span",{"class":246,"line":328},1,[326,330,332],{"class":331},"sMTad","pip",[326,334,336],{"class":335},"srMev"," install",[326,338,339],{"class":335}," pandas",[326,341,342],{"class":335}," openpyxl\n",[10,344,345],{},"The examples use a workbook shaped like the one above: a hidden lookup sheet first, two monthly sales tabs with slightly inconsistent names, and a notes sheet.",[317,347,351],{"className":348,"code":349,"language":350,"meta":322,"style":322},"language-python shiki shiki-themes github-light github-dark-high-contrast","from openpyxl import Workbook\n\nwb = Workbook()\nlookups = wb.active\nlookups.title = \"Lookups\"\nlookups.sheet_state = \"hidden\"\nlookups.append([\"region\", \"manager\"])\nlookups.append([\"North\", \"Dana\"])\n\nfor name, amounts in [(\"Sales Feb 2026\", [3900, 3610, 2700]), (\"sales mar 2026 \", [4100, 3820, 2950])]:\n    ws = wb.create_sheet(name)\n    ws.append([\"region\", \"rep\", \"amount\"])\n    for region, rep, amount in zip([\"North\", \"South\", \"East\"], [\"Ana\", \"Ben\", \"Cy\"], amounts):\n        ws.append([region, rep, amount])\n\nnotes = wb.create_sheet(\"Notes\")\nnotes.append([\"Figures are provisional until the 5th working day.\"])\nwb.save(\"sales_export.xlsx\")\n","python",[14,352,353,369,376,388,399,410,421,439,454,459,518,529,549,597,603,608,625,636],{"__ignoreMap":322},[326,354,355,359,363,366],{"class":246,"line":328},[326,356,358],{"class":357},"s-kum","from",[326,360,362],{"class":361},"skGVy"," openpyxl ",[326,364,365],{"class":357},"import",[326,367,368],{"class":361}," Workbook\n",[326,370,372],{"class":246,"line":371},2,[326,373,375],{"emptyLinePlaceholder":374},true,"\n",[326,377,379,382,385],{"class":246,"line":378},3,[326,380,381],{"class":361},"wb ",[326,383,384],{"class":357},"=",[326,386,387],{"class":361}," Workbook()\n",[326,389,391,394,396],{"class":246,"line":390},4,[326,392,393],{"class":361},"lookups ",[326,395,384],{"class":357},[326,397,398],{"class":361}," wb.active\n",[326,400,402,405,407],{"class":246,"line":401},5,[326,403,404],{"class":361},"lookups.title ",[326,406,384],{"class":357},[326,408,409],{"class":335}," \"Lookups\"\n",[326,411,413,416,418],{"class":246,"line":412},6,[326,414,415],{"class":361},"lookups.sheet_state ",[326,417,384],{"class":357},[326,419,420],{"class":335}," \"hidden\"\n",[326,422,424,427,430,433,436],{"class":246,"line":423},7,[326,425,426],{"class":361},"lookups.append([",[326,428,429],{"class":335},"\"region\"",[326,431,432],{"class":361},", ",[326,434,435],{"class":335},"\"manager\"",[326,437,438],{"class":361},"])\n",[326,440,442,444,447,449,452],{"class":246,"line":441},8,[326,443,426],{"class":361},[326,445,446],{"class":335},"\"North\"",[326,448,432],{"class":361},[326,450,451],{"class":335},"\"Dana\"",[326,453,438],{"class":361},[326,455,457],{"class":246,"line":456},9,[326,458,375],{"emptyLinePlaceholder":374},[326,460,462,465,468,471,474,477,480,484,486,489,491,494,497,500,502,505,507,510,512,515],{"class":246,"line":461},10,[326,463,464],{"class":357},"for",[326,466,467],{"class":361}," name, amounts ",[326,469,470],{"class":357},"in",[326,472,473],{"class":361}," [(",[326,475,476],{"class":335},"\"Sales Feb 2026\"",[326,478,479],{"class":361},", [",[326,481,483],{"class":482},"sP0c6","3900",[326,485,432],{"class":361},[326,487,488],{"class":482},"3610",[326,490,432],{"class":361},[326,492,493],{"class":482},"2700",[326,495,496],{"class":361},"]), (",[326,498,499],{"class":335},"\"sales mar 2026 \"",[326,501,479],{"class":361},[326,503,504],{"class":482},"4100",[326,506,432],{"class":361},[326,508,509],{"class":482},"3820",[326,511,432],{"class":361},[326,513,514],{"class":482},"2950",[326,516,517],{"class":361},"])]:\n",[326,519,521,524,526],{"class":246,"line":520},11,[326,522,523],{"class":361},"    ws ",[326,525,384],{"class":357},[326,527,528],{"class":361}," wb.create_sheet(name)\n",[326,530,532,535,537,539,542,544,547],{"class":246,"line":531},12,[326,533,534],{"class":361},"    ws.append([",[326,536,429],{"class":335},[326,538,432],{"class":361},[326,540,541],{"class":335},"\"rep\"",[326,543,432],{"class":361},[326,545,546],{"class":335},"\"amount\"",[326,548,438],{"class":361},[326,550,552,555,558,560,563,566,568,570,573,575,578,581,584,586,589,591,594],{"class":246,"line":551},13,[326,553,554],{"class":357},"    for",[326,556,557],{"class":361}," region, rep, amount ",[326,559,470],{"class":357},[326,561,562],{"class":482}," zip",[326,564,565],{"class":361},"([",[326,567,446],{"class":335},[326,569,432],{"class":361},[326,571,572],{"class":335},"\"South\"",[326,574,432],{"class":361},[326,576,577],{"class":335},"\"East\"",[326,579,580],{"class":361},"], [",[326,582,583],{"class":335},"\"Ana\"",[326,585,432],{"class":361},[326,587,588],{"class":335},"\"Ben\"",[326,590,432],{"class":361},[326,592,593],{"class":335},"\"Cy\"",[326,595,596],{"class":361},"], amounts):\n",[326,598,600],{"class":246,"line":599},14,[326,601,602],{"class":361},"        ws.append([region, rep, amount])\n",[326,604,606],{"class":246,"line":605},15,[326,607,375],{"emptyLinePlaceholder":374},[326,609,611,614,616,619,622],{"class":246,"line":610},16,[326,612,613],{"class":361},"notes ",[326,615,384],{"class":357},[326,617,618],{"class":361}," wb.create_sheet(",[326,620,621],{"class":335},"\"Notes\"",[326,623,624],{"class":361},")\n",[326,626,628,631,634],{"class":246,"line":627},17,[326,629,630],{"class":361},"notes.append([",[326,632,633],{"class":335},"\"Figures are provisional until the 5th working day.\"",[326,635,438],{"class":361},[326,637,639,642,645],{"class":246,"line":638},18,[326,640,641],{"class":361},"wb.save(",[326,643,644],{"class":335},"\"sales_export.xlsx\"",[326,646,624],{"class":361},[312,648,650],{"id":649},"step-1-list-the-sheets-without-reading-them","Step 1 — List the sheets without reading them",[10,652,653,656,657,660],{},[14,654,655],{},"pd.ExcelFile"," opens the workbook once and exposes the tab names. It reads the workbook's structure, not the cells, so it is cheap even on large files — and you can pass the same object to ",[14,658,659],{},"read_excel"," afterwards, which avoids opening the file twice.",[317,662,664],{"className":348,"code":663,"language":350,"meta":322,"style":322},"import pandas as pd\n\nbook = pd.ExcelFile(\"sales_export.xlsx\")\nprint(book.sheet_names)\n# ['Lookups', 'Sales Feb 2026', 'sales mar 2026 ', 'Notes']\nprint(pd.read_excel(book, sheet_name=0).columns.tolist())\n# ['region', 'manager']  \u003C- the hidden sheet, not sales\n",[14,665,666,679,683,697,705,711,729],{"__ignoreMap":322},[326,667,668,670,673,676],{"class":246,"line":328},[326,669,365],{"class":357},[326,671,672],{"class":361}," pandas ",[326,674,675],{"class":357},"as",[326,677,678],{"class":361}," pd\n",[326,680,681],{"class":246,"line":371},[326,682,375],{"emptyLinePlaceholder":374},[326,684,685,688,690,693,695],{"class":246,"line":378},[326,686,687],{"class":361},"book ",[326,689,384],{"class":357},[326,691,692],{"class":361}," pd.ExcelFile(",[326,694,644],{"class":335},[326,696,624],{"class":361},[326,698,699,702],{"class":246,"line":390},[326,700,701],{"class":482},"print",[326,703,704],{"class":361},"(book.sheet_names)\n",[326,706,707],{"class":246,"line":401},[326,708,710],{"class":709},"s-wDw","# ['Lookups', 'Sales Feb 2026', 'sales mar 2026 ', 'Notes']\n",[326,712,713,715,718,722,724,726],{"class":246,"line":412},[326,714,701],{"class":482},[326,716,717],{"class":361},"(pd.read_excel(book, ",[326,719,721],{"class":720},"sa561","sheet_name",[326,723,384],{"class":357},[326,725,60],{"class":482},[326,727,728],{"class":361},").columns.tolist())\n",[326,730,731],{"class":246,"line":423},[326,732,733],{"class":709},"# ['region', 'manager']  \u003C- the hidden sheet, not sales\n",[10,735,736,737,740,741,744,745,748],{},"The second print shows why positions are fragile: ",[14,738,739],{},"sheet_name=0"," is the first tab in workbook order, hidden or not. And ",[14,742,743],{},"read_excel(book, sheet_name=\"Sales Mar 2026\")"," would raise, because the real name has a lower-case ",[14,746,747],{},"s"," and a trailing space — pandas compares names exactly.",[312,750,752],{"id":751},"step-2-match-by-a-normalised-pattern","Step 2 — Match by a normalised pattern",[10,754,755],{},"Compare names after normalising case and whitespace, and match a pattern rather than a literal. A small helper keeps the rule in one place:",[317,757,759],{"className":348,"code":758,"language":350,"meta":322,"style":322},"import re\n\ndef normalise(name: str) -> str:\n    return re.sub(r\"\\s+\", \" \", name).strip().casefold()\n\ndef find_sheet(book: pd.ExcelFile, pattern: str) -> str:\n    regex = re.compile(pattern, re.IGNORECASE)\n    matches = [name for name in book.sheet_names if regex.search(normalise(name))]\n    if not matches:\n        raise ValueError(f\"no sheet matches {pattern!r}; found {book.sheet_names}\")\n    if len(matches) > 1:\n        raise ValueError(f\"{pattern!r} is ambiguous: {matches}\")\n    return matches[0]\n\nprint(find_sheet(book, r\"^notes$\"))            # 'Notes'\n",[14,760,761,768,772,795,825,829,847,862,888,899,943,961,995,1007,1011],{"__ignoreMap":322},[326,762,763,765],{"class":246,"line":328},[326,764,365],{"class":357},[326,766,767],{"class":361}," re\n",[326,769,770],{"class":246,"line":371},[326,771,375],{"emptyLinePlaceholder":374},[326,773,774,777,781,784,787,790,792],{"class":246,"line":378},[326,775,776],{"class":357},"def",[326,778,780],{"class":779},"s_Opv"," normalise",[326,782,783],{"class":361},"(name: ",[326,785,786],{"class":482},"str",[326,788,789],{"class":361},") -> ",[326,791,786],{"class":482},[326,793,794],{"class":361},":\n",[326,796,797,800,803,806,809,812,815,817,819,822],{"class":246,"line":390},[326,798,799],{"class":357},"    return",[326,801,802],{"class":361}," re.sub(",[326,804,805],{"class":357},"r",[326,807,808],{"class":335},"\"",[326,810,811],{"class":482},"\\s",[326,813,814],{"class":357},"+",[326,816,808],{"class":335},[326,818,432],{"class":361},[326,820,821],{"class":335},"\" \"",[326,823,824],{"class":361},", name).strip().casefold()\n",[326,826,827],{"class":246,"line":401},[326,828,375],{"emptyLinePlaceholder":374},[326,830,831,833,836,839,841,843,845],{"class":246,"line":412},[326,832,776],{"class":357},[326,834,835],{"class":779}," find_sheet",[326,837,838],{"class":361},"(book: pd.ExcelFile, pattern: ",[326,840,786],{"class":482},[326,842,789],{"class":361},[326,844,786],{"class":482},[326,846,794],{"class":361},[326,848,849,852,854,857,860],{"class":246,"line":423},[326,850,851],{"class":361},"    regex ",[326,853,384],{"class":357},[326,855,856],{"class":361}," re.compile(pattern, re.",[326,858,859],{"class":482},"IGNORECASE",[326,861,624],{"class":361},[326,863,864,867,869,872,874,877,879,882,885],{"class":246,"line":441},[326,865,866],{"class":361},"    matches ",[326,868,384],{"class":357},[326,870,871],{"class":361}," [name ",[326,873,464],{"class":357},[326,875,876],{"class":361}," name ",[326,878,470],{"class":357},[326,880,881],{"class":361}," book.sheet_names ",[326,883,884],{"class":357},"if",[326,886,887],{"class":361}," regex.search(normalise(name))]\n",[326,889,890,893,896],{"class":246,"line":456},[326,891,892],{"class":357},"    if",[326,894,895],{"class":357}," not",[326,897,898],{"class":361}," matches:\n",[326,900,901,904,907,910,913,916,920,923,926,929,932,934,937,939,941],{"class":246,"line":461},[326,902,903],{"class":357},"        raise",[326,905,906],{"class":482}," ValueError",[326,908,909],{"class":361},"(",[326,911,912],{"class":357},"f",[326,914,915],{"class":335},"\"no sheet matches ",[326,917,919],{"class":918},"sSjpA","{",[326,921,922],{"class":361},"pattern",[326,924,925],{"class":357},"!r",[326,927,928],{"class":918},"}",[326,930,931],{"class":335},"; found ",[326,933,919],{"class":918},[326,935,936],{"class":361},"book.sheet_names",[326,938,928],{"class":918},[326,940,808],{"class":335},[326,942,624],{"class":361},[326,944,945,947,950,953,956,959],{"class":246,"line":520},[326,946,892],{"class":357},[326,948,949],{"class":482}," len",[326,951,952],{"class":361},"(matches) ",[326,954,955],{"class":357},">",[326,957,958],{"class":482}," 1",[326,960,794],{"class":361},[326,962,963,965,967,969,971,973,975,977,979,981,984,986,989,991,993],{"class":246,"line":531},[326,964,903],{"class":357},[326,966,906],{"class":482},[326,968,909],{"class":361},[326,970,912],{"class":357},[326,972,808],{"class":335},[326,974,919],{"class":918},[326,976,922],{"class":361},[326,978,925],{"class":357},[326,980,928],{"class":918},[326,982,983],{"class":335}," is ambiguous: ",[326,985,919],{"class":918},[326,987,988],{"class":361},"matches",[326,990,928],{"class":918},[326,992,808],{"class":335},[326,994,624],{"class":361},[326,996,997,999,1002,1004],{"class":246,"line":551},[326,998,799],{"class":357},[326,1000,1001],{"class":361}," matches[",[326,1003,60],{"class":482},[326,1005,1006],{"class":361},"]\n",[326,1008,1009],{"class":246,"line":599},[326,1010,375],{"emptyLinePlaceholder":374},[326,1012,1013,1015,1018,1020,1022,1025,1028,1031,1033,1036],{"class":246,"line":605},[326,1014,701],{"class":482},[326,1016,1017],{"class":361},"(find_sheet(book, ",[326,1019,805],{"class":357},[326,1021,808],{"class":335},[326,1023,1024],{"class":482},"^",[326,1026,1027],{"class":335},"notes",[326,1029,1030],{"class":482},"$",[326,1032,808],{"class":335},[326,1034,1035],{"class":361},"))            ",[326,1037,1038],{"class":709},"# 'Notes'\n",[10,1040,1041,1042,1045,1046,1048],{},"Returning the ",[27,1043,1044],{},"real"," name, not the normalised one, matters: ",[14,1047,659],{}," needs the exact string stored in the file. Refusing to guess when two sheets match is just as important as refusing when none does.",[38,1050,47,1056,47,1059,47,1062,47,1066,47,1071,47,1078,47,1083,47,1087,47,1091,47,1094,47,1097,47,1105,47,1110,47,1117,47,1122,47,1126,47,1130,47,1133,47,1136,47,1140,47,1143,47,1146,47,1150,47,1154,47,1158,47,1162,47,1165,47,1168,47,1172,47,1175,47,1180,47,1184,47,1188,47,1191,47,1193,47,1196,47,1200,47,1203,47,1206,47,1209],{"viewBox":1051,"role":41,"ariaLabelledBy":1052,"xmlns":45,"style":1055},"4 0 752 274",[1053,1054],"names-rules-t","names-rules-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[49,1057,1058],{"id":1053},"Choosing a rule for finding the sheet",[53,1060,1061],{"id":1054},"If the name has a stable part, match it with a pattern. If the name contains a date and you want the newest, parse the dates and take the maximum. If names are meaningless but the layout is stable, match by the header columns. Position is a last resort and should count only visible sheets.",[57,1063],{"x":206,"y":60,"width":1064,"height":1065,"fill":63},"752","274",[57,1067],{"x":1068,"y":67,"width":1069,"height":1070,"rx":282,"fill":139,"stroke":140,"style":270},"207.8","344.3","75.5",[80,1072,1074],{"transform":1073},"translate(368.0,29.0) scale(1.000)",[84,1075],{"d":1076,"fill":87,"stroke":1077,"style":292},"M11 4a7 7 0 1 0 .01 0z M16 16l5 5","var(--brand-strong,#4338ca)",[91,1079,1082],{"x":1080,"y":1070,"style":1081},"380.0","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","What is stable about the sheet you want?",[246,1084],{"x1":1080,"y1":1085,"x2":1080,"y2":1086,"stroke":140,"style":270},"91.5","111.5",[246,1088],{"x1":1089,"y1":1086,"x2":1090,"y2":1086,"stroke":140,"style":270},"104.0","656.0",[246,1092],{"x1":1089,"y1":1086,"x2":1089,"y2":1093,"stroke":140,"style":270},"157.5",[272,1095],{"points":1096,"fill":275},"104.0,161.5 99.0,152.5 109.0,152.5",[57,1098],{"x":1099,"y":1100,"width":1101,"height":1102,"rx":1103,"fill":63,"stroke":72,"style":1104},"46.7","121.5","114.6","21.0","10","stroke-width:1.2px",[91,1106,1109],{"x":1089,"y":1107,"style":1108},"136.0","font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","part of the name",[57,1111],{"x":1112,"y":1113,"width":1114,"height":1115,"rx":282,"fill":1116,"stroke":251,"style":270},"20.0","163.5","168.0","94.0","#d9f4f1",[91,1118,1121],{"x":1089,"y":1119,"style":1120},"197.8","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","regex on the name",[91,1123,1125],{"x":1089,"y":1124,"style":305},"217.2","normalise case and",[91,1127,1129],{"x":1089,"y":1128,"style":305},"234.8","spaces",[246,1131],{"x1":1132,"y1":1086,"x2":1132,"y2":1093,"stroke":140,"style":270},"288.0",[272,1134],{"points":1135,"fill":275},"288.0,161.5 283.0,152.5 293.0,152.5",[57,1137],{"x":1138,"y":1100,"width":1139,"height":1102,"rx":1103,"fill":63,"stroke":72,"style":1104},"225.3","125.4",[91,1141,1142],{"x":1132,"y":1107,"style":1108},"a date in the name",[57,1144],{"x":1145,"y":1113,"width":1114,"height":1115,"rx":282,"fill":139,"stroke":140,"style":270},"204.0",[91,1147,1149],{"x":1132,"y":1148,"style":1081},"189.0","parse and take the",[91,1151,1153],{"x":1132,"y":1152,"style":1081},"206.5","latest",[91,1155,1157],{"x":1132,"y":1156,"style":305},"226.0","Sales Mar 2026 →",[91,1159,1161],{"x":1132,"y":1160,"style":305},"243.5","2026-03",[246,1163],{"x1":1164,"y1":1086,"x2":1164,"y2":1093,"stroke":140,"style":270},"472.0",[272,1166],{"points":1167,"fill":275},"472.0,161.5 467.0,152.5 477.0,152.5",[57,1169],{"x":1170,"y":1100,"width":1171,"height":1102,"rx":1103,"fill":63,"stroke":72,"style":1104},"429.0","85.9",[91,1173,1174],{"x":1164,"y":1107,"style":1108},"its columns",[57,1176],{"x":1177,"y":1113,"width":1114,"height":1115,"rx":282,"fill":1178,"stroke":1179,"style":270},"388.0","#fdefd8","var(--gold,#b4740a)",[91,1181,1183],{"x":1164,"y":1119,"style":1182},"font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","match the header",[91,1185,1187],{"x":1164,"y":1186,"style":1182},"215.2","row",[91,1189,1190],{"x":1164,"y":1128,"style":305},"needs region, amount",[246,1192],{"x1":1090,"y1":1086,"x2":1090,"y2":1093,"stroke":140,"style":270},[272,1194],{"points":1195,"fill":275},"656.0,161.5 651.0,152.5 661.0,152.5",[57,1197],{"x":1198,"y":1100,"width":1199,"height":1102,"rx":1103,"fill":63,"stroke":72,"style":1104},"624.4","63.3",[91,1201,1202],{"x":1090,"y":1107,"style":1108},"nothing",[57,1204],{"x":1205,"y":1113,"width":1114,"height":1115,"rx":282,"fill":283,"stroke":284,"style":270},"572.0",[91,1207,1208],{"x":1090,"y":1152,"style":297},"first visible sheet",[91,1210,1211],{"x":1090,"y":1156,"style":305},"last resort",[312,1213,1215],{"id":1214},"step-3-pick-the-latest-dated-sheet","Step 3 — Pick the latest dated sheet",[10,1217,1218],{},"When every month adds a tab, \"the sheet I want\" usually means \"the newest one\". Parse the date out of each name and take the maximum, ignoring tabs that do not carry a date:",[317,1220,1222],{"className":348,"code":1221,"language":350,"meta":322,"style":322},"from datetime import datetime\n\ndef sheet_date(name: str):\n    match = re.search(r\"([a-z]{3})\\w*\\s+(\\d{4})\", normalise(name))\n    if not match:\n        return None\n    try:\n        return datetime.strptime(f\"{match[1]} {match[2]}\", \"%b %Y\")\n    except ValueError:\n        return None\n\ndated = {name: sheet_date(name) for name in book.sheet_names}\nlatest = max((n for n, d in dated.items() if d), key=lambda n: dated[n])\nprint(repr(latest))                              # 'sales mar 2026 '\n\ncurrent = pd.read_excel(book, sheet_name=latest)\nprint(current[\"amount\"].sum())                   # 10870\n",[14,1223,1224,1236,1240,1254,1303,1312,1320,1327,1374,1383,1389,1393,1412,1449,1464,1468,1485],{"__ignoreMap":322},[326,1225,1226,1228,1231,1233],{"class":246,"line":328},[326,1227,358],{"class":357},[326,1229,1230],{"class":361}," datetime ",[326,1232,365],{"class":357},[326,1234,1235],{"class":361}," datetime\n",[326,1237,1238],{"class":246,"line":371},[326,1239,375],{"emptyLinePlaceholder":374},[326,1241,1242,1244,1247,1249,1251],{"class":246,"line":378},[326,1243,776],{"class":357},[326,1245,1246],{"class":779}," sheet_date",[326,1248,783],{"class":361},[326,1250,786],{"class":482},[326,1252,1253],{"class":361},"):\n",[326,1255,1256,1259,1261,1264,1266,1268,1270,1273,1276,1279,1282,1285,1287,1289,1292,1295,1298,1300],{"class":246,"line":390},[326,1257,1258],{"class":361},"    match ",[326,1260,384],{"class":357},[326,1262,1263],{"class":361}," re.search(",[326,1265,805],{"class":357},[326,1267,808],{"class":335},[326,1269,565],{"class":482},[326,1271,1272],{"class":918},"a-z",[326,1274,1275],{"class":482},"]",[326,1277,1278],{"class":357},"{3}",[326,1280,1281],{"class":482},")\\w",[326,1283,1284],{"class":357},"*",[326,1286,811],{"class":482},[326,1288,814],{"class":357},[326,1290,1291],{"class":482},"(\\d",[326,1293,1294],{"class":357},"{4}",[326,1296,1297],{"class":482},")",[326,1299,808],{"class":335},[326,1301,1302],{"class":361},", normalise(name))\n",[326,1304,1305,1307,1309],{"class":246,"line":401},[326,1306,892],{"class":357},[326,1308,895],{"class":357},[326,1310,1311],{"class":361}," match:\n",[326,1313,1314,1317],{"class":246,"line":412},[326,1315,1316],{"class":357},"        return",[326,1318,1319],{"class":482}," None\n",[326,1321,1322,1325],{"class":246,"line":423},[326,1323,1324],{"class":357},"    try",[326,1326,794],{"class":361},[326,1328,1329,1331,1334,1336,1338,1340,1343,1345,1347,1349,1352,1354,1356,1358,1360,1362,1364,1366,1369,1372],{"class":246,"line":441},[326,1330,1316],{"class":357},[326,1332,1333],{"class":361}," datetime.strptime(",[326,1335,912],{"class":357},[326,1337,808],{"class":335},[326,1339,919],{"class":918},[326,1341,1342],{"class":361},"match[",[326,1344,136],{"class":482},[326,1346,1275],{"class":361},[326,1348,928],{"class":918},[326,1350,1351],{"class":918}," {",[326,1353,1342],{"class":361},[326,1355,161],{"class":482},[326,1357,1275],{"class":361},[326,1359,928],{"class":918},[326,1361,808],{"class":335},[326,1363,432],{"class":361},[326,1365,808],{"class":335},[326,1367,1368],{"class":918},"%b",[326,1370,1371],{"class":335}," %Y\"",[326,1373,624],{"class":361},[326,1375,1376,1379,1381],{"class":246,"line":456},[326,1377,1378],{"class":357},"    except",[326,1380,906],{"class":482},[326,1382,794],{"class":361},[326,1384,1385,1387],{"class":246,"line":461},[326,1386,1316],{"class":357},[326,1388,1319],{"class":482},[326,1390,1391],{"class":246,"line":520},[326,1392,375],{"emptyLinePlaceholder":374},[326,1394,1395,1398,1400,1403,1405,1407,1409],{"class":246,"line":531},[326,1396,1397],{"class":361},"dated ",[326,1399,384],{"class":357},[326,1401,1402],{"class":361}," {name: sheet_date(name) ",[326,1404,464],{"class":357},[326,1406,876],{"class":361},[326,1408,470],{"class":357},[326,1410,1411],{"class":361}," book.sheet_names}\n",[326,1413,1414,1417,1419,1422,1425,1427,1430,1432,1435,1437,1440,1443,1446],{"class":246,"line":551},[326,1415,1416],{"class":361},"latest ",[326,1418,384],{"class":357},[326,1420,1421],{"class":482}," max",[326,1423,1424],{"class":361},"((n ",[326,1426,464],{"class":357},[326,1428,1429],{"class":361}," n, d ",[326,1431,470],{"class":357},[326,1433,1434],{"class":361}," dated.items() ",[326,1436,884],{"class":357},[326,1438,1439],{"class":361}," d), ",[326,1441,1442],{"class":720},"key",[326,1444,1445],{"class":357},"=lambda",[326,1447,1448],{"class":361}," n: dated[n])\n",[326,1450,1451,1453,1455,1458,1461],{"class":246,"line":599},[326,1452,701],{"class":482},[326,1454,909],{"class":361},[326,1456,1457],{"class":482},"repr",[326,1459,1460],{"class":361},"(latest))                              ",[326,1462,1463],{"class":709},"# 'sales mar 2026 '\n",[326,1465,1466],{"class":246,"line":605},[326,1467,375],{"emptyLinePlaceholder":374},[326,1469,1470,1473,1475,1478,1480,1482],{"class":246,"line":610},[326,1471,1472],{"class":361},"current ",[326,1474,384],{"class":357},[326,1476,1477],{"class":361}," pd.read_excel(book, ",[326,1479,721],{"class":720},[326,1481,384],{"class":357},[326,1483,1484],{"class":361},"latest)\n",[326,1486,1487,1489,1492,1494,1497],{"class":246,"line":627},[326,1488,701],{"class":482},[326,1490,1491],{"class":361},"(current[",[326,1493,546],{"class":335},[326,1495,1496],{"class":361},"].sum())                   ",[326,1498,1499],{"class":709},"# 10870\n",[10,1501,1502,1503,1506,1507,1509,1510,1513,1514,1517,1518,1521,1522,1525],{},"Parsing the date is safer than sorting names as text: alphabetically, ",[14,1504,1505],{},"Sales Apr 2026"," comes before ",[14,1508,240],{},", and ",[14,1511,1512],{},"Dec 2025"," sorts after ",[14,1515,1516],{},"Apr 2026",". The pattern accepts both ",[14,1519,1520],{},"Mar"," and ",[14,1523,1524],{},"March",", because it keeps only the first three letters of the month.",[312,1527,1529],{"id":1528},"step-4-skip-hidden-sheets-when-position-is-all-you-have","Step 4 — Skip hidden sheets when position is all you have",[10,1531,1532,1533,1536],{},"Some workbooks give you nothing stable in the name at all. If the rule really is \"the first sheet\", make it \"the first ",[27,1534,1535],{},"visible"," sheet\" by asking openpyxl for each sheet's state. Read-only mode keeps this fast:",[317,1538,1540],{"className":348,"code":1539,"language":350,"meta":322,"style":322},"from openpyxl import load_workbook\n\ndef visible_sheets(path: str) -> list[str]:\n    wb = load_workbook(path, read_only=True)\n    try:\n        return [ws.title for ws in wb.worksheets if ws.sheet_state == \"visible\"]\n    finally:\n        wb.close()\n\nprint(visible_sheets(\"sales_export.xlsx\"))\n# ['Sales Feb 2026', 'sales mar 2026 ', 'Notes']\n",[14,1541,1542,1553,1557,1577,1597,1603,1633,1640,1645,1649,1661],{"__ignoreMap":322},[326,1543,1544,1546,1548,1550],{"class":246,"line":328},[326,1545,358],{"class":357},[326,1547,362],{"class":361},[326,1549,365],{"class":357},[326,1551,1552],{"class":361}," load_workbook\n",[326,1554,1555],{"class":246,"line":371},[326,1556,375],{"emptyLinePlaceholder":374},[326,1558,1559,1561,1564,1567,1569,1572,1574],{"class":246,"line":378},[326,1560,776],{"class":357},[326,1562,1563],{"class":779}," visible_sheets",[326,1565,1566],{"class":361},"(path: ",[326,1568,786],{"class":482},[326,1570,1571],{"class":361},") -> list[",[326,1573,786],{"class":482},[326,1575,1576],{"class":361},"]:\n",[326,1578,1579,1582,1584,1587,1590,1592,1595],{"class":246,"line":390},[326,1580,1581],{"class":361},"    wb ",[326,1583,384],{"class":357},[326,1585,1586],{"class":361}," load_workbook(path, ",[326,1588,1589],{"class":720},"read_only",[326,1591,384],{"class":357},[326,1593,1594],{"class":482},"True",[326,1596,624],{"class":361},[326,1598,1599,1601],{"class":246,"line":401},[326,1600,1324],{"class":357},[326,1602,794],{"class":361},[326,1604,1605,1607,1610,1612,1615,1617,1620,1622,1625,1628,1631],{"class":246,"line":412},[326,1606,1316],{"class":357},[326,1608,1609],{"class":361}," [ws.title ",[326,1611,464],{"class":357},[326,1613,1614],{"class":361}," ws ",[326,1616,470],{"class":357},[326,1618,1619],{"class":361}," wb.worksheets ",[326,1621,884],{"class":357},[326,1623,1624],{"class":361}," ws.sheet_state ",[326,1626,1627],{"class":357},"==",[326,1629,1630],{"class":335}," \"visible\"",[326,1632,1006],{"class":361},[326,1634,1635,1638],{"class":246,"line":423},[326,1636,1637],{"class":357},"    finally",[326,1639,794],{"class":361},[326,1641,1642],{"class":246,"line":441},[326,1643,1644],{"class":361},"        wb.close()\n",[326,1646,1647],{"class":246,"line":456},[326,1648,375],{"emptyLinePlaceholder":374},[326,1650,1651,1653,1656,1658],{"class":246,"line":461},[326,1652,701],{"class":482},[326,1654,1655],{"class":361},"(visible_sheets(",[326,1657,644],{"class":335},[326,1659,1660],{"class":361},"))\n",[326,1662,1663],{"class":246,"line":520},[326,1664,1665],{"class":709},"# ['Sales Feb 2026', 'sales mar 2026 ', 'Notes']\n",[10,1667,1668,1669,1673],{},"Hidden tabs are usually lookup tables, staging areas or old versions somebody did not want to delete — rarely the data you are after. The states are explained in ",[32,1670,1672],{"href":1671},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Fhide-sheets-rows-and-columns-with-openpyxl\u002F","Hide Sheets, Rows and Columns with openpyxl",".",[312,1675,1677],{"id":1676},"step-5-find-the-sheet-by-its-columns","Step 5 — Find the sheet by its columns",[10,1679,1680,1681,1684],{},"The most robust rule ignores names entirely: the sheet you want is the one whose header contains the columns you need. Read just the header row of each candidate with ",[14,1682,1683],{},"nrows=0"," and test it:",[317,1686,1688],{"className":348,"code":1687,"language":350,"meta":322,"style":322},"def sheet_with_columns(book: pd.ExcelFile, required: set[str]) -> str:\n    hits = []\n    for name in book.sheet_names:\n        header = pd.read_excel(book, sheet_name=name, nrows=0).columns\n        found = {str(c).strip().casefold() for c in header}\n        if required \u003C= found:\n            hits.append(name)\n    if len(hits) != 1:\n        raise ValueError(f\"expected one sheet with {sorted(required)}, got {hits}\")\n    return hits[0]\n\ntry:\n    sheet_with_columns(book, {\"region\", \"rep\", \"amount\"})\nexcept ValueError as exc:\n    print(exc)   # both monthly sheets qualify, so the rule alone is ambiguous\n",[14,1689,1690,1709,1719,1730,1756,1780,1794,1799,1815,1852,1863,1867,1874,1892,1905],{"__ignoreMap":322},[326,1691,1692,1694,1697,1700,1702,1705,1707],{"class":246,"line":328},[326,1693,776],{"class":357},[326,1695,1696],{"class":779}," sheet_with_columns",[326,1698,1699],{"class":361},"(book: pd.ExcelFile, required: set[",[326,1701,786],{"class":482},[326,1703,1704],{"class":361},"]) -> ",[326,1706,786],{"class":482},[326,1708,794],{"class":361},[326,1710,1711,1714,1716],{"class":246,"line":371},[326,1712,1713],{"class":361},"    hits ",[326,1715,384],{"class":357},[326,1717,1718],{"class":361}," []\n",[326,1720,1721,1723,1725,1727],{"class":246,"line":378},[326,1722,554],{"class":357},[326,1724,876],{"class":361},[326,1726,470],{"class":357},[326,1728,1729],{"class":361}," book.sheet_names:\n",[326,1731,1732,1735,1737,1739,1741,1743,1746,1749,1751,1753],{"class":246,"line":390},[326,1733,1734],{"class":361},"        header ",[326,1736,384],{"class":357},[326,1738,1477],{"class":361},[326,1740,721],{"class":720},[326,1742,384],{"class":357},[326,1744,1745],{"class":361},"name, ",[326,1747,1748],{"class":720},"nrows",[326,1750,384],{"class":357},[326,1752,60],{"class":482},[326,1754,1755],{"class":361},").columns\n",[326,1757,1758,1761,1763,1765,1767,1770,1772,1775,1777],{"class":246,"line":401},[326,1759,1760],{"class":361},"        found ",[326,1762,384],{"class":357},[326,1764,1351],{"class":361},[326,1766,786],{"class":482},[326,1768,1769],{"class":361},"(c).strip().casefold() ",[326,1771,464],{"class":357},[326,1773,1774],{"class":361}," c ",[326,1776,470],{"class":357},[326,1778,1779],{"class":361}," header}\n",[326,1781,1782,1785,1788,1791],{"class":246,"line":412},[326,1783,1784],{"class":357},"        if",[326,1786,1787],{"class":361}," required ",[326,1789,1790],{"class":357},"\u003C=",[326,1792,1793],{"class":361}," found:\n",[326,1795,1796],{"class":246,"line":423},[326,1797,1798],{"class":361},"            hits.append(name)\n",[326,1800,1801,1803,1805,1808,1811,1813],{"class":246,"line":441},[326,1802,892],{"class":357},[326,1804,949],{"class":482},[326,1806,1807],{"class":361},"(hits) ",[326,1809,1810],{"class":357},"!=",[326,1812,958],{"class":482},[326,1814,794],{"class":361},[326,1816,1817,1819,1821,1823,1825,1828,1830,1833,1836,1838,1841,1843,1846,1848,1850],{"class":246,"line":456},[326,1818,903],{"class":357},[326,1820,906],{"class":482},[326,1822,909],{"class":361},[326,1824,912],{"class":357},[326,1826,1827],{"class":335},"\"expected one sheet with ",[326,1829,919],{"class":918},[326,1831,1832],{"class":482},"sorted",[326,1834,1835],{"class":361},"(required)",[326,1837,928],{"class":918},[326,1839,1840],{"class":335},", got ",[326,1842,919],{"class":918},[326,1844,1845],{"class":361},"hits",[326,1847,928],{"class":918},[326,1849,808],{"class":335},[326,1851,624],{"class":361},[326,1853,1854,1856,1859,1861],{"class":246,"line":461},[326,1855,799],{"class":357},[326,1857,1858],{"class":361}," hits[",[326,1860,60],{"class":482},[326,1862,1006],{"class":361},[326,1864,1865],{"class":246,"line":520},[326,1866,375],{"emptyLinePlaceholder":374},[326,1868,1869,1872],{"class":246,"line":531},[326,1870,1871],{"class":357},"try",[326,1873,794],{"class":361},[326,1875,1876,1879,1881,1883,1885,1887,1889],{"class":246,"line":551},[326,1877,1878],{"class":361},"    sheet_with_columns(book, {",[326,1880,429],{"class":335},[326,1882,432],{"class":361},[326,1884,541],{"class":335},[326,1886,432],{"class":361},[326,1888,546],{"class":335},[326,1890,1891],{"class":361},"})\n",[326,1893,1894,1897,1899,1902],{"class":246,"line":599},[326,1895,1896],{"class":357},"except",[326,1898,906],{"class":482},[326,1900,1901],{"class":357}," as",[326,1903,1904],{"class":361}," exc:\n",[326,1906,1907,1910,1913],{"class":246,"line":605},[326,1908,1909],{"class":482},"    print",[326,1911,1912],{"class":361},"(exc)   ",[326,1914,1915],{"class":709},"# both monthly sheets qualify, so the rule alone is ambiguous\n",[10,1917,1918,1919,1923],{},"Here both monthly sheets share the same columns, so the content rule correctly refuses to choose. Rules combine well: filter candidates by columns, then pick the latest date among them. When the header is not on the first row, the header search in ",[32,1920,1922],{"href":1921},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Ffind-the-header-row-in-an-excel-sheet-with-a-shifting-layout\u002F","Find the Header Row in an Excel Sheet with a Shifting Layout"," slots into the same loop.",[312,1925,1927],{"id":1926},"step-6-combine-the-rules-into-one-resolver","Step 6 — Combine the rules into one resolver",[10,1929,1930],{},"In a real job you want one function that applies the rules in order of trust, logs which rule decided, and raises when the file is genuinely ambiguous. Filtering by content first and breaking ties by date handles the monthly-export case without any name pattern at all:",[317,1932,1934],{"className":348,"code":1933,"language":350,"meta":322,"style":322},"import logging\n\nlog = logging.getLogger(\"sheets\")\n\ndef resolve_sheet(path: str, required: set[str]) -> str:\n    book = pd.ExcelFile(path)\n    visible = set(visible_sheets(path))\n    candidates = []\n    for name in book.sheet_names:\n        if name not in visible:\n            continue\n        header = pd.read_excel(book, sheet_name=name, nrows=0).columns\n        if required \u003C= {str(c).strip().casefold() for c in header}:\n            candidates.append(name)\n    if not candidates:\n        raise ValueError(f\"no visible sheet has {sorted(required)}; found {book.sheet_names}\")\n    if len(candidates) == 1:\n        log.info(\"sheet %r chosen by columns\", candidates[0])\n        return candidates[0]\n    dated = [(sheet_date(n), n) for n in candidates if sheet_date(n)]\n    if len(dated) != len(candidates):\n        raise ValueError(f\"several sheets match and not all carry a date: {candidates}\")\n    chosen = max(dated)[1]\n    log.info(\"sheet %r chosen as the latest of %s\", chosen, candidates)\n    return chosen\n\nprint(repr(resolve_sheet(\"sales_export.xlsx\", {\"region\", \"rep\", \"amount\"})))\n# 'sales mar 2026 '\n",[14,1935,1936,1943,1947,1962,1966,1988,1998,2011,2020,2030,2045,2050,2072,2095,2100,2109,2142,2157,2178,2190,2216,2233,2258,2275,2296,2304,2309,2339],{"__ignoreMap":322},[326,1937,1938,1940],{"class":246,"line":328},[326,1939,365],{"class":357},[326,1941,1942],{"class":361}," logging\n",[326,1944,1945],{"class":246,"line":371},[326,1946,375],{"emptyLinePlaceholder":374},[326,1948,1949,1952,1954,1957,1960],{"class":246,"line":378},[326,1950,1951],{"class":361},"log ",[326,1953,384],{"class":357},[326,1955,1956],{"class":361}," logging.getLogger(",[326,1958,1959],{"class":335},"\"sheets\"",[326,1961,624],{"class":361},[326,1963,1964],{"class":246,"line":390},[326,1965,375],{"emptyLinePlaceholder":374},[326,1967,1968,1970,1973,1975,1977,1980,1982,1984,1986],{"class":246,"line":401},[326,1969,776],{"class":357},[326,1971,1972],{"class":779}," resolve_sheet",[326,1974,1566],{"class":361},[326,1976,786],{"class":482},[326,1978,1979],{"class":361},", required: set[",[326,1981,786],{"class":482},[326,1983,1704],{"class":361},[326,1985,786],{"class":482},[326,1987,794],{"class":361},[326,1989,1990,1993,1995],{"class":246,"line":412},[326,1991,1992],{"class":361},"    book ",[326,1994,384],{"class":357},[326,1996,1997],{"class":361}," pd.ExcelFile(path)\n",[326,1999,2000,2003,2005,2008],{"class":246,"line":423},[326,2001,2002],{"class":361},"    visible ",[326,2004,384],{"class":357},[326,2006,2007],{"class":482}," set",[326,2009,2010],{"class":361},"(visible_sheets(path))\n",[326,2012,2013,2016,2018],{"class":246,"line":441},[326,2014,2015],{"class":361},"    candidates ",[326,2017,384],{"class":357},[326,2019,1718],{"class":361},[326,2021,2022,2024,2026,2028],{"class":246,"line":456},[326,2023,554],{"class":357},[326,2025,876],{"class":361},[326,2027,470],{"class":357},[326,2029,1729],{"class":361},[326,2031,2032,2034,2036,2039,2042],{"class":246,"line":461},[326,2033,1784],{"class":357},[326,2035,876],{"class":361},[326,2037,2038],{"class":357},"not",[326,2040,2041],{"class":357}," in",[326,2043,2044],{"class":361}," visible:\n",[326,2046,2047],{"class":246,"line":520},[326,2048,2049],{"class":357},"            continue\n",[326,2051,2052,2054,2056,2058,2060,2062,2064,2066,2068,2070],{"class":246,"line":531},[326,2053,1734],{"class":361},[326,2055,384],{"class":357},[326,2057,1477],{"class":361},[326,2059,721],{"class":720},[326,2061,384],{"class":357},[326,2063,1745],{"class":361},[326,2065,1748],{"class":720},[326,2067,384],{"class":357},[326,2069,60],{"class":482},[326,2071,1755],{"class":361},[326,2073,2074,2076,2078,2080,2082,2084,2086,2088,2090,2092],{"class":246,"line":551},[326,2075,1784],{"class":357},[326,2077,1787],{"class":361},[326,2079,1790],{"class":357},[326,2081,1351],{"class":361},[326,2083,786],{"class":482},[326,2085,1769],{"class":361},[326,2087,464],{"class":357},[326,2089,1774],{"class":361},[326,2091,470],{"class":357},[326,2093,2094],{"class":361}," header}:\n",[326,2096,2097],{"class":246,"line":599},[326,2098,2099],{"class":361},"            candidates.append(name)\n",[326,2101,2102,2104,2106],{"class":246,"line":605},[326,2103,892],{"class":357},[326,2105,895],{"class":357},[326,2107,2108],{"class":361}," candidates:\n",[326,2110,2111,2113,2115,2117,2119,2122,2124,2126,2128,2130,2132,2134,2136,2138,2140],{"class":246,"line":610},[326,2112,903],{"class":357},[326,2114,906],{"class":482},[326,2116,909],{"class":361},[326,2118,912],{"class":357},[326,2120,2121],{"class":335},"\"no visible sheet has ",[326,2123,919],{"class":918},[326,2125,1832],{"class":482},[326,2127,1835],{"class":361},[326,2129,928],{"class":918},[326,2131,931],{"class":335},[326,2133,919],{"class":918},[326,2135,936],{"class":361},[326,2137,928],{"class":918},[326,2139,808],{"class":335},[326,2141,624],{"class":361},[326,2143,2144,2146,2148,2151,2153,2155],{"class":246,"line":627},[326,2145,892],{"class":357},[326,2147,949],{"class":482},[326,2149,2150],{"class":361},"(candidates) ",[326,2152,1627],{"class":357},[326,2154,958],{"class":482},[326,2156,794],{"class":361},[326,2158,2159,2162,2165,2168,2171,2174,2176],{"class":246,"line":638},[326,2160,2161],{"class":361},"        log.info(",[326,2163,2164],{"class":335},"\"sheet ",[326,2166,2167],{"class":918},"%r",[326,2169,2170],{"class":335}," chosen by columns\"",[326,2172,2173],{"class":361},", candidates[",[326,2175,60],{"class":482},[326,2177,438],{"class":361},[326,2179,2181,2183,2186,2188],{"class":246,"line":2180},19,[326,2182,1316],{"class":357},[326,2184,2185],{"class":361}," candidates[",[326,2187,60],{"class":482},[326,2189,1006],{"class":361},[326,2191,2193,2196,2198,2201,2203,2206,2208,2211,2213],{"class":246,"line":2192},20,[326,2194,2195],{"class":361},"    dated ",[326,2197,384],{"class":357},[326,2199,2200],{"class":361}," [(sheet_date(n), n) ",[326,2202,464],{"class":357},[326,2204,2205],{"class":361}," n ",[326,2207,470],{"class":357},[326,2209,2210],{"class":361}," candidates ",[326,2212,884],{"class":357},[326,2214,2215],{"class":361}," sheet_date(n)]\n",[326,2217,2219,2221,2223,2226,2228,2230],{"class":246,"line":2218},21,[326,2220,892],{"class":357},[326,2222,949],{"class":482},[326,2224,2225],{"class":361},"(dated) ",[326,2227,1810],{"class":357},[326,2229,949],{"class":482},[326,2231,2232],{"class":361},"(candidates):\n",[326,2234,2236,2238,2240,2242,2244,2247,2249,2252,2254,2256],{"class":246,"line":2235},22,[326,2237,903],{"class":357},[326,2239,906],{"class":482},[326,2241,909],{"class":361},[326,2243,912],{"class":357},[326,2245,2246],{"class":335},"\"several sheets match and not all carry a date: ",[326,2248,919],{"class":918},[326,2250,2251],{"class":361},"candidates",[326,2253,928],{"class":918},[326,2255,808],{"class":335},[326,2257,624],{"class":361},[326,2259,2261,2264,2266,2268,2271,2273],{"class":246,"line":2260},23,[326,2262,2263],{"class":361},"    chosen ",[326,2265,384],{"class":357},[326,2267,1421],{"class":482},[326,2269,2270],{"class":361},"(dated)[",[326,2272,136],{"class":482},[326,2274,1006],{"class":361},[326,2276,2278,2281,2283,2285,2288,2291,2293],{"class":246,"line":2277},24,[326,2279,2280],{"class":361},"    log.info(",[326,2282,2164],{"class":335},[326,2284,2167],{"class":918},[326,2286,2287],{"class":335}," chosen as the latest of ",[326,2289,2290],{"class":918},"%s",[326,2292,808],{"class":335},[326,2294,2295],{"class":361},", chosen, candidates)\n",[326,2297,2299,2301],{"class":246,"line":2298},25,[326,2300,799],{"class":357},[326,2302,2303],{"class":361}," chosen\n",[326,2305,2307],{"class":246,"line":2306},26,[326,2308,375],{"emptyLinePlaceholder":374},[326,2310,2312,2314,2316,2318,2321,2323,2326,2328,2330,2332,2334,2336],{"class":246,"line":2311},27,[326,2313,701],{"class":482},[326,2315,909],{"class":361},[326,2317,1457],{"class":482},[326,2319,2320],{"class":361},"(resolve_sheet(",[326,2322,644],{"class":335},[326,2324,2325],{"class":361},", {",[326,2327,429],{"class":335},[326,2329,432],{"class":361},[326,2331,541],{"class":335},[326,2333,432],{"class":361},[326,2335,546],{"class":335},[326,2337,2338],{"class":361},"})))\n",[326,2340,2342],{"class":246,"line":2341},28,[326,2343,1463],{"class":709},[38,2345,47,2350,47,2353,47,2356,47,2359,47,2363,47,2369,47,2375,47,2378,47,2383,47,2387,47,2392,47,2395,47,2398,47,2404,47,2408,47,2411,47,2415,47,2417,47,2421,47,2424,47,2427,47,2433,47,2438,47,2442,47,2445,47,2448,47,2450,47,2454,47,2457,47,2460,47,2466,47,2470,47,2473,47,2477,47,2479],{"viewBox":2346,"role":41,"ariaLabelledBy":2347,"xmlns":45,"style":1055},"4 0 752 176",[2348,2349],"names-resolve-t","names-resolve-d",[49,2351,2352],{"id":2348},"The order in which the resolver narrows the sheets down",[53,2354,2355],{"id":2349},"Start from every sheet name, keep only visible sheets, keep only sheets whose header has the required columns, and if more than one remains pick the one with the latest date in its name; otherwise raise with the list of candidates.",[57,2357],{"x":206,"y":60,"width":1064,"height":2358,"fill":63},"176",[57,2360],{"x":1112,"y":67,"width":2361,"height":2362,"rx":282,"fill":71,"stroke":72,"style":270},"153.0","118.5",[80,2364,2366],{"transform":2365},"translate(84.5,41.8) scale(1.000)",[84,2367],{"d":86,"fill":87,"stroke":2368,"style":292},"var(--text,#172033)",[91,2370,2374],{"x":2371,"y":2372,"style":2373},"96.5","88.2","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","All sheet names",[91,2376,655],{"x":2371,"y":2377,"style":305},"107.8",[2379,2380],"circle",{"cx":94,"cy":100,"r":2381,"fill":2382,"stroke":87,"style":78},"11.0","#5b6780",[91,2384,136],{"x":94,"y":2385,"style":2386},"36.5","font-size:12px;font-weight:700;fill:#ffffff;text-anchor:middle",[246,2388],{"x1":2389,"y1":2390,"x2":2391,"y2":2390,"stroke":140,"style":270},"178.0","75.2","200.0",[272,2393],{"points":2394,"fill":275},"204.0,75.2 195.0,80.2 195.0,70.2",[57,2396],{"x":2397,"y":67,"width":2361,"height":2362,"rx":282,"fill":139,"stroke":140,"style":270},"209.0",[80,2399,2401],{"transform":2400},"translate(273.5,41.8) scale(1.000)",[84,2402],{"d":2403,"fill":87,"stroke":1077,"style":292},"M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12z M12 9a3 3 0 1 0 .01 0z",[91,2405,2407],{"x":2406,"y":2372,"style":1081},"285.5","Visible only",[91,2409,2410],{"x":2406,"y":2377,"style":305},"sheet_state check",[2379,2412],{"cx":2413,"cy":100,"r":2381,"fill":2414,"stroke":87,"style":78},"225.0","#4338ca",[91,2416,161],{"x":2413,"y":2385,"style":2386},[246,2418],{"x1":2419,"y1":2390,"x2":2420,"y2":2390,"stroke":140,"style":270},"367.0","389.0",[272,2422],{"points":2423,"fill":275},"393.0,75.2 384.0,80.2 384.0,70.2",[57,2425],{"x":2426,"y":67,"width":2361,"height":2362,"rx":282,"fill":139,"stroke":140,"style":270},"398.0",[80,2428,2430],{"transform":2429},"translate(462.5,33.0) scale(1.000)",[84,2431],{"d":2432,"fill":87,"stroke":1077,"style":292},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14",[91,2434,2437],{"x":2435,"y":2436,"style":1081},"474.5","79.5","Required columns",[91,2439,2441],{"x":2435,"y":2440,"style":305},"99.0","header read with",[91,2443,1683],{"x":2435,"y":2444,"style":305},"116.5",[2379,2446],{"cx":2447,"cy":100,"r":2381,"fill":2414,"stroke":87,"style":78},"414.0",[91,2449,184],{"x":2447,"y":2385,"style":2386},[246,2451],{"x1":2452,"y1":2390,"x2":2453,"y2":2390,"stroke":140,"style":270},"556.0","578.0",[272,2455],{"points":2456,"fill":275},"582.0,75.2 573.0,80.2 573.0,70.2",[57,2458],{"x":2459,"y":67,"width":2361,"height":2362,"rx":282,"fill":1116,"stroke":251,"style":270},"587.0",[80,2461,2463],{"transform":2462},"translate(651.5,41.8) scale(1.000)",[84,2464],{"d":2465,"fill":87,"stroke":88,"style":292},"M4 5h16v15H4z M4 10h16 M8 3v4 M16 3v4",[91,2467,2469],{"x":2468,"y":2372,"style":1120},"663.5","Latest date",[91,2471,2472],{"x":2468,"y":2377,"style":305},"tie-break or raise",[2379,2474],{"cx":2475,"cy":100,"r":2381,"fill":2476,"stroke":87,"style":78},"603.0","#0b6157",[91,2478,206],{"x":2475,"y":2385,"style":2386},[91,2480,2483],{"x":1080,"y":2481,"style":2482},"156.5","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","every step can only remove candidates, so the rule never picks a sheet it has not justified",[10,2485,2486],{},"The log line is worth keeping in production. When a figure looks wrong a month later, \"sheet 'Sales Mar 2026 (2)' chosen as the latest\" explains it immediately — someone duplicated a tab and edited the copy.",[312,2488,2490],{"id":2489},"common-pitfalls","Common pitfalls",[2492,2493,2494,2506,2514,2527],"ul",{},[2495,2496,2497,2501,2502,2505],"li",{},[2498,2499,2500],"strong",{},"Falling back silently."," ",[14,2503,2504],{},"except KeyError: sheet_name = 0"," guarantees a wrong report one day. Every failure path should raise with the list of sheets actually present.",[2495,2507,2508,2513],{},[2498,2509,2510,2511,1673],{},"Matching the normalised name but passing it to ",[14,2512,659],{}," Normalisation is for comparing; always pass the original string back.",[2495,2515,2516,2519,2520,2523,2524,2526],{},[2498,2517,2518],{},"Opening the file repeatedly."," Each ",[14,2521,2522],{},"read_excel(\"file.xlsx\", …)"," call re-opens and re-parses the workbook. Build one ",[14,2525,655],{}," and pass it to every read.",[2495,2528,2529,2501,2532,2535,2536,2539,2540,2543,2544,2547,2548,1673],{},[2498,2530,2531],{},"Forgetting very hidden sheets.",[14,2533,2534],{},"sheet_state"," can be ",[14,2537,2538],{},"\"veryHidden\""," as well as ",[14,2541,2542],{},"\"hidden\"","; test for ",[14,2545,2546],{},"== \"visible\""," rather than ",[14,2549,2550],{},"!= \"hidden\"",[312,2552,2554],{"id":2553},"performance-and-scale-notes","Performance and scale notes",[10,2556,2557,2558,2560,2561,1673],{},"Listing sheet names and reading a header with ",[14,2559,1683],{}," are both cheap, so trying every sheet costs little even on a wide workbook. The expensive part is reading data, which happens once, for the sheet you chose. When a job processes a folder of exports, resolve the sheet name per file inside the loop — never assume one file's layout holds for the next — and log the name you picked, so a surprising result can be traced to the tab it came from. Folder-level processing is covered in ",[32,2562,2564],{"href":2563},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fcombine-multiple-excel-files-into-one-python\u002F","Combine Multiple Excel Files into One in Python",[312,2566,2568],{"id":2567},"conclusion","Conclusion",[10,2570,2571,2572,2574,2575,2578],{},"Treat the sheet name as data, not as a constant. List the tabs with ",[14,2573,655],{},", compare them normalised, and choose by the most stable thing you know: a name pattern, the newest date, visibility, or the header columns — combining rules when one is not enough. Return the exact stored name, reuse the ",[14,2576,2577],{},"ExcelFile"," object, and raise with the list of sheets whenever the rule finds nothing or more than one.",[312,2580,2582],{"id":2581},"frequently-asked-questions","Frequently asked questions",[10,2584,2585,2588],{},[2498,2586,2587],{},"Are Excel sheet names case-sensitive in pandas?","\nYes. read_excel matches sheet_name exactly, so \"sales\" will not find \"Sales\". Excel itself treats names case-insensitively, which is why people do not notice the difference until a script fails. Normalise both sides before comparing.",[10,2590,2591,2594],{},[2498,2592,2593],{},"Does sheet_name=0 skip hidden sheets?","\nNo. Positions count every sheet in workbook order, hidden and very hidden ones included, so sheet_name=0 can return a hidden lookup sheet. Check visibility with openpyxl if position matters.",[10,2596,2597,2600],{},[2498,2598,2599],{},"How do I list sheet names without reading the data?","\nOpen the file with pd.ExcelFile and read its sheet_names attribute. It reads the workbook's structure, not the cell data, so it is fast even for large files.",[10,2602,2603,2606],{},[2498,2604,2605],{},"What should happen when no sheet matches?","\nFail with an error that lists the sheet names that were found. A job that falls back to the first sheet silently will one day report on the wrong data.",[312,2608,2610],{"id":2609},"related","Related",[2492,2612,2613,2619,2626,2633],{},[2495,2614,2615,2616,2618],{},"Up one level: ",[32,2617,35],{"href":34}," — all the read_excel options.",[2495,2620,2621,2625],{},[32,2622,2624],{"href":2623},"\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"," — when you want every tab.",[2495,2627,2628,2632],{},[32,2629,2631],{"href":2630},"\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"," — the error hard-coded names produce.",[2495,2634,2635,2639],{},[32,2636,2638],{"href":2637},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fread-an-excel-table-by-name-into-pandas\u002F","Read an Excel Table by Name into pandas"," — another way to address data without trusting the sheet.",[2641,2642,2643],"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-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":322,"searchDepth":371,"depth":371,"links":2645},[2646,2647,2648,2649,2650,2651,2652,2653,2654,2655,2656,2657],{"id":314,"depth":371,"text":315},{"id":649,"depth":371,"text":650},{"id":751,"depth":371,"text":752},{"id":1214,"depth":371,"text":1215},{"id":1528,"depth":371,"text":1529},{"id":1676,"depth":371,"text":1677},{"id":1926,"depth":371,"text":1927},{"id":2489,"depth":371,"text":2490},{"id":2553,"depth":371,"text":2554},{"id":2567,"depth":371,"text":2568},{"id":2581,"depth":371,"text":2582},{"id":2609,"depth":371,"text":2610},"2026-09-23","Find the right sheet when its name changes every month, differs in case or spacing, moves position or hides behind a hidden tab — by pattern, by date in the name, by visibility or by the columns it contains.","md",[2662,2664,2666,2668],{"q":2587,"a":2663},"Yes. read_excel matches sheet_name exactly, so \"sales\" will not find \"Sales\". Excel itself treats names case-insensitively, which is why people do not notice the difference until a script fails. Normalise both sides before comparing.",{"q":2593,"a":2665},"No. Positions count every sheet in workbook order, hidden and very hidden ones included, so sheet_name=0 can return a hidden lookup sheet. Check visibility with openpyxl if position matters.",{"q":2599,"a":2667},"Open the file with pd.ExcelFile and read its sheet_names attribute. It reads the workbook's structure, not the cell data, so it is fast even for large files.",{"q":2605,"a":2669},"Fail with an error that lists the sheet names that were found. A job that falls back to the first sheet silently will one day report on the wrong data.","read excel sheets whose names change with pandas",null,{"breadcrumb":2673},[2674,2677,2680],{"name":2675,"item":2676},"Home","\u002F",{"name":2678,"item":2679},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":2681,"item":34},"Reading Excel Files with Pandas","\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-excel-sheets-whose-names-change-with-pandas",{"title":2684,"description":2685},"Read an Excel Sheet When the Sheet Name Changes (pandas)","Stop hard-coding sheet_name. Match sheets by regex, pick the latest dated tab, skip hidden sheets, and find a sheet by its header columns with pandas and openpyxl.","read-excel-sheets-whose-names-change-with-pandas","getting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-excel-sheets-whose-names-change-with-pandas\u002Findex",[2689,2690,2691],"reading-excel","sheets-and-files","pandas","how-to","6wCWOsbGVhHjh4NA_YNPqAHdgcuPCEQcppIuRBw1rqY",[2695,2699],{"title":2696,"path":2697,"stem":2698,"children":-1},"Read Excel Files Inside a ZIP Archive with Python","\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-excel-files-inside-a-zip-archive-with-python","getting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-excel-files-inside-a-zip-archive-with-python\u002Findex",{"title":2700,"path":2701,"stem":2702,"children":-1},"Read Only Some Rows of an Excel File with pandas","\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-only-some-rows-of-an-excel-file-with-pandas","getting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-only-some-rows-of-an-excel-file-with-pandas\u002Findex",[2704,2706],{"path":2705,"title":2678},"\u002Fgetting-started-with-python-excel-automation",{"path":2707,"title":2681},"\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas",[],[2710,2713,2716,2719,2722,2725],{"path":2711,"title":2712,"kind":2692},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-an-excel-file-from-a-url-or-bytes-in-python","Read an Excel File from a URL or Bytes in Python",{"path":2714,"title":2715,"kind":2692},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Ffind-the-last-row-with-data-in-openpyxl","Find the Last Row with Data in openpyxl",{"path":2717,"title":2718,"kind":2692},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fiterate-over-rows-and-columns-with-openpyxl","Iterate over Rows and Columns with openpyxl",{"path":2720,"title":2721,"kind":2692},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fread-cell-value-from-excel-with-openpyxl","Read a Cell Value From Excel With openpyxl",{"path":2723,"title":2724,"kind":2692},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstandardise-excel-column-names-with-pandas","Standardise Excel Column Names with pandas",{"path":2726,"title":2727,"kind":2728},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets","Extracting Data from Messy Excel Sheets","guide",1790972065241]