FSCrawler 3.0 来了!你知道的,就是用来处理文件的!

0 阅读9分钟

作者:来自 Elastic David Pilato

事情发生了。FSCrawler 3.0 正式发布了!

如果你从来没有听说过 FSCrawler,它就是我在 2011 年和我的朋友 Malloum 一起创建的那个小型开源爬虫。当时我们正在法国海关工作。它可以帮助将办公文档 — — PDF、Word、PowerPoint、图片,应有尽有 — — 索引到 Elasticsearch 中。你可以把它理解为“你知道的,就是用来处理文件的”,这和著名的 “you know, for kids” 是同一种精神,而后者正是 Elasticsearch 标语 “You know, for search” 的来源。

2.9 版本于 2022 年 1 月 10 日 发布。今天,2026 年 8 月 26 日,我们发布 3.0。这意味着我们经历了 4 年 7 个月零 16 天 的耐心(有时也并不那么耐心的)折腾。对于一个本身就是“青少年”的项目来说,这是一个“青少年”版本!

从数字来看

我统计了 fscrawler-2.9 和 fscrawler-3.0 两个标签之间的 git 数据。这……嗯……是一项巨大的工作量。下面是其中的一些亮点:

指标值
两个版本之间的时间4 年 7 个月 16 天(约 1,689 天)
提交次数2,877
修改的文件901
新增行数+56,403
删除行数−24,919
净变化+31,484 行
2.9 版本代码库大小(Java、YAML、XML、文档)27,833 行
3.0 版本代码库大小59,632 行
增长+114%
仅 Java:2.9 → 3.023,543 → 44,109 行(+87%)
人类贡献者(不包括机器人)18

贡献者名人堂包括 David Pilato(就是本人)、Ilian Maciuba、Alex Steele、iadcode、Benjamin Dauvissat、Kevin Trebing、Martin Bussmann、betofilippi 等人。当然,还有那些帮助我们保持严谨的机器人:Dependabot、Mergify、GitHub Actions,以及 — — 没错 — — Cursor Agent。我们看到你们了。🤖

作为背景,2.9 是一个相对适中的版本(Tika 2.2.1、Elasticsearch 7.16.2,以及对 Workplace Search 的一些调整)。而 3.0……可一点都不适中。

4.5 年能带来多大的变化

2.9 发布时,Elasticsearch 8 甚至还不存在。 ChatGPT 也不存在。Tika 4 还是遥不可及的梦想。我们当时仍然需要针对每个 Elasticsearch 主版本分别提供 FSCrawler 发行版。Docker 镜像要求你显式传入 fscrawler 二进制文件。Job 设置则会在首次运行时隐式创建。

3.0 有意彻底抛弃了这一切。这是一个全新安装版本:不支持从 2.9 原地升级。安装 3.0,运行 --setup,重新索引。升级指南 会一步步指导你完成整个过程。

说实话?这种痛苦是值得的。

功能亮点

3.0 中有几十项新功能。下面这些是我认为值得放烟花庆祝的功能 — — 每一项都附带了你可以直接复制粘贴的设置或命令。

一个爬虫统治一切:插件架构

挑战: FSCrawler 是逐步自然发展起来的。本地文件系统、FTP、SSH — — 每种协议的接入方式都不一样。添加新的数据源意味着必须修改核心代码。这种方式无法扩展。

解决方案: 基于 PF4J 的统一插件架构。现在你可以使用 fs.provider 来选择 provider,而不再使用已弃用的 server.protocol。相同的 Job 设置、相同的 checkpoint 模型、相同的 REST API — — 不同的后端。

完整的从 server.protocol 迁移的指南,请参阅**爬虫 provider 文档**。

请注意,在 3.1 中,我们还会更进一步,彻底弃用 server 命名空间。fs.provider 才是未来。更多详情请参阅 #2522。

编写你自己的插件

