[{"data":1,"prerenderedAt":1854},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-excel-files-inside-a-zip-archive-with-python":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-excel-files-inside-a-zip-archive-with-python":1820,"ancestors:\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-excel-files-inside-a-zip-archive-with-python":1829,"children:\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-excel-files-inside-a-zip-archive-with-python":1834,"related:\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-excel-files-inside-a-zip-archive-with-python":1835},{"id":4,"title":5,"body":6,"dateModified":1785,"datePublished":1785,"description":1786,"extension":1787,"faq":1788,"keyword":1797,"kind":1798,"meta":1799,"navigation":112,"path":1808,"seo":1809,"slug":1812,"stem":1813,"topics":1814,"type":1818,"__hash__":1819},"docs\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-excel-files-inside-a-zip-archive-with-python\u002Findex.md","Read Excel Files Inside a ZIP Archive with Python",{"type":7,"value":8,"toc":1770},"minimark",[9,32,37,65,86,90,196,206,210,283,300,447,451,477,751,773,894,898,909,1021,1037,1041,1050,1241,1260,1264,1277,1327,1352,1433,1437,1444,1554,1569,1573,1652,1656,1672,1676,1705,1709,1715,1721,1727,1733,1737,1766],[10,11,12,13,17,18,21,22,25,26,31],"p",{},"Monthly exports often arrive as a ZIP: a download from a finance system, an email attachment that bundles twelve branch workbooks, a vendor's nightly drop on an SFTP server. The usual reflex is to unzip into a temporary folder and read the files from disk, then remember to clean up. Python's standard ",[14,15,16],"code",{},"zipfile"," module makes that unnecessary — each member of the archive can be opened as a file object and handed straight to ",[14,19,20],{},"pd.read_excel",", so nothing touches the disk and there is nothing to delete afterwards. This page shows the pattern, the junk entries that break naive loops, archives held in memory, passwords, and the confusing error you get if you pass the ",[14,23,24],{},".zip"," to pandas directly. It is part of ",[27,28,30],"a",{"href":29},"\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002F","Reading Excel Files with Pandas",".",[33,34,36],"h2",{"id":35},"prerequisites","Prerequisites",[38,39,44],"pre",{"className":40,"code":41,"language":42,"meta":43,"style":43},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,45,46],{"__ignoreMap":43},[47,48,51,55,59,62],"span",{"class":49,"line":50},"line",1,[47,52,54],{"class":53},"sMTad","pip",[47,56,58],{"class":57},"srMev"," install",[47,60,61],{"class":57}," pandas",[47,63,64],{"class":57}," openpyxl\n",[10,66,67,69,70,73,74,77,78,81,82,85],{},[14,68,16],{}," ships with Python. The example archive, ",[14,71,72],{},"monthly_sales.zip",", contains three monthly workbooks in a ",[14,75,76],{},"2026\u002F"," folder — plus the debris real archives collect: a ",[14,79,80],{},"__MACOSX"," resource-fork entry added by the macOS \"Compress\" command, an Excel owner-lock file (",[14,83,84],{},"~$sales_feb.xlsx",") zipped up because the workbook was open at the time, and a README.",[33,87,89],{"id":88},"step-1-list-what-is-inside","Step 1 — List what is inside",[38,91,95],{"className":92,"code":93,"language":94,"meta":43,"style":43},"language-python shiki shiki-themes github-light github-dark-high-contrast","import zipfile\n\nwith zipfile.ZipFile(\"monthly_sales.zip\") as z:\n    for info in z.infolist():\n        print(f\"{info.filename:35} {info.file_size:>7} bytes\")\n","python",[14,96,97,107,114,135,150],{"__ignoreMap":43},[47,98,99,103],{"class":49,"line":50},[47,100,102],{"class":101},"s-kum","import",[47,104,106],{"class":105},"skGVy"," zipfile\n",[47,108,110],{"class":49,"line":109},2,[47,111,113],{"emptyLinePlaceholder":112},true,"\n",[47,115,117,120,123,126,129,132],{"class":49,"line":116},3,[47,118,119],{"class":101},"with",[47,121,122],{"class":105}," zipfile.ZipFile(",[47,124,125],{"class":57},"\"monthly_sales.zip\"",[47,127,128],{"class":105},") ",[47,130,131],{"class":101},"as",[47,133,134],{"class":105}," z:\n",[47,136,138,141,144,147],{"class":49,"line":137},4,[47,139,140],{"class":101},"    for",[47,142,143],{"class":105}," info ",[47,145,146],{"class":101},"in",[47,148,149],{"class":105}," z.infolist():\n",[47,151,153,157,160,163,166,170,173,176,179,182,185,188,190,193],{"class":49,"line":152},5,[47,154,156],{"class":155},"sP0c6","        print",[47,158,159],{"class":105},"(",[47,161,162],{"class":101},"f",[47,164,165],{"class":57},"\"",[47,167,169],{"class":168},"sSjpA","{",[47,171,172],{"class":105},"info.filename",[47,174,175],{"class":101},":35",[47,177,178],{"class":168},"}",[47,180,181],{"class":168}," {",[47,183,184],{"class":105},"info.file_size",[47,186,187],{"class":101},":>7",[47,189,178],{"class":168},[47,191,192],{"class":57}," bytes\"",[47,194,195],{"class":105},")\n",[10,197,198,201,202,205],{},[14,199,200],{},"infolist()"," returns one ",[14,203,204],{},"ZipInfo"," per member with its path inside the archive, its uncompressed size and its modification time. Paths always use forward slashes, whatever system created the archive. Listing first is worth the two lines: it shows the folder structure you have to match and the junk you will have to skip.",[33,207,209],{"id":208},"step-2-read-one-workbook-without-extracting-it","Step 2 — Read one workbook without extracting it",[38,211,213],{"className":92,"code":212,"language":94,"meta":43,"style":43},"import pandas as pd\n\nwith zipfile.ZipFile(\"monthly_sales.zip\") as z:\n    with z.open(\"2026\u002Fsales_jan.xlsx\") as fh:\n        jan = pd.read_excel(fh)\nprint(jan)\n",[14,214,215,227,231,245,263,274],{"__ignoreMap":43},[47,216,217,219,222,224],{"class":49,"line":50},[47,218,102],{"class":101},[47,220,221],{"class":105}," pandas ",[47,223,131],{"class":101},[47,225,226],{"class":105}," pd\n",[47,228,229],{"class":49,"line":109},[47,230,113],{"emptyLinePlaceholder":112},[47,232,233,235,237,239,241,243],{"class":49,"line":116},[47,234,119],{"class":101},[47,236,122],{"class":105},[47,238,125],{"class":57},[47,240,128],{"class":105},[47,242,131],{"class":101},[47,244,134],{"class":105},[47,246,247,250,253,256,258,260],{"class":49,"line":137},[47,248,249],{"class":101},"    with",[47,251,252],{"class":105}," z.open(",[47,254,255],{"class":57},"\"2026\u002Fsales_jan.xlsx\"",[47,257,128],{"class":105},[47,259,131],{"class":101},[47,261,262],{"class":105}," fh:\n",[47,264,265,268,271],{"class":49,"line":152},[47,266,267],{"class":105},"        jan ",[47,269,270],{"class":101},"=",[47,272,273],{"class":105}," pd.read_excel(fh)\n",[47,275,277,280],{"class":49,"line":276},6,[47,278,279],{"class":155},"print",[47,281,282],{"class":105},"(jan)\n",[10,284,285,288,289,292,293,296,297,299],{},[14,286,287],{},"z.open()"," returns a ",[14,290,291],{},"ZipExtFile",", a read-only, seekable file object that decompresses on demand. ",[14,294,295],{},"read_excel"," accepts any binary file object, so it reads the workbook exactly as if it were on disk. Keep the read inside the ",[14,298,119],{}," blocks: once the archive is closed, the member's file object can no longer be read.",[301,302,310,311,310,315,310,319,310,327,310,337,310,348,310,354,310,359,310,363,310,369,310,374,310,378,310,385,310,390,310,393,310,396,310,400,310,403,310,406,310,412,310,417,310,421,310,425,310,428,310,433,310,440,310,444],"svg",{"viewBox":303,"role":304,"ariaLabelledBy":305,"xmlns":308,"style":309},"4 0 752 142","img",[306,307],"zx-flow-t","zx-flow-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[312,313,314],"title",{"id":306},"From archive member to DataFrame without touching the disk",[316,317,318],"desc",{"id":307},"The ZIP archive is opened with zipfile.ZipFile, one member is opened as a file object with z.open, and that object is passed to pd.read_excel, which returns a DataFrame. No temporary folder is involved.",[320,321],"rect",{"x":322,"y":323,"width":324,"height":325,"fill":326},"4","0","752","142","#ffffff",[320,328],{"x":329,"y":330,"width":331,"height":332,"rx":333,"fill":334,"stroke":335,"style":336},"20.0","16.0","153.0","110.5","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[338,339,341],"g",{"transform":340},"translate(84.5,29.0) scale(1.000)",[342,343],"path",{"d":344,"fill":345,"stroke":346,"style":347},"M3 6h6l2 2h10v11H3z","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[349,350,72],"text",{"x":351,"y":352,"style":353},"96.5","75.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle",[349,355,358],{"x":351,"y":356,"style":357},"95.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","the archive on disk or",[349,360,362],{"x":351,"y":361,"style":357},"112.5","in memory",[49,364],{"x1":365,"y1":366,"x2":367,"y2":366,"stroke":368,"style":336},"178.0","71.2","200.0","var(--brand,#5b5cf0)",[370,371],"polygon",{"points":372,"fill":373},"204.0,71.2 195.0,76.2 195.0,66.2","#5b5cf0",[320,375],{"x":376,"y":330,"width":331,"height":332,"rx":333,"fill":377,"stroke":368,"style":336},"209.0","#f0f4ff",[338,379,381],{"transform":380},"translate(273.5,29.0) scale(1.000)",[342,382],{"d":383,"fill":345,"stroke":384,"style":347},"M12 2.5l8.5 4.7v9.6L12 21.5l-8.5-4.7V7.2z M3.5 7.2l8.5 4.7 8.5-4.7 M12 11.9v9.6","var(--brand-strong,#4338ca)",[349,386,389],{"x":387,"y":352,"style":388},"285.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","zipfile.ZipFile",[349,391,392],{"x":387,"y":356,"style":357},"reads the central",[349,394,395],{"x":387,"y":361,"style":357},"directory",[49,397],{"x1":398,"y1":366,"x2":399,"y2":366,"stroke":368,"style":336},"367.0","389.0",[370,401],{"points":402,"fill":373},"393.0,71.2 384.0,76.2 384.0,66.2",[320,404],{"x":405,"y":330,"width":331,"height":332,"rx":333,"fill":377,"stroke":368,"style":336},"398.0",[338,407,409],{"transform":408},"translate(462.5,37.8) scale(1.000)",[342,410],{"d":411,"fill":345,"stroke":384,"style":347},"M6 3h9l4 4v14H6z M15 3v4h4",[349,413,416],{"x":414,"y":415,"style":388},"474.5","84.2","z.open(name)",[349,418,420],{"x":414,"y":419,"style":357},"103.8","seekable file object",[49,422],{"x1":423,"y1":366,"x2":424,"y2":366,"stroke":368,"style":336},"556.0","578.0",[370,426],{"points":427,"fill":373},"582.0,71.2 573.0,76.2 573.0,66.2",[320,429],{"x":430,"y":330,"width":331,"height":332,"rx":333,"fill":431,"stroke":432,"style":336},"587.0","#d9f4f1","var(--teal,#0f9488)",[338,434,436],{"transform":435},"translate(651.5,37.8) scale(1.000)",[342,437],{"d":438,"fill":345,"stroke":439,"style":347},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14","var(--teal-ink,#0b6157)",[349,441,20],{"x":442,"y":415,"style":443},"663.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[349,445,446],{"x":442,"y":419,"style":357},"DataFrame",[33,448,450],{"id":449},"step-3-read-every-workbook-and-skip-the-junk","Step 3 — Read every workbook and skip the junk",[10,452,453,454,457,458,461,462,465,466,469,470,472,473,476],{},"A loop over ",[14,455,456],{},"namelist()"," that reads everything ending in ",[14,459,460],{},".xlsx"," trips over both junk entries: the ",[14,463,464],{},"__MACOSX\u002F._sales_jan.xlsx"," file and the ",[14,467,468],{},"~$"," lock file have the right extension but are not workbooks, and ",[14,471,295],{}," fails on them with ",[14,474,475],{},"ValueError: Excel file format cannot be determined",". Filter on the file name, not just the extension:",[38,478,480],{"className":92,"code":479,"language":94,"meta":43,"style":43},"from pathlib import PurePosixPath\n\ndef workbook_members(z):\n    for info in z.infolist():\n        path = PurePosixPath(info.filename)\n        if info.is_dir() or path.parts[0] == \"__MACOSX\":\n            continue\n        if path.name.startswith((\"~$\", \"._\")) or path.suffix.lower() not in {\".xlsx\", \".xlsm\", \".xls\"}:\n            continue\n        yield info\n\nframes = []\nwith zipfile.ZipFile(\"monthly_sales.zip\") as z:\n    for info in workbook_members(z):\n        with z.open(info) as fh:\n            df = pd.read_excel(fh)\n        df.insert(0, \"Source\", PurePosixPath(info.filename).name)\n        frames.append(df)\n\nsales = pd.concat(frames, ignore_index=True)\nprint(sales)\n",[14,481,482,495,499,511,521,531,559,565,614,619,628,633,644,659,671,684,694,710,716,721,743],{"__ignoreMap":43},[47,483,484,487,490,492],{"class":49,"line":50},[47,485,486],{"class":101},"from",[47,488,489],{"class":105}," pathlib ",[47,491,102],{"class":101},[47,493,494],{"class":105}," PurePosixPath\n",[47,496,497],{"class":49,"line":109},[47,498,113],{"emptyLinePlaceholder":112},[47,500,501,504,508],{"class":49,"line":116},[47,502,503],{"class":101},"def",[47,505,507],{"class":506},"s_Opv"," workbook_members",[47,509,510],{"class":105},"(z):\n",[47,512,513,515,517,519],{"class":49,"line":137},[47,514,140],{"class":101},[47,516,143],{"class":105},[47,518,146],{"class":101},[47,520,149],{"class":105},[47,522,523,526,528],{"class":49,"line":152},[47,524,525],{"class":105},"        path ",[47,527,270],{"class":101},[47,529,530],{"class":105}," PurePosixPath(info.filename)\n",[47,532,533,536,539,542,545,547,550,553,556],{"class":49,"line":276},[47,534,535],{"class":101},"        if",[47,537,538],{"class":105}," info.is_dir() ",[47,540,541],{"class":101},"or",[47,543,544],{"class":105}," path.parts[",[47,546,323],{"class":155},[47,548,549],{"class":105},"] ",[47,551,552],{"class":101},"==",[47,554,555],{"class":57}," \"__MACOSX\"",[47,557,558],{"class":105},":\n",[47,560,562],{"class":49,"line":561},7,[47,563,564],{"class":101},"            continue\n",[47,566,568,570,573,576,579,582,585,587,590,593,596,598,601,603,606,608,611],{"class":49,"line":567},8,[47,569,535],{"class":101},[47,571,572],{"class":105}," path.name.startswith((",[47,574,575],{"class":57},"\"~$\"",[47,577,578],{"class":105},", ",[47,580,581],{"class":57},"\"._\"",[47,583,584],{"class":105},")) ",[47,586,541],{"class":101},[47,588,589],{"class":105}," path.suffix.lower() ",[47,591,592],{"class":101},"not",[47,594,595],{"class":101}," in",[47,597,181],{"class":105},[47,599,600],{"class":57},"\".xlsx\"",[47,602,578],{"class":105},[47,604,605],{"class":57},"\".xlsm\"",[47,607,578],{"class":105},[47,609,610],{"class":57},"\".xls\"",[47,612,613],{"class":105},"}:\n",[47,615,617],{"class":49,"line":616},9,[47,618,564],{"class":101},[47,620,622,625],{"class":49,"line":621},10,[47,623,624],{"class":101},"        yield",[47,626,627],{"class":105}," info\n",[47,629,631],{"class":49,"line":630},11,[47,632,113],{"emptyLinePlaceholder":112},[47,634,636,639,641],{"class":49,"line":635},12,[47,637,638],{"class":105},"frames ",[47,640,270],{"class":101},[47,642,643],{"class":105}," []\n",[47,645,647,649,651,653,655,657],{"class":49,"line":646},13,[47,648,119],{"class":101},[47,650,122],{"class":105},[47,652,125],{"class":57},[47,654,128],{"class":105},[47,656,131],{"class":101},[47,658,134],{"class":105},[47,660,662,664,666,668],{"class":49,"line":661},14,[47,663,140],{"class":101},[47,665,143],{"class":105},[47,667,146],{"class":101},[47,669,670],{"class":105}," workbook_members(z):\n",[47,672,674,677,680,682],{"class":49,"line":673},15,[47,675,676],{"class":101},"        with",[47,678,679],{"class":105}," z.open(info) ",[47,681,131],{"class":101},[47,683,262],{"class":105},[47,685,687,690,692],{"class":49,"line":686},16,[47,688,689],{"class":105},"            df ",[47,691,270],{"class":101},[47,693,273],{"class":105},[47,695,697,700,702,704,707],{"class":49,"line":696},17,[47,698,699],{"class":105},"        df.insert(",[47,701,323],{"class":155},[47,703,578],{"class":105},[47,705,706],{"class":57},"\"Source\"",[47,708,709],{"class":105},", PurePosixPath(info.filename).name)\n",[47,711,713],{"class":49,"line":712},18,[47,714,715],{"class":105},"        frames.append(df)\n",[47,717,719],{"class":49,"line":718},19,[47,720,113],{"emptyLinePlaceholder":112},[47,722,724,727,729,732,736,738,741],{"class":49,"line":723},20,[47,725,726],{"class":105},"sales ",[47,728,270],{"class":101},[47,730,731],{"class":105}," pd.concat(frames, ",[47,733,735],{"class":734},"sa561","ignore_index",[47,737,270],{"class":101},[47,739,740],{"class":155},"True",[47,742,195],{"class":105},[47,744,746,748],{"class":49,"line":745},21,[47,747,279],{"class":155},[47,749,750],{"class":105},"(sales)\n",[10,752,753,756,757,760,761,765,766,769,770,31],{},[14,754,755],{},"PurePosixPath"," handles the forward-slash paths regardless of the operating system the script runs on. Adding a ",[14,758,759],{},"Source"," column before concatenating keeps every row traceable to its file, the same habit recommended in ",[27,762,764],{"href":763},"\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 with Python",". The suffix check is case-insensitive because Windows users produce ",[14,767,768],{},"REPORT.XLSX"," as often as ",[14,771,772],{},"report.xlsx",[301,774,310,779,310,782,310,785,310,788,310,793,310,800,310,806,310,812,310,818,310,824,310,828,310,833,310,837,310,842,310,846,310,853,310,858,310,862,310,866,310,872,310,876,310,880,310,885,310,889],{"viewBox":775,"role":304,"ariaLabelledBy":776,"xmlns":308,"style":309},"4 0 752 224",[777,778],"zx-junk-t","zx-junk-d",[312,780,781],{"id":777},"Which archive members the filter keeps",[316,783,784],{"id":778},"Members with an xlsx extension that are real workbooks are kept. The __MACOSX resource fork, the Excel owner lock file and the README text file are skipped.",[320,786],{"x":322,"y":323,"width":324,"height":787,"fill":326},"224",[320,789],{"x":329,"y":330,"width":790,"height":791,"rx":792,"fill":431,"stroke":432,"style":336},"351.0","192.0","14",[338,794,796],{"transform":795},"translate(182.5,29.0) scale(1.083)",[342,797],{"d":798,"fill":345,"stroke":439,"style":799},"M5 12.5l4.5 4.5L19 7","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[349,801,805],{"x":802,"y":803,"style":804},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Read",[49,807],{"x1":808,"y1":809,"x2":810,"y2":809,"stroke":432,"style":811},"36.0","91.0","355.0","stroke-width:1px",[349,813,817],{"x":814,"y":815,"style":816},"60.0","111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","2026\u002Fsales_jan.xlsx",[338,819,821],{"transform":820},"translate(38.5,99.5) scale(0.625)",[342,822],{"d":798,"fill":345,"stroke":432,"style":823},"stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[349,825,827],{"x":814,"y":826,"style":816},"137.0","2026\u002Fsales_feb.xlsx",[338,829,831],{"transform":830},"translate(38.5,125.5) scale(0.625)",[342,832],{"d":798,"fill":345,"stroke":432,"style":823},[349,834,836],{"x":814,"y":835,"style":816},"163.0","2026\u002Fsales_mar.xlsx",[338,838,840],{"transform":839},"translate(38.5,151.5) scale(0.625)",[342,841],{"d":798,"fill":345,"stroke":432,"style":823},[320,843],{"x":399,"y":330,"width":790,"height":791,"rx":792,"fill":844,"stroke":845,"style":336},"#fce9e9","var(--danger,#dc2626)",[338,847,849],{"transform":848},"translate(551.5,29.0) scale(1.083)",[342,850],{"d":851,"fill":345,"stroke":852,"style":799},"M6 6l12 12 M18 6L6 18","var(--accent-ink,#be185d)",[349,854,857],{"x":855,"y":803,"style":856},"564.5","font-size:14px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","Skip",[49,859],{"x1":860,"y1":809,"x2":861,"y2":809,"stroke":845,"style":811},"405.0","724.0",[349,863,865],{"x":864,"y":815,"style":816},"429.0","__MACOSX\u002F2026\u002F._sales_jan.xlsx",[338,867,869],{"transform":868},"translate(407.5,99.5) scale(0.625)",[342,870],{"d":851,"fill":345,"stroke":871,"style":823},"var(--accent,#f43f8f)",[349,873,875],{"x":864,"y":874,"style":816},"128.0","(macOS metadata)",[349,877,879],{"x":864,"y":878,"style":816},"154.0","2026\u002F~$sales_feb.xlsx (owner lock file)",[338,881,883],{"transform":882},"translate(407.5,142.5) scale(0.625)",[342,884],{"d":851,"fill":345,"stroke":871,"style":823},[349,886,888],{"x":864,"y":887,"style":816},"180.0","2026\u002FREADME.txt (not a workbook)",[338,890,892],{"transform":891},"translate(407.5,168.5) scale(0.625)",[342,893],{"d":851,"fill":345,"stroke":871,"style":823},[33,895,897],{"id":896},"step-4-read-an-archive-that-only-exists-in-memory","Step 4 — Read an archive that only exists in memory",[10,899,900,901,904,905,908],{},"Archives downloaded over HTTP or fetched from object storage do not need to be saved first. ",[14,902,903],{},"ZipFile"," accepts any seekable binary file object, so wrap the bytes in ",[14,906,907],{},"io.BytesIO",":",[38,910,912],{"className":92,"code":911,"language":94,"meta":43,"style":43},"import io\n\nfrom pathlib import Path\n\npayload = Path(\"monthly_sales.zip\").read_bytes()   # stands in for requests.get(url).content\nwith zipfile.ZipFile(io.BytesIO(payload)) as z:\n    names = [i.filename for i in workbook_members(z)]\n    feb = pd.read_excel(z.open(names[1]))\nprint(names)\nprint(feb)\n",[14,913,914,921,925,936,940,959,970,991,1007,1014],{"__ignoreMap":43},[47,915,916,918],{"class":49,"line":50},[47,917,102],{"class":101},[47,919,920],{"class":105}," io\n",[47,922,923],{"class":49,"line":109},[47,924,113],{"emptyLinePlaceholder":112},[47,926,927,929,931,933],{"class":49,"line":116},[47,928,486],{"class":101},[47,930,489],{"class":105},[47,932,102],{"class":101},[47,934,935],{"class":105}," Path\n",[47,937,938],{"class":49,"line":137},[47,939,113],{"emptyLinePlaceholder":112},[47,941,942,945,947,950,952,955],{"class":49,"line":152},[47,943,944],{"class":105},"payload ",[47,946,270],{"class":101},[47,948,949],{"class":105}," Path(",[47,951,125],{"class":57},[47,953,954],{"class":105},").read_bytes()   ",[47,956,958],{"class":957},"s-wDw","# stands in for requests.get(url).content\n",[47,960,961,963,966,968],{"class":49,"line":276},[47,962,119],{"class":101},[47,964,965],{"class":105}," zipfile.ZipFile(io.BytesIO(payload)) ",[47,967,131],{"class":101},[47,969,134],{"class":105},[47,971,972,975,977,980,983,986,988],{"class":49,"line":561},[47,973,974],{"class":105},"    names ",[47,976,270],{"class":101},[47,978,979],{"class":105}," [i.filename ",[47,981,982],{"class":101},"for",[47,984,985],{"class":105}," i ",[47,987,146],{"class":101},[47,989,990],{"class":105}," workbook_members(z)]\n",[47,992,993,996,998,1001,1004],{"class":49,"line":567},[47,994,995],{"class":105},"    feb ",[47,997,270],{"class":101},[47,999,1000],{"class":105}," pd.read_excel(z.open(names[",[47,1002,1003],{"class":155},"1",[47,1005,1006],{"class":105},"]))\n",[47,1008,1009,1011],{"class":49,"line":616},[47,1010,279],{"class":155},[47,1012,1013],{"class":105},"(names)\n",[47,1015,1016,1018],{"class":49,"line":621},[47,1017,279],{"class":155},[47,1019,1020],{"class":105},"(feb)\n",[10,1022,1023,1024,1027,1028,1031,1032,1036],{},"In a real job ",[14,1025,1026],{},"payload"," would be ",[14,1029,1030],{},"requests.get(url, timeout=60).content"," or the body of an S3 object; the rest is identical. The approach mirrors ",[27,1033,1035],{"href":1034},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-an-excel-file-from-a-url-or-bytes-in-python\u002F","Read an Excel File from a URL or Bytes in Python"," with one more layer of wrapping. The whole archive sits in memory, which is fine for the tens of megabytes typical of report bundles.",[33,1038,1040],{"id":1039},"step-5-password-protected-archives","Step 5 — Password-protected archives",[10,1042,1043,1045,1046,1049],{},[14,1044,16],{}," can decrypt the classic ZIP password scheme (often called ZipCrypto) that ",[14,1047,1048],{},"zip -P"," and most \"add password\" dialogs produce. Pass the password as bytes:",[38,1051,1053],{"className":92,"code":1052,"language":94,"meta":43,"style":43},"import subprocess\n\nsubprocess.run([\"zip\", \"-q\", \"-P\", \"s3cret\", \"locked.zip\", \"monthly_sales.zip\"], check=True)\nwith zipfile.ZipFile(\"locked.zip\") as z:\n    info = z.infolist()[0]\n    print(\"encrypted:\", bool(info.flag_bits & 0x1))\n    try:\n        z.read(info.filename)\n    except RuntimeError as exc:\n        print(\"RuntimeError:\", exc)\n    inner = io.BytesIO(z.read(info.filename, pwd=b\"s3cret\"))\nprint(zipfile.ZipFile(inner).namelist()[:3])\n",[14,1054,1055,1062,1066,1110,1124,1139,1168,1175,1180,1194,1206,1228],{"__ignoreMap":43},[47,1056,1057,1059],{"class":49,"line":50},[47,1058,102],{"class":101},[47,1060,1061],{"class":105}," subprocess\n",[47,1063,1064],{"class":49,"line":109},[47,1065,113],{"emptyLinePlaceholder":112},[47,1067,1068,1071,1074,1076,1079,1081,1084,1086,1089,1091,1094,1096,1098,1101,1104,1106,1108],{"class":49,"line":116},[47,1069,1070],{"class":105},"subprocess.run([",[47,1072,1073],{"class":57},"\"zip\"",[47,1075,578],{"class":105},[47,1077,1078],{"class":57},"\"-q\"",[47,1080,578],{"class":105},[47,1082,1083],{"class":57},"\"-P\"",[47,1085,578],{"class":105},[47,1087,1088],{"class":57},"\"s3cret\"",[47,1090,578],{"class":105},[47,1092,1093],{"class":57},"\"locked.zip\"",[47,1095,578],{"class":105},[47,1097,125],{"class":57},[47,1099,1100],{"class":105},"], ",[47,1102,1103],{"class":734},"check",[47,1105,270],{"class":101},[47,1107,740],{"class":155},[47,1109,195],{"class":105},[47,1111,1112,1114,1116,1118,1120,1122],{"class":49,"line":137},[47,1113,119],{"class":101},[47,1115,122],{"class":105},[47,1117,1093],{"class":57},[47,1119,128],{"class":105},[47,1121,131],{"class":101},[47,1123,134],{"class":105},[47,1125,1126,1129,1131,1134,1136],{"class":49,"line":152},[47,1127,1128],{"class":105},"    info ",[47,1130,270],{"class":101},[47,1132,1133],{"class":105}," z.infolist()[",[47,1135,323],{"class":155},[47,1137,1138],{"class":105},"]\n",[47,1140,1141,1144,1146,1149,1151,1154,1157,1160,1163,1165],{"class":49,"line":276},[47,1142,1143],{"class":155},"    print",[47,1145,159],{"class":105},[47,1147,1148],{"class":57},"\"encrypted:\"",[47,1150,578],{"class":105},[47,1152,1153],{"class":155},"bool",[47,1155,1156],{"class":105},"(info.flag_bits ",[47,1158,1159],{"class":101},"&",[47,1161,1162],{"class":101}," 0x",[47,1164,1003],{"class":155},[47,1166,1167],{"class":105},"))\n",[47,1169,1170,1173],{"class":49,"line":561},[47,1171,1172],{"class":101},"    try",[47,1174,558],{"class":105},[47,1176,1177],{"class":49,"line":567},[47,1178,1179],{"class":105},"        z.read(info.filename)\n",[47,1181,1182,1185,1188,1191],{"class":49,"line":616},[47,1183,1184],{"class":101},"    except",[47,1186,1187],{"class":155}," RuntimeError",[47,1189,1190],{"class":101}," as",[47,1192,1193],{"class":105}," exc:\n",[47,1195,1196,1198,1200,1203],{"class":49,"line":621},[47,1197,156],{"class":155},[47,1199,159],{"class":105},[47,1201,1202],{"class":57},"\"RuntimeError:\"",[47,1204,1205],{"class":105},", exc)\n",[47,1207,1208,1211,1213,1216,1219,1221,1224,1226],{"class":49,"line":630},[47,1209,1210],{"class":105},"    inner ",[47,1212,270],{"class":101},[47,1214,1215],{"class":105}," io.BytesIO(z.read(info.filename, ",[47,1217,1218],{"class":734},"pwd",[47,1220,270],{"class":101},[47,1222,1223],{"class":101},"b",[47,1225,1088],{"class":57},[47,1227,1167],{"class":105},[47,1229,1230,1232,1235,1238],{"class":49,"line":635},[47,1231,279],{"class":155},[47,1233,1234],{"class":105},"(zipfile.ZipFile(inner).namelist()[:",[47,1236,1237],{"class":155},"3",[47,1239,1240],{"class":105},"])\n",[10,1242,1243,1244,1247,1248,1251,1252,1255,1256,31],{},"Without the password, reading raises ",[14,1245,1246],{},"RuntimeError: File 'monthly_sales.zip' is encrypted, password required for extraction","; with it, the bytes come back and can be opened as before (here the protected member is itself a ZIP, which nests just as easily). Archives encrypted with AES — 7-Zip's and WinZip's stronger option — raise ",[14,1249,1250],{},"NotImplementedError: That compression method is not supported",", because the standard library does not implement AES. Use the third-party ",[14,1253,1254],{},"pyzipper"," package, which offers the same interface, or ask the sender for ZipCrypto. Keep the password out of the script, as described in ",[27,1257,1259],{"href":1258},"\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fstore-credentials-for-excel-report-scripts-safely\u002F","Store Credentials for Excel Report Scripts Safely",[33,1261,1263],{"id":1262},"step-6-do-not-hand-the-zip-to-pandas","Step 6 — Do not hand the ZIP to pandas",[10,1265,1266,1267,1269,1270,1273,1274,1276],{},"An ",[14,1268,460],{}," workbook is itself a ZIP file of XML parts — ",[14,1271,1272],{},"zipfile.is_zipfile(\"sales.xlsx\")"," returns ",[14,1275,740],{},". pandas therefore looks inside any ZIP it is given to decide what kind of workbook it is. A report bundle has none of the parts it looks for, and the error is baffling:",[38,1278,1280],{"className":92,"code":1279,"language":94,"meta":43,"style":43},"try:\n    pd.read_excel(\"monthly_sales.zip\")\nexcept Exception as exc:\n    print(type(exc).__name__, exc)\n",[14,1281,1282,1289,1298,1310],{"__ignoreMap":43},[47,1283,1284,1287],{"class":49,"line":50},[47,1285,1286],{"class":101},"try",[47,1288,558],{"class":105},[47,1290,1291,1294,1296],{"class":49,"line":109},[47,1292,1293],{"class":105},"    pd.read_excel(",[47,1295,125],{"class":57},[47,1297,195],{"class":105},[47,1299,1300,1303,1306,1308],{"class":49,"line":116},[47,1301,1302],{"class":101},"except",[47,1304,1305],{"class":155}," Exception",[47,1307,1190],{"class":101},[47,1309,1193],{"class":105},[47,1311,1312,1314,1316,1319,1322,1325],{"class":49,"line":137},[47,1313,1143],{"class":155},[47,1315,159],{"class":105},[47,1317,1318],{"class":155},"type",[47,1320,1321],{"class":105},"(exc).",[47,1323,1324],{"class":155},"__name__",[47,1326,1205],{"class":105},[10,1328,1329,1330,1333,1334,1337,1338,1340,1341,1344,1345,1348,1349,1351],{},"pandas 3 raises ",[14,1331,1332],{},"OptionError: No such keys(s): 'io.excel.zip.reader'"," — it fell back to treating the extension ",[14,1335,1336],{},"zip"," as a format name and found no reader registered for it. ",[14,1339,295],{}," also has no ",[14,1342,1343],{},"compression="," argument, unlike ",[14,1346,1347],{},"read_csv",", which can read a single CSV out of a ZIP on its own. For workbooks, always open the archive with ",[14,1350,16],{}," first.",[301,1353,310,1359,310,1362,310,1365,310,1370,310,1375,310,1380,310,1383,310,1386,310,1390,310,1393,310,1396,310,1402,310,1405,310,1409,310,1413,310,1416,310,1419,310,1425,310,1429],{"viewBox":1354,"role":304,"ariaLabelledBy":1355,"xmlns":308,"style":1358},"84 0 592 383",[1356,1357],"zx-nesting-t","zx-nesting-d","width:100%;max-width:592px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[312,1360,1361],{"id":1356},"An archive of workbooks is a ZIP of ZIPs",[316,1363,1364],{"id":1357},"The outer layer is the report archive monthly_sales.zip. Inside it each member such as sales_jan.xlsx is itself a ZIP package, and inside that are the XML parts such as workbook.xml and sheet1.xml that openpyxl parses.",[320,1366],{"x":1367,"y":323,"width":1368,"height":1369,"fill":326},"84","592","383",[320,1371],{"x":1372,"y":330,"width":1373,"height":1374,"rx":333,"fill":334,"stroke":335,"style":336},"100.0","560.0","93.0",[338,1376,1378],{"transform":1377},"translate(368.0,29.0) scale(1.000)",[342,1379],{"d":344,"fill":345,"stroke":346,"style":347},[349,1381,72],{"x":1382,"y":352,"style":353},"380.0",[349,1384,1385],{"x":1382,"y":356,"style":357},"opened with zipfile.ZipFile",[49,1387],{"x1":1382,"y1":1388,"x2":1382,"y2":1389,"stroke":368,"style":336},"114.0","136.0",[370,1391],{"points":1392,"fill":373},"380.0,140.0 375.0,131.0 385.0,131.0",[320,1394],{"x":1372,"y":1395,"width":1373,"height":1374,"rx":333,"fill":377,"stroke":368,"style":336},"145.0",[338,1397,1399],{"transform":1398},"translate(368.0,158.0) scale(1.000)",[342,1400],{"d":1401,"fill":345,"stroke":384,"style":347},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6",[349,1403,817],{"x":1382,"y":1404,"style":388},"204.5",[349,1406,1408],{"x":1382,"y":1407,"style":357},"224.0","a ZIP package passed to read_excel",[49,1410],{"x1":1382,"y1":1411,"x2":1382,"y2":1412,"stroke":368,"style":336},"243.0","265.0",[370,1414],{"points":1415,"fill":373},"380.0,269.0 375.0,260.0 385.0,260.0",[320,1417],{"x":1372,"y":1418,"width":1373,"height":1374,"rx":333,"fill":431,"stroke":432,"style":336},"274.0",[338,1420,1422],{"transform":1421},"translate(368.0,287.0) scale(1.000)",[342,1423],{"d":1424,"fill":345,"stroke":439,"style":347},"M9 8l-4 4 4 4 M15 8l4 4-4 4",[349,1426,1428],{"x":1382,"y":1427,"style":443},"333.5","xl\u002Fworkbook.xml, xl\u002Fworksheets\u002Fsheet1.xml",[349,1430,1432],{"x":1382,"y":1431,"style":357},"353.0","XML parts parsed by the engine",[33,1434,1436],{"id":1435},"step-7-write-the-results-back-into-an-archive","Step 7 — Write the results back into an archive",[10,1438,1439,1440,1443],{},"The reverse trip is just as short. When the output is several workbooks — one per region, say — build each one in memory and add it to a new archive with ",[14,1441,1442],{},"writestr",", again without temporary files:",[38,1445,1447],{"className":92,"code":1446,"language":94,"meta":43,"style":43},"with zipfile.ZipFile(\"by_region.zip\", \"w\", zipfile.ZIP_DEFLATED) as out:\n    for region, part in sales.groupby(\"Region\"):\n        buf = io.BytesIO()\n        part.to_excel(buf, index=False)\n        out.writestr(f\"sales_{region.lower()}.xlsx\", buf.getvalue())\nprint(zipfile.ZipFile(\"by_region.zip\").namelist())\n",[14,1448,1449,1476,1494,1504,1519,1542],{"__ignoreMap":43},[47,1450,1451,1453,1455,1458,1460,1463,1466,1469,1471,1473],{"class":49,"line":50},[47,1452,119],{"class":101},[47,1454,122],{"class":105},[47,1456,1457],{"class":57},"\"by_region.zip\"",[47,1459,578],{"class":105},[47,1461,1462],{"class":57},"\"w\"",[47,1464,1465],{"class":105},", zipfile.",[47,1467,1468],{"class":155},"ZIP_DEFLATED",[47,1470,128],{"class":105},[47,1472,131],{"class":101},[47,1474,1475],{"class":105}," out:\n",[47,1477,1478,1480,1483,1485,1488,1491],{"class":49,"line":109},[47,1479,140],{"class":101},[47,1481,1482],{"class":105}," region, part ",[47,1484,146],{"class":101},[47,1486,1487],{"class":105}," sales.groupby(",[47,1489,1490],{"class":57},"\"Region\"",[47,1492,1493],{"class":105},"):\n",[47,1495,1496,1499,1501],{"class":49,"line":116},[47,1497,1498],{"class":105},"        buf ",[47,1500,270],{"class":101},[47,1502,1503],{"class":105}," io.BytesIO()\n",[47,1505,1506,1509,1512,1514,1517],{"class":49,"line":137},[47,1507,1508],{"class":105},"        part.to_excel(buf, ",[47,1510,1511],{"class":734},"index",[47,1513,270],{"class":101},[47,1515,1516],{"class":155},"False",[47,1518,195],{"class":105},[47,1520,1521,1524,1526,1529,1531,1534,1536,1539],{"class":49,"line":152},[47,1522,1523],{"class":105},"        out.writestr(",[47,1525,162],{"class":101},[47,1527,1528],{"class":57},"\"sales_",[47,1530,169],{"class":168},[47,1532,1533],{"class":105},"region.lower()",[47,1535,178],{"class":168},[47,1537,1538],{"class":57},".xlsx\"",[47,1540,1541],{"class":105},", buf.getvalue())\n",[47,1543,1544,1546,1549,1551],{"class":49,"line":276},[47,1545,279],{"class":155},[47,1547,1548],{"class":105},"(zipfile.ZipFile(",[47,1550,1457],{"class":57},[47,1552,1553],{"class":105},").namelist())\n",[10,1555,1556,1557,1560,1561,1563,1564,1568],{},"Each ",[14,1558,1559],{},"BytesIO"," buffer holds a complete workbook; ",[14,1562,1442],{}," stores its bytes under the given name. The result is ready to attach to an email, as in ",[27,1565,1567],{"href":1566},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fzip-large-excel-attachments-before-emailing\u002F","Zip Large Excel Attachments Before Emailing",", or to upload in one request.",[33,1570,1572],{"id":1571},"common-pitfalls","Common pitfalls",[1574,1575,1576,1595,1613,1633,1643],"ul",{},[1577,1578,1579,1583,1584,1587,1588,1591,1592,1594],"li",{},[1580,1581,1582],"strong",{},"Junk members."," ",[14,1585,1586],{},"__MACOSX\u002F._*"," and ",[14,1589,1590],{},"~$*"," entries end in ",[14,1593,460],{}," but are not workbooks; filter them by name.",[1577,1596,1597,1600,1601,1603,1604,1606,1607,1609,1610,31],{},[1580,1598,1599],{},"Reading after the archive closes."," A member opened with ",[14,1602,287],{}," is unreadable once the ",[14,1605,903],{}," is closed; read inside the ",[14,1608,119],{}," block or copy the bytes out with ",[14,1611,1612],{},"z.read()",[1577,1614,1615,1618,1619,1622,1623,1625,1626,1628,1629,1632],{},[1580,1616,1617],{},"Backslashes in member names."," Some Windows tools write ",[14,1620,1621],{},"folder\\file.xlsx",". ",[14,1624,16],{}," keeps such names as given, so a ",[14,1627,755],{}," check sees one long name — normalise with ",[14,1630,1631],{},"info.filename.replace(\"\\\\\", \"\u002F\")"," when matching.",[1577,1634,1635,1638,1639,1642],{},[1580,1636,1637],{},"Non-ASCII file names."," Archives created without the UTF-8 flag store names in the old DOS code page and come back garbled. Python 3.11+ accepts ",[14,1640,1641],{},"ZipFile(path, metadata_encoding=\"cp437\")"," or the sender's code page to decode them.",[1577,1644,1645,1648,1649,1651],{},[1580,1646,1647],{},"Expecting compression savings."," Zipping workbooks barely shrinks them, because each ",[14,1650,460],{}," is already compressed; a 2.4 MB workbook became 2.39 MB in the archive during testing.",[33,1653,1655],{"id":1654},"performance-notes","Performance notes",[10,1657,1658,1659,1661,1662,1664,1665,1668,1669,1671],{},"Reading from a ",[14,1660,291],{}," costs about the same as reading from disk: on a 60,000-row workbook, streaming it from the archive took 1.7 seconds with openpyxl and copying it into ",[14,1663,907],{}," first took 1.5 seconds — both dominated by XML parsing. Switching the engine matters far more: ",[14,1666,1667],{},"engine=\"calamine\""," read the same member in 0.23 seconds. For archives with dozens of large workbooks, read members in parallel processes, each opening the archive itself; a ",[14,1670,903],{}," object should not be shared between processes.",[33,1673,1675],{"id":1674},"conclusion","Conclusion",[10,1677,1678,1679,1681,1682,1684,1685,1687,1688,1690,1691,1693,1694,1697,1698,1700,1701,1704],{},"Open the archive with ",[14,1680,389],{},", filter ",[14,1683,200],{}," down to real workbooks by name, and pass each member's file object from ",[14,1686,287],{}," to ",[14,1689,20],{}," — no extraction, no temporary folder. Wrap downloaded bytes in ",[14,1692,907],{},", pass ",[14,1695,1696],{},"pwd="," for classic password ZIPs and switch to ",[14,1699,1254],{}," for AES, and never hand the archive itself to pandas, which mistakes it for a workbook and fails with an ",[14,1702,1703],{},"io.excel.zip.reader"," error.",[33,1706,1708],{"id":1707},"frequently-asked-questions","Frequently asked questions",[10,1710,1711,1714],{},[1580,1712,1713],{},"Can pandas read an Excel file inside a ZIP without extracting it?","\nYes. Open the archive with zipfile.ZipFile, open the member with z.open(\"folder\u002Ffile.xlsx\") and pass that file object to pd.read_excel. Nothing is written to disk.",[10,1716,1717,1720],{},[1580,1718,1719],{},"Why does pd.read_excel on a .zip file fail with \"No such keys(s): io.excel.zip.reader\"?","\npandas treats any ZIP as a possible workbook package, finds none of the parts it expects, and falls back to using the extension zip as a reader name, for which no reader exists. Open the archive with zipfile and read the members instead.",[10,1722,1723,1726],{},[1580,1724,1725],{},"How do I read every Excel file in a ZIP into one DataFrame?","\nLoop over z.infolist(), skip directories, __MACOSX entries and names starting with ~$ or ._, read each remaining .xlsx with pd.read_excel(z.open(info)), add a column with the member name, and pd.concat the results.",[10,1728,1729,1732],{},[1580,1730,1731],{},"Can Python open a password-protected ZIP of Excel files?","\nThe standard zipfile module decrypts classic ZipCrypto archives when you pass pwd=b\"password\". AES-encrypted archives raise NotImplementedError; use the pyzipper package for those.",[33,1734,1736],{"id":1735},"related","Related",[1574,1738,1739,1745,1752,1759],{},[1577,1740,1741,1742,1744],{},"Up one level: ",[27,1743,30],{"href":29}," — the read_excel options in depth.",[1577,1746,1747,1751],{},[27,1748,1750],{"href":1749},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fignore-excel-lock-files-when-processing-a-folder\u002F","Ignore Excel Lock Files When Processing a Folder"," — the same ~$ files on disk.",[1577,1753,1754,1758],{},[27,1755,1757],{"href":1756},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fdownload-excel-attachments-from-an-email-inbox\u002F","Download Excel Attachments from an Email Inbox"," — where many of these archives come from.",[1577,1760,1761,1765],{},[27,1762,1764],{"href":1763},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-badzipfile-error-when-reading-excel-in-python\u002F","Fix BadZipFile When Reading an Excel File in Python"," — when the workbook itself is not a valid package.",[1767,1768,1769],"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 .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":43,"searchDepth":109,"depth":109,"links":1771},[1772,1773,1774,1775,1776,1777,1778,1779,1780,1781,1782,1783,1784],{"id":35,"depth":109,"text":36},{"id":88,"depth":109,"text":89},{"id":208,"depth":109,"text":209},{"id":449,"depth":109,"text":450},{"id":896,"depth":109,"text":897},{"id":1039,"depth":109,"text":1040},{"id":1262,"depth":109,"text":1263},{"id":1435,"depth":109,"text":1436},{"id":1571,"depth":109,"text":1572},{"id":1654,"depth":109,"text":1655},{"id":1674,"depth":109,"text":1675},{"id":1707,"depth":109,"text":1708},{"id":1735,"depth":109,"text":1736},"2026-10-02","Read workbooks straight out of a .zip without unpacking it: list the members, open one with zipfile and pass it to pd.read_excel, loop over every workbook while skipping __MACOSX and lock-file junk, read archives downloaded into memory, handle password-protected ZIPs, and understand the errors pandas gives when handed the archive itself.","md",[1789,1791,1793,1795],{"q":1713,"a":1790},"Yes. Open the archive with zipfile.ZipFile, open the member with z.open(\"folder\u002Ffile.xlsx\") and pass that file object to pd.read_excel. Nothing is written to disk.",{"q":1719,"a":1792},"pandas treats any ZIP as a possible workbook package, finds none of the parts it expects, and falls back to using the extension zip as a reader name, for which no reader exists. Open the archive with zipfile and read the members instead.",{"q":1725,"a":1794},"Loop over z.infolist(), skip directories, __MACOSX entries and names starting with ~$ or ._, read each remaining .xlsx with pd.read_excel(z.open(info)), add a column with the member name, and pd.concat the results.",{"q":1731,"a":1796},"The standard zipfile module decrypts classic ZipCrypto archives when you pass pwd=b\"password\". AES-encrypted archives raise NotImplementedError; use the pyzipper package for those.","read excel files inside a zip archive with python",null,{"breadcrumb":1800},[1801,1804,1807],{"name":1802,"item":1803},"Home","\u002F",{"name":1805,"item":1806},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":30,"item":29},"\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-excel-files-inside-a-zip-archive-with-python",{"title":1810,"description":1811},"Read Excel Files Inside a ZIP Archive with Python and pandas","Open .xlsx files inside a .zip with zipfile and pd.read_excel — no extraction: filter real workbooks, combine them with a source column, read ZIPs from bytes or a URL, ZipCrypto passwords, AES limits and the io.excel.zip.reader error.","read-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",[1815,1816,1817],"reading-excel","file-formats","sheets-and-files","how-to","12_dgbCJNg4mn5Tu7m8BGsQ3BxWB3gjSj4mquijWJSs",[1821,1825],{"title":1822,"path":1823,"stem":1824,"children":-1},"Read Yes\u002FNo and TRUE\u002FFALSE Columns as Booleans with pandas","\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-boolean-columns-from-excel-with-pandas","getting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-boolean-columns-from-excel-with-pandas\u002Findex",{"title":1826,"path":1827,"stem":1828,"children":-1},"Read Excel Sheets Whose Names Change with pandas","\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-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",[1830,1832],{"path":1831,"title":1805},"\u002Fgetting-started-with-python-excel-automation",{"path":1833,"title":30},"\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas",[],[1836,1838,1841,1844,1847,1850],{"path":1837,"title":1035,"kind":1818},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-an-excel-file-from-a-url-or-bytes-in-python",{"path":1839,"title":1840,"kind":1818},"\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":1842,"title":1843,"kind":1818},"\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":1845,"title":1846,"kind":1818},"\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":1848,"title":1849,"kind":1818},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstandardise-excel-column-names-with-pandas","Standardise Excel Column Names with pandas",{"path":1851,"title":1852,"kind":1853},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets","Extracting Data from Messy Excel Sheets","guide",1790972065215]