news 2026/11/2 21:07:09

在 Kotlin(Android)中使用 Xberg 递归提取 URL 文档链接

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
在 Kotlin(Android)中使用 Xberg 递归提取 URL 文档链接
  • 后端
  • AI 应用
  • NLP

【免费下载链接】xberg

Polyglot document intelligence with a Rust core: extract text, metadata, images, tables, and structured data from 106 formats across 140 file extensions, plus code intelligence for 371 languages. Fifteen bindings, with CLI, REST API, and MCP server.

项目地址:https://gitcode.com/gh_mirrors/kr/xberg
点击查看免费下载

递归 URL 提取(recursive URL extraction)是 Xberg URL 摄取能力中的一项进阶特性:当以document模式抓取一个网页时,引擎会从提取结果中发现的文档链接(PDF、TXT、DOCX 等)继续向下抽取,形成一条"网页 → 文档 → 文档内链接 → …"的递归链路。本文以 url_recursive_document_urls.md 中的 Kotlin(Android)示例为主线,讲解如何用Xberg.extract打开递归文档链接抓取、如何通过CrawlConfig.follow_document_urls与document_url_depth控制递归深度与边界,并结合仓库源码说明其底层实现原理。读完本文,你将能在自己的 Android/JVM 工程中直接落地"一个 URL 一次调用、递归收获整条文档链"的提取方案。

说明:文中的 Kotlin 示例来自 alef 自动生成的 e2e 夹具(fixture),其完整定义位于 url_recursive_document_urls.json,对应的端到端测试用例见 UrlTest.kt。

场景:一次调用拿到"页面 + 文档链"

官方夹具对这条特性的一句话概括是:

extract: recursive URL extraction follows document links discovered in results

翻译过来即:递归 URL 提取会跟随提取结果中发现的文档链接。典型使用场景是:

  • 一个 HTML 首页上列出了一批下载链接(.pdf、.txt、.docx…),你想把首页正文和所有链接指向的文档内容一并提取出来;
  • 一个文档(如 PDF)内部又引用了其他文档,需要沿引用链继续抓取;
  • 站点资源分散在不同 CDN 域名上,文档链接默认允许跨主机跟随(见下文stay_on_domain的说明)。

在 Xberg 中这不需要写任何爬虫循环——document模式加上递归开关即可,引擎内部会自动把"结果中发现的新 URL"重新投喂给提取管线。

完整可运行的 Kotlin(Android)示例

夹具生成的 Kotlin 代码形态如下,它同时演示了 Jackson(snake_case)反序列化、ExtractInput与ExtractionConfig的 JSON 构造,以及Xberg.extract的调用与结果访问:

import io.xberg.* import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper fun main() = kotlinx.coroutines.runBlocking { val mapper = jacksonObjectMapper().setPropertyNamingStrategy(com.fasterxml.jackson.databind.PropertyNamingStrategies.SNAKE_CASE) val input = mapper.readValue("{\"kind\":\"uri\",\"uri\":\"https://example.com\"}", ExtractInput::class.java) val config = mapper.readValue("{\"url\":{\"crawl\":{\"document_url_depth\":1,\"follow_document_urls\":true,\"respect_robots_txt\":false,\"ssrf\":{}},\"mode\":\"document\"}}", ExtractionConfig::class.java) val result = Xberg.extract(input, config) println(result.results) println(result.results.get(1).content) }

逐段拆解

  1. ExtractInput(输入):{"kind":"uri","uri":"https://example.com"}表示本次输入是一个 HTTP(S) URI。在 Kotlin 绑定中它对应 ExtractInput.kt 的ExtractInput(kind = ExtractInputKind.URI, uri = ...),与本地路径、file://URI 一样共用Xberg.extract入口。
  2. ExtractionConfig(配置):核心是url段——mode = "document"告诉引擎把种子 URL 当作一个远程文档/页面来抓取,而不是站点级crawl;crawl段内打开递归文档链接的两个开关(见下节)。
  3. Xberg.extract(input, config):同步(阻塞)调用,返回ExtractionResult。由于递归开启,results不再是单元素,results.get(1)取到的是递归抓取到的第二个文档。
  4. Jackson 注意点:Kotlin 绑定通过 JNI 与 Rust 核心通信,使用 snake_case 命名策略保证 JSON 字段与 Rust serde 的 wire 格式一致;runBlocking说明该绑定是协程友好的。

