# 批量导入 (/zh/docs/guides/import)

把旧图床或博客里的图片批量转存进 ImgYoung，支持图片地址清单、站点地图、订阅源、网页与 Markdown、HTML 文本，并导出新旧链接对照表。

## 什么时候用 [#什么时候用]

旧图床要下线，或者博客里的图片散落在各处，想全部收进 ImgYoung，并拿到「旧链接 → 新链接」的对照表去替换文章里的地址。批量导入就是做这件事：

1. 给出图片地址，或者给出能找到图片的站点、文本。
2. 确认要导入哪些域名下的图片。
3. 服务端逐张抓取并入库，完成后下载对照表。

导入的图片归属当前账号，走与[上传](./upload.mdx)相同的处理流程：格式白名单、像素预算、EXIF 处理和存储配额照常生效。只想转存一张图时，用[URL 转存](./upload.mdx#url-转存)。

所有登录用户都能使用。普通用户只能看到和操作自己创建的任务；管理员能看到所有人的任务，并能取消、删除任何人的任务，但确认和重试只有创建者能做。整个实例同一时刻只处理一个导入任务，管理员可以借此取消卡住的任务，让后面的任务继续。

## 新建导入 [#新建导入]

在侧栏「内容」组打开「导入」，点「新建导入」；也可以在「图片」页点「上传图片」旁的「批量导入」。对话框有三个标签，按手头已有的材料选一个：

| 标签 | 填什么 | 之后 |
|---|---|---|
| 图片地址 | 图片地址，一行一个 | 直接开始导入 |
| 网站 | 站点地图、订阅源或网页地址，一行一个 | 扫描后，确认要导入的域名 |
| 文本与文件 | 粘贴 Markdown 或 HTML 文本，或选择文件 | 扫描后，确认要导入的域名 |

### 图片地址 [#图片地址]

每行一个 `http` 或 `https` 的图片地址。同一个地址出现多次只导入一次。提交后任务直接进入「导入中」，没有确认步骤。

### 网站 [#网站]

每行一个地址，类型由抓回的内容决定，不看扩展名：

| 地址指向 | 处理 |
|---|---|
| 站点地图（`urlset`、`sitemapindex`，含 gzip 压缩） | 逐个扫描其中的文章页面；索引里的子站点地图依次展开 |
| 订阅源（RSS、Atom） | 逐个扫描其中的文章页面 |
| 每行一个地址的纯文本 | 按文本站点地图处理，逐个扫描 |
| 网页 | 扫描这一页 |
| 图片 | 直接作为待导入的图片 |

服务端只展开到文章页面，不会顺着页面里的链接继续抓取。站点地图或订阅源里不属于它所在域名的页面会被跳过，直接填写的网页地址不受这条限制。

任务创建后进入「扫描中」，页面显示已处理的页面数。扫描结束后进入「待确认」。

### 文本与文件 [#文本与文件]

粘贴文本，或选择一个或多个 `.md`、`.markdown`、`.html`、`.htm`、`.txt` 文件。粘贴的文本在报告里记作「粘贴的文本」，文件以文件名为文档名。

文本里的图片若写的是相对路径，在「基准地址」里填文章所在页面的地址，服务端据此补全。没有基准地址，相对路径无法抓取，会列在扫描报告的「无法抓取的引用」里。HTML 里的 `<base href>` 优先于基准地址。

扫描在提交时完成，任务直接进入「待确认」。

### 高级选项 [#高级选项]

三个标签共用，都可以留空：

| 选项 | 作用 |
|---|---|
| 有效期 | 导入图片的有效期。留空时先取你的默认有效期，没有再取全局默认，见[生命周期与查重](./lifecycle.mdx) |
| EXIF | 剥离或保留元数据。留空时用全局默认，见[EXIF 处理](./upload.mdx#exif-处理) |
| Referer | 抓取图片时发送的来源页地址，用于有防盗链的旧图床，见[防盗链的图床](#防盗链的图床) |

## 确认要导入的域名 [#确认要导入的域名]

「待确认」的任务打开后，页面显示：

- **扫描报告**：扫描了多少页面或文档，几页没有找到图片，几页抓取失败（逐条列出地址与原因，站点地图或订阅源的内容无法解析时原因码为 `import_listing_invalid`），几处图片引用无法抓取（逐条列出），几个页面因不在站点域名下被跳过。
- **域名表**：发现的图片按域名分组，每个域名一行，显示图片数和最多 3 个示例地址，数量多的在前。

默认一个域名都不选。勾选旧图床所在的域名，点「开始导入」：所选域名下的图片进入导入，其余的不导入。可以用「全选」「清空」批量操作。域名表只显示地址文本，不加载缩略图。

没有勾选任何域名时，「开始导入」不可点。扫描没有发现任何图片时，页面显示「没有发现图片」，这时只能删除任务。

一个页面里图片的域名往往不止一个：旧图床、统计图标、第三方头像都会出现在表里。只勾选你要搬走的那个。

## 导入过程 [#导入过程]

任务进入「导入中」后，页面显示进度和各状态的数量，失败的条目实时列出，不需要手动刷新。

- **并发**：一个任务同时转存 4 张。这 4 个名额与[上传并发上限](./upload.mdx#格式与上限)相互独立，不占用上传频率额度；规划内存时按两者之和估算。
- **排队**：整个实例同一时刻只处理一个导入任务，其余任务按创建先后排队，期间没有进展。
- **处理流程**：每张图抓取后走与上传相同的流程，成功后记下新图片的短 ID。文件名取地址路径的最后一段。
- **失败**：单张图失败不影响其他图，失败原因记在条目上。全部条目处理完后，任务进入「已完成」，即使其中有失败的。
- **重启**：服务重启后，「导入中」的任务从没处理的条目继续，已经入库的图不会重复导入；「扫描中」的任务从头重新扫描。

### 失败的条目 [#失败的条目]

| 错误码 | 常见原因 | 处理 |
|---|---|---|
| `url_blocked` | 地址不是公网地址 | 内网地址不能导入，见[抓取限制](./upload.mdx#抓取限制) |
| `url_too_large` | 图片超过单图大小上限 | 调大「单图上限」后重试 |
| `url_bad_status` | 远端返回了非 2xx，常见于防盗链 | 填写 Referer 后重新创建任务，或确认旧地址是否已失效 |
| `url_fetch_failed` | 超时或连接失败 | 稍后重试 |
| `url_too_many_redirects` | 重定向超过 5 次 | 检查地址 |
| `upload_unsupported_type` | 格式不在白名单内，如 SVG、AVIF | 无法导入，见[格式与上限](./upload.mdx#格式与上限) |
| `upload_invalid_image` | 内容不是可解析的图片 | 检查地址是否真的指向图片 |
| `upload_too_many_pixels` | 分辨率超过像素预算 | 调大「像素预算」后重试 |

页面按错误码显示本地化的说明，并附远端或服务端返回的原文（最长 500 个字符）。每张图最长处理 20 分钟。

### 重试 [#重试]

任务结束后点「重试」：失败的条目和没处理的条目一起重新进入「导入中」。已经成功的不受影响。任务不会自动重试失败项。

「已完成」「已中止」「已取消」的任务都可以重试；扫描没有完成就中止或取消的站点任务没有候选可以继续，重试会从头重新扫描；没有失败和未处理的条目时重试会被拒绝（`import_nothing_to_retry`）。

### 中止 [#中止]

没有可用渠道或数据库等服务端故障时，继续下去只会让后面的图全部同样失败，所以整个任务中止，状态为「已中止」，页面显示原因，没处理的条目保留。存储配额同理：连续 3 张图都因配额不足失败，就认为配额已满而中止；只是某一张图超过剩余额度时，只有这一张失败，后面放得下的图照常导入。先解决问题（调高配额、修复渠道），再点「重试」继续。

### 取消 [#取消]

「扫描中」「待确认」「导入中」的任务可以取消。正在转存的图片会被中断，没处理完的条目保持待转存，已经导入的图片保留。取消后的任务可以重试。

## 对照表 [#对照表]

任务结束后（「已完成」「已中止」「已取消」），点「下载对照表」选择 JSON 或 CSV。对照表只包含已导入成功、图片仍然存在的条目。新地址是图片的完整访问地址：设置了对外站点地址（`base_url`）时用它，否则按当前访问的域名生成。

| 列 | 含义 |
|---|---|
| `old_url` | 旧图片的地址 |
| `new_url` | 新图片的地址 |
| `image_id` | 新图片的短 ID |
| `source` | 引用这张图的页面地址或文档名 |
| `raw` | 引用在该处的原文，可能是相对路径，用来在源文章里查找替换 |

- **CSV**：UTF-8，首行是表头，每处引用一行；没有引用位置的图片输出一行，`source` 与 `raw` 为空。以 `=`、`+`、`-`、`@` 开头的单元格前会加一个单引号，避免用电子表格打开时被当成公式；要按原文精确查找替换时，用 JSON 对照表。
- **JSON**：每张图一个对象，所有引用位置嵌套在 `references` 数组里：

```json
[
  {
    "old_url": "https://old.example.com/img/a.png",
    "new_url": "https://img.example.com/i/R0RjUyP1UW",
    "image_id": "R0RjUyP1UW",
    "references": [
      { "source": "https://blog.example.com/post-1", "raw": "/img/a.png" }
    ]
  }
]
```

对照表只用于你自己替换文章里的地址，ImgYoung 不会改动源文章。

## 删除任务 [#删除任务]

「待确认」「已完成」「已取消」「已中止」的任务可以删除，任务记录、条目和对照表一并删除，已经导入的图片不受影响。「扫描中」「导入中」的任务要先取消。删除前先下载需要的对照表。

## 限制与注意事项 [#限制与注意事项]

- **只扫描静态 HTML**：靠脚本渲染出来的图片找不到，这类页面会计入报告里「没有找到图片的页面」。
- **站点模式给出的是对照表**：抓回的是渲染后的网页，不是文章源文件，所以 ImgYoung 不改写文章，由你按对照表替换。
- **不识别本地文件引用**：`data:` 内嵌图片、本地路径和 Obsidian 的 `![[文件]]` 都不是可抓取的地址，会列在「无法抓取的引用」里。本地图片请用[上传](./upload.mdx#在管理后台上传)。
- **格式**：只能导入 JPEG、PNG、GIF 和 WebP；SVG、AVIF 等会以 `upload_unsupported_type` 失败。
- **只抓公网地址**：与 URL 转存相同，内网地址被拒绝，见[抓取限制](./upload.mdx#抓取限制)。
- **扫描的限制**：失败页面与无法抓取的引用在报告里各列出前 1000 条。并发抓取 4 个页面，单个站点地图、订阅源或页面最大 50 MiB，抓取一个地址最长 2 分钟。页面数与图片数不设上限。不读取 `robots.txt`，请求的 `User-Agent` 为 `ImgYoung/<版本> (+https://github.com/x-haose/imgyoung)`。
- **提交大小**：「文本与文件」一次提交的内容不超过 32 MiB，其中每份文档不超过 4 MiB，解析一份文档超过 15 秒会被拒绝。

### 防盗链的图床 [#防盗链的图床]

图片抓取时会带 `Referer`：「高级选项」里填了就用它，没填就用引用这张图的页面地址；两者都没有（例如图片地址清单）就不发。只放行自家页面的旧图床，对不带来源的请求会返回 403 之类的状态码，表现为 `url_bad_status`。这时在 Referer 里填旧站的首页或文章页地址，重新创建任务。

## 用脚本调用 [#用脚本调用]

导入接口使用登录会话认证，不接受上传 Token。认证方式见[接口参考](../api/index.mdx)，每个接口的参数与返回见[接口参考：后台·批量导入](../api/admin-import.mdx)。

登录后创建一个图片地址清单的任务（`csrf_token` 取自登录响应）：

```bash title="终端"
curl -b jar.txt \
  -H "X-CSRF-Token: <csrf_token>" \
  -H "Content-Type: application/json" \
  -d '{"mode": "images", "urls": ["https://old.example.com/img/a.png", "https://old.example.com/img/b.jpg"]}' \
  https://img.example.com/api/admin/imports
```

下载对照表：

```bash title="终端"
curl -b jar.txt -OJ \
  "https://img.example.com/api/admin/imports/<任务 ID>/mapping?format=csv"
```

## 错误码 [#错误码]

| 操作 | 状态码 | 错误码 | 含义 |
|---|---|---|---|
| 新建 | 400 | `import_input_invalid` | 请求不合法，提示里指出哪一项有问题，例如某个地址不是 `http` 或 `https` 绝对地址 |
| 新建 | 413 | `import_input_invalid` | 请求体超过 32 MiB |
| 查看、取消、重试、删除、确认、对照表 | 404 | `import_not_found` | 任务不存在，或属于别的用户（管理员能看到所有人的任务） |
| 新建 | 429 | `import_busy` | 文本解析的名额被占满，等待超过 15 秒仍没有轮到，稍后重试 |
| 确认、重试 | 403 | `import_owner_only` | 管理员操作别人的任务，这两项只有创建者能做 |
| 取消、重试、删除、确认 | 409 | `import_state_invalid` | 任务当前状态不允许这个操作 |
| 确认 | 400 | `import_no_selection` | 所选域名下没有待导入的图片地址 |
| 重试 | 409 | `import_nothing_to_retry` | 没有失败或未处理的条目 |

各状态允许的操作：

| 状态 | 界面显示 | 可以做 |
|---|---|---|
| `scanning` | 扫描中 | 取消 |
| `ready` | 待确认 | 开始导入、取消、删除 |
| `importing` | 导入中 | 取消 |
| `done` | 已完成 | 重试、下载对照表、删除 |
| `cancelled` | 已取消 | 重试、下载对照表、删除 |
| `aborted` | 已中止 | 重试、下载对照表、删除 |

条目失败时的错误码见[失败的条目](#失败的条目)。完整的错误码列表见[接口参考](../api/index.mdx#错误格式)里的 `ErrorCode`。