内置 provider 位于代码库中的 **[plugins/](https://github.com/dadoonet/fscrawler/tree/master/plugins "plugins/")** 目录下:

  • fs-local-plugin

  • fs-ftp-plugin

  • fs-ssh-plugin

  • fs-http-plugin

  • fs-s3-plugin

每一个插件都实现了 FsCrawlerExtensionFsProvider 扩展点。

最简单的形式是一个仅 REST 的 provider(获取单个文件,不进行目录爬取) — — 可以把它想象成 HTTP 或 S3。下面是一个精简后的骨架:

统一技术术语的中文表达修正破折号的排版格式让标题更贴近原文含义

`

1.  public class MyPlugin extends FsCrawlerPlugin {
2.      @Override
3.      protected String getName() {
4.          return "my-plugin";
5.      }

7.      @Extension
8.      public static class MyProvider extends FsCrawlerExtensionFsProviderAbstract {
9.          @Override
10.          public String getType() {
11.              return "mytype";  // used in REST: "type": "mytype"
12.          }

14.          @Override
15.          public InputStream readFile() throws FsCrawlerPluginException {
16.              // return bytes for the requested document
17.          }

19.          @Override
20.          public Doc createDocument() throws FsCrawlerPluginException {
21.              // fill filename, filesize, path.real, path.virtual
22.          }

24.          @Override
25.          protected void parseSettings() throws PathNotFoundException {
26.              // read JSON from REST body, e.g. document.read("$.mytype.url")
27.          }

29.          @Override
30.          protected void validateSettings() {
31.              // throw FsCrawlerIllegalConfigurationException if settings are invalid
32.          }
33.      }
34.  }

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

对于爬取 provider(遍历目录),请重写 supportsCrawling() 使其返回 true,并实现 openConnection()、getFiles()、getInputStream() 等方法 — — 可以参考 fs-local-plugin 中的 FileAbstractorFile,或者 FTP/SSH 对应的 FsCrawlerExtensionRemoteProviderAbstract。

将你的插件构建为一个 Maven 模块(父模块:fscrawler-plugins),将其打包为 PF4J 插件 JAR,然后把它放入发行版的 plugins/ 目录,与内置插件放在一起。**[FsHttpPlugin](https://github.com/dadoonet/fscrawler/blob/master/plugins/fs-http-plugin/src/main/java/fr/pilato/elasticsearch/crawler/plugins/fs/http/FsHttpPlugin.java "FsHttpPlugin")** 是一个非常好的参考示例,代码大约 100 行,可以从这里开始。

Elasticsearch 7、8、9 — — 一个二进制文件统治一切

挑战: 发布 fscrawler-es7、fscrawler-es8 等版本简直是一场维护噩梦。每次 Elasticsearch 客户端升级,都意味着要维护 N 个发行版。

解决方案: 我们为 Elasticsearch 编写了自己的 HTTP 客户端,并取消了针对不同版本的构建。一个 ZIP 文件。一个 Docker 镜像。Elasticsearch 7.17、8.x 和 9.x — — 包括 Elastic Cloud Serverless。

下面是一个使用 API key 进行身份验证的最小 Job 文件。它适用于 Elasticsearch 7、8 和 9,无论是在本地还是云端:

`

1.  ---
2.  name: "fscrawler"
3.  fs:
4.    url: "/tmp/es"
5.  elasticsearch:
6.    urls:
7.      - "http://127.0.0.1:9200"
8.    api_key: "YOUR_API_KEY"

`AI写代码

使用 start-local 时,只需从 elastic-start-local/.env 中复制 API key。

Apache Tika 4:这是我们不得不完成的一次升级

挑战: Apache Tika 4.0.0 是一个存在破坏性变更的版本。XML 配置已经移除(现在仅支持 JSON)。Metadata key 也进行了重命名(X-TIKA: → tk:、ICC: → icc:,PDF 权限现在使用连字符……)。我们整个测试套件都报出了大量错误。

解决方案: 我们还是完成了升级 — — 因为继续停留在 Tika 2.x 已经走不通了。我们在**发布说明**中记录了每一个 Metadata key 的重命名。我们还修复了 stream 处理逻辑,确保由调用方拥有的 stream 在解析过程中保持打开状态。

现在默认的 PDF OCR 策略是 auto — — 对于已经包含文本的页面跳过 OCR:

翻译并补全最后一句修正破折号和标点格式统一技术术语的中文表达

`

1.  name: "test"
2.  fs:
3.    url: "/path/to/data"
4.    ocr:
5.      pdf_strategy: "auto"   # default in 3.0; use "ocr_and_text" for the old behaviour

`AI写代码

默认链路保持不变:如果 Tesseract 可用,就使用 Tesseract。但现在你可以更进一步……

VLM OCR:因为有时候 Tesseract 需要帮助

挑战: 布局奇怪的扫描 PDF、手写内容、低质量传真 — — Tesseract 应对起来比较困难。与此同时,Vision Language Model 几乎可以读取任何内容。

解决方案: FSCrawler 自带 Apache Tika 的 **tika-vlm** 模块。通过 Job 设置中引用的自定义 Tika JSON 配置,将它指向一个兼容 OpenAI 的端点。VLM 是可选启用的 — — 如果没有设置 fs.tika_config_path,Tesseract 会继续正常工作。

选项 A — — Ollama(最简单的本地设置)

Ollama 在 11434 端口提供一个**兼容 OpenAI 的 API**。首先拉取一个视觉模型:

统一破折号格式补充选项A的后续内容统一英文术语的翻译方式

`ollama pull qwen2.5vl:7b`AI写代码

创建 ~/tika-ollama.json:

`

1.  {
2.    "parsers": [
3.      { "default-parser": { "exclude": ["tesseract-ocr-parser"] } },
4.      { "pdf-parser": {
5.          "ocr": {
6.            "strategy": "AUTO",
7.            "maxPagesToOcr": 10
8.          }
9.        } },
10.      { "openai-vlm-deterministic-parser": {
11.          "baseUrl": "http://host.docker.internal:11434/v1",
12.          "model": "qwen2.5vl:7b",
13.          "maxTokens": 4096,
14.          "timeoutSeconds": 300
15.        } }
16.    ]
17.  }

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

在 _settings.yaml 中接入它:

`

1.  name: "scans"
2.  fs:
3.    url: "/tmp/es"
4.    tika_config_path: "/root/tika-ollama.json"

`AI写代码

当 FSCrawler 在 Docker 中运行时,需要挂载配置文件,并通过 host.docker.internal 访问主机上的 Ollama。请先启动 Ollama,再启动 FSCrawler — — VLM parser 会在启动时通过 GET /v1/models 执行健康检查;如果 Ollama 没有运行,那么整个运行过程中 OCR 都会被静默跳过。

我们推荐使用 openai-vlm-deterministic-parser(temperature 为 0),而不是 openai-vlm-parser,以避免小型视觉模型产生幻觉。

选项 B — — 通过 vLLM 使用 jina-vlm

jina-vlm 是 Jina AI 的 2.4B 多语言 视觉语言 模型 — — 在 OCRBench 和文档 VQA 方面表现出色。目前它不在 Ollama 模型库中(Ollama 上的 Jina 模型目前仅支持 embedding ),但通过 vLLM 等兼容 OpenAI 的端点提供服务时,它可以使用相同的 Tika 配置:

修正破折号格式和翻译表达

`

1.  pip install vllm
2.  vllm serve jinaai/jina-vlm --host 0.0.0.0 --port 8000

`AI写代码

然后将 parser 指向 vLLM:

`

1.  {
2.    "parsers": [
3.      { "default-parser": { "exclude": ["tesseract-ocr-parser"] } },
4.      { "pdf-parser": { "ocr": { "strategy": "AUTO", "maxPagesToOcr": 10 } } },
5.      { "openai-vlm-deterministic-parser": {
6.          "baseUrl": "http://localhost:8000/v1",
7.          "model": "jinaai/jina-vlm",
8.          "maxTokens": 4096,
9.          "timeoutSeconds": 300
10.        } }
11.    ]
12.  }

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

始终显式设置 maxPagesToOcr — — 每个 OCR 页面都会产生一次 VLM 请求。完整详情请参阅:VLM OCR 文档。

REST API 重生:_document 无所不能

挑战: 旧的 _upload 端点功能有限。不支持删除,也无法从远程数据源获取文件,而且 /fscrawler/ 下的 URL 结构也不够简洁。

解决方案: 认识一下 **_document**。无论文件来自本地磁盘、HTTP URL 还是 S3 bucket,都可以进行上传、获取和删除。REST 服务默认运行在 /。使用 --rest 启动:

修正双破折号的格式补充 --rest 启动示例统一技术术语的翻译风格

`

1.  docker run -it --rm \
2.    --add-host=host.docker.internal:host-gateway \
3.    -v ~/.fscrawler:/root/.fscrawler \
4.    -p 8080:8080 \
5.    -e FSCRAWLER_ELASTICSEARCH_URLS=http://host.docker.internal:9200 \
6.    -e FSCRAWLER_ELASTICSEARCH_API_KEY="${ES_LOCAL_API_KEY}" \
7.    dadoonet/fscrawler myjob --rest

`AI写代码

上传文件:

`

1.  echo "Hello FSCrawler 3.0" > hello.txt
2.  curl -F "file=@hello.txt" "http://127.0.0.1:8080/_document"

`AI写代码

从 Web 获取并建立索引:

`

1.  curl -XPOST http://127.0.0.1:8080/_document \
2.    -H 'Content-Type: application/json' \
3.    -d '{
4.      "type": "http",
5.      "http": {
6.        "url": "https://david.pilato.fr/index.xml"
7.      }
8.    }'