夹具背后的断言(怎么验证"它真的递归了")

同一 fixture 在 url_recursive_document_urls.json 中给出了服务端 mock 与断言:

  • mock 服务返回一个 HTML 首页,内含链接linked.txt:
    • /→<html><body><h1>Recursive source</h1><a href="/linked.txt">Linked document</a></body></html>
    • /linked.txt→Recursive document target reached by Xberg.\n
  • 断言count_min(results) >= 2:只有递归真正发生了,才会产生第 2 个 result;
  • 断言results[1].content包含Recursive document target:验证第二个结果确实是顺着链接抓到的文档内容。

对应地,端到端测试 UrlTest.kt 中的testUrlRecursiveDocumentUrls()把上面的 JSON 原样搬进 Kotlin 测试,并在本地起 mock server 后运行同样的断言。这套"fixture → 生成代码 → e2e 测试"的链路,正是仓库中验证递归文档提取行为的方式。

递归文档链接的配置项详解

递归行为由CrawlConfig上的两个字段控制,在 Kotlin 绑定中位于 CrawlConfig.kt,与它们配合的还有UrlExtractionConfig的若干边界参数(见 UrlExtractionConfig.kt)。

follow_document_urls:总开关(默认false)

/** Re-enqueue discovered LinkType.Document URLs into the crawl frontier so * the crawl follows links *from* document pages (PDFs, etc.) as it would * from HTML pages. Default: false (documents terminate at materialisation). */ val followDocumentUrls: Boolean = false

默认关闭时,文档在"物化"(下载并提取)后就终结了——不会继续跟随文档内部发现的链接。置为true后,提取结果里发现的文档链接会被重新入队(re-enqueue)到抓取前沿,文档页和 HTML 页一样可以产生后续跳转。

document_url_depth:文档链深度(默认继承max_depth)

/** Maximum document-depth (from the seed URL through document links only) * when follow_document_urls is true. null means inherit max_depth. * Independent of max_depth: a document URL is enqueued only if BOTH the * outer max_depth and (if set) document_url_depth permit it. */ val documentUrlDepth: Int? = null

注意两点关键语义:

  • 独立于max_depth:max_depth是"页面链接跳数",document_url_depth是"仅沿文档链接走的跳数",二者是**与(AND)**关系——一个文档 URL 只有在外层max_depth和(若设置的)document_url_depth同时放行时才会被入队;
  • null时继承max_depth的值;夹具里显式设为1,即只沿文档链接追一层。

递归边界与安全兜底

递归不是无界的,UrlExtractionConfig提供了两道上限(默认值见 types.rs 中UrlExtractionConfig::default):

参数默认值作用
document_url_patternNone正则过滤器,只允许匹配到的文档 URL 被跟随
max_document_urls_per_result100单个提取结果最多跟随的 URL 数
max_total_urls1000整个提取调用累计跟随的 URL 总数上限

此外,种子域名约束stay_on_domain/allow_subdomains对文档链接的默认行为是:页面链接始终限定在种子域名(及其子域)内,文档链接却默认允许跨主机——因为文档常托管在 CDN 或对象存储上;只有当你显式把stay_on_domain置为true时,才要求文档也必须落在种子域名内(见 CrawlConfig.kt 的注释说明)。

示例中的respect_robots_txt与ssrf

夹具配置里还有两项:

  • respect_robots_txt: false:测试场景为快速抓取 mock 服务,临时关闭 robots.txt 约束。生产环境抓取不受信任的站点时,官方指南建议保持respect_robots_txt与stay_on_domain开启,并显式设置max_pages/max_total_urls上限;
  • ssrf: {}:空对象即使用默认 SSRF 策略——拒绝私有/内网地址、仅允许 http/https、最多 5 次重定向。Kotlin 端对应SsrfPolicy(见 SsrfPolicy.kt),默认deny_private = true。抓取用户提供的 URL 属于 SSRF 攻击面,务必保留该策略。

