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

  1. 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+'.
  2. 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.
  3. 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.
  4. 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

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


AI-assisted analysis of opendatalab/MinerU@4fe4bde114 (2026-08-14). Data as JSON: /api/errors/93a6fe5024d04c2d. Report an issue: GitHub.