`AI写代码

按文件名删除:

`curl -X DELETE "http://127.0.0.1:8080/_document?file`AI写代码

模拟解析但不建立索引(非常适合调试 Tika):

`curl -F "file=@scan.pdf" "http://127.0.0.1:8080/_document?simulate=true&debug=true"`AI写代码

更多示例(S3、SSH、FTP、自定义 ID、标签):REST 服务文档。

Checkpoint:我多年来一直想要的功能

这一点对我来说很特别。真正完善的暂停/恢复以及 checkpoint 持久化功能,一直存在于我的长期规划中。爬取大型文件系统需要花费数小时。网络偶尔会中断。笔记本电脑也会进入睡眠状态。你希望周五停止任务,周一继续,而不必从头开始重新索引所有内容。

这个设计并不简单:需要跟踪每个目录的扫描进度,以原子方式持久化状态,应对崩溃,处理 Elasticsearch bulk 操作失败,通过 REST 提供控制能力,同时还要在没有启用 --rest 的情况下正常工作。我原本估计这需要很多很多天的专注开发时间 — — 然后一直把它往后推。

然后 coding agent 出现了。有了 Cursor 和 Claude Code,我们可以进行非常紧凑的迭代循环:编写一个集成测试,使用 TestContainers 运行它,修复竞态条件,然后重复这个过程。原本可能需要数周时间完成的业余项目,最终变成了我们可以真正随 3.0 一起发布的功能。机器人帮了大忙。🤖

