opendatalab/MinerU · error · ValueError
Invalid XLSX package: file is not a ZIP archive.
Error message
Invalid XLSX package: file is not a ZIP archive.
What it means
Raised by MineRU's XLSX package normalizer when the input bytes passed off as an .xlsx workbook are not a ZIP archive at all. Every real XLSX file is a ZIP container (OOXML), so Python's zipfile.ZipFile raises BadZipFile when opening it, which the normalizer re-raises as this ValueError. Typical root causes: a renamed .xls (legacy binary BIFF), .csv, or HTML file with an .xlsx extension, a truncated upload, or plain-text/HTML error content returned by a download instead of the spreadsheet.
Source
Thrown at mineru/model/xlsx/package_normalizer.py:52
MAX_EXCEL_COLUMN = "XFD"
def normalize_xlsx_package(file_bytes: bytes) -> bytes:
"""在进入 openpyxl 前修复常见 XLSX 包级兼容问题。"""
try:
with ZipFile(BytesIO(file_bytes)) as source:
rewritten_members: list[tuple[ZipInfo, bytes]] = []
changed = False
for info in source.infolist():
member_data = source.read(info.filename)
normalized_data = _normalize_xlsx_member(info.filename, member_data)
if normalized_data != member_data:
changed = True
member_data = normalized_data
rewritten_members.append((info, member_data))
except BadZipFile as exc:
raise ValueError("Invalid XLSX package: file is not a ZIP archive.") from exc
if not changed:
return file_bytes
return _write_package(rewritten_members)
def _normalize_xlsx_member(member_name: str, member_data: bytes) -> bytes:
"""根据 XLSX 包内成员路径分发 XML 兼容性规范化逻辑。"""
if member_name == SHARED_STRINGS_PATH:
return _normalize_shared_strings_xml(member_data)
if member_name == STYLES_PATH:
return _normalize_styles_xml(member_data)
if _is_worksheet_xml(member_name):
return _normalize_worksheet_xml(member_data)
return member_data
View on GitHub (pinned to 4fe4bde114)
Solutions
- Verify the file is genuinely XLSX: check the first 4 bytes are b'PK\x03\x04' (file magic) or run `file report.xlsx` — it should say 'Microsoft Excel 2007+'.
- If the file is a legacy .xls, CSV, or HTML table, re-export it as 'Excel Workbook (.xlsx)' from the source application before feeding it to MineRU.
- If the file came over HTTP, re-download it and confirm the response is not an HTML error page (check Content-Type and body); enable HTTP status checking so failures surface at download time.
- If the file may be corrupt, try opening it in Excel/LibreOffice — if they also fail, obtain a fresh copy.
Example fix
// before
with open(path, 'rb') as f:
result = parse_xlsx(f.read()) # ValueError: Invalid XLSX package
// after
def load_xlsx(path):
data = Path(path).read_bytes()
if not data.startswith(b'PK'):
raise ValueError(f'{path} is not an XLSX (ZIP) archive; re-export as .xlsx')
return parse_xlsx(data) Defensive patterns
Strategy: validation
Validate before calling
def is_valid_xlsx(data: bytes) -> bool:
return len(data) >= 4 and data[:4] == b'PK\x03\x04' Type guard
def is_xlsx_zip(data: bytes) -> bool:
return isinstance(data, (bytes, bytearray)) and bytes(data[:4]) == b'PK\x03\x04' Try / catch
try:
result = parse_xlsx(data)
except ValueError as e:
if 'not a ZIP archive' in str(e):
raise HTTPException(415, 'File is not a valid XLSX workbook; re-export as .xlsx') from e
raise Prevention
- Check the PK\x03\x04 magic bytes of every uploaded .xlsx before handing it to the parser.
- Never trust file extensions from user uploads; validate content signatures at the trust boundary.
- When downloading files over HTTP, fail on non-2xx and verify Content-Type before saving with the original filename.
When it happens
Trigger: Calling the XLSX parsing entry point (mineru.model.xlsx package_normalizer) with bytes that fail ZipFile(BytesIO(file_bytes)) — e.g. feeding a legacy .xls (OLE2 compound document starting with D0 CF 11 E0), a CSV, or an HTML page saved as .xlsx. Only files whose leading bytes are not a ZIP local-file-header signature (PK\x03\x04, including empty/self-extracting variants) trigger it.
Common situations: Users export 'Excel' from a web tool that actually emits CSV/HTML; a legacy .xls is renamed to .xlsx; a partially downloaded or zero-byte file is processed; a proxy returns an HTML login page with a 200 status that gets saved with the original filename.
Related errors
- Unsupported office suffix: {file_suffix}
- Unknown process_mode: {process_mode}
- Unsupported file type: {file_suffix}
- Invalid latex delimiters type: {latex_delimiters_type}.
- Unknown backend type: {backend}
AI-assisted analysis of opendatalab/MinerU@4fe4bde114 (2026-08-14).
Data as JSON: /api/errors/93a6fe5024d04c2d.
Report an issue: GitHub.