一、先把问题收窄

RAG 最终检索的不是用户上传的文件,而是入库 Pipeline 生产的 Chunk。解析顺序错误、清洗误删正文、分块拆散表头与数据,都会让后续检索建立在错误数据上。

本文只讨论一条链路:

原始文件 → 解析 → 清洗 → 分块 → Embedding → 索引发布

混合检索、Rerank 和 Prompt 编排不在本文展开。目标是把文档稳定地转换成可追踪、可重建、可评测的索引数据。


二、入库架构与数据边界

2.1 总体流程

flowchart TB
    Upload["文件上传 / 数据源同步"] --> Preflight["输入预检"]
    Preflight --> Router{"解析器路由"}

    Router -->|"复杂 PDF / 图片"| MinerU["MinerU"]
    Router -->|"Office / 长尾格式回退"| MarkItDown["Microsoft MarkItDown"]
    Router -->|"MD / TXT / HTML / Code"| Builtin["内置解析器"]

    MinerU --> UDM["统一文档模型 UDM"]
    MarkItDown --> UDM
    Builtin --> UDM

    UDM --> ParseGate{"解析质量门禁"}
    ParseGate -->|"通过"| Clean["清洗 Pipeline"]
    ParseGate -->|"存在备用路线"| Router
    ParseGate -->|"无法修复"| Quarantine["隔离 / 人工处理"]

    Clean --> Chunk["结构化分块"]
    Chunk --> Batch["Token 动态合批"]
    Batch --> Embed["Embedding Gateway"]
    Embed --> Validate["向量与索引校验"]
    Validate --> Staging["STAGING 索引"]
    Staging --> Publish["原子发布"]

    Object[("对象存储
原文与阶段产物")] Meta[("关系数据库
状态、版本、ACL")] Index[("向量 / 全文索引")] Upload --> Object UDM -.-> Object Clean -.-> Object Chunk -.-> Object Preflight -.-> Meta ParseGate -.-> Meta Publish --> Meta Staging --> Index classDef entry fill:#e8f1ff,stroke:#4c78a8,color:#1f2937; classDef process fill:#ecf8f1,stroke:#3b8c61,color:#1f2937; classDef storage fill:#f2ecff,stroke:#7e57c2,color:#1f2937; classDef warn fill:#fff4df,stroke:#d18b24,color:#1f2937; class Upload,Preflight entry; class MinerU,MarkItDown,Builtin,UDM,Clean,Chunk,Batch,Embed,Validate,Staging,Publish process; class Object,Meta,Index storage; class Router,ParseGate,Quarantine warn;

2.2 哪些数据是事实源

数据 存储位置 作用
原始文件 对象存储 审计和重新解析,不可替代
解析器原始输出 对象存储 复现解析问题
UDM 与清洗结果 对象存储 / Parquet 重跑清洗和分块
Chunk Manifest 数据库 / Parquet 重建向量与全文索引
文档版本、ACL、状态 关系数据库 权威业务状态
向量和倒排索引 检索引擎 派生数据,可重建

不要把唯一一份 Chunk 正文、页码或 ACL 只保存在向量数据库的 Payload 中。


三、输入预检与解析器路由

3.1 输入预检

文件扩展名不可信,解析前至少完成以下检查:

检查 目的 失败处理
Magic Bytes、MIME、扩展名一致性 识别真实格式 拒绝伪造格式
SHA-256 创建不可变文档版本 哈希失败则停止
文件大小、页数、解压后大小 防止资源耗尽和压缩炸弹 超过硬限制直接失败
损坏、加密、密码保护 避免无效重试 提示用户修复或提供密码
PDF 文本层和文本密度 判断是否需要 OCR 按页选择解析方式
宏、脚本和外部资源 防止执行不可信内容 禁止执行和主动加载

所有解析器都应运行在受限容器中,并限制 CPU、内存、临时磁盘、运行时间和网络访问。

3.2 路由流程

flowchart TD
    Input["通过预检的文件"] --> Detect["识别真实 MIME 与结构特征"]
    Detect --> Text{"确定性文本格式?"}

    Text -->|"MD / TXT / HTML / Code"| Builtin["内置解析器"]
    Text -->|"否"| MinerCap{"当前 MinerU 支持?"}

    MinerCap -->|"支持且需要版面能力"| MinerU["MinerU 解析"]
    MinerCap -->|"不支持或无需复杂版面"| MarkCap{"MarkItDown 支持?"}
    MarkCap -->|"支持"| MarkItDown["MarkItDown 转 Markdown"]
    MarkCap -->|"不支持"| Convert["受控格式转换或拒绝"]

    Builtin --> Gate{"质量门禁"}
    MinerU --> Gate
    MarkItDown --> Gate
    Convert --> Gate

    Gate -->|"通过"| UDM["输出 UDM"]
    Gate -->|"质量差且有备用解析器"| Detect
    Gate -->|"无备用路线"| Quarantine["隔离"]

路由依据包括真实 MIME、版面复杂度、解析器能力快照、数据出域策略、历史质量和资源成本。不要把官网支持列表永久硬编码进业务逻辑。

3.3 推荐路由矩阵

MinerU 当前官方 CLI 支持 PDF、图片、DOCX、PPTX 和 XLSX,但不同部署版本与 API 形态的能力可能不同。

输入 首选 回退 关键要求
有文本层的复杂 PDF MinerU MarkItDown 低保真回退 保留页码、坐标、表格和阅读顺序
扫描 PDF、文档图片 MinerU OCR 其他 OCR 或人工处理 按页 OCR,保留置信度
DOCX / PPTX / XLSX 当前 MinerU 原生解析 MarkItDown 用黄金样本决定优先级
XLS MarkItDown 受控 Office 转换 依赖对应可选组件
DOC / PPT 等旧格式 先受控转换为新格式或 PDF 提示用户转换 不执行宏和嵌入脚本
Markdown / TXT 内置解析器 保留标题、代码块和源行号
HTML 内置 DOM 解析器 MarkItDown 去除脚本、导航和隐藏节点
CSV / JSON / XML 内置结构解析器 MarkItDown 按记录和字段组织内容
未识别格式 MarkItDown 能力探测 拒绝并隔离 禁止按 TXT 强行读取

3.4 三类解析器的边界

MinerU

复杂 PDF 和图片优先使用 MinerU。接入时应保存完整输出包,并记录版本、后端、模型、OCR 语言、表格和公式开关。

机器处理优先读取 content_list.jsoncontent_list_v2.jsonmiddle.json,Markdown 主要用于人工预览。不同 MinerU 版本通过独立 Adapter 转换为 UDM,避免清洗和分块代码直接依赖外部 Schema。

混合 PDF 应按页判断:有效文本层直接读取,无文本页启用 OCR,乱码页比较原生文本与 OCR 质量。

Microsoft MarkItDown

MarkItDown 适合将 DOCX、PPTX、XLSX、XLS、HTML、CSV、JSON、XML 等输入转换为 Markdown,再恢复成结构节点。

它通常不提供复杂 PDF 所需的可靠页码和坐标,因此引用精度应降级为章节或转换后行号。服务端只允许处理受控文件或字节流,禁止把用户提供的路径和 URL 直接传入转换器。

内置解析器

  • Markdown 使用 AST 保留标题、表格、列表、代码块和源行号
  • TXT 做编码检测、换行归一化和行号定位
  • HTML 使用 DOM 提取正文结构
  • CSV、JSON、XML 按记录和字段组织
  • 代码按语言 AST 或语法树保留类、函数和签名

确定性文本不需要经过 OCR 或视觉模型。

3.5 解析成功不等于质量合格

以下情况都应触发回退或隔离:

  • 长文档只解析出少量字符
  • 双栏文本顺序交叉
  • 表格只剩无结构数字
  • OCR 乱码或替换字符比例过高
  • 结构化文档完全没有标题
  • 解析页数与原文差异过大

每份文档最多执行一次明确的备用路线,禁止解析器之间循环切换。


四、统一文档模型与解析质量

4.1 UDM 是解析器防腐层

UDM 不只保存 Markdown,还要保存结构和来源:

字段 作用
document_version_id 关联不可变文档版本
block_idorder 稳定标识和阅读顺序
type 标题、段落、列表、表格、代码、公式、图片等
raw_text 解析器原始文本,不允许覆盖
text 当前阶段处理后的文本
section_path 所属章节路径
pagebbox 页码和坐标,不存在时必须为空
parent_id 图片与说明、标题与正文等结构关系
confidence OCR 或解析置信度
provenance 外部解析器节点或源文件行号

表格、代码和公式使用独立结构,不强行拍平成段落。

4.2 解析质量门禁

指标 主要发现的问题
有效字符数、可打印字符比例 空结果、乱码
替换字符比例 编码或 OCR 错误
页数与页面覆盖率 漏页
标题数量和层级 结构丢失
表格有效率 行列识别失败
OCR 低分位置信度 集中低质量页面
重复行比例 水印、页眉页脚污染
阅读顺序评分 双栏和复杂版面错序

阈值按文档类型配置。小说没有标题并不异常,技术手册没有标题通常需要复核。


五、清洗 Pipeline

5.1 清洗原则

清洗只做三件事:去除确定性噪声、修复解析断裂、补全结构关系。

默认禁止:

  • 使用大模型改写原文
  • 自动补齐缺失事实
  • 合并存在冲突的条款
  • 跨文档删除“相似内容”
  • 用摘要替代可引用正文

raw_text 永不覆盖,所有删除和修复都要记录 Operator、版本、原因以及变更前后哈希。

5.2 Pipeline 流程

flowchart LR
    UDM["UDM"] --> Unicode["字符与换行归一化"]
    Unicode --> Repair["断行 / 断词修复"]
    Repair --> Margin["页眉页脚与水印识别"]
    Margin --> Section["标题树与章节路径"]
    Section --> Structure["表格 / 列表 / 代码修复"]
    Structure --> Dedup["文档内去重标记"]
    Dedup --> Gate{"清洗质量门禁"}
    Gate -->|"通过"| Cleaned["Cleaned UDM"]
    Gate -->|"删除或改写异常"| Quarantine["隔离 / 抽查"]

    Change[("Change Log")]
    Unicode -.-> Change
    Repair -.-> Change
    Margin -.-> Change
    Section -.-> Change
    Structure -.-> Change
    Dedup -.-> Change

5.3 核心 Operator

Operator 处理内容 安全边界
字符归一化 Unicode NFC、换行、零宽字符 代码和公式不做全角半角替换
空白归一化 连续空格、段落边界 不破坏代码缩进和表格
断行修复 PDF 视觉行合并 结合标点、缩进和坐标
英文断词修复 行尾连字符 不修改编号、复合词和代码
页边噪声识别 重复页眉、页脚、水印、页码 合同和论文脚注默认保留
标题树修复 章节路径、标题跳级告警 不凭空创建缺失标题
表格修复 跨页表头、Caption、Footnote 无法恢复行列时保留原始产物
精确去重 坐标重叠的重复节点 近重复只标记,不直接删除

5.4 几个容易误删的场景

页眉页脚

只有同时满足“跨页高频重复”和“长期位于固定页边区域”时才标记为页边噪声。页脚中的合同条款、论文注释和免责声明不能按位置直接删除。

英文连字符

只有连字符位于视觉行尾、下一行以小写字母开始、合并词合理且当前内容不是代码或表格时才修复。

重复内容

跨文档相同正文可以复用 Embedding 结果,但不能删除业务文档。相同内容仍可能拥有不同 ACL、来源和生效时间。

5.5 版本与质量门禁

清洗配置指纹由 Operator 名称、版本、顺序、参数和构建版本共同生成。任何一项变化都产生新产物。

清洗完成后检查:

  • 有效正文是否为空
  • 删除字符比例是否异常
  • Block 类型数量是否突变
  • 页边噪声是否覆盖正文区域
  • 表格行列是否完整
  • 章节路径是否大量跳级
  • 乱码、重复和超长段落比例

删除比例超过业务阈值时进入隔离,不直接发布。


六、结构化分块

6.1 先保留结构,再控制 Token

flowchart TB
    Cleaned["Cleaned UDM"] --> Tree["构建标题树"]
    Tree --> Atomic["生成 Atomic Units
句子 / 列表项 / 表格行 / 函数"] Atomic --> Type{"内容类型"} Type -->|"普通正文"| Paragraph["按章节顺序聚合"] Type -->|"表格"| Table["表头 + 行组 + 脚注"] Type -->|"FAQ"| FAQ["一问一答"] Type -->|"代码"| Code["类 / 函数边界"] Paragraph --> Budget["Token 预算检查"] Table --> Budget FAQ --> Budget Code --> Budget Budget --> Child["Child Chunk
精确召回"] Child --> Parent["Parent Chunk
上下文扩展"] Child --> Link["前后邻接关系"]

固定字符切分会破坏标题、表格和代码边界。推荐三层结构:

  • Atomic Unit:句子、列表项、表格行、代码函数等最小语义单元
  • Child Chunk:用于 Embedding 和精确召回
  • Parent Chunk:命中后用于补充上下文

6.2 Chunk 必备字段

字段 作用
chunk_idsequence 稳定标识和文档内顺序
parent_chunk_id Parent / Child 关系
display_text 上下文和引用使用,接近原文
embedding_text 可附加文档标题和章节路径
source_block_ids 反向定位 UDM
section_path 章节语义
page_startpage_end 引用位置
prev_chunk_idnext_chunk_id 邻接扩展
token_countcontent_hash 预算控制和增量重建
acl_version 权限索引版本

embedding_text 中补充的标题不能作为原文引用,因此必须与 display_text 分开保存。

6.3 初始 Token 基线

以下数值只用于启动,最终由真实评测集决定:

内容类型 目标 Token 最大 Token 重叠方式
制度、手册 450~700 900 前后 1~2 句
FAQ 一组问答 600 不跨问答
法律条款 一条或一组关联条款 800 携带章、节
表格 300~600 800 每块重复必要表头
代码 一个函数或小类 800 携带路径、类名和签名
Parent Chunk 1200~2000 按生成预算 默认不参与向量召回

Chunk 太小会丢失完整证据,太大会稀释语义;重叠过大会让 Top K 被相邻副本占满。

6.4 专用分块规则

内容 规则
普通正文 不跨一级章节;超长段落再按句子拆分
大表格 按行组切分,每块携带 Caption、完整表头和适用脚注
多级表头 展开为完整列路径,例如“华东 / 2026 / 销售额”
FAQ 问题与答案永不分离,问题加入检索文本
操作步骤 优先整体保留;切分后携带任务标题和前置条件
代码 按 AST 的类和函数边界;超长函数才按逻辑块拆分
图片与图表 说明文字、Caption 和图片引用保持关联

6.5 增量重建

Chunk ID 由文档版本、分块配置指纹、来源 Block 范围和内容哈希稳定生成。

变化 处理
正文和配置完全相同 复用 Embedding
正文变化 重新 Embedding
位置变化但正文相同 复用向量,更新位置和顺序
ACL 变化 不重算向量,只更新索引 Payload
分块配置变化 生成新 Chunk 和配置指纹

6.6 分块验收

重点检查 Token 分布、碎片率、跨章节合并率、表头丢失率、相邻重复率和黄金问题 Recall@K。平均 Chunk 长度不能代表分块质量。


七、Embedding 调度

7.1 调度流程

flowchart LR
    Pending["待处理 Chunk"] --> Cache{"向量缓存命中?"}
    Cache -->|"是"| Reuse["复用向量值"]
    Cache -->|"否"| Queue["有界优先级队列"]
    Queue --> Pack["按 Item 数 + Token 数合批"]
    Pack --> Limit["RPM / TPM 双维限流"]
    Limit --> Semaphore["并发槽"]
    Semaphore --> Provider["云端 API / 本地推理服务"]
    Provider --> Check{"结果校验"}

    Check -->|"通过"| Checkpoint["批次 Checkpoint"]
    Check -->|"429 / 5xx"| Backoff["退避并重新计入限额"]
    Check -->|"非法单条"| Isolate["二分定位坏 Chunk"]
    Check -->|"维度 / NaN 异常"| Stop["停止该配置发布"]

    Backoff --> Limit
    Isolate --> Queue
    Reuse --> Index["STAGING 索引"]
    Checkpoint --> Index

7.2 动态合批

一个 Batch 同时满足:

  • Item 数不超过 max_batch_items
  • 总 Token 不超过 max_batch_tokens
  • 单个 Chunk 不超过 max_input_tokens

固定“每 100 条一批”不可靠。短 FAQ 和长制度 Chunk 的请求大小可能相差几十倍。Token 数应使用目标 Embedding 模型对应的 Tokenizer 预计算。

7.3 并发与速率是两个控制面

Semaphore 只限制同时飞行的请求,不能保证 RPM 和 TPM 不超限,因此发送前必须同时获取:

  • 一个请求配额
  • 本批 Token 配额
  • 一个并发槽

初始并发可根据 RPM、TPM、平均 Batch Token、请求 P95 和服务端硬上限估算,并预留约 20% 抖动空间。运行中根据 429、队列时延和 P95 逐步调整。

任务队列必须有界。不能把数百万个 Chunk 一次性加载进内存或创建等量异步任务。

7.4 云端与本地模型

场景 调度重点
云端 API RPM、TPM、连接并发、Retry-After 和成本
本地 GPU 显存、动态合批窗口、模型副本和设备利用率
在线查询 高优先级、低时延、小批次
文档入库 普通优先级、追求吞吐
全库重建 低优先级,只使用剩余资源

本地模型由每个推理副本维护有界队列,在短时间窗口内动态合批。不要让多个 Worker 无限制并发调用同一个模型进程。

7.5 错误处理

错误 处理
429 遵循 Retry-After,降低速率和并发
网络超时、408、5xx 有上限指数退避并增加随机抖动
401、403 立即停止队列,修复配置
请求体过大 Batch 二分
单条输入过长 返回分块阶段修复,禁止静默截断
单条输入非法 二分定位,其他 Chunk 继续
数量、维度、NaN、Infinity 异常 禁止写入并停止该模型配置发布

每次重试都是新请求,必须重新计入 RPM 和 TPM;退避期间释放并发槽。

7.6 配置指纹、缓存与断点续跑

Embedding 配置指纹至少包含:

  • Provider、模型 ID 和模型修订版本
  • 向量维度、距离度量和归一化方式
  • 最大输入 Token
  • Document / Query 指令模板
  • Tokenizer 和文本预处理版本

缓存键由规范化检索文本和配置指纹生成。缓存只复用向量值,不复用 Chunk ID、ACL、页码和生效时间。

每个 Batch 成功后立即记录 Checkpoint。Worker 崩溃后只扫描未完成 Item,不重算整份文档。


八、索引构建与发布

8.1 状态机

flowchart LR
    Uploaded["UPLOADED"] --> Preflight["PREFLIGHT"]
    Preflight --> Parsing["PARSING"]
    Parsing --> Cleaning["CLEANING"]
    Cleaning --> Chunking["CHUNKING"]
    Chunking --> Embedding["EMBEDDING"]
    Embedding --> Indexing["INDEXING"]
    Indexing --> Validating["VALIDATING"]
    Validating --> Ready["READY"]
    Ready --> Published["PUBLISHED"]

    Preflight -.-> Failed["FAILED / QUARANTINED"]
    Parsing -.-> Failed
    Cleaning -.-> Failed
    Chunking -.-> Failed
    Embedding -.-> Failed
    Indexing -.-> Failed
    Validating -.-> Failed

每个阶段记录输入输出哈希、Artifact 地址、配置指纹、耗时、指标、错误码和重试次数。修复后从失败阶段继续。

8.2 阶段级幂等

阶段 幂等维度
解析 文档版本 + Parser 指纹
清洗 解析产物哈希 + Cleaner 指纹
分块 清洗产物哈希 + Chunker 指纹
Embedding Chunk 内容哈希 + Embedding 指纹
索引 Index Version + Chunk ID
发布 预期当前版本 + CAS

8.3 STAGING 与原子发布

sequenceDiagram
    participant W as Ingestion Worker
    participant I as STAGING Index
    participant V as Validator
    participant DB as Metadata DB
    participant Q as Query Service

    W->>I: 写入新文档版本
    W->>V: 提交 Chunk / Vector Manifest
    V->>I: 核对数量、维度和抽样召回
    V-->>W: 校验通过
    W->>DB: CAS 切换 current_version
    DB->>DB: content_revision + 1
    Q->>DB: 查询权威 current_version 与 ACL
    W-->>I: 延迟回收旧版本

发布前新版本不能通过权威版本校验;发布后旧版本不能通过校验,因此一次回答不会混用同一文档的新旧版本。

index_version 表示整代索引结构,通常在模型、维度或 Schema 变化时切换;content_revision 表示当前索引代内的内容和 ACL 修订,用于缓存失效。普通文档更新不需要重建整代索引。


九、最小数据模型

文章不展开完整 DDL,只保留四类核心记录:

记录 必备字段 关键约束
文档版本 文档 ID、版本、原文哈希、对象地址、MIME、状态、各阶段指纹 文档 ID + 版本唯一
阶段产物 阶段、输入输出哈希、配置指纹、Artifact 地址、质量指标 同输入和配置只能有一个有效产物
Chunk Manifest Chunk ID、顺序、Parent、正文哈希、Token、来源 Block、章节和页码 文档版本 + 顺序唯一
Embedding 任务 Chunk ID、模型指纹、状态、优先级、租约、重试和错误 Chunk + 模型指纹唯一

Worker 通过租约领取任务。进程崩溃后租约过期,其他 Worker 可以继续处理,避免任务永久停留在 RUNNING


十、可观测性与质量评测

10.1 分层指标

层级 重点指标
解析 有效字符、漏页、阅读顺序、标题层级、表格有效率、OCR 置信度、回退率
清洗 删除比例、Operator 变更数、噪声保留率、正文误删率、隔离率
分块 Token 分布、碎片率、重叠率、表头丢失率、跨章节率、Recall@K
Embedding 队列时延、Batch Item、Batch Token、Tokens/s、缓存命中、429、重试和成本
发布 Manifest 差异、校验失败、CAS 冲突、入库到可检索时延

每份文档使用统一 Trace 串联预检、解析器路由、清洗 Operator、分块、Embedding Batch、索引校验和发布。普通日志不记录完整敏感正文。

10.2 黄金文档集

黄金集应覆盖:

  • 文本型、扫描型和混合型 PDF
  • 双栏、跨页表格、脚注和水印
  • DOCX 标题与表格
  • PPTX 文本框与备注
  • XLSX 多工作表与合并单元格
  • Markdown 代码块和表格
  • 公式、中英混排和长列表
  • 损坏、加密、伪造和超限文件

每次 Parser、Cleaner、Chunker 或 Embedding 配置变化,都必须重跑同一批黄金文档和黄金问题。

10.3 变更门禁

flowchart LR
    Change["配置或模型变更"] --> Golden["重跑黄金文档"]
    Golden --> Compare{"质量、检索、时延、成本达标?"}
    Compare -->|"否"| Reject["拒绝发布"]
    Compare -->|"是"| Gray["小知识库灰度"]
    Gray --> Staging["构建 STAGING"]
    Staging --> Observe{"在线指标稳定?"}
    Observe -->|"否"| Rollback["回滚"]
    Observe -->|"是"| Publish["全量发布"]

十一、落地顺序与验收

11.1 分阶段实施

阶段 交付内容 验收重点
解析闭环 Preflight、Parser Router、MinerU、MarkItDown、内置解析器、UDM 能发现静默解析错误
清洗与分块 Operator Pipeline、结构化 Chunker、Parent / Child、来源定位 清洗可回放,正确证据可独立召回
Embedding 与发布 动态合批、双维限流、Checkpoint、STAGING、CAS 崩溃可续跑,新旧版本不混查
规模化 资源池隔离、多租户配额、灰度和质量看板 在线查询不受离线重建影响

11.2 上线前必须满足

  • 解析器能力来自当前部署快照,质量差时能回退或隔离
  • 原文、解析结果、清洗结果和 Chunk 均可追溯
  • 清洗不会静默覆盖原文或删除高风险内容
  • 表格、FAQ 和代码使用专用分块规则
  • display_textembedding_text 分离
  • Batch 同时限制 Item 数、总 Token 和单条 Token
  • 并发槽、RPM 和 TPM 同时受控
  • 重试、缓存、Checkpoint 和索引写入均幂等
  • 新版本只写 STAGING,通过校验后 CAS 发布
  • 黄金文档和黄金问题进入变更门禁

十二、总结

生产级 RAG 入库的关键不是堆叠解析器,而是建立一条可验证的数据生产线:

  1. MinerU、MarkItDown 和内置解析器按能力与质量路由
  2. UDM 隔离解析器差异并保留来源
  3. 清洗以可审计 Operator 处理噪声和结构
  4. 分块以文档结构为先、Token 预算为后
  5. Embedding 同时控制批量、速率、并发和版本
  6. 新索引通过 STAGING 校验后原子发布

这条链路稳定后,检索和生成优化才有可信的数据基础。


参考资料