工作原理: FSCrawler 会在 ~/.fscrawler/{job_name}/ 中保存一个 _checkpoint.json。每处理 100 个文件以及状态发生变化时,都会持久化进度。发生崩溃并重新启动后,它会从上次停止的位置继续。

监控爬取过程:

统一中文与英文术语修正破折号和 Markdown 格式

`curl http://127.0.0.1:8080/_crawler/status`AI写代码

暂停并保存:

`curl -X POST http://127.0.0.1:8080/_crawler/pause`AI写代码

恢复(或触发按需运行):

`curl -X POST http://127.0.0.1:8080/_crawler/resume`AI写代码

强制重新建立索引(爬虫必须处于暂停或停止状态):

`curl -X DELETE http://127.0.0.1:8080/_crawler/checkpoint`AI写代码

你也可以在不使用 REST 的情况下,对正在运行的爬虫进行调整 — — 将 _checkpoint.json 中的 next_check 设置为 null,FSCrawler 就会在下一次等待周期启动新的扫描。网络错误会触发指数退避(可通过 elasticsearch.retry_* 设置进行配置);连续失败 10 次后,checkpoint 会进入 ERROR 状态。

完整 API 参考:爬虫控制文档。

第一天就拥有 Kibana dashboard

挑战: 你建立了文档索引……然后盯着空空如也的 Kibana,不知道该如何构建你的第一个 dashboard。

解决方案: 启动时,FSCrawler 可以通过 Dashboards API 自动创建默认 Kibana dashboard(Kibana 9.5+)。将 kibana.url 指向你的实例:

`

1.  name: "resumes"
2.  fs:
3.    url: "/tmp/es"
4.  elasticsearch:
5.    urls:
6.      - "http://host.docker.internal:9200"
7.    api_key: "YOUR_API_KEY"
8.  kibana:
9.    url: "http://host.docker.internal:5601"

`AI写代码

这解决了 issue #2477。详情请参阅:Kibana 设置。

它还附带了一个漂亮的默认 dashboard,例如:

语义搜索、外部 Metadata、ACL,以及其他细节

快速了解一下 — — 每项功能都链接到对应文档:

  • 语义搜索 — — 在 Elasticsearch 8.17+ 上自动创建 content_semantic 字段,需要 trial 或 enterprise license。

  • 外部 Metadata — — 在文件旁边放置 .meta.yml,或者设置 tags.staticMetaFilename,为整个 Job 设置标签。

  • NTFS ACL 提取 — — 提取 principals、permissions、flags(感谢 Alex Steele!)。通过 fs.attributes_support: true 和 fs.acl_support: true 启用。

  • 环境变量 — — 支持 twelve-factor 风格的覆盖设置(FSCRAWLER_ELASTICSEARCH_URLS、……),并支持在 _settings/ 中拆分设置。

  • 外部 JAR — — 通过 external/ 目录支持自定义 Tika parser 或 JPEG2000。

  • Apple Keynote(.key)支持 — — 解决了 issue #782,该 issue 创建于……2017 年。耐心终会有回报。说实话,这项功能是由 Tika 提供的!