底层实现:递归链路在 Rust 核心中的样子

递归文档链接的"发动机"在 extract_impl.rs 的follow_recursive_document_urls(L1670-L1717)与enqueue_discovered_urls(L1719-L1744)两个函数中,逻辑清晰可读:

follow_recursive_document_urls ├─ follow_document_urls(config)? // 总开关关闭则直接返回 ├─ document_url_depth(config) == 0? // 深度为 0 同样直接返回 ├─ 编译 document_url_pattern 正则(非法正则 → validation 错误) ├─ enqueue_discovered_urls(初始结果, depth=1) // 首次入队 └─ while 队列非空: ├─ 以 ExtractInput::from_uri(uri) 递归调用 extract_one() ├─ depth < max_depth 时再次 enqueue_discovered_urls(depth+1) ├─ 累加 remote_urls / pages_crawled / documents_downloaded └─ results / errors 追加进总输出

几个值得注意的实现细节:

  1. URL 从哪里来(urls_from_result):每个ExtractedDocument结果中,uris里Hyperlink/Reference/Citation三类 URI 会被收集;同时正文content里出现的https?://文本也会被正则扫描出来(末尾标点会被 trim)。也就是说,无论是结构化的链接元数据还是纯文本里的 URL,都能进入递归队列。
  2. 跟随条件(should_follow_discovered_url):必须是http/https协议;若有document_url_pattern则必须匹配;若stay_on_domain开启则主机必须属于种子域名或(allow_subdomains时)其子域。
  3. 去重与上限:seen集合去重,命中max_total_urls(默认 1000)即停止入队;每个结果最多取max_document_urls_per_result(默认 100)个链接。
  4. 深度语义:document_url_depth(config)在未显式设置时回退到crawl.max_depth,再回退到默认 1(见 L1802-L1817)。
  5. 结果顺序:递归抓到的文档按遍历顺序追加在初始结果之后,这正解释了示例中results.get(1)能拿到第二个文档。

这段实现同时服务于extract的多种路径(L177/L385/L514 三处调用点),即无论走哪种 URL 摄取分支,递归逻辑都是同一份。

在 Android 工程中落地

把示例接入真实 Android 工程,推荐做法是直接使用 Kotlin 数据类构造而非字符串反序列化(仓库 packages/kotlin-android/README.md 的 Quick Start 即采用这种风格):

