[{"data":1,"prerenderedAt":2095},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fcount-rows-in-an-excel-file-without-loading-it":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fcount-rows-in-an-excel-file-without-loading-it":2062,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fcount-rows-in-an-excel-file-without-loading-it":2071,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fcount-rows-in-an-excel-file-without-loading-it":2076,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fcount-rows-in-an-excel-file-without-loading-it":2077},{"id":4,"title":5,"body":6,"dateModified":2028,"datePublished":2028,"description":2029,"extension":2030,"faq":2031,"keyword":2040,"kind":2041,"meta":2042,"navigation":291,"path":2051,"seo":2052,"slug":2055,"stem":2056,"topics":2057,"type":2060,"__hash__":2061},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fcount-rows-in-an-excel-file-without-loading-it\u002Findex.md","Count Rows in an Excel File Without Loading It",{"type":7,"value":8,"toc":2015},"minimark",[9,29,197,202,229,244,248,255,405,415,419,426,551,569,580,703,706,821,825,831,962,985,989,996,1549,1564,1666,1670,1673,1862,1869,1873,1921,1925,1933,1937,1951,1955,1961,1967,1973,1979,1983,2011],[10,11,12,13,18,19,23,24,28],"p",{},"A script that receives Excel files of unknown size often needs the row count first: to choose between reading the file whole and ",[14,15,17],"a",{"href":16},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fread-large-excel-file-in-chunks-with-pandas\u002F","reading it in chunks",", to show progress, to reject a file that is suspiciously small, or to check that an export arrived complete. Loading the sheet into pandas just to call ",[20,21,22],"code",{},"len()"," wastes seconds or minutes on a big workbook. Every xlsx sheet declares its own size at the top of its XML, which openpyxl can read almost instantly — but that declaration is sometimes missing or misleading, and it is worth knowing when to trust it and what to do instead. This page is part of ",[14,25,27],{"href":26},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002F","Working with Large Excel Files in Python",".",[30,31,39,40,39,44,39,48,39,56,39,66,39,77,39,83,39,88,39,92,39,95,39,100,39,109,39,114,39,122,39,129,39,134,39,139,39,141,39,144,39,148,39,151,39,156,39,163,39,167,39,170,39,172,39,175,39,179,39,182,39,185,39,191,39,194],"svg",{"viewBox":32,"role":33,"ariaLabelledBy":34,"xmlns":37,"style":38},"4 0 752 272","img",[35,36],"count-decide-t","count-decide-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  ",[41,42,43],"title",{"id":35},"Choosing how to count rows",[45,46,47],"desc",{"id":36},"A decision chart. If the file comes from a writer that records its size correctly, read max_row in read-only mode, which is instant. If the declared size is missing or says A1, or formatted empty rows may inflate it, count the rows that hold values with python-calamine, or stream the sheet XML when you cannot add a dependency.",[49,50],"rect",{"x":51,"y":52,"width":53,"height":54,"fill":55},"4","0","752","272","#ffffff",[49,57],{"x":58,"y":59,"width":60,"height":61,"rx":62,"fill":63,"stroke":64,"style":65},"234.1","16.0","291.7","75.5","12","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:2px",[67,68,70],"g",{"transform":69},"translate(368.0,29.0) scale(1.000)",[71,72],"path",{"d":73,"fill":74,"stroke":75,"style":76},"M11 4a7 7 0 1 0 .01 0z M16 16l5 5","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[78,79,82],"text",{"x":80,"y":61,"style":81},"380.0","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Can the declared size be trusted?",[84,85],"line",{"x1":80,"y1":86,"x2":80,"y2":87,"stroke":64,"style":65},"91.5","111.5",[84,89],{"x1":90,"y1":87,"x2":91,"y2":87,"stroke":64,"style":65},"134.7","625.3",[84,93],{"x1":90,"y1":87,"x2":90,"y2":94,"stroke":64,"style":65},"157.5",[96,97],"polygon",{"points":98,"fill":99},"134.7,161.5 129.7,152.5 139.7,152.5","#5b5cf0",[49,101],{"x":102,"y":103,"width":104,"height":105,"rx":106,"fill":55,"stroke":107,"style":108},"85.3","121.5","98.7","21.0","10","var(--line,#cdd5e6)","stroke-width:1.2px",[78,110,113],{"x":90,"y":111,"style":112},"136.0","font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","trusted writer",[49,115],{"x":116,"y":117,"width":118,"height":119,"rx":62,"fill":120,"stroke":121,"style":65},"20.0","163.5","229.3","93.0","#d9f4f1","var(--teal,#0f9488)",[67,123,125],{"transform":124},"translate(122.7,176.5) scale(1.000)",[71,126],{"d":127,"fill":74,"stroke":128,"style":76},"M5 12.5l4.5 4.5L19 7","var(--teal-ink,#0b6157)",[78,130,133],{"x":90,"y":131,"style":132},"223.0","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","max_row",[78,135,138],{"x":90,"y":136,"style":137},"242.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","read-only, instant",[84,140],{"x1":80,"y1":87,"x2":80,"y2":94,"stroke":64,"style":65},[96,142],{"points":143,"fill":99},"380.0,161.5 375.0,152.5 385.0,152.5",[49,145],{"x":146,"y":103,"width":147,"height":105,"rx":106,"fill":55,"stroke":107,"style":108},"331.7","96.5",[78,149,150],{"x":80,"y":111,"style":112},"missing or A1",[49,152],{"x":153,"y":117,"width":118,"height":119,"rx":62,"fill":154,"stroke":155,"style":65},"265.3","#fdefd8","var(--gold,#b4740a)",[67,157,159],{"transform":158},"translate(368.0,176.5) scale(1.000)",[71,160],{"d":161,"fill":74,"stroke":162,"style":76},"M13 2L4 14h7l-1 8 9-12h-7z","var(--gold-ink,#7a4e06)",[78,164,166],{"x":80,"y":131,"style":165},"font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","calamine",[78,168,169],{"x":80,"y":136,"style":137},"counts rows with values",[84,171],{"x1":91,"y1":87,"x2":91,"y2":94,"stroke":64,"style":65},[96,173],{"points":174,"fill":99},"625.3,161.5 620.3,152.5 630.3,152.5",[49,176],{"x":177,"y":103,"width":178,"height":105,"rx":106,"fill":55,"stroke":107,"style":108},"562.1","126.5",[78,180,181],{"x":91,"y":111,"style":112},"no extra packages",[49,183],{"x":184,"y":117,"width":118,"height":119,"rx":62,"fill":63,"stroke":64,"style":65},"510.7",[67,186,188],{"transform":187},"translate(613.3,176.5) scale(1.000)",[71,189],{"d":190,"fill":74,"stroke":75,"style":76},"M9 8l-4 4 4 4 M15 8l4 4-4 4",[78,192,193],{"x":91,"y":131,"style":81},"XML stream",[78,195,196],{"x":91,"y":136,"style":137},"standard library",[198,199,201],"h2",{"id":200},"prerequisites","Prerequisites",[203,204,209],"pre",{"className":205,"code":206,"language":207,"meta":208,"style":208},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl python-calamine\n","bash","",[20,210,211],{"__ignoreMap":208},[212,213,215,219,223,226],"span",{"class":84,"line":214},1,[212,216,218],{"class":217},"sMTad","pip",[212,220,222],{"class":221},"srMev"," install",[212,224,225],{"class":221}," openpyxl",[212,227,228],{"class":221}," python-calamine\n",[10,230,231,232,235,236,239,240,243],{},"Three example files: ",[20,233,234],{},"big.xlsx"," has 200,000 data rows plus a header on a Data sheet and a small Notes sheet; ",[20,237,238],{},"export.xlsx"," is the same data with its declared size broken, as some export tools write it; ",[20,241,242],{},"formatted.xlsx"," has 10 data rows followed by 489 rows that are highlighted but empty.",[198,245,247],{"id":246},"step-1-read-the-declared-size","Step 1 — Read the declared size",[10,249,250,251,254],{},"The first thing in each sheet's XML is a ",[20,252,253],{},"\u003Cdimension ref=\"A1:H200001\"\u002F>"," tag giving the used range. In read-only mode, openpyxl reads it without touching the rows:",[203,256,260],{"className":257,"code":258,"language":259,"meta":208,"style":208},"language-python shiki shiki-themes github-light github-dark-high-contrast","import time\nfrom openpyxl import load_workbook\n\nstart = time.perf_counter()\nwb = load_workbook(\"big.xlsx\", read_only=True)\nws = wb[\"Data\"]\nprint(ws.calculate_dimension(), ws.max_row, ws.max_column)\nprint(f\"{time.perf_counter() - start:.3f} s\")\nwb.close()\n","python",[20,261,262,272,286,293,305,335,352,361,399],{"__ignoreMap":208},[212,263,264,268],{"class":84,"line":214},[212,265,267],{"class":266},"s-kum","import",[212,269,271],{"class":270},"skGVy"," time\n",[212,273,275,278,281,283],{"class":84,"line":274},2,[212,276,277],{"class":266},"from",[212,279,280],{"class":270}," openpyxl ",[212,282,267],{"class":266},[212,284,285],{"class":270}," load_workbook\n",[212,287,289],{"class":84,"line":288},3,[212,290,292],{"emptyLinePlaceholder":291},true,"\n",[212,294,296,299,302],{"class":84,"line":295},4,[212,297,298],{"class":270},"start ",[212,300,301],{"class":266},"=",[212,303,304],{"class":270}," time.perf_counter()\n",[212,306,308,311,313,316,319,322,326,328,332],{"class":84,"line":307},5,[212,309,310],{"class":270},"wb ",[212,312,301],{"class":266},[212,314,315],{"class":270}," load_workbook(",[212,317,318],{"class":221},"\"big.xlsx\"",[212,320,321],{"class":270},", ",[212,323,325],{"class":324},"sa561","read_only",[212,327,301],{"class":266},[212,329,331],{"class":330},"sP0c6","True",[212,333,334],{"class":270},")\n",[212,336,338,341,343,346,349],{"class":84,"line":337},6,[212,339,340],{"class":270},"ws ",[212,342,301],{"class":266},[212,344,345],{"class":270}," wb[",[212,347,348],{"class":221},"\"Data\"",[212,350,351],{"class":270},"]\n",[212,353,355,358],{"class":84,"line":354},7,[212,356,357],{"class":330},"print",[212,359,360],{"class":270},"(ws.calculate_dimension(), ws.max_row, ws.max_column)\n",[212,362,364,366,369,372,375,379,382,385,388,391,394,397],{"class":84,"line":363},8,[212,365,357],{"class":330},[212,367,368],{"class":270},"(",[212,370,371],{"class":266},"f",[212,373,374],{"class":221},"\"",[212,376,378],{"class":377},"sSjpA","{",[212,380,381],{"class":270},"time.perf_counter() ",[212,383,384],{"class":266},"-",[212,386,387],{"class":270}," start",[212,389,390],{"class":266},":.3f",[212,392,393],{"class":377},"}",[212,395,396],{"class":221}," s\"",[212,398,334],{"class":270},[212,400,402],{"class":84,"line":401},9,[212,403,404],{"class":270},"wb.close()\n",[10,406,407,408,410,411,414],{},"200,001 rows — 200,000 data rows plus the header — and eight columns, in a few milliseconds. ",[20,409,133],{}," includes the header, so subtract one for data rows. Closing the workbook matters in read-only mode, because openpyxl keeps the file open while the workbook object exists. Without ",[20,412,413],{},"read_only=True",", openpyxl would parse every cell before answering. For a CSV export the equivalent quick count is the number of lines, with the caveat that a quoted field containing a line break spans two lines and inflates the total.",[198,416,418],{"id":417},"step-2-recognise-a-size-you-cannot-trust","Step 2 — Recognise a size you cannot trust",[10,420,421,422,425],{},"The declared size is written by whatever program produced the file, and not all of them get it right. Some write ",[20,423,424],{},"A1"," regardless of the data; some omit the tag. openpyxl in read-only mode takes the tag at its word:",[203,427,429],{"className":257,"code":428,"language":259,"meta":208,"style":208},"wb = load_workbook(\"export.xlsx\", read_only=True)\nws = wb[\"Data\"]\nprint(\"declared:\", ws.calculate_dimension(), \"max_row:\", ws.max_row)\n\nws.reset_dimensions()\nstart = time.perf_counter()\nprint(\"scanned:\", ws.calculate_dimension(force=True), \"max_row:\", ws.max_row, f\"{time.perf_counter() - start:.1f} s\")\nwb.close()\n",[20,430,431,452,464,482,486,491,499,547],{"__ignoreMap":208},[212,432,433,435,437,439,442,444,446,448,450],{"class":84,"line":214},[212,434,310],{"class":270},[212,436,301],{"class":266},[212,438,315],{"class":270},[212,440,441],{"class":221},"\"export.xlsx\"",[212,443,321],{"class":270},[212,445,325],{"class":324},[212,447,301],{"class":266},[212,449,331],{"class":330},[212,451,334],{"class":270},[212,453,454,456,458,460,462],{"class":84,"line":274},[212,455,340],{"class":270},[212,457,301],{"class":266},[212,459,345],{"class":270},[212,461,348],{"class":221},[212,463,351],{"class":270},[212,465,466,468,470,473,476,479],{"class":84,"line":288},[212,467,357],{"class":330},[212,469,368],{"class":270},[212,471,472],{"class":221},"\"declared:\"",[212,474,475],{"class":270},", ws.calculate_dimension(), ",[212,477,478],{"class":221},"\"max_row:\"",[212,480,481],{"class":270},", ws.max_row)\n",[212,483,484],{"class":84,"line":295},[212,485,292],{"emptyLinePlaceholder":291},[212,487,488],{"class":84,"line":307},[212,489,490],{"class":270},"ws.reset_dimensions()\n",[212,492,493,495,497],{"class":84,"line":337},[212,494,298],{"class":270},[212,496,301],{"class":266},[212,498,304],{"class":270},[212,500,501,503,505,508,511,514,516,518,521,523,526,528,530,532,534,536,538,541,543,545],{"class":84,"line":354},[212,502,357],{"class":330},[212,504,368],{"class":270},[212,506,507],{"class":221},"\"scanned:\"",[212,509,510],{"class":270},", ws.calculate_dimension(",[212,512,513],{"class":324},"force",[212,515,301],{"class":266},[212,517,331],{"class":330},[212,519,520],{"class":270},"), ",[212,522,478],{"class":221},[212,524,525],{"class":270},", ws.max_row, ",[212,527,371],{"class":266},[212,529,374],{"class":221},[212,531,378],{"class":377},[212,533,381],{"class":270},[212,535,384],{"class":266},[212,537,387],{"class":270},[212,539,540],{"class":266},":.1f",[212,542,393],{"class":377},[212,544,396],{"class":221},[212,546,334],{"class":270},[212,548,549],{"class":84,"line":363},[212,550,404],{"class":270},[10,552,553,554,557,558,561,562,565,566,568],{},"The export claims a single row. ",[20,555,556],{},"reset_dimensions()"," discards the declared size and ",[20,559,560],{},"calculate_dimension(force=True)"," works it out by reading every row — correct, but now the full scan takes seconds rather than milliseconds. A sheet reporting ",[20,563,564],{},"A1:A1",", or a ",[20,567,133],{}," of 1 for a file several megabytes in size, is the signal to fall back to counting.",[10,570,571,572,576,577,579],{},"The opposite problem is a size that is too ",[573,574,575],"em",{},"large",". Rows that carry formatting but no values count toward the used range, so a fill applied to a block of empty rows inflates ",[20,578,133],{},":",[203,581,583],{"className":257,"code":582,"language":259,"meta":208,"style":208},"wb = load_workbook(\"formatted.xlsx\", read_only=True)\nws = wb[\"Data\"]\nwith_values = sum(1 for row in ws.iter_rows(values_only=True) if any(v is not None for v in row))\nprint(\"max_row:\", ws.max_row, \"rows with values:\", with_values)\nwb.close()\n",[20,584,585,606,618,683,699],{"__ignoreMap":208},[212,586,587,589,591,593,596,598,600,602,604],{"class":84,"line":214},[212,588,310],{"class":270},[212,590,301],{"class":266},[212,592,315],{"class":270},[212,594,595],{"class":221},"\"formatted.xlsx\"",[212,597,321],{"class":270},[212,599,325],{"class":324},[212,601,301],{"class":266},[212,603,331],{"class":330},[212,605,334],{"class":270},[212,607,608,610,612,614,616],{"class":84,"line":274},[212,609,340],{"class":270},[212,611,301],{"class":266},[212,613,345],{"class":270},[212,615,348],{"class":221},[212,617,351],{"class":270},[212,619,620,623,625,628,630,633,636,639,642,645,648,650,652,655,658,661,664,667,670,673,675,678,680],{"class":84,"line":288},[212,621,622],{"class":270},"with_values ",[212,624,301],{"class":266},[212,626,627],{"class":330}," sum",[212,629,368],{"class":270},[212,631,632],{"class":330},"1",[212,634,635],{"class":266}," for",[212,637,638],{"class":270}," row ",[212,640,641],{"class":266},"in",[212,643,644],{"class":270}," ws.iter_rows(",[212,646,647],{"class":324},"values_only",[212,649,301],{"class":266},[212,651,331],{"class":330},[212,653,654],{"class":270},") ",[212,656,657],{"class":266},"if",[212,659,660],{"class":330}," any",[212,662,663],{"class":270},"(v ",[212,665,666],{"class":266},"is",[212,668,669],{"class":266}," not",[212,671,672],{"class":330}," None",[212,674,635],{"class":266},[212,676,677],{"class":270}," v ",[212,679,641],{"class":266},[212,681,682],{"class":270}," row))\n",[212,684,685,687,689,691,693,696],{"class":84,"line":295},[212,686,357],{"class":330},[212,688,368],{"class":270},[212,690,478],{"class":221},[212,692,525],{"class":270},[212,694,695],{"class":221},"\"rows with values:\"",[212,697,698],{"class":270},", with_values)\n",[212,700,701],{"class":84,"line":307},[212,702,404],{"class":270},[10,704,705],{},"Five hundred declared rows, eleven with data. For a quick \"is this file empty?\" check the declared size is still fine; for anything that relies on the count being exact, count rows with values.",[30,707,39,712,39,715,39,718,39,721,39,728,39,736,39,742,39,748,39,754,39,761,39,765,39,770,39,774,39,779,39,782,39,787,39,792,39,796,39,800,39,805,39,808,39,813,39,816],{"viewBox":708,"role":33,"ariaLabelledBy":709,"xmlns":37,"style":38},"4 0 752 207",[710,711],"count-traps-t","count-traps-d",[41,713,714],{"id":710},"When the declared size misleads",[45,716,717],{"id":711},"Two panels. A missing or wrong dimension, as some export tools write it, makes max_row report 1 for a sheet of 200,001 rows; the fix is to reset dimensions and scan, or count with calamine. Formatted empty rows make max_row report 500 for a sheet with 11 rows of values; the fix is to count rows that hold at least one value.",[49,719],{"x":51,"y":52,"width":53,"height":720,"fill":55},"207",[49,722],{"x":116,"y":59,"width":723,"height":724,"rx":725,"fill":726,"stroke":727,"style":65},"351.0","175.0","14","#fee8f2","var(--accent,#f43f8f)",[67,729,731],{"transform":730},"translate(182.5,29.0) scale(1.083)",[71,732],{"d":733,"fill":74,"stroke":734,"style":735},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--accent-ink,#be185d)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[78,737,741],{"x":738,"y":739,"style":740},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","Too small — dimension says A1",[84,743],{"x1":744,"y1":745,"x2":746,"y2":745,"stroke":727,"style":747},"36.0","91.0","355.0","stroke-width:1px",[78,749,753],{"x":750,"y":751,"style":752},"60.0","111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","max_row 1",[67,755,757],{"transform":756},"translate(38.5,99.5) scale(0.625)",[71,758],{"d":759,"fill":74,"stroke":727,"style":760},"M6 6l12 12 M18 6L6 18","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[78,762,764],{"x":750,"y":763,"style":752},"137.0","really 200,001 rows",[67,766,768],{"transform":767},"translate(38.5,125.5) scale(0.625)",[71,769],{"d":733,"fill":74,"stroke":155,"style":760},[78,771,773],{"x":750,"y":772,"style":752},"163.0","reset and scan, or calamine",[67,775,777],{"transform":776},"translate(38.5,151.5) scale(0.625)",[71,778],{"d":127,"fill":74,"stroke":121,"style":760},[49,780],{"x":781,"y":59,"width":723,"height":724,"rx":725,"fill":154,"stroke":155,"style":65},"389.0",[67,783,785],{"transform":784},"translate(551.5,29.0) scale(1.083)",[71,786],{"d":733,"fill":74,"stroke":162,"style":735},[78,788,791],{"x":789,"y":739,"style":790},"564.5","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Too large — formatted blanks",[84,793],{"x1":794,"y1":745,"x2":795,"y2":745,"stroke":155,"style":747},"405.0","724.0",[78,797,799],{"x":798,"y":751,"style":752},"429.0","max_row 500",[67,801,803],{"transform":802},"translate(407.5,99.5) scale(0.625)",[71,804],{"d":759,"fill":74,"stroke":727,"style":760},[78,806,807],{"x":798,"y":763,"style":752},"really 11 rows with values",[67,809,811],{"transform":810},"translate(407.5,125.5) scale(0.625)",[71,812],{"d":733,"fill":74,"stroke":155,"style":760},[78,814,815],{"x":798,"y":772,"style":752},"count rows with values",[67,817,819],{"transform":818},"translate(407.5,151.5) scale(0.625)",[71,820],{"d":127,"fill":74,"stroke":121,"style":760},[198,822,824],{"id":823},"step-3-count-real-rows-quickly-with-calamine","Step 3 — Count real rows quickly with calamine",[10,826,827,830],{},[20,828,829],{},"python-calamine"," is a Rust spreadsheet reader. It ignores the declared size, skips rows without values, and reads fast enough that a full count is usually acceptable:",[203,832,834],{"className":257,"code":833,"language":259,"meta":208,"style":208},"from python_calamine import CalamineWorkbook\n\nfor path in [\"big.xlsx\", \"export.xlsx\", \"formatted.xlsx\"]:\n    start = time.perf_counter()\n    sheet = CalamineWorkbook.from_path(path).get_sheet_by_name(\"Data\")\n    print(f\"{path:15} {sheet.height:>7} rows  {sheet.width} columns  {time.perf_counter() - start:.2f} s\")\n",[20,835,836,848,852,878,887,901],{"__ignoreMap":208},[212,837,838,840,843,845],{"class":84,"line":214},[212,839,277],{"class":266},[212,841,842],{"class":270}," python_calamine ",[212,844,267],{"class":266},[212,846,847],{"class":270}," CalamineWorkbook\n",[212,849,850],{"class":84,"line":274},[212,851,292],{"emptyLinePlaceholder":291},[212,853,854,857,860,862,865,867,869,871,873,875],{"class":84,"line":288},[212,855,856],{"class":266},"for",[212,858,859],{"class":270}," path ",[212,861,641],{"class":266},[212,863,864],{"class":270}," [",[212,866,318],{"class":221},[212,868,321],{"class":270},[212,870,441],{"class":221},[212,872,321],{"class":270},[212,874,595],{"class":221},[212,876,877],{"class":270},"]:\n",[212,879,880,883,885],{"class":84,"line":295},[212,881,882],{"class":270},"    start ",[212,884,301],{"class":266},[212,886,304],{"class":270},[212,888,889,892,894,897,899],{"class":84,"line":307},[212,890,891],{"class":270},"    sheet ",[212,893,301],{"class":266},[212,895,896],{"class":270}," CalamineWorkbook.from_path(path).get_sheet_by_name(",[212,898,348],{"class":221},[212,900,334],{"class":270},[212,902,903,906,908,910,912,914,916,919,921,924,927,930,932,935,937,940,942,945,947,949,951,953,956,958,960],{"class":84,"line":337},[212,904,905],{"class":330},"    print",[212,907,368],{"class":270},[212,909,371],{"class":266},[212,911,374],{"class":221},[212,913,378],{"class":377},[212,915,71],{"class":270},[212,917,918],{"class":266},":15",[212,920,393],{"class":377},[212,922,923],{"class":377}," {",[212,925,926],{"class":270},"sheet.height",[212,928,929],{"class":266},":>7",[212,931,393],{"class":377},[212,933,934],{"class":221}," rows  ",[212,936,378],{"class":377},[212,938,939],{"class":270},"sheet.width",[212,941,393],{"class":377},[212,943,944],{"class":221}," columns  ",[212,946,378],{"class":377},[212,948,381],{"class":270},[212,950,384],{"class":266},[212,952,387],{"class":270},[212,954,955],{"class":266},":.2f",[212,957,393],{"class":377},[212,959,396],{"class":221},[212,961,334],{"class":270},[10,963,964,965,968,969,321,972,975,976,979,980,984],{},"All three are counted correctly — 200,001 for both large files, whatever their dimension tag says, and 11 for the formatted one — in well under a second for the large ones. ",[20,966,967],{},"height"," includes the header row. calamine also reads ",[20,970,971],{},".xls",[20,973,974],{},".xlsb"," and ",[20,977,978],{},".ods",", so the same code counts older formats too; ",[14,981,983],{"href":982},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fspeed-up-pandas-excel-reads-with-the-calamine-engine\u002F","Speed Up pandas Excel Reads with the calamine Engine"," covers using it through pandas.",[198,986,988],{"id":987},"step-4-stream-the-sheet-xml-with-the-standard-library","Step 4 — Stream the sheet XML with the standard library",[10,990,991,992,995],{},"When you cannot add a dependency, count rows by streaming the sheet's XML out of the zip file. An xlsx is a zip archive; each sheet is an XML file whose ",[20,993,994],{},"\u003Crow>"," elements hold the cells. Find the right file through the workbook's relationships — sheet names do not map to file names directly — then count rows containing a value:",[203,997,999],{"className":257,"code":998,"language":259,"meta":208,"style":208},"import posixpath\nimport zipfile\nimport xml.etree.ElementTree as ET\n\nM = \"{http:\u002F\u002Fschemas.openxmlformats.org\u002Fspreadsheetml\u002F2006\u002Fmain}\"\nR = \"{http:\u002F\u002Fschemas.openxmlformats.org\u002FofficeDocument\u002F2006\u002Frelationships}\"\n\ndef sheet_files(z):\n    workbook = ET.fromstring(z.read(\"xl\u002Fworkbook.xml\"))\n    rels = {r.get(\"Id\"): r.get(\"Target\") for r in ET.fromstring(z.read(\"xl\u002F_rels\u002Fworkbook.xml.rels\"))}\n    files = {}\n    for s in workbook.find(f\"{M}sheets\"):\n        target = rels[s.get(f\"{R}id\")]\n        files[s.get(\"name\")] = target.lstrip(\"\u002F\") if target.startswith(\"\u002F\") else posixpath.join(\"xl\", target)\n    return files\n\ndef count_rows(path, sheet):\n    with zipfile.ZipFile(path) as z, z.open(sheet_files(z)[sheet]) as xml:\n        n = 0\n        for _, el in ET.iterparse(xml):\n            if el.tag == f\"{M}row\":\n                if el.find(f\"{M}c\u002F{M}v\") is not None or el.find(f\"{M}c\u002F{M}is\") is not None:\n                    n += 1\n                el.clear()\n        return n\n\nfor path in [\"big.xlsx\", \"export.xlsx\", \"formatted.xlsx\"]:\n    start = time.perf_counter()\n    print(f\"{path:15} {count_rows(path, 'Data'):>7} rows  {time.perf_counter() - start:.2f} s\")\n",[20,1000,1001,1008,1015,1028,1032,1053,1071,1075,1087,1106,1145,1156,1187,1215,1258,1267,1272,1283,1302,1313,1329,1358,1433,1445,1451,1460,1465,1488,1497],{"__ignoreMap":208},[212,1002,1003,1005],{"class":84,"line":214},[212,1004,267],{"class":266},[212,1006,1007],{"class":270}," posixpath\n",[212,1009,1010,1012],{"class":84,"line":274},[212,1011,267],{"class":266},[212,1013,1014],{"class":270}," zipfile\n",[212,1016,1017,1019,1022,1025],{"class":84,"line":288},[212,1018,267],{"class":266},[212,1020,1021],{"class":270}," xml.etree.ElementTree ",[212,1023,1024],{"class":266},"as",[212,1026,1027],{"class":330}," ET\n",[212,1029,1030],{"class":84,"line":295},[212,1031,292],{"emptyLinePlaceholder":291},[212,1033,1034,1037,1039,1042,1045,1047,1050],{"class":84,"line":307},[212,1035,1036],{"class":270},"M ",[212,1038,301],{"class":266},[212,1040,1041],{"class":221}," \"",[212,1043,1044],{"class":377},"{http",[212,1046,579],{"class":266},[212,1048,1049],{"class":377},"\u002F\u002Fschemas.openxmlformats.org\u002Fspreadsheetml\u002F2006\u002Fmain}",[212,1051,1052],{"class":221},"\"\n",[212,1054,1055,1058,1060,1062,1064,1066,1069],{"class":84,"line":337},[212,1056,1057],{"class":270},"R ",[212,1059,301],{"class":266},[212,1061,1041],{"class":221},[212,1063,1044],{"class":377},[212,1065,579],{"class":266},[212,1067,1068],{"class":377},"\u002F\u002Fschemas.openxmlformats.org\u002FofficeDocument\u002F2006\u002Frelationships}",[212,1070,1052],{"class":221},[212,1072,1073],{"class":84,"line":354},[212,1074,292],{"emptyLinePlaceholder":291},[212,1076,1077,1080,1084],{"class":84,"line":363},[212,1078,1079],{"class":266},"def",[212,1081,1083],{"class":1082},"s_Opv"," sheet_files",[212,1085,1086],{"class":270},"(z):\n",[212,1088,1089,1092,1094,1097,1100,1103],{"class":84,"line":401},[212,1090,1091],{"class":270},"    workbook ",[212,1093,301],{"class":266},[212,1095,1096],{"class":330}," ET",[212,1098,1099],{"class":270},".fromstring(z.read(",[212,1101,1102],{"class":221},"\"xl\u002Fworkbook.xml\"",[212,1104,1105],{"class":270},"))\n",[212,1107,1109,1112,1114,1117,1120,1123,1126,1128,1130,1133,1135,1137,1139,1142],{"class":84,"line":1108},10,[212,1110,1111],{"class":270},"    rels ",[212,1113,301],{"class":266},[212,1115,1116],{"class":270}," {r.get(",[212,1118,1119],{"class":221},"\"Id\"",[212,1121,1122],{"class":270},"): r.get(",[212,1124,1125],{"class":221},"\"Target\"",[212,1127,654],{"class":270},[212,1129,856],{"class":266},[212,1131,1132],{"class":270}," r ",[212,1134,641],{"class":266},[212,1136,1096],{"class":330},[212,1138,1099],{"class":270},[212,1140,1141],{"class":221},"\"xl\u002F_rels\u002Fworkbook.xml.rels\"",[212,1143,1144],{"class":270},"))}\n",[212,1146,1148,1151,1153],{"class":84,"line":1147},11,[212,1149,1150],{"class":270},"    files ",[212,1152,301],{"class":266},[212,1154,1155],{"class":270}," {}\n",[212,1157,1159,1162,1165,1167,1170,1172,1174,1176,1179,1181,1184],{"class":84,"line":1158},12,[212,1160,1161],{"class":266},"    for",[212,1163,1164],{"class":270}," s ",[212,1166,641],{"class":266},[212,1168,1169],{"class":270}," workbook.find(",[212,1171,371],{"class":266},[212,1173,374],{"class":221},[212,1175,378],{"class":377},[212,1177,1178],{"class":270},"M",[212,1180,393],{"class":377},[212,1182,1183],{"class":221},"sheets\"",[212,1185,1186],{"class":270},"):\n",[212,1188,1190,1193,1195,1198,1200,1202,1204,1207,1209,1212],{"class":84,"line":1189},13,[212,1191,1192],{"class":270},"        target ",[212,1194,301],{"class":266},[212,1196,1197],{"class":270}," rels[s.get(",[212,1199,371],{"class":266},[212,1201,374],{"class":221},[212,1203,378],{"class":377},[212,1205,1206],{"class":270},"R",[212,1208,393],{"class":377},[212,1210,1211],{"class":221},"id\"",[212,1213,1214],{"class":270},")]\n",[212,1216,1218,1221,1224,1227,1229,1232,1235,1237,1239,1242,1244,1246,1249,1252,1255],{"class":84,"line":1217},14,[212,1219,1220],{"class":270},"        files[s.get(",[212,1222,1223],{"class":221},"\"name\"",[212,1225,1226],{"class":270},")] ",[212,1228,301],{"class":266},[212,1230,1231],{"class":270}," target.lstrip(",[212,1233,1234],{"class":221},"\"\u002F\"",[212,1236,654],{"class":270},[212,1238,657],{"class":266},[212,1240,1241],{"class":270}," target.startswith(",[212,1243,1234],{"class":221},[212,1245,654],{"class":270},[212,1247,1248],{"class":266},"else",[212,1250,1251],{"class":270}," posixpath.join(",[212,1253,1254],{"class":221},"\"xl\"",[212,1256,1257],{"class":270},", target)\n",[212,1259,1261,1264],{"class":84,"line":1260},15,[212,1262,1263],{"class":266},"    return",[212,1265,1266],{"class":270}," files\n",[212,1268,1270],{"class":84,"line":1269},16,[212,1271,292],{"emptyLinePlaceholder":291},[212,1273,1275,1277,1280],{"class":84,"line":1274},17,[212,1276,1079],{"class":266},[212,1278,1279],{"class":1082}," count_rows",[212,1281,1282],{"class":270},"(path, sheet):\n",[212,1284,1286,1289,1292,1294,1297,1299],{"class":84,"line":1285},18,[212,1287,1288],{"class":266},"    with",[212,1290,1291],{"class":270}," zipfile.ZipFile(path) ",[212,1293,1024],{"class":266},[212,1295,1296],{"class":270}," z, z.open(sheet_files(z)[sheet]) ",[212,1298,1024],{"class":266},[212,1300,1301],{"class":270}," xml:\n",[212,1303,1305,1308,1310],{"class":84,"line":1304},19,[212,1306,1307],{"class":270},"        n ",[212,1309,301],{"class":266},[212,1311,1312],{"class":330}," 0\n",[212,1314,1316,1319,1322,1324,1326],{"class":84,"line":1315},20,[212,1317,1318],{"class":266},"        for",[212,1320,1321],{"class":270}," _, el ",[212,1323,641],{"class":266},[212,1325,1096],{"class":330},[212,1327,1328],{"class":270},".iterparse(xml):\n",[212,1330,1332,1335,1338,1341,1344,1346,1348,1350,1352,1355],{"class":84,"line":1331},21,[212,1333,1334],{"class":266},"            if",[212,1336,1337],{"class":270}," el.tag ",[212,1339,1340],{"class":266},"==",[212,1342,1343],{"class":266}," f",[212,1345,374],{"class":221},[212,1347,378],{"class":377},[212,1349,1178],{"class":270},[212,1351,393],{"class":377},[212,1353,1354],{"class":221},"row\"",[212,1356,1357],{"class":270},":\n",[212,1359,1361,1364,1367,1369,1371,1373,1375,1377,1380,1382,1384,1386,1389,1391,1393,1395,1397,1400,1402,1404,1406,1408,1410,1412,1414,1416,1418,1420,1423,1425,1427,1429,1431],{"class":84,"line":1360},22,[212,1362,1363],{"class":266},"                if",[212,1365,1366],{"class":270}," el.find(",[212,1368,371],{"class":266},[212,1370,374],{"class":221},[212,1372,378],{"class":377},[212,1374,1178],{"class":270},[212,1376,393],{"class":377},[212,1378,1379],{"class":221},"c\u002F",[212,1381,378],{"class":377},[212,1383,1178],{"class":270},[212,1385,393],{"class":377},[212,1387,1388],{"class":221},"v\"",[212,1390,654],{"class":270},[212,1392,666],{"class":266},[212,1394,669],{"class":266},[212,1396,672],{"class":330},[212,1398,1399],{"class":266}," or",[212,1401,1366],{"class":270},[212,1403,371],{"class":266},[212,1405,374],{"class":221},[212,1407,378],{"class":377},[212,1409,1178],{"class":270},[212,1411,393],{"class":377},[212,1413,1379],{"class":221},[212,1415,378],{"class":377},[212,1417,1178],{"class":270},[212,1419,393],{"class":377},[212,1421,1422],{"class":221},"is\"",[212,1424,654],{"class":270},[212,1426,666],{"class":266},[212,1428,669],{"class":266},[212,1430,672],{"class":330},[212,1432,1357],{"class":270},[212,1434,1436,1439,1442],{"class":84,"line":1435},23,[212,1437,1438],{"class":270},"                    n ",[212,1440,1441],{"class":266},"+=",[212,1443,1444],{"class":330}," 1\n",[212,1446,1448],{"class":84,"line":1447},24,[212,1449,1450],{"class":270},"                el.clear()\n",[212,1452,1454,1457],{"class":84,"line":1453},25,[212,1455,1456],{"class":266},"        return",[212,1458,1459],{"class":270}," n\n",[212,1461,1463],{"class":84,"line":1462},26,[212,1464,292],{"emptyLinePlaceholder":291},[212,1466,1468,1470,1472,1474,1476,1478,1480,1482,1484,1486],{"class":84,"line":1467},27,[212,1469,856],{"class":266},[212,1471,859],{"class":270},[212,1473,641],{"class":266},[212,1475,864],{"class":270},[212,1477,318],{"class":221},[212,1479,321],{"class":270},[212,1481,441],{"class":221},[212,1483,321],{"class":270},[212,1485,595],{"class":221},[212,1487,877],{"class":270},[212,1489,1491,1493,1495],{"class":84,"line":1490},28,[212,1492,882],{"class":270},[212,1494,301],{"class":266},[212,1496,304],{"class":270},[212,1498,1500,1502,1504,1506,1508,1510,1512,1514,1516,1518,1521,1524,1527,1529,1531,1533,1535,1537,1539,1541,1543,1545,1547],{"class":84,"line":1499},29,[212,1501,905],{"class":330},[212,1503,368],{"class":270},[212,1505,371],{"class":266},[212,1507,374],{"class":221},[212,1509,378],{"class":377},[212,1511,71],{"class":270},[212,1513,918],{"class":266},[212,1515,393],{"class":377},[212,1517,923],{"class":377},[212,1519,1520],{"class":270},"count_rows(path, ",[212,1522,1523],{"class":221},"'Data'",[212,1525,1526],{"class":270},")",[212,1528,929],{"class":266},[212,1530,393],{"class":377},[212,1532,934],{"class":221},[212,1534,378],{"class":377},[212,1536,381],{"class":270},[212,1538,384],{"class":266},[212,1540,387],{"class":270},[212,1542,955],{"class":266},[212,1544,393],{"class":377},[212,1546,396],{"class":221},[212,1548,334],{"class":270},[10,1550,1551,1552,1555,1556,1559,1560,1563],{},"The same counts as calamine, in constant memory: ",[20,1553,1554],{},"el.clear()"," discards each row once counted, so a million-row sheet uses no more memory than a small one. A row counts only if one of its cells has a value (",[20,1557,1558],{},"\u003Cv>",") or inline text (",[20,1561,1562],{},"\u003Cis>","), which excludes the formatted blanks. It is slower than calamine but needs nothing beyond Python itself.",[30,1565,39,1571,39,1574,39,1577,39,1581,39,1586,39,1593,39,1599,39,1604,39,1609,39,1612,39,1615,39,1618,39,1622,39,1625,39,1628,39,1631,39,1636,39,1639,39,1643,39,1646,39,1651,39,1654,39,1658,39,1661],{"viewBox":1566,"role":33,"ariaLabelledBy":1567,"xmlns":37,"style":1570},"4 0 725 252",[1568,1569],"count-speed-t","count-speed-d","width:100%;max-width:725px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[41,1572,1573],{"id":1568},"Time to count a 200,000-row sheet",[45,1575,1576],{"id":1569},"Bar chart of timings on one machine for a sheet of 200,000 rows and 8 columns. Reading the declared size took 0.004 seconds, python-calamine 0.7 seconds, streaming the XML with the standard library 1.8 seconds, a full openpyxl read-only scan 4.5 seconds and reading one column with pandas 5.7 seconds.",[49,1578],{"x":51,"y":52,"width":1579,"height":1580,"fill":55},"725","252",[78,1582,1585],{"x":80,"y":1583,"style":1584},"31.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Seconds — lower is better",[49,1587],{"x":116,"y":1588,"width":1589,"height":1590,"rx":1591,"fill":1592,"stroke":107,"style":747},"48.0","121.6","28.0","7","#e7ebef",[78,1594,1598],{"x":1595,"y":1596,"style":1597},"80.8","66.5","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","declared size",[49,1600],{"x":1601,"y":1588,"width":1602,"height":1590,"rx":1591,"fill":120,"stroke":121,"style":1603},"151.6","6.0","stroke-width:1.5px",[78,1605,1608],{"x":1606,"y":1596,"style":1607},"165.6","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start","0.004",[49,1610],{"x":116,"y":1611,"width":1589,"height":1590,"rx":1591,"fill":1592,"stroke":107,"style":747},"88.0",[78,1613,166],{"x":1595,"y":1614,"style":1597},"106.5",[49,1616],{"x":1601,"y":1611,"width":1617,"height":1590,"rx":1591,"fill":120,"stroke":121,"style":1603},"65.6",[78,1619,1621],{"x":1620,"y":1614,"style":1607},"225.2","0.7",[49,1623],{"x":116,"y":1624,"width":1589,"height":1590,"rx":1591,"fill":1592,"stroke":107,"style":747},"128.0",[78,1626,193],{"x":1595,"y":1627,"style":1597},"146.5",[49,1629],{"x":1601,"y":1624,"width":1630,"height":1590,"rx":1591,"fill":63,"stroke":64,"style":1603},"168.7",[78,1632,1635],{"x":1633,"y":1627,"style":1634},"328.3","font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:start","1.8",[49,1637],{"x":116,"y":1638,"width":1589,"height":1590,"rx":1591,"fill":1592,"stroke":107,"style":747},"168.0",[78,1640,1642],{"x":1595,"y":1641,"style":1597},"186.5","openpyxl scan",[49,1644],{"x":1601,"y":1638,"width":1645,"height":1590,"rx":1591,"fill":154,"stroke":155,"style":1603},"421.7",[78,1647,1650],{"x":1648,"y":1641,"style":1649},"581.3","font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:start","4.5",[49,1652],{"x":116,"y":1653,"width":1589,"height":1590,"rx":1591,"fill":1592,"stroke":107,"style":747},"208.0",[78,1655,1657],{"x":1595,"y":1656,"style":1597},"226.5","pandas read",[49,1659],{"x":1601,"y":1653,"width":1660,"height":1590,"rx":1591,"fill":726,"stroke":727,"style":1603},"534.2",[78,1662,1665],{"x":1663,"y":1656,"style":1664},"693.8","font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:start","5.7",[198,1667,1669],{"id":1668},"step-5-count-every-sheet-in-a-folder-of-files","Step 5 — Count every sheet in a folder of files",[10,1671,1672],{},"A common use is an inventory: how many rows does each sheet in each incoming file have? Use the declared size first and fall back to calamine only when it looks wrong:",[203,1674,1676],{"className":257,"code":1675,"language":259,"meta":208,"style":208},"from pathlib import Path\n\ndef sheet_counts(path):\n    counts = {}\n    wb = load_workbook(path, read_only=True)\n    for ws in wb.worksheets:\n        declared = ws.max_row\n        if not declared or ws.calculate_dimension() == \"A1:A1\":\n            declared = CalamineWorkbook.from_path(path).get_sheet_by_name(ws.title).height\n        counts[ws.title] = declared\n    wb.close()\n    return counts\n\nfor path in sorted(Path(\".\").glob(\"*.xlsx\")):\n    print(f\"{path.name:15}\", sheet_counts(path))\n",[20,1677,1678,1690,1694,1704,1713,1731,1743,1753,1776,1786,1796,1801,1808,1812,1838],{"__ignoreMap":208},[212,1679,1680,1682,1685,1687],{"class":84,"line":214},[212,1681,277],{"class":266},[212,1683,1684],{"class":270}," pathlib ",[212,1686,267],{"class":266},[212,1688,1689],{"class":270}," Path\n",[212,1691,1692],{"class":84,"line":274},[212,1693,292],{"emptyLinePlaceholder":291},[212,1695,1696,1698,1701],{"class":84,"line":288},[212,1697,1079],{"class":266},[212,1699,1700],{"class":1082}," sheet_counts",[212,1702,1703],{"class":270},"(path):\n",[212,1705,1706,1709,1711],{"class":84,"line":295},[212,1707,1708],{"class":270},"    counts ",[212,1710,301],{"class":266},[212,1712,1155],{"class":270},[212,1714,1715,1718,1720,1723,1725,1727,1729],{"class":84,"line":307},[212,1716,1717],{"class":270},"    wb ",[212,1719,301],{"class":266},[212,1721,1722],{"class":270}," load_workbook(path, ",[212,1724,325],{"class":324},[212,1726,301],{"class":266},[212,1728,331],{"class":330},[212,1730,334],{"class":270},[212,1732,1733,1735,1738,1740],{"class":84,"line":337},[212,1734,1161],{"class":266},[212,1736,1737],{"class":270}," ws ",[212,1739,641],{"class":266},[212,1741,1742],{"class":270}," wb.worksheets:\n",[212,1744,1745,1748,1750],{"class":84,"line":354},[212,1746,1747],{"class":270},"        declared ",[212,1749,301],{"class":266},[212,1751,1752],{"class":270}," ws.max_row\n",[212,1754,1755,1758,1760,1763,1766,1769,1771,1774],{"class":84,"line":363},[212,1756,1757],{"class":266},"        if",[212,1759,669],{"class":266},[212,1761,1762],{"class":270}," declared ",[212,1764,1765],{"class":266},"or",[212,1767,1768],{"class":270}," ws.calculate_dimension() ",[212,1770,1340],{"class":266},[212,1772,1773],{"class":221}," \"A1:A1\"",[212,1775,1357],{"class":270},[212,1777,1778,1781,1783],{"class":84,"line":401},[212,1779,1780],{"class":270},"            declared ",[212,1782,301],{"class":266},[212,1784,1785],{"class":270}," CalamineWorkbook.from_path(path).get_sheet_by_name(ws.title).height\n",[212,1787,1788,1791,1793],{"class":84,"line":1108},[212,1789,1790],{"class":270},"        counts[ws.title] ",[212,1792,301],{"class":266},[212,1794,1795],{"class":270}," declared\n",[212,1797,1798],{"class":84,"line":1147},[212,1799,1800],{"class":270},"    wb.close()\n",[212,1802,1803,1805],{"class":84,"line":1158},[212,1804,1263],{"class":266},[212,1806,1807],{"class":270}," counts\n",[212,1809,1810],{"class":84,"line":1189},[212,1811,292],{"emptyLinePlaceholder":291},[212,1813,1814,1816,1818,1820,1823,1826,1829,1832,1835],{"class":84,"line":1217},[212,1815,856],{"class":266},[212,1817,859],{"class":270},[212,1819,641],{"class":266},[212,1821,1822],{"class":330}," sorted",[212,1824,1825],{"class":270},"(Path(",[212,1827,1828],{"class":221},"\".\"",[212,1830,1831],{"class":270},").glob(",[212,1833,1834],{"class":221},"\"*.xlsx\"",[212,1836,1837],{"class":270},")):\n",[212,1839,1840,1842,1844,1846,1848,1850,1853,1855,1857,1859],{"class":84,"line":1260},[212,1841,905],{"class":330},[212,1843,368],{"class":270},[212,1845,371],{"class":266},[212,1847,374],{"class":221},[212,1849,378],{"class":377},[212,1851,1852],{"class":270},"path.name",[212,1854,918],{"class":266},[212,1856,393],{"class":377},[212,1858,374],{"class":221},[212,1860,1861],{"class":270},", sheet_counts(path))\n",[10,1863,1864,1865,1868],{},"The broken export is caught and recounted; the good files cost milliseconds each. The formatted file still reports 500 — the declared size is not ",[573,1866,1867],{},"wrong"," there, it includes formatted rows — so use calamine's count throughout if you need rows with values rather than the used range.",[198,1870,1872],{"id":1871},"common-pitfalls","Common pitfalls",[1874,1875,1876,1888,1899,1905,1911],"ul",{},[1877,1878,1879,1883,1884,1887],"li",{},[1880,1881,1882],"strong",{},"Loading the data to count it."," ",[20,1885,1886],{},"len(pd.read_excel(...))"," parses every cell; read the declared size first.",[1877,1889,1890,1883,1893,1895,1896,1898],{},[1880,1891,1892],{},"Trusting the declared size blindly.",[20,1894,564],{}," or a ",[20,1897,133],{}," of 1 on a large file means it was not recorded.",[1877,1900,1901,1904],{},[1880,1902,1903],{},"Forgetting the header."," Every count here includes it; subtract one for data rows.",[1877,1906,1907,1910],{},[1880,1908,1909],{},"Formatted empty rows."," They inflate the used range; count rows with values when it matters.",[1877,1912,1913,1916,1917,1920],{},[1880,1914,1915],{},"Leaving read-only workbooks open."," Call ",[20,1918,1919],{},"wb.close()"," or the file stays locked on Windows.",[198,1922,1924],{"id":1923},"performance-notes","Performance notes",[10,1926,1927,1928,1932],{},"The declared size costs the same for any file size. calamine and the XML stream grow with the number of rows but stay several times faster than pandas or a full openpyxl scan; see ",[14,1929,1931],{"href":1930},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fspeed-up-openpyxl-with-read-only-mode\u002F","Speed Up openpyxl with Read-Only Mode"," for the openpyxl side.",[198,1934,1936],{"id":1935},"conclusion","Conclusion",[10,1938,1939,1940,1942,1943,1895,1945,1947,1948,1950],{},"Read ",[20,1941,133],{}," from a read-only openpyxl workbook for an instant answer, and close it afterwards. Treat ",[20,1944,564],{},[20,1946,133],{}," of 1 on a large file as a missing size, and remember that formatted empty rows inflate the count. When the declared size cannot be trusted, count rows with values using python-calamine's ",[20,1949,967],{},", or stream the sheet XML with the standard library — both far faster than loading the sheet into pandas.",[198,1952,1954],{"id":1953},"frequently-asked-questions","Frequently asked questions",[10,1956,1957,1960],{},[1880,1958,1959],{},"What is the fastest way to get the row count of an xlsx file?","\nOpen it with openpyxl in read-only mode and read ws.max_row. That comes from the dimension the file declares at the top of the sheet, so it takes milliseconds even for a million rows — as long as the program that wrote the file recorded it correctly.",[10,1962,1963,1966],{},[1880,1964,1965],{},"Why does openpyxl say max_row is 1 for a sheet with thousands of rows?","\nSome programs write a dimension of \"A1\" or none at all. In read-only mode openpyxl trusts it. Call ws.reset_dimensions() and then calculate_dimension(force=True), which scans the sheet, or count the rows another way.",[10,1968,1969,1972],{},[1880,1970,1971],{},"Why is max_row much larger than the number of rows with data?","\nRows that have formatting but no values — a fill or border applied to a whole block — still count toward the sheet's dimension. Count rows that contain at least one value instead, for example with python-calamine's height.",[10,1974,1975,1978],{},[1880,1976,1977],{},"Does counting rows require reading the whole file?","\nOnly when the declared dimension cannot be trusted. Then the rows have to be scanned, but streaming the sheet XML or using calamine is several times faster than loading the data into pandas.",[198,1980,1982],{"id":1981},"related","Related",[1874,1984,1985,1991,1997,2004],{},[1877,1986,1987,1988,1990],{},"Up one level: ",[14,1989,27],{"href":26}," — reading and writing big workbooks.",[1877,1992,1993,1996],{},[14,1994,1995],{"href":16},"Read a Large Excel File in Chunks with pandas"," — what to do once you know it is big.",[1877,1998,1999,2003],{},[14,2000,2002],{"href":2001},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-openpyxl-max-row-counting-empty-rows\u002F","Fix openpyxl max_row Counting Empty Rows"," — the same inflation when editing.",[1877,2005,2006,2010],{},[14,2007,2009],{"href":2008},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Freduce-memory-when-reading-excel-with-pandas\u002F","Reduce Memory When Reading Excel with pandas"," — loading big sheets lean.",[2012,2013,2014],"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 .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}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}",{"title":208,"searchDepth":274,"depth":274,"links":2016},[2017,2018,2019,2020,2021,2022,2023,2024,2025,2026,2027],{"id":200,"depth":274,"text":201},{"id":246,"depth":274,"text":247},{"id":417,"depth":274,"text":418},{"id":823,"depth":274,"text":824},{"id":987,"depth":274,"text":988},{"id":1668,"depth":274,"text":1669},{"id":1871,"depth":274,"text":1872},{"id":1923,"depth":274,"text":1924},{"id":1935,"depth":274,"text":1936},{"id":1953,"depth":274,"text":1954},{"id":1981,"depth":274,"text":1982},"2026-09-25","Find out how many rows a large workbook has before deciding how to process it — in milliseconds, without reading it into pandas. Read the size the file declares with openpyxl, spot when that size is missing or inflated by formatted empty rows, and count real rows with calamine or by streaming the sheet's XML.","md",[2032,2034,2036,2038],{"q":1959,"a":2033},"Open it with openpyxl in read-only mode and read ws.max_row. That comes from the dimension the file declares at the top of the sheet, so it takes milliseconds even for a million rows — as long as the program that wrote the file recorded it correctly.",{"q":1965,"a":2035},"Some programs write a dimension of \"A1\" or none at all. In read-only mode openpyxl trusts it. Call ws.reset_dimensions() and then calculate_dimension(force=True), which scans the sheet, or count the rows another way.",{"q":1971,"a":2037},"Rows that have formatting but no values — a fill or border applied to a whole block — still count toward the sheet's dimension. Count rows that contain at least one value instead, for example with python-calamine's height.",{"q":1977,"a":2039},"Only when the declared dimension cannot be trusted. Then the rows have to be scanned, but streaming the sheet XML or using calamine is several times faster than loading the data into pandas.","count rows in an excel file without loading it",null,{"breadcrumb":2043},[2044,2047,2050],{"name":2045,"item":2046},"Home","\u002F",{"name":2048,"item":2049},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":27,"item":26},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fcount-rows-in-an-excel-file-without-loading-it",{"title":2053,"description":2054},"Count Excel Rows in Python Without Loading the File","Count rows in a large xlsx quickly: openpyxl read-only max_row and the dimension tag, why it can say 1 or include formatted blank rows, reset_dimensions, python-calamine height, streaming the sheet XML, and counting every sheet in many files.","count-rows-in-an-excel-file-without-loading-it","advanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fcount-rows-in-an-excel-file-without-loading-it\u002Findex",[2058,2059],"large-files","openpyxl","how-to","Gr6NgVkp3_-1oHfii-TvgAwJ-kXzvokdRukXHysNe7w",[2063,2067],{"title":2064,"path":2065,"stem":2066,"children":-1},"Convert Excel to CSV with Python","\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fconvert-excel-to-csv-with-python","advanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fconvert-excel-to-csv-with-python\u002Findex",{"title":2068,"path":2069,"stem":2070,"children":-1},"Find Out Why an Excel File Is Slow to Open","\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Ffind-out-why-an-excel-file-is-slow-to-open","advanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Ffind-out-why-an-excel-file-is-slow-to-open\u002Findex",[2072,2074],{"path":2073,"title":2048},"\u002Fadvanced-data-transformation-and-cleaning",{"path":2075,"title":27},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python",[],[2078,2080,2083,2086,2089,2092],{"path":2079,"title":983,"kind":2060},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fspeed-up-pandas-excel-reads-with-the-calamine-engine",{"path":2081,"title":2082,"kind":2060},"\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fprofile-a-slow-excel-report-script","Profile a Slow Excel Report Script in Python",{"path":2084,"title":2085,"kind":2060},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-a-million-rows-to-excel-with-xlsxwriter-constant-memory","Write a Million Rows to Excel with xlsxwriter",{"path":2087,"title":2088,"kind":2060},"\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fbenchmark-python-excel-read-and-write-speed","Benchmark Python Excel Read and Write Speed",{"path":2090,"title":2091,"kind":2060},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmoving-data-between-excel-and-databases\u002Fexport-a-large-database-table-to-excel-in-chunks","Export a Large Database Table to Excel in Chunks",{"path":2093,"title":2094,"kind":2060},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmoving-data-between-excel-and-databases\u002Fquery-excel-files-with-sql-using-duckdb","Query Excel Files with SQL Using DuckDB",1790972106642]