5 分钟体验(Docker + start-local)

这是最快的方式。你需要运行中的 Docker。

启动 Elasticsearch 和 Kibana

统一术语和语言风格修正破折号和标点格式完整翻译剩余英文术语

`

1.  curl -fsSL https://elastic.co/start-local | sh
2.  cd elastic-start-local
3.  source .env
4.  echo "$ES_LOCAL_API_KEY"

`AI写代码

Elasticsearch:http://localhost:9200
Kibana:http://localhost:5601

start-local 仅用于本地开发。localhost 使用 HTTP,而不是 HTTPS。

拉取 FSCrawler 并创建 Job

`

1.  docker pull dadoonet/fscrawler

3.  docker run -it --rm \
4.    -v ~/.fscrawler:/root/.fscrawler \
5.    dadoonet/fscrawler --setup resumes

`AI写代码

编辑 ~/.fscrawler/resumes/_settings.yaml:

`

1.  ---
2.  name: "resumes"
3.  fs:
4.    url: "/tmp/es"

`AI写代码

不要设置 elasticsearch.index — — FSCrawler 会创建 resumes_docs 和 resumes alias。

为你的文件建立索引

将 PDF 或 Word 文档放入 ~/resumes,然后:

`

1.  docker run -it --rm \
2.    --add-host=host.docker.internal:host-gateway \
3.    -v ~/.fscrawler:/root/.fscrawler \
4.    -v ~/resumes:/tmp/es:ro \
5.    -e FSCRAWLER_ELASTICSEARCH_URLS=http://host.docker.internal:9200 \
6.    -e FSCRAWLER_ELASTICSEARCH_API_KEY="${ES_LOCAL_API_KEY}" \
7.    -e FSCRAWLER_KIBANA_URL=http://host.docker.internal:5601 \
8.    dadoonet/fscrawler resumes

`AI写代码

在 Linux 上,--add-host=host.docker.internal:host-gateway 可以让容器访问主机上的服务。在 Docker Desktop(macOS/Windows)上,host.docker.internal 通常开箱即用 — — 添加这个参数也不会造成影响。

FSCrawler 会连接到 Elasticsearch,为你的文件建立索引,并且 — — 如果正在运行 Kibana 9.5+ — — 创建那个漂亮的默认 dashboard。

搜索

在 Dev Tools 中或通过 curl:

`

1.  GET resumes/_search
2.  {
3.    "query": {
4.      "match": {
5.        "content": "elastic"
6.      }
7.    }
8.  }

`AI写代码

或者在 Kibana 中使用 ES|QL:

`

1.  FROM resumes
2.  | WHERE content : "elastic"

`AI写代码

不兼容变更(也就是那些小字说明)

我不骗你 — — 3.0 确实会破坏一些东西。不过这是有意为之的:

  • 新 job 必须使用 **--setup**(不再隐式创建配置)。

  • **elasticsearch.nodes** → **elasticsearch.urls** — — 未知的 key 会被静默忽略,而我们在测试期间就因此吃了亏。

  • **_upload** → **_document**

  • 文件夹排除路径需要以通配符结尾:使用 /tmp/foo/*,而不是 /tmp/foo

  • Docker 不再需要在命令中指定 fscrawler binary — — 只需要 job name。

  • Tika 4 Metadata key 重命名 — — 请更新你的查询和 index template。

  • 新 job 的默认 hash 算法为 SHA-256(现有 job 继续使用 MD5)。

  • Elasticsearch 6.x 不再经过测试,也不再受支持。它可能碰巧仍然可以工作 — — 但我不建议尝试。

完整详情:FSCrawler 3.0 release notes。

接下来是什么?

FSCrawler 作为一个项目已经有 15 年历史了。3.0 是我在疫情期间疯狂写代码时,于 2022 年谈到它 时就希望看到的版本。

roadmap 上仍然有很多事情要做 — — 而你的 issue 和 pull request 正在塑造它。如果 3.0 帮助你搜索那些你以为已经遗失在共享驱动器、FTP 服务器或 S3 bucket 中的文档,欢迎来 GitHub 或 Discuss 告诉我们。

现在,去给某些东西建立索引吧。🚀

原文:🎉 FSCrawler 3.0 is here! You know, for files! | David Pilato