[{"data":1,"prerenderedAt":2188},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-xls-files-in-python-with-xlrd-and-pandas":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-xls-files-in-python-with-xlrd-and-pandas":2180},{"id":4,"title":5,"body":6,"dateModified":2153,"datePublished":2153,"description":2154,"extension":2155,"faq":2156,"meta":2171,"navigation":305,"path":2172,"seo":2173,"slug":2176,"stem":2177,"type":2178,"__hash__":2179},"docs\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-xls-files-in-python-with-xlrd-and-pandas\u002Findex.md","Read .xls Files in Python with xlrd and pandas",{"type":7,"value":8,"toc":2141},"minimark",[9,28,173,178,189,233,244,265,269,278,343,346,375,387,495,499,514,825,832,836,859,956,962,1178,1181,1281,1298,1302,1323,1363,1372,1376,1525,1528,1619,1623,1700,1708,1714,1755,1773,1776,1967,1971,1989,1993,2016,2037,2052,2075,2097,2101,2137],[10,11,12,13,17,18,21,22,27],"p",{},"Legacy ",[14,15,16],"code",{},".xls"," workbooks keep arriving — from ERP exports, from banking portals, from a colleague's Excel 2003 habit. They are the one spreadsheet format that openpyxl flatly refuses, so the usual ",[14,19,20],{},"load_workbook"," reflex fails with a blunt error. This guide covers the working path: install the right reader, get the data into a DataFrame, deal with the date serials the format stores instead of real dates, and know the row ceiling that silently truncates data upstream. It is the hands-on companion to ",[23,24,26],"a",{"href":25},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002F","Handling Excel File Formats and Conversions",".",[29,30,39,40,39,44,39,48,39,55,39,64,39,71,39,76,39,81,39,94,39,103,39,109,39,113,39,118,39,123,39,127,39,132,39,137,39,142,39,146,39,149,39,154,39,158,39,165,39,170],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"ariaLabelledBy":34,"xmlns":37,"style":38},"0 0 800 190","img","Reading path for a legacy xls file: openpyxl rejects it, while xlrd or python-calamine parse the BIFF binary into cell values that pandas assembles into a DataFrame.",[35,36],"xls-read-t","xls-read-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:800px;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},"Two routes at a legacy .xls file, one of which is a dead end",[45,46,47],"desc",{"id":36},"A legacy.xls file branches two ways. The upper branch to openpyxl ends in an InvalidFileException because openpyxl only reads the OOXML zip formats. The lower branch goes through xlrd or python-calamine, which parse the BIFF binary records into cell values, and pandas turns those into a DataFrame.",[49,50],"rect",{"x":51,"y":51,"width":52,"height":53,"fill":54},"0","800","190","#ffffff",[49,56],{"x":57,"y":58,"width":59,"height":58,"rx":60,"fill":61,"stroke":62,"style":63},"14","66","140","12","#fdefd8","var(--gold,#b4740a)","stroke-width:2px",[65,66,70],"text",{"x":67,"y":68,"style":69},"84","52","font-size:11px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","input",[65,72,75],{"x":67,"y":73,"style":74},"94","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","legacy.xls",[65,77,80],{"x":67,"y":78,"style":79},"114","font-size:10.5px;fill:var(--muted,#5b6780);text-anchor:middle","BIFF8 binary",[82,83,86,87,86,91,39],"g",{"stroke":84,"style":63,"fill":85},"var(--line,#cdd5e6)","none","\n    ",[88,89],"path",{"d":90},"M154 88 H 186 V 40 H 214",[88,92],{"d":93},"M154 112 H 186 V 140 H 214",[49,95],{"x":96,"y":97,"width":98,"height":99,"rx":100,"fill":101,"stroke":102,"style":63},"222","16","184","48","11","#fee8f2","var(--accent,#f43f8f)",[65,104,108],{"x":105,"y":106,"style":107},"314","36","font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","openpyxl",[65,110,112],{"x":105,"y":111,"style":79},"54","InvalidFileException",[49,114],{"x":96,"y":115,"width":98,"height":99,"rx":100,"fill":116,"stroke":117,"style":63},"116","#ebebfd","var(--brand,#5b5cf0)",[65,119,122],{"x":105,"y":120,"style":121},"136","font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","xlrd \u002F calamine",[65,124,126],{"x":105,"y":125,"style":79},"154","parses BIFF records",[128,129],"line",{"x1":130,"y1":59,"x2":131,"y2":59,"stroke":117,"style":63},"406","446",[133,134],"polygon",{"points":135,"fill":136},"454,140 442,134 442,146","#5b5cf0",[49,138],{"x":139,"y":115,"width":140,"height":99,"rx":100,"fill":141,"stroke":117,"style":63},"462","150","#f0f4ff",[65,143,145],{"x":144,"y":120,"style":121},"537","cell values",[65,147,148],{"x":144,"y":125,"style":79},"+ date serials",[128,150],{"x1":151,"y1":59,"x2":152,"y2":59,"stroke":153,"style":63},"612","652","var(--teal,#0f9488)",[133,155],{"points":156,"fill":157},"660,140 648,134 648,146","#0f9488",[49,159],{"x":160,"y":161,"width":162,"height":163,"rx":100,"fill":164,"stroke":153,"style":63},"668","112","118","56","#d9f4f1",[65,166,169],{"x":167,"y":120,"style":168},"727","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","DataFrame",[65,171,172],{"x":167,"y":125,"style":79},"pandas",[174,175,177],"h2",{"id":176},"prerequisites","Prerequisites",[179,180,181],"ul",{},[182,183,184,188],"li",{},[185,186,187],"strong",{},"Python 3"," with pandas and a legacy reader:",[190,191,196],"pre",{"className":192,"code":193,"language":194,"meta":195,"style":195},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas xlrd            # xlrd 2.x reads .xls only\npip install python-calamine        # optional, faster alternative\n","bash","",[14,197,198,220],{"__ignoreMap":195},[199,200,202,206,210,213,216],"span",{"class":128,"line":201},1,[199,203,205],{"class":204},"sMTad","pip",[199,207,209],{"class":208},"srMev"," install",[199,211,212],{"class":208}," pandas",[199,214,215],{"class":208}," xlrd",[199,217,219],{"class":218},"s-wDw","            # xlrd 2.x reads .xls only\n",[199,221,223,225,227,230],{"class":128,"line":222},2,[199,224,205],{"class":204},[199,226,209],{"class":208},[199,228,229],{"class":208}," python-calamine",[199,231,232],{"class":218},"        # optional, faster alternative\n",[179,234,235],{},[182,236,237,243],{},[185,238,239,240,242],{},"An ",[14,241,16],{}," file to read."," If you do not have one to hand, any spreadsheet program will \"Save As → Excel 97-2003 Workbook (*.xls)\". You cannot create one with openpyxl or xlsxwriter — neither can write the format.",[10,245,246,247,257,258,261,262,264],{},"One thing to internalise before the first line of code: ",[185,248,249,252,253,256],{},[14,250,251],{},"xlrd"," 2.0 removed ",[14,254,255],{},".xlsx"," support",". A large amount of tutorial code on the internet still says ",[14,259,260],{},"pd.read_excel(path, engine=\"xlrd\")"," for modern files, and that now fails. ",[14,263,251],{}," is a legacy-format reader and nothing else.",[174,266,268],{"id":267},"step-1-read-the-whole-file-into-dataframes","Step 1 — Read the whole file into DataFrames",[10,270,271,272,274,275,277],{},"The simplest correct call lets pandas route on the extension. Because the file ends in ",[14,273,16],{},", pandas reaches for ",[14,276,251],{}," automatically:",[190,279,283],{"className":280,"code":281,"language":282,"meta":195,"style":195},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\ndf = pd.read_excel(\"legacy.xls\")\nprint(df.head())\nprint(df.dtypes)\n","python",[14,284,285,301,307,325,335],{"__ignoreMap":195},[199,286,287,291,295,298],{"class":128,"line":201},[199,288,290],{"class":289},"s-kum","import",[199,292,294],{"class":293},"skGVy"," pandas ",[199,296,297],{"class":289},"as",[199,299,300],{"class":293}," pd\n",[199,302,303],{"class":128,"line":222},[199,304,306],{"emptyLinePlaceholder":305},true,"\n",[199,308,310,313,316,319,322],{"class":128,"line":309},3,[199,311,312],{"class":293},"df ",[199,314,315],{"class":289},"=",[199,317,318],{"class":293}," pd.read_excel(",[199,320,321],{"class":208},"\"legacy.xls\"",[199,323,324],{"class":293},")\n",[199,326,328,332],{"class":128,"line":327},4,[199,329,331],{"class":330},"sP0c6","print",[199,333,334],{"class":293},"(df.head())\n",[199,336,338,340],{"class":128,"line":337},5,[199,339,331],{"class":330},[199,341,342],{"class":293},"(df.dtypes)\n",[10,344,345],{},"If the extension is wrong or you want to be explicit, name the engine:",[190,347,349],{"className":280,"code":348,"language":282,"meta":195,"style":195},"df = pd.read_excel(\"legacy.xls\", engine=\"xlrd\")\n",[14,350,351],{"__ignoreMap":195},[199,352,353,355,357,359,361,364,368,370,373],{"class":128,"line":201},[199,354,312],{"class":293},[199,356,315],{"class":289},[199,358,318],{"class":293},[199,360,321],{"class":208},[199,362,363],{"class":293},", ",[199,365,367],{"class":366},"sa561","engine",[199,369,315],{"class":289},[199,371,372],{"class":208},"\"xlrd\"",[199,374,324],{"class":293},[10,376,377,378,381,382,386],{},"Reading every sheet at once is the same call with ",[14,379,380],{},"sheet_name=None",", which returns a dict keyed by sheet name — the same shape as ",[23,383,385],{"href":384},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fread-all-sheets-from-an-excel-file-into-dataframes\u002F","reading all sheets from a modern workbook",":",[190,388,390],{"className":280,"code":389,"language":282,"meta":195,"style":195},"sheets = pd.read_excel(\"legacy.xls\", sheet_name=None)\n\nfor name, frame in sheets.items():\n    print(f\"{name:\u003C20} {frame.shape[0]:>6} rows x {frame.shape[1]} cols\")\n",[14,391,392,415,419,433],{"__ignoreMap":195},[199,393,394,397,399,401,403,405,408,410,413],{"class":128,"line":201},[199,395,396],{"class":293},"sheets ",[199,398,315],{"class":289},[199,400,318],{"class":293},[199,402,321],{"class":208},[199,404,363],{"class":293},[199,406,407],{"class":366},"sheet_name",[199,409,315],{"class":289},[199,411,412],{"class":330},"None",[199,414,324],{"class":293},[199,416,417],{"class":128,"line":222},[199,418,306],{"emptyLinePlaceholder":305},[199,420,421,424,427,430],{"class":128,"line":309},[199,422,423],{"class":289},"for",[199,425,426],{"class":293}," name, frame ",[199,428,429],{"class":289},"in",[199,431,432],{"class":293}," sheets.items():\n",[199,434,435,438,441,444,447,451,454,457,460,463,466,468,471,474,476,479,481,483,486,488,490,493],{"class":128,"line":327},[199,436,437],{"class":330},"    print",[199,439,440],{"class":293},"(",[199,442,443],{"class":289},"f",[199,445,446],{"class":208},"\"",[199,448,450],{"class":449},"sSjpA","{",[199,452,453],{"class":293},"name",[199,455,456],{"class":289},":\u003C20",[199,458,459],{"class":449},"}",[199,461,462],{"class":449}," {",[199,464,465],{"class":293},"frame.shape[",[199,467,51],{"class":330},[199,469,470],{"class":293},"]",[199,472,473],{"class":289},":>6",[199,475,459],{"class":449},[199,477,478],{"class":208}," rows x ",[199,480,450],{"class":449},[199,482,465],{"class":293},[199,484,485],{"class":330},"1",[199,487,470],{"class":293},[199,489,459],{"class":449},[199,491,492],{"class":208}," cols\"",[199,494,324],{"class":293},[174,496,498],{"id":497},"step-2-use-xlrd-directly-when-you-need-cell-types","Step 2 — Use xlrd directly when you need cell types",[10,500,501,502,506,507,509,510,513],{},"pandas gives you values. When you need to know what Excel ",[503,504,505],"em",{},"thought"," a cell was — text, number, date, boolean, error — you drop to ",[14,508,251],{}," itself. Every cell carries a ",[14,511,512],{},"ctype"," code alongside its value:",[190,515,517],{"className":280,"code":516,"language":282,"meta":195,"style":195},"import xlrd\n\nbook = xlrd.open_workbook(\"legacy.xls\")\nprint(\"sheets:\", book.sheet_names())\nprint(\"datemode:\", book.datemode)      # 0 = 1900 system, 1 = 1904 (old Mac)\n\nsheet = book.sheet_by_index(0)\nprint(f\"{sheet.nrows} rows x {sheet.ncols} cols\")\n\nTYPES = {\n    xlrd.XL_CELL_EMPTY: \"empty\",\n    xlrd.XL_CELL_TEXT: \"text\",\n    xlrd.XL_CELL_NUMBER: \"number\",\n    xlrd.XL_CELL_DATE: \"date\",\n    xlrd.XL_CELL_BOOLEAN: \"bool\",\n    xlrd.XL_CELL_ERROR: \"error\",\n}\n\nfor col in range(sheet.ncols):\n    cell = sheet.cell(1, col)          # first data row\n    print(f\"col {col}: {TYPES[cell.ctype]:\u003C7} {cell.value!r}\")\n",[14,518,519,526,530,544,556,571,576,591,622,627,639,657,672,687,702,717,732,738,743,759,778],{"__ignoreMap":195},[199,520,521,523],{"class":128,"line":201},[199,522,290],{"class":289},[199,524,525],{"class":293}," xlrd\n",[199,527,528],{"class":128,"line":222},[199,529,306],{"emptyLinePlaceholder":305},[199,531,532,535,537,540,542],{"class":128,"line":309},[199,533,534],{"class":293},"book ",[199,536,315],{"class":289},[199,538,539],{"class":293}," xlrd.open_workbook(",[199,541,321],{"class":208},[199,543,324],{"class":293},[199,545,546,548,550,553],{"class":128,"line":327},[199,547,331],{"class":330},[199,549,440],{"class":293},[199,551,552],{"class":208},"\"sheets:\"",[199,554,555],{"class":293},", book.sheet_names())\n",[199,557,558,560,562,565,568],{"class":128,"line":337},[199,559,331],{"class":330},[199,561,440],{"class":293},[199,563,564],{"class":208},"\"datemode:\"",[199,566,567],{"class":293},", book.datemode)      ",[199,569,570],{"class":218},"# 0 = 1900 system, 1 = 1904 (old Mac)\n",[199,572,574],{"class":128,"line":573},6,[199,575,306],{"emptyLinePlaceholder":305},[199,577,579,582,584,587,589],{"class":128,"line":578},7,[199,580,581],{"class":293},"sheet ",[199,583,315],{"class":289},[199,585,586],{"class":293}," book.sheet_by_index(",[199,588,51],{"class":330},[199,590,324],{"class":293},[199,592,594,596,598,600,602,604,607,609,611,613,616,618,620],{"class":128,"line":593},8,[199,595,331],{"class":330},[199,597,440],{"class":293},[199,599,443],{"class":289},[199,601,446],{"class":208},[199,603,450],{"class":449},[199,605,606],{"class":293},"sheet.nrows",[199,608,459],{"class":449},[199,610,478],{"class":208},[199,612,450],{"class":449},[199,614,615],{"class":293},"sheet.ncols",[199,617,459],{"class":449},[199,619,492],{"class":208},[199,621,324],{"class":293},[199,623,625],{"class":128,"line":624},9,[199,626,306],{"emptyLinePlaceholder":305},[199,628,630,633,636],{"class":128,"line":629},10,[199,631,632],{"class":330},"TYPES",[199,634,635],{"class":289}," =",[199,637,638],{"class":293}," {\n",[199,640,642,645,648,651,654],{"class":128,"line":641},11,[199,643,644],{"class":293},"    xlrd.",[199,646,647],{"class":330},"XL_CELL_EMPTY",[199,649,650],{"class":293},": ",[199,652,653],{"class":208},"\"empty\"",[199,655,656],{"class":293},",\n",[199,658,660,662,665,667,670],{"class":128,"line":659},12,[199,661,644],{"class":293},[199,663,664],{"class":330},"XL_CELL_TEXT",[199,666,650],{"class":293},[199,668,669],{"class":208},"\"text\"",[199,671,656],{"class":293},[199,673,675,677,680,682,685],{"class":128,"line":674},13,[199,676,644],{"class":293},[199,678,679],{"class":330},"XL_CELL_NUMBER",[199,681,650],{"class":293},[199,683,684],{"class":208},"\"number\"",[199,686,656],{"class":293},[199,688,690,692,695,697,700],{"class":128,"line":689},14,[199,691,644],{"class":293},[199,693,694],{"class":330},"XL_CELL_DATE",[199,696,650],{"class":293},[199,698,699],{"class":208},"\"date\"",[199,701,656],{"class":293},[199,703,705,707,710,712,715],{"class":128,"line":704},15,[199,706,644],{"class":293},[199,708,709],{"class":330},"XL_CELL_BOOLEAN",[199,711,650],{"class":293},[199,713,714],{"class":208},"\"bool\"",[199,716,656],{"class":293},[199,718,720,722,725,727,730],{"class":128,"line":719},16,[199,721,644],{"class":293},[199,723,724],{"class":330},"XL_CELL_ERROR",[199,726,650],{"class":293},[199,728,729],{"class":208},"\"error\"",[199,731,656],{"class":293},[199,733,735],{"class":128,"line":734},17,[199,736,737],{"class":293},"}\n",[199,739,741],{"class":128,"line":740},18,[199,742,306],{"emptyLinePlaceholder":305},[199,744,746,748,751,753,756],{"class":128,"line":745},19,[199,747,423],{"class":289},[199,749,750],{"class":293}," col ",[199,752,429],{"class":289},[199,754,755],{"class":330}," range",[199,757,758],{"class":293},"(sheet.ncols):\n",[199,760,762,765,767,770,772,775],{"class":128,"line":761},20,[199,763,764],{"class":293},"    cell ",[199,766,315],{"class":289},[199,768,769],{"class":293}," sheet.cell(",[199,771,485],{"class":330},[199,773,774],{"class":293},", col)          ",[199,776,777],{"class":218},"# first data row\n",[199,779,781,783,785,787,790,792,795,797,799,801,803,806,809,811,813,816,819,821,823],{"class":128,"line":780},21,[199,782,437],{"class":330},[199,784,440],{"class":293},[199,786,443],{"class":289},[199,788,789],{"class":208},"\"col ",[199,791,450],{"class":449},[199,793,794],{"class":293},"col",[199,796,459],{"class":449},[199,798,650],{"class":208},[199,800,450],{"class":449},[199,802,632],{"class":330},[199,804,805],{"class":293},"[cell.ctype]",[199,807,808],{"class":289},":\u003C7",[199,810,459],{"class":449},[199,812,462],{"class":449},[199,814,815],{"class":293},"cell.value",[199,817,818],{"class":289},"!r",[199,820,459],{"class":449},[199,822,446],{"class":208},[199,824,324],{"class":293},[10,826,827,828,831],{},"That ",[14,829,830],{},"datemode"," value matters for the next step, and it is why blindly converting serials with a hard-coded 1899-12-30 epoch is a bug waiting to happen on files produced by older Macs.",[174,833,835],{"id":834},"step-3-turn-date-serials-into-real-datetimes","Step 3 — Turn date serials into real datetimes",[10,837,838,839,841,842,845,846,848,849,851,852,855,856,27],{},"The ",[14,840,16],{}," format has no date type. A date is a floating-point day count, and whether a given cell ",[503,843,844],{},"is"," a date is a property of its number format, not its value. ",[14,847,251],{}," reports ",[14,850,694],{}," when the format looks like a date, but plenty of real files store dates in cells formatted as plain numbers — and then you get ",[14,853,854],{},"45292.0"," where you expected ",[14,857,858],{},"2024-01-01",[29,860,39,867,39,870,39,873,39,877,39,883,39,889,39,894,39,898,39,903,39,906,39,911,39,919,39,922,39,928,39,934,39,938,39,942,39,945,39,949,39,953],{"viewBox":861,"role":32,"ariaLabel":862,"ariaLabelledBy":863,"xmlns":37,"style":866},"0 0 780 208","How an xls date serial becomes a Python datetime: the stored float 45292.0 plus the workbook datemode goes through xldate_as_datetime to produce the first of January 2024.",[864,865],"serial-t","serial-d","width:100%;max-width:780px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[41,868,869],{"id":864},"Converting an .xls date serial to a datetime",[45,871,872],{"id":865},"The workbook stores 45292.0, a day count. Combined with the workbook's datemode, which selects the 1900 or 1904 epoch, xlrd's xldate_as_datetime function converts it to the first of January 2024. The datemode is required because the two epochs differ by more than four years.",[49,874],{"x":51,"y":51,"width":875,"height":876,"fill":54},"780","208",[65,878,882],{"x":879,"y":880,"style":881},"390","26","font-size:12px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","a stored day count is not a date until you supply the epoch",[49,884],{"x":885,"y":886,"width":887,"height":888,"rx":60,"fill":61,"stroke":62,"style":63},"20","44","180","70",[65,890,893],{"x":891,"y":892,"style":69},"110","72","cell value",[65,895,854],{"x":891,"y":896,"style":897},"98","font-size:17px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle",[49,899],{"x":885,"y":900,"width":887,"height":901,"rx":60,"fill":902,"stroke":84,"style":63},"126","62","#f0f2f5",[65,904,905],{"x":891,"y":140,"style":69},"book.datemode",[65,907,910],{"x":891,"y":908,"style":909},"174","font-size:13px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","0 = 1900 · 1 = 1904",[82,912,86,913,86,916,39],{"stroke":117,"style":63,"fill":85},[88,914],{"d":915},"M200 79 H 244 V 110 H 282",[88,917],{"d":918},"M200 157 H 244 V 110 H 282",[133,920],{"points":921,"fill":136},"290,110 278,104 278,116",[49,923],{"x":924,"y":925,"width":926,"height":927,"rx":60,"fill":116,"stroke":117,"style":63},"298","76","248","68",[65,929,933],{"x":930,"y":931,"style":932},"422","104","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","xldate_as_datetime",[65,935,937],{"x":930,"y":900,"style":936},"font-size:11px;fill:var(--muted,#5b6780);text-anchor:middle","value, datemode",[128,939],{"x1":940,"y1":891,"x2":941,"y2":891,"stroke":153,"style":63},"546","586",[133,943],{"points":944,"fill":157},"594,110 582,104 582,116",[49,946],{"x":947,"y":925,"width":948,"height":927,"rx":60,"fill":164,"stroke":153,"style":63},"602","160",[65,950,952],{"x":951,"y":931,"style":168},"682","datetime",[65,954,858],{"x":951,"y":900,"style":955},"font-size:11.5px;fill:var(--text,#172033);text-anchor:middle",[10,957,958,959,961],{},"Convert with the workbook's own ",[14,960,830],{}," so both epochs are handled:",[190,963,965],{"className":280,"code":964,"language":282,"meta":195,"style":195},"import xlrd\nfrom xlrd import xldate_as_datetime\n\nbook = xlrd.open_workbook(\"legacy.xls\")\nsheet = book.sheet_by_index(0)\n\ndef cell_to_python(cell, datemode):\n    \"\"\"Normalise one xlrd cell into a plain Python value.\"\"\"\n    if cell.ctype == xlrd.XL_CELL_DATE:\n        return xldate_as_datetime(cell.value, datemode)\n    if cell.ctype == xlrd.XL_CELL_BOOLEAN:\n        return bool(cell.value)\n    if cell.ctype == xlrd.XL_CELL_EMPTY:\n        return None\n    return cell.value\n\nrows = [\n    [cell_to_python(sheet.cell(r, c), book.datemode) for c in range(sheet.ncols)]\n    for r in range(sheet.nrows)\n]\nprint(rows[1])\n",[14,966,967,973,986,990,1002,1014,1018,1030,1035,1054,1062,1076,1086,1100,1107,1115,1119,1129,1146,1161,1166],{"__ignoreMap":195},[199,968,969,971],{"class":128,"line":201},[199,970,290],{"class":289},[199,972,525],{"class":293},[199,974,975,978,981,983],{"class":128,"line":222},[199,976,977],{"class":289},"from",[199,979,980],{"class":293}," xlrd ",[199,982,290],{"class":289},[199,984,985],{"class":293}," xldate_as_datetime\n",[199,987,988],{"class":128,"line":309},[199,989,306],{"emptyLinePlaceholder":305},[199,991,992,994,996,998,1000],{"class":128,"line":327},[199,993,534],{"class":293},[199,995,315],{"class":289},[199,997,539],{"class":293},[199,999,321],{"class":208},[199,1001,324],{"class":293},[199,1003,1004,1006,1008,1010,1012],{"class":128,"line":337},[199,1005,581],{"class":293},[199,1007,315],{"class":289},[199,1009,586],{"class":293},[199,1011,51],{"class":330},[199,1013,324],{"class":293},[199,1015,1016],{"class":128,"line":573},[199,1017,306],{"emptyLinePlaceholder":305},[199,1019,1020,1023,1027],{"class":128,"line":578},[199,1021,1022],{"class":289},"def",[199,1024,1026],{"class":1025},"s_Opv"," cell_to_python",[199,1028,1029],{"class":293},"(cell, datemode):\n",[199,1031,1032],{"class":128,"line":593},[199,1033,1034],{"class":208},"    \"\"\"Normalise one xlrd cell into a plain Python value.\"\"\"\n",[199,1036,1037,1040,1043,1046,1049,1051],{"class":128,"line":624},[199,1038,1039],{"class":289},"    if",[199,1041,1042],{"class":293}," cell.ctype ",[199,1044,1045],{"class":289},"==",[199,1047,1048],{"class":293}," xlrd.",[199,1050,694],{"class":330},[199,1052,1053],{"class":293},":\n",[199,1055,1056,1059],{"class":128,"line":629},[199,1057,1058],{"class":289},"        return",[199,1060,1061],{"class":293}," xldate_as_datetime(cell.value, datemode)\n",[199,1063,1064,1066,1068,1070,1072,1074],{"class":128,"line":641},[199,1065,1039],{"class":289},[199,1067,1042],{"class":293},[199,1069,1045],{"class":289},[199,1071,1048],{"class":293},[199,1073,709],{"class":330},[199,1075,1053],{"class":293},[199,1077,1078,1080,1083],{"class":128,"line":659},[199,1079,1058],{"class":289},[199,1081,1082],{"class":330}," bool",[199,1084,1085],{"class":293},"(cell.value)\n",[199,1087,1088,1090,1092,1094,1096,1098],{"class":128,"line":674},[199,1089,1039],{"class":289},[199,1091,1042],{"class":293},[199,1093,1045],{"class":289},[199,1095,1048],{"class":293},[199,1097,647],{"class":330},[199,1099,1053],{"class":293},[199,1101,1102,1104],{"class":128,"line":689},[199,1103,1058],{"class":289},[199,1105,1106],{"class":330}," None\n",[199,1108,1109,1112],{"class":128,"line":704},[199,1110,1111],{"class":289},"    return",[199,1113,1114],{"class":293}," cell.value\n",[199,1116,1117],{"class":128,"line":719},[199,1118,306],{"emptyLinePlaceholder":305},[199,1120,1121,1124,1126],{"class":128,"line":734},[199,1122,1123],{"class":293},"rows ",[199,1125,315],{"class":289},[199,1127,1128],{"class":293}," [\n",[199,1130,1131,1134,1136,1139,1141,1143],{"class":128,"line":740},[199,1132,1133],{"class":293},"    [cell_to_python(sheet.cell(r, c), book.datemode) ",[199,1135,423],{"class":289},[199,1137,1138],{"class":293}," c ",[199,1140,429],{"class":289},[199,1142,755],{"class":330},[199,1144,1145],{"class":293},"(sheet.ncols)]\n",[199,1147,1148,1151,1154,1156,1158],{"class":128,"line":745},[199,1149,1150],{"class":289},"    for",[199,1152,1153],{"class":293}," r ",[199,1155,429],{"class":289},[199,1157,755],{"class":330},[199,1159,1160],{"class":293},"(sheet.nrows)\n",[199,1162,1163],{"class":128,"line":761},[199,1164,1165],{"class":293},"]\n",[199,1167,1168,1170,1173,1175],{"class":128,"line":780},[199,1169,331],{"class":330},[199,1171,1172],{"class":293},"(rows[",[199,1174,485],{"class":330},[199,1176,1177],{"class":293},"])\n",[10,1179,1180],{},"When the dates came through pandas as bare floats because the cells were not date-formatted, convert the column afterwards. pandas has the Excel epoch built in:",[190,1182,1184],{"className":280,"code":1183,"language":282,"meta":195,"style":195},"import pandas as pd\n\ndf = pd.read_excel(\"legacy.xls\")\n\n# origin=\"1899-12-30\" is the 1900 date system Excel actually implements.\ndf[\"invoice_date\"] = pd.to_datetime(\n    df[\"invoice_date\"], unit=\"D\", origin=\"1899-12-30\"\n)\nprint(df[\"invoice_date\"].head())\n",[14,1185,1186,1196,1200,1212,1216,1221,1237,1265,1269],{"__ignoreMap":195},[199,1187,1188,1190,1192,1194],{"class":128,"line":201},[199,1189,290],{"class":289},[199,1191,294],{"class":293},[199,1193,297],{"class":289},[199,1195,300],{"class":293},[199,1197,1198],{"class":128,"line":222},[199,1199,306],{"emptyLinePlaceholder":305},[199,1201,1202,1204,1206,1208,1210],{"class":128,"line":309},[199,1203,312],{"class":293},[199,1205,315],{"class":289},[199,1207,318],{"class":293},[199,1209,321],{"class":208},[199,1211,324],{"class":293},[199,1213,1214],{"class":128,"line":327},[199,1215,306],{"emptyLinePlaceholder":305},[199,1217,1218],{"class":128,"line":337},[199,1219,1220],{"class":218},"# origin=\"1899-12-30\" is the 1900 date system Excel actually implements.\n",[199,1222,1223,1226,1229,1232,1234],{"class":128,"line":573},[199,1224,1225],{"class":293},"df[",[199,1227,1228],{"class":208},"\"invoice_date\"",[199,1230,1231],{"class":293},"] ",[199,1233,315],{"class":289},[199,1235,1236],{"class":293}," pd.to_datetime(\n",[199,1238,1239,1242,1244,1247,1250,1252,1255,1257,1260,1262],{"class":128,"line":578},[199,1240,1241],{"class":293},"    df[",[199,1243,1228],{"class":208},[199,1245,1246],{"class":293},"], ",[199,1248,1249],{"class":366},"unit",[199,1251,315],{"class":289},[199,1253,1254],{"class":208},"\"D\"",[199,1256,363],{"class":293},[199,1258,1259],{"class":366},"origin",[199,1261,315],{"class":289},[199,1263,1264],{"class":208},"\"1899-12-30\"\n",[199,1266,1267],{"class":128,"line":593},[199,1268,324],{"class":293},[199,1270,1271,1273,1276,1278],{"class":128,"line":624},[199,1272,331],{"class":330},[199,1274,1275],{"class":293},"(df[",[199,1277,1228],{"class":208},[199,1279,1280],{"class":293},"].head())\n",[10,1282,1283,1284,1287,1288,1290,1291,1293,1294,27],{},"Use ",[14,1285,1286],{},"origin=\"1904-01-01\""," instead if ",[14,1289,905],{}," reported ",[14,1292,485],{},". Date handling across all Excel formats is covered more fully in ",[23,1295,1297],{"href":1296},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002F","working with dates and times in Excel data",[174,1299,1301],{"id":1300},"step-4-the-faster-alternative-python-calamine","Step 4 — The faster alternative: python-calamine",[10,1303,1304,1306,1307,1310,1311,363,1313,363,1315,1318,1319,1322],{},[14,1305,251],{}," is pure Python and shows it on big files. ",[14,1308,1309],{},"python-calamine"," wraps a Rust parser and reads ",[14,1312,16],{},[14,1314,255],{},[14,1316,1317],{},".xlsb"," and ",[14,1320,1321],{},".ods"," behind one engine name. pandas supports it directly:",[190,1324,1326],{"className":280,"code":1325,"language":282,"meta":195,"style":195},"import pandas as pd\n\ndf = pd.read_excel(\"legacy.xls\", engine=\"calamine\")\n",[14,1327,1328,1338,1342],{"__ignoreMap":195},[199,1329,1330,1332,1334,1336],{"class":128,"line":201},[199,1331,290],{"class":289},[199,1333,294],{"class":293},[199,1335,297],{"class":289},[199,1337,300],{"class":293},[199,1339,1340],{"class":128,"line":222},[199,1341,306],{"emptyLinePlaceholder":305},[199,1343,1344,1346,1348,1350,1352,1354,1356,1358,1361],{"class":128,"line":309},[199,1345,312],{"class":293},[199,1347,315],{"class":289},[199,1349,318],{"class":293},[199,1351,321],{"class":208},[199,1353,363],{"class":293},[199,1355,367],{"class":366},[199,1357,315],{"class":289},[199,1359,1360],{"class":208},"\"calamine\"",[199,1362,324],{"class":293},[10,1364,1365,1366,1368,1369,1371],{},"The trade-off: calamine returns values only, with no per-cell type introspection and no styling. When you need ",[14,1367,512],{},", stay on ",[14,1370,251],{},"; when you just want the numbers in a DataFrame quickly, calamine wins, often by a factor of three to five on multi-megabyte legacy files.",[174,1373,1375],{"id":1374},"common-pitfalls-and-fixes","Common pitfalls and fixes",[1377,1378,1379,1395],"table",{},[1380,1381,1382],"thead",{},[1383,1384,1385,1389,1392],"tr",{},[1386,1387,1388],"th",{},"Symptom",[1386,1390,1391],{},"Cause",[1386,1393,1394],{},"Fix",[1396,1397,1398,1425,1440,1456,1472,1486,1503],"tbody",{},[1383,1399,1400,1406,1415],{},[1401,1402,1403],"td",{},[14,1404,1405],{},"XLRDError: Excel xlsx file; not supported",[1401,1407,1408,1409,1411,1412,1414],{},"Passing an ",[14,1410,255],{}," to ",[14,1413,251],{}," 2.x",[1401,1416,1417,1418,1421,1422,27],{},"Drop the ",[14,1419,1420],{},"engine=\"xlrd\""," argument, or use ",[14,1423,1424],{},"engine=\"openpyxl\"",[1383,1426,1427,1432,1435],{},[1401,1428,1429],{},[14,1430,1431],{},"ImportError: Missing optional dependency 'xlrd'",[1401,1433,1434],{},"pandas routed to xlrd but it is not installed",[1401,1436,1437],{},[14,1438,1439],{},"pip install xlrd",[1383,1441,1442,1447,1450],{},[1401,1443,1444,1445],{},"Dates read as ",[14,1446,854],{},[1401,1448,1449],{},"Cells not formatted as dates in the source",[1401,1451,1452,1453,27],{},"Convert with ",[14,1454,1455],{},"pd.to_datetime(col, unit=\"D\", origin=\"1899-12-30\")",[1383,1457,1458,1461,1464],{},[1401,1459,1460],{},"Dates off by ~4 years",[1401,1462,1463],{},"Workbook uses the 1904 date system",[1401,1465,1466,1467,1469,1470,27],{},"Check ",[14,1468,905],{},"; use ",[14,1471,1286],{},[1383,1473,1474,1477,1480],{},[1401,1475,1476],{},"File has exactly 65,536 rows",[1401,1478,1479],{},"BIFF8 row ceiling hit during export",[1401,1481,1482,1483,1485],{},"Fix the export to emit ",[14,1484,255],{}," or CSV — the data is already lost.",[1383,1487,1488,1494,1500],{},[1401,1489,1490,1493],{},[14,1491,1492],{},"CompDocError"," \u002F corrupt file",[1401,1495,1496,1497,1499],{},"Not really an ",[14,1498,16],{},"; often HTML or CSV renamed",[1401,1501,1502],{},"Sniff the leading bytes before reading; see the detection recipe in the parent topic.",[1383,1504,1505,1508,1511],{},[1401,1506,1507],{},"Blank leading rows",[1401,1509,1510],{},"A title block above the header",[1401,1512,1513,1514,1318,1517,1520,1521,27],{},"Pass ",[14,1515,1516],{},"skiprows=",[14,1518,1519],{},"header=",", as in ",[23,1522,1524],{"href":1523},"\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fskip-rows-and-set-header-when-reading-excel-with-pandas\u002F","skipping rows when reading Excel",[10,1526,1527],{},"The 65,536-row case deserves emphasis because it looks like a Python problem and is not. If a sheet has exactly 65,536 rows, assume truncation until proven otherwise:",[190,1529,1531],{"className":280,"code":1530,"language":282,"meta":195,"style":195},"import pandas as pd\n\ndf = pd.read_excel(\"legacy.xls\")\nif len(df) >= 65_535:\n    raise ValueError(\n        f\"{len(df)} rows — at the .xls ceiling. \"\n        \"The export almost certainly truncated; request .xlsx or CSV.\"\n    )\n",[14,1532,1533,1543,1547,1559,1578,1589,1609,1614],{"__ignoreMap":195},[199,1534,1535,1537,1539,1541],{"class":128,"line":201},[199,1536,290],{"class":289},[199,1538,294],{"class":293},[199,1540,297],{"class":289},[199,1542,300],{"class":293},[199,1544,1545],{"class":128,"line":222},[199,1546,306],{"emptyLinePlaceholder":305},[199,1548,1549,1551,1553,1555,1557],{"class":128,"line":309},[199,1550,312],{"class":293},[199,1552,315],{"class":289},[199,1554,318],{"class":293},[199,1556,321],{"class":208},[199,1558,324],{"class":293},[199,1560,1561,1564,1567,1570,1573,1576],{"class":128,"line":327},[199,1562,1563],{"class":289},"if",[199,1565,1566],{"class":330}," len",[199,1568,1569],{"class":293},"(df) ",[199,1571,1572],{"class":289},">=",[199,1574,1575],{"class":330}," 65_535",[199,1577,1053],{"class":293},[199,1579,1580,1583,1586],{"class":128,"line":337},[199,1581,1582],{"class":289},"    raise",[199,1584,1585],{"class":330}," ValueError",[199,1587,1588],{"class":293},"(\n",[199,1590,1591,1594,1596,1598,1601,1604,1606],{"class":128,"line":573},[199,1592,1593],{"class":289},"        f",[199,1595,446],{"class":208},[199,1597,450],{"class":449},[199,1599,1600],{"class":330},"len",[199,1602,1603],{"class":293},"(df)",[199,1605,459],{"class":449},[199,1607,1608],{"class":208}," rows — at the .xls ceiling. \"\n",[199,1610,1611],{"class":128,"line":578},[199,1612,1613],{"class":208},"        \"The export almost certainly truncated; request .xlsx or CSV.\"\n",[199,1615,1616],{"class":128,"line":593},[199,1617,1618],{"class":293},"    )\n",[174,1620,1622],{"id":1621},"performance-and-scale-notes","Performance and scale notes",[29,1624,39,1630,39,1633,39,1636,39,1639,39,1643,39,1648,39,1651,39,1655,39,1658,39,1662,39,1666,39,1669,39,1674,39,1679,39,1682,39,1685,39,1691,39,1694],{"viewBox":1625,"role":32,"ariaLabel":1626,"ariaLabelledBy":1627,"xmlns":37,"style":866},"0 0 780 232","Memory profile comparison: xlrd materialises the entire workbook as Python objects before the first cell is read, while converting once to xlsx unlocks openpyxl's read-only streaming mode with a flat memory profile.",[1628,1629],"xlsmem-t","xlsmem-d",[41,1631,1632],{"id":1628},"Memory profile: xlrd's whole-file load versus streaming an .xlsx",[45,1634,1635],{"id":1629},"Two memory-over-time curves. The xlrd curve rises steeply to a high plateau as the entire workbook is materialised as Python objects before any cell can be read, and stays there for the whole job. The converted xlsx curve read in openpyxl read-only mode stays low and flat, rising only slightly, because rows are yielded one at a time instead of being held.",[49,1637],{"x":51,"y":51,"width":875,"height":1638,"fill":54},"232",[65,1640,1642],{"x":879,"y":1641,"style":881},"24","peak memory while reading the same data",[128,1644],{"x1":888,"y1":1645,"x2":1646,"y2":1645,"stroke":84,"style":1647},"186","734","stroke-width:1.5px",[128,1649],{"x1":888,"y1":1645,"x2":888,"y2":1650,"stroke":84,"style":1647},"42",[65,1652,1654],{"x":901,"y":68,"style":1653},"font-size:10.5px;fill:var(--muted,#5b6780);text-anchor:end","high",[65,1656,1657],{"x":901,"y":98,"style":1653},"low",[65,1659,1661],{"x":1660,"y":876,"style":936},"400","time →",[88,1663],{"d":1664,"fill":85,"stroke":102,"style":1665},"M70 184 L 130 178 L 180 120 L 230 66 L 280 58 L 640 58 L 660 180 L 734 182","stroke-width:3px",[88,1667],{"d":1668,"fill":85,"stroke":153,"style":1665},"M70 184 L 200 176 L 380 172 L 560 174 L 734 176",[49,1670],{"x":1671,"y":106,"width":57,"height":57,"rx":1672,"fill":1673},"292","3","#f43f8f",[65,1675,1678],{"x":105,"y":1676,"style":1677},"47","font-size:11px;fill:var(--muted,#5b6780)","xlrd — whole workbook held",[49,1680],{"x":1681,"y":106,"width":57,"height":57,"rx":1672,"fill":157},"524",[65,1683,1684],{"x":940,"y":1676,"style":1677},"converted .xlsx, streamed",[65,1686,1690],{"x":1687,"y":1688,"style":1689},"230","102","font-size:10.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","parse completes",[65,1692,1693],{"x":1687,"y":162,"style":1689},"before cell 1",[65,1695,1699],{"x":1696,"y":1697,"style":1698},"420","164","font-size:10.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","one row at a time — flat",[10,1701,1702,1704,1705,1707],{},[14,1703,251],{}," loads the whole workbook into memory before you touch a cell, and there is no streaming mode — the format's structure does not allow one. A 20 MB ",[14,1706,16],{}," can occupy several hundred megabytes as Python objects, which is a real constraint in a container with a memory limit.",[10,1709,1710,1711,1713],{},"Two mitigations. First, read only the sheet you need rather than letting ",[14,1712,380],{}," materialise all of them:",[190,1715,1717],{"className":280,"code":1716,"language":282,"meta":195,"style":195},"# Only sheet \"Detail\" is parsed into a DataFrame.\ndf = pd.read_excel(\"legacy.xls\", sheet_name=\"Detail\", usecols=\"A:F\")\n",[14,1718,1719,1724],{"__ignoreMap":195},[199,1720,1721],{"class":128,"line":201},[199,1722,1723],{"class":218},"# Only sheet \"Detail\" is parsed into a DataFrame.\n",[199,1725,1726,1728,1730,1732,1734,1736,1738,1740,1743,1745,1748,1750,1753],{"class":128,"line":222},[199,1727,312],{"class":293},[199,1729,315],{"class":289},[199,1731,318],{"class":293},[199,1733,321],{"class":208},[199,1735,363],{"class":293},[199,1737,407],{"class":366},[199,1739,315],{"class":289},[199,1741,1742],{"class":208},"\"Detail\"",[199,1744,363],{"class":293},[199,1746,1747],{"class":366},"usecols",[199,1749,315],{"class":289},[199,1751,1752],{"class":208},"\"A:F\"",[199,1754,324],{"class":293},[10,1756,1757,1758,1763,1764,1768,1769,27],{},"Second — and this is the durable fix — ",[185,1759,1760,1761],{},"convert once, then work in ",[14,1762,255],{},". Every downstream read gets faster, gains the streaming options described in ",[23,1765,1767],{"href":1766},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fread-large-excel-file-in-chunks-with-pandas\u002F","reading large Excel files in chunks",", and stops depending on a legacy parser. The conversion recipe is in ",[23,1770,1772],{"href":1771},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-xls-to-xlsx-with-python\u002F","convert .xls to .xlsx with Python",[10,1774,1775],{},"For a directory of legacy files, convert them all in one pass and keep the originals for audit:",[190,1777,1779],{"className":280,"code":1778,"language":282,"meta":195,"style":195},"from pathlib import Path\nimport pandas as pd\n\nfor src in Path(\"legacy\").glob(\"*.xls\"):\n    frames = pd.read_excel(src, sheet_name=None)\n    dest = Path(\"converted\") \u002F (src.stem + \".xlsx\")\n    dest.parent.mkdir(exist_ok=True)\n    with pd.ExcelWriter(dest, engine=\"xlsxwriter\") as writer:\n        for name, frame in frames.items():\n            frame.to_excel(writer, sheet_name=name[:31], index=False)\n    print(src.name, \"->\", dest.name)\n",[14,1780,1781,1793,1803,1807,1831,1849,1878,1893,1915,1927,1954],{"__ignoreMap":195},[199,1782,1783,1785,1788,1790],{"class":128,"line":201},[199,1784,977],{"class":289},[199,1786,1787],{"class":293}," pathlib ",[199,1789,290],{"class":289},[199,1791,1792],{"class":293}," Path\n",[199,1794,1795,1797,1799,1801],{"class":128,"line":222},[199,1796,290],{"class":289},[199,1798,294],{"class":293},[199,1800,297],{"class":289},[199,1802,300],{"class":293},[199,1804,1805],{"class":128,"line":309},[199,1806,306],{"emptyLinePlaceholder":305},[199,1808,1809,1811,1814,1816,1819,1822,1825,1828],{"class":128,"line":327},[199,1810,423],{"class":289},[199,1812,1813],{"class":293}," src ",[199,1815,429],{"class":289},[199,1817,1818],{"class":293}," Path(",[199,1820,1821],{"class":208},"\"legacy\"",[199,1823,1824],{"class":293},").glob(",[199,1826,1827],{"class":208},"\"*.xls\"",[199,1829,1830],{"class":293},"):\n",[199,1832,1833,1836,1838,1841,1843,1845,1847],{"class":128,"line":337},[199,1834,1835],{"class":293},"    frames ",[199,1837,315],{"class":289},[199,1839,1840],{"class":293}," pd.read_excel(src, ",[199,1842,407],{"class":366},[199,1844,315],{"class":289},[199,1846,412],{"class":330},[199,1848,324],{"class":293},[199,1850,1851,1854,1856,1858,1861,1864,1867,1870,1873,1876],{"class":128,"line":573},[199,1852,1853],{"class":293},"    dest ",[199,1855,315],{"class":289},[199,1857,1818],{"class":293},[199,1859,1860],{"class":208},"\"converted\"",[199,1862,1863],{"class":293},") ",[199,1865,1866],{"class":289},"\u002F",[199,1868,1869],{"class":293}," (src.stem ",[199,1871,1872],{"class":289},"+",[199,1874,1875],{"class":208}," \".xlsx\"",[199,1877,324],{"class":293},[199,1879,1880,1883,1886,1888,1891],{"class":128,"line":578},[199,1881,1882],{"class":293},"    dest.parent.mkdir(",[199,1884,1885],{"class":366},"exist_ok",[199,1887,315],{"class":289},[199,1889,1890],{"class":330},"True",[199,1892,324],{"class":293},[199,1894,1895,1898,1901,1903,1905,1908,1910,1912],{"class":128,"line":593},[199,1896,1897],{"class":289},"    with",[199,1899,1900],{"class":293}," pd.ExcelWriter(dest, ",[199,1902,367],{"class":366},[199,1904,315],{"class":289},[199,1906,1907],{"class":208},"\"xlsxwriter\"",[199,1909,1863],{"class":293},[199,1911,297],{"class":289},[199,1913,1914],{"class":293}," writer:\n",[199,1916,1917,1920,1922,1924],{"class":128,"line":624},[199,1918,1919],{"class":289},"        for",[199,1921,426],{"class":293},[199,1923,429],{"class":289},[199,1925,1926],{"class":293}," frames.items():\n",[199,1928,1929,1932,1934,1936,1939,1942,1944,1947,1949,1952],{"class":128,"line":629},[199,1930,1931],{"class":293},"            frame.to_excel(writer, ",[199,1933,407],{"class":366},[199,1935,315],{"class":289},[199,1937,1938],{"class":293},"name[:",[199,1940,1941],{"class":330},"31",[199,1943,1246],{"class":293},[199,1945,1946],{"class":366},"index",[199,1948,315],{"class":289},[199,1950,1951],{"class":330},"False",[199,1953,324],{"class":293},[199,1955,1956,1958,1961,1964],{"class":128,"line":641},[199,1957,437],{"class":330},[199,1959,1960],{"class":293},"(src.name, ",[199,1962,1963],{"class":208},"\"->\"",[199,1965,1966],{"class":293},", dest.name)\n",[174,1968,1970],{"id":1969},"conclusion","Conclusion",[10,1972,1973,1974,1976,1977,1979,1980,1982,1983,1985,1986,1988],{},"Reading ",[14,1975,16],{}," is a solved problem once you accept that it needs its own reader: ",[14,1978,251],{}," for full cell-type detail, ",[14,1981,1309],{}," when you want speed and only need values, and pandas over the top of either. Watch two things specifically — date serials, which need the workbook's ",[14,1984,830],{}," to decode correctly, and the 65,536-row ceiling, which quietly truncates upstream exports. Then convert to ",[14,1987,255],{}," at the boundary of your pipeline so nothing further downstream has to care.",[174,1990,1992],{"id":1991},"frequently-asked-questions","Frequently asked questions",[10,1994,1995,2005,252,2007,2009,2010,2012,2013,2015],{},[185,1996,1997,1998,2001,2002,2004],{},"Why does xlrd raise ",[14,1999,2000],{},"XLRDError"," on my ",[14,2003,255],{}," file?",[14,2006,251],{},[14,2008,255],{}," support on purpose. It now reads only the legacy ",[14,2011,16],{}," binary format. For ",[14,2014,255],{}," use openpyxl, which is what pandas already picks by default for that extension.",[10,2017,2018,2024,2025,2027,2028,2030,2031,2033,2034,2036],{},[185,2019,2020,2021,2023],{},"Why are my dates coming back as numbers like ",[14,2022,854],{},"?","\nThe ",[14,2026,16],{}," format stores dates as a serial day count, and ",[14,2029,251],{}," hands that number back unless the cell is typed as a date. Convert with ",[14,2032,933],{}," and the workbook's ",[14,2035,830],{},", or let pandas parse the column after the read.",[10,2038,2039,2045,2046,2048,2049,2051],{},[185,2040,2041,2042,2044],{},"My ",[14,2043,16],{}," file only has 65,536 rows but the source had more — where did they go?","\nThey were dropped by whatever wrote the file. The BIFF8 format used by ",[14,2047,16],{}," has a hard ceiling of 65,536 rows and 256 columns; the truncation happened before Python saw the file, so the fix is to have the export produce ",[14,2050,255],{}," or CSV instead.",[10,2053,2054,2057,2058,2060,2061,2063,2064,363,2066,1318,2068,2070,2071,2074],{},[185,2055,2056],{},"Is there a faster alternative to xlrd?","\nYes — ",[14,2059,1309],{}," is a Rust-backed reader that handles ",[14,2062,16],{}," as well as ",[14,2065,255],{},[14,2067,1317],{},[14,2069,1321],{},". pandas supports it directly with ",[14,2072,2073],{},"engine=\"calamine\"",", and it is usually several times faster on large legacy files.",[10,2076,2077,2083,2084,2086,2087,2090,2091,2093,2094,2096],{},[185,2078,2079,2080,2082],{},"Can I write ",[14,2081,16],{}," files from Python?","\nNot with ",[14,2085,251],{},", which is read-only. The old ",[14,2088,2089],{},"xlwt"," package could write ",[14,2092,16],{}," but is unmaintained and does not support modern Excel features. Write ",[14,2095,255],{}," instead — every current version of Excel opens it.",[174,2098,2100],{"id":2099},"related","Related",[179,2102,2103,2109,2115,2122,2129],{},[182,2104,2105,2106,2108],{},"Up to the parent: ",[23,2107,26],{"href":25}," — the full format map and engine table.",[182,2110,2111,2114],{},[23,2112,2113],{"href":1771},"Convert .xls to .xlsx with Python"," — the batch conversion this guide points to.",[182,2116,2117,2121],{},[23,2118,2120],{"href":2119},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-and-write-ods-files-with-python\u002F","Read and Write .ods Files with Python"," — the other non-Excel format you will meet.",[182,2123,2124,2128],{},[23,2125,2127],{"href":2126},"\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fhow-to-read-excel-with-pandas-step-by-step\u002F","How to Read Excel with pandas Step by Step"," — the modern-format equivalent of this walkthrough.",[182,2130,2131,2134,2135,27],{},[23,2132,2133],{"href":1766},"Read Large Excel Files in Chunks with pandas"," — what becomes possible once you are off ",[14,2136,16],{},[2138,2139,2140],"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 pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}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 .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}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":195,"searchDepth":222,"depth":222,"links":2142},[2143,2144,2145,2146,2147,2148,2149,2150,2151,2152],{"id":176,"depth":222,"text":177},{"id":267,"depth":222,"text":268},{"id":497,"depth":222,"text":498},{"id":834,"depth":222,"text":835},{"id":1300,"depth":222,"text":1301},{"id":1374,"depth":222,"text":1375},{"id":1621,"depth":222,"text":1622},{"id":1969,"depth":222,"text":1970},{"id":1991,"depth":222,"text":1992},{"id":2099,"depth":222,"text":2100},"2026-08-15","Open legacy .xls workbooks from Python: install xlrd 2.x, read sheets into DataFrames, decode the date serials xlrd returns, and handle the 65,536-row ceiling.","md",[2157,2160,2163,2166,2168],{"q":2158,"a":2159},"Why does xlrd raise XLRDError on my .xlsx file?","xlrd 2.0 removed .xlsx support on purpose. It now reads only the legacy .xls binary format. For .xlsx use openpyxl, which is what pandas already picks by default for that extension.",{"q":2161,"a":2162},"Why are my dates coming back as numbers like 45292.0?","The .xls format stores dates as a serial day count, and xlrd hands that number back unless the cell is typed as a date. Convert with xlrd.xldate_as_datetime and the workbook's datemode, or let pandas parse the column after the read.",{"q":2164,"a":2165},"My .xls file only has 65536 rows but the source had more — where did they go?","They were dropped by whatever wrote the file. The BIFF8 format used by .xls has a hard ceiling of 65,536 rows and 256 columns; the truncation happened before Python saw the file, so the fix is to have the export produce .xlsx or CSV instead.",{"q":2056,"a":2167},"Yes — python-calamine is a Rust-backed reader that handles .xls as well as .xlsx, .xlsb and .ods. pandas supports it directly with engine=\"calamine\", and it is usually several times faster on large legacy files.",{"q":2169,"a":2170},"Can I write .xls files from Python?","Not with xlrd, which is read-only. The old xlwt package could write .xls but is unmaintained and does not support modern Excel features. Write .xlsx instead — every current version of Excel opens it.",{},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-xls-files-in-python-with-xlrd-and-pandas",{"title":2174,"description":2175},"Read .xls Files in Python (xlrd + pandas)","Step-by-step guide to reading old .xls workbooks in Python — the xlrd 2.0 breaking change, pandas engine selection, date conversion, and the calamine alternative.","read-xls-files-in-python-with-xlrd-and-pandas","getting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-xls-files-in-python-with-xlrd-and-pandas\u002Findex","how-to","-GLMFpjVMdR_o2OTEBLLLIIYxk8XOoubwSDHpvJ7IIs",[2181,2184],{"title":2120,"path":2182,"stem":2183,"children":-1},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-and-write-ods-files-with-python","getting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fread-and-write-ods-files-with-python\u002Findex",{"title":2185,"path":2186,"stem":2187,"children":-1},"Work with Macro-Enabled .xlsm Files in openpyxl","\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fwork-with-macro-enabled-xlsm-files-in-openpyxl","getting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fwork-with-macro-enabled-xlsm-files-in-openpyxl\u002Findex",1786800027102]