import io.xberg.* fun extractRecursively(url: String): ExtractionResult { val config = ExtractionConfig( url = UrlExtractionConfig( mode = UrlExtractionMode.DOCUMENT, crawl = CrawlConfig( followDocumentUrls = true, documentUrlDepth = 1, // 仅沿文档链接追一层 respectRobotsTxt = true, // 生产环境建议开启 maxPages = 50, maxConcurrent = 5, ssrf = SsrfPolicy(), // 默认拒绝内网地址 ), maxDocumentUrlsPerResult = 100, maxTotalUrls = 1000, ), ) return Xberg.extract( ExtractInput(kind = ExtractInputKind.URI, uri = url), config, ) } // 使用:results[0] 是种子页面/文档,后续元素是递归发现的文档 val output = extractRecursively("https://example.com") output.results.forEachIndexed { index, doc -> println("[$index] ${doc.content.take(200)}") }

要点提示:

  • JNI 加载:e2e 测试套件在companion object中通过System.loadLibrary("xberg_jni")加载本地库,并默认开启CRAWLBERG_ALLOW_PRIVATE_NETWORK环境变量以允许测试访问本地 mock server(见 UrlTest.kt)。生产环境请勿放开内网访问限制;
  • 依赖版本:Kotlin DSL 中声明implementation("io.xberg:xberg-android:1.3.6")即可(见 README.md);
  • 特性前提:递归文档链接依赖url-ingestionCargo 特性(full构建已包含);WASM 目标不提供 URL 摄取能力,Android/JVM 不受影响;
  • 结果语义:ExtractionResult信封里results为发现顺序的文档列表,errors记录单个输入的非致命错误,summary汇总remote_urls/pages_crawled/documents_downloaded等计数(见 types.rs),可用于在 UI 上展示抓取概况。

总结

Xberg 的递归文档 URL 提取,让你用一次Xberg.extract调用即可"页面 + 文档链"一网打尽。打开CrawlConfig.follow_document_urls、用document_url_depth设定文档链深度,再用max_document_urls_per_result/max_total_urls/document_url_pattern划定边界,即可在 Android 上获得可控、安全、可观测的递归提取能力。仓库内的 fixture(url_recursive_document_urls.json)、自动生成的 Kotlin 示例(url_recursive_document_urls.md)与端到端测试(UrlTest.kt)三份材料互相印证,是深入理解与二次开发这条特性的最佳入口。

  • 后端
  • AI 应用
  • NLP

【免费下载链接】xberg

Polyglot document intelligence with a Rust core: extract text, metadata, images, tables, and structured data from 106 formats across 140 file extensions, plus code intelligence for 371 languages. Fifteen bindings, with CLI, REST API, and MCP server.

项目地址:https://gitcode.com/gh_mirrors/kr/xberg
点击查看免费下载

相关推荐

上一篇:如何快速开发text-generation-inference自定义后端:扩展支持新模型的完整指南
下一篇:毫秒级响应!MediaMTX赋能AR/VR的超低延迟流媒体方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/2 21:06:09

pyprobml 软件学习路线指南:Python、ML 工具链与开发环境选型

机器学习深度学习 【免费下载链接】pyprobml Python code for "Probabilistic Machine learning" book by Kevin Murphy 项目地址&#xff1a; https://gitcode.com/gh_mirrors/py/pyprobml 点击查看 免费下载 本文是 pyprobml 项目&#xff08;《Probabilistic Mac…

作者头像 李华
网站建设 2026/10/20 0:01:18

题解:洛谷 P1104 生日

本文分享的必刷题目是从蓝桥云课、洛谷、AcWing等知名刷题平台精心挑选而来,并结合各平台提供的算法标签和难度等级进行了系统分类。题目涵盖了从基础到进阶的多种算法和数据结构,旨在为不同阶段的编程学习者提供一条清晰、平稳的学习提升路径。 欢迎大家订阅我的专栏:算法…

作者头像 李华
网站建设 2026/10/20 0:01:17

零 GC 委托优化实战:游戏大厂的帧预算保卫战

背景&#xff1a;16.6ms 里没有 GC 的位置 AAA 手游和端游的帧预算是死线&#xff0c;不是建议。60fps 意味着每帧 16.6ms&#xff0c;其中渲染、物理、逻辑、音频都在争这块蛋糕。托管 GC&#xff08;哪怕是 Unity 的增量式 GC&#xff09;一旦触发&#xff0c;轻则 0.5ms&…

作者头像 李华
网站建设 2026/10/20 0:01:16

25天打卡训练营实况:从三次弃营到稳定输出的系统方法

这篇稿子的缘起很简单&#xff1a;我今天早上在训练营小程序里按下“1.25训练营打卡”的确认键时&#xff0c;发现自己已经连续打卡 25 天&#xff0c;而这期 30 天的营期只剩下最后 5 天。过去半年里&#xff0c;我报名过三次类似的目标型打卡训练营&#xff0c;分别坚持了 11…

作者头像 李华
网站建设 2026/10/20 0:01:17

题解:洛谷 P2141 [NOIP 2014 普及组] 珠心算测验

本文分享的必刷题目是从蓝桥云课、洛谷、AcWing等知名刷题平台精心挑选而来,并结合各平台提供的算法标签和难度等级进行了系统分类。题目涵盖了从基础到进阶的多种算法和数据结构,旨在为不同阶段的编程学习者提供一条清晰、平稳的学习提升路径。 欢迎大家订阅我的专栏:算法…

作者头像 李华