Read the Docs 服务端搜索集成:主节点识别、噪音清理与章节解析的 HTML 约定
后端文档【免费下载链接】readthedocs.orgThe source code that powers readthedocs.org项目地址https://gitcode.com/gh_mirrors/re/readthedocs.org点击查看免费下载本文基于 Read the Docs 仓库中的 search-integration.rst 设计文档展开讲解其服务端搜索Server Side Search, SSS如何直接解析站点 HTML 页面建立搜索索引包括主内容节点main/rolemain的识别回退链、无关内容导航、搜索框、行号等的剔除规则以及标题与定义列表章节的切分约定。读完后主题作者或静态站点生成器开发者可以对照仓库 GenericParser 源码实现 让自己的 HTML 输出被 SSS 正确索引并在 Sphinx 项目中理解 Read the Docs 如何接管默认客户端搜索。服务端搜索与 HTML 解析的背景Read the Docs 提供服务端搜索以替代站点自带的默认搜索引擎。其核心思路是不依赖任何站点专属的搜索数据文件而是直接从构建产物HTML 页面中解析内容[*]_对 Sphinx 项目主节点内容会在构建流程的中间步骤提供但节点中的 HTML 结构保持不变见 search-integration.rst 脚注。对主题作者或静态站点生成器作者而言只要产出符合下述约定的 HTML就可以免费获得 SSS 支持通过 Dashboard 或调用搜索 API参见 server-side-search 文档。整个索引流程分三步与原文档一致识别主内容节点main content node从主节点中移除所有无关内容解析主节点内的所有章节sections。从源码结构看这三步对应 readthedocs/search/parsers.py 中GenericParser类的方法链parse()入口先调用_get_main_node()定位主节点再经_process_content()依次执行_clean_body()移除无关节点和_get_sections()切分章节。解析结果最终写入 ElasticsearchHTMLFile.processed_json 属性直接实例化GenericParser并返回结构化 JSON而 PageDocument 将其映射为 ES 文档。第一步识别主内容节点页面应把主体内容放在main标签或带有rolemain的元素内且每页只能有一个。只有该节点内部的内容会被索引其外部的内容如页脚、面包屑一律忽略html head ... /head body div This content isnt processed /div div rolemain All content inside the main node is processed /div footer This content isnt processed /footer /body /html如果页面没有找到显式的主节点解析器会按启发式规则回退取第一个h1标题的父元素作为主节点因为通常所有章节都是同一父节点的子元素body div This content isnt processed /div div idparent h1First title/h1 p The parent of the h1 title will be taken as the main node, this is the div tag. /p h2Second title/h2 pMore content/p /div /body如果连h1都不存在则最终回退到body标签本身body pContent/p /body对照 源码_get_main_node()实际检测顺序是先查[rolemain]被多个静态站点和主题采用再查main标签然后取第一个h1的容器父节点若h1直接包在header中则视为header是其容器见_get_header_container()全部失败才返回body。这与原文档描述一致并补充了实现细节回退链是role 优先于语义标签的。第二步移除无关内容如果主节点内部存在与正文无关的内容导航项、菜单、搜索框等必须为其使用正确的 ARIA role 或标签。遵循 ARIA 规范的同时也能改善站点可访问性。会被忽略的 ARIA rolenavigationsearch会被忽略的标签nav从具体文档工具约定派生的特殊规则应用于通用解析器类名来源用途.linenos、.linenoMkDocs 与 Sphinx 均有代码块行号.headerlinkSphinx标题中的Permalink锚点.toctree-wrapperSphinxtoctree指令生成的目录树示例导航区域被剔除不进入索引div rolemain ... nav rolenavigation ... /nav ... /div对照 源码_clean_body()除上述 role 与类名外解析器在切分章节前还会一并移除script、style、template、noscript节点并对 Sphinx 的 permalink.headerlink和toctree目录.toctree-wrapper因 Sphinx 不将其包在nav内而需单独处理做定向清理。方法注释中明确说明它会直接变更mutate传入的 body所有被选中的节点通过decompose()从 DOM 中拆出。第三步解析章节Sections每个章节被存为包含id、title、content三个键的字典这也是 PageDocument 中sections嵌套字段的属性结构id用 Keyword 字段、content使用term_vectorwith_positions_offsets以加速大文档高亮。章节的两种定义方式h1–h6标题从一个标题到下一个同级标题之间的所有内容都作为该章节的 content。原文档写作h1-h7从 源码_parse_sections()看实际遍历的是h1到h6。带id属性的dt元素title映射自dt元素content映射自其紧邻的dd元素。每个章节必须通过 DOM 容器的id属性标识该 id 将用于生成跳转章节的链接。id 的取值规则因元素类型而异h1-h7元素优先用标题自身的id属性否则用其section父容器的id见_parse_section_title()的两级回退dt元素用dt自身的id属性。为避免章节引用重复和歧义所有被索引的dl元素会在索引其他章节之前从 DOM 中移除源码中对已处理的dt/dd调用decompose()见_parse_sections()与_parse_dls()。标题之下、直到下一个章节之前的所有内容段落、列表等都会被索引为该章节内容div rolemain h1 idsection-title Section title /h1 p Content to be indexed /p ul liThis is also part of the section and will be indexed as well/li /ul h2 id2 This is the start of a new section /h2 p ... /p ... header h1 id3This is also a valid section title/h1 /header p This is the content of the third section. /p /div嵌套章节的深度限制章节标题最多可以包在两层嵌套标签内且章节可以包含子章节nested sections注意章节内容仍必须位于章节标题之后。例如子章节的h2在标题下两级两层div内仍能被识别div rolemain div classsection h1 idsection-title Section title /h1 p Content to be indexed /p ul liThis is also part of the section/li /ul div classsection div idnested-section h2 This is the start of a sub-section /h2 p With the h tag within two levels /p /div /div /div /div从源码看_parse_section_content()以depth2递归查找章节边界即标题之下两层内遇到新标题即截断而对页面开头、第一个标题之前的内容则用depth3探测命中的内容会先以页面标题id为空索引——这解释了无标题页面或前置导语的处理方式。页面标题的确定第一个章节的标题将作为页面标题如果页面没有标题则回退到title标签见_get_page_title()先取第一个h1文本再取title标签最后回退到页面路径。其他特殊节点的处理约定锚点Anchors如果章节标题中包含锚点链接请把它包在带headerlink类的元素内使其不会被索引进标题h2 Section title a classheaderlink titlePermalink to this headline¶/a /h2代码块Code blocks如果代码块带行号把行号包在linenos或lineno类内使其不会作为代码内容被索引table classhighlighttable tr td classlinenos div classlinenodiv pre1 2 3/pre /div /td td classcode div classhighlight preFirst line Second line Third line/pre /div /td /tr /table源码对此有两道防线一是_clean_body()在全局范围移除.linenos/.lineno节点二是专门的代码章节解析——_is_code_section()识别出包含pre且 class 以highlight开头的容器Sphinx 与 MkDocs 代码块的通用约定再由_parse_code_section()在块内再次剔除行号节点只提取pre文本。另外几个从源码补充的约束帮助理解索引数据的形态块级元素p、div、table、li等见block_level_elements列表在拼接正文时前后各加一个换行行内元素则直接拼接单节内容超过约 1MBmax_content_length 1024 * 1024字符会被截断并记日志每页最多索引 10000 个章节max_inner_documents 10000与 ES 索引设置index.mapping.nested_objects.limit对齐超出会告警。GenericParser.parse()最终输出的 JSON 结构为见 源码{ path: file path, title: Title, sections: [ {id: section-anchor, title: Section title, content: Section content}, ], # 另有 main_content_hash / text_hash / markup_hash 用于文件树 diff 判断页面是否变化 }接管默认搜索Sphinx 的 Search.query 覆盖静态站点通常自带静态搜索索引搜索结果通过 JavaScript 检索。Read the Docs仅对 Sphinx 项目覆盖默认搜索并在出错或无结果时回退到原始搜索。Sphinx 的 basic 主题提供static/searchtools.js文件其中Search.init()方法初始化搜索。Read the Docs 的做法是覆盖Search.query方法并复用Search.output.append来插入结果简化后的示意代码如下与原文档一致var original_search Search.query; function search_override(query) { var results fetch_resuls(query); if (results) { for (var i 0; i results.length; i 1) { var result process_result(results[i]); Search.output.append(result); } } else { original_search(query); } } Search.query search_override; $(document).ready(function() { Search.init(); });结果中的高亮词会放在带highlighted类的span标签内即This is a span classhighlightedresult/span。如果你的主题兼容 basic 主题的搜索渲染那么它就天然兼容 Read the Docs 的 SSS。后端侧高亮数据由 ES 索引中sections.content字段的with_positions_offsetsterm vector 提供见 documents.py 注释这也是搜索结果能按词高亮定位的实现基础。其他静态站点生成器所有产出符合本文档约定的 HTML 页面的项目都可以直接通过 Dashboard 或调用搜索 API 使用服务端搜索无需修改自身 JS。验证约定是否生效解析器测试如果你想验证自己的主题输出是否符合上述约定可以参照仓库中的解析器测试 test_parsers.py它为 MkDocsdefault、gitbook、material、windmill 等主题、Sphinx 等不同文档类型准备了真实构建产物 HTML 作为输入位于 readthedocs/search/tests/data/断言HTMLFile.processed_json的输出与预期 JSON 完全一致。这些测试数据本身就是符合 SSS 约定的 HTML的参照样例——主题作者可以比对其中rolemain的选取、导航剔除和章节切分结果确认自己的页面能产生预期结构。索引链路本身由 Celery 任务驱动构建完成后search/tasks.py 中的index_objects_to_es等任务把Project/HTMLFile对象写入对应 ES 索引PageDocument.get_queryset()会排除被忽略文件、已下架delisted及垃圾项目保证只有正常项目参与搜索。主题与站点生成器的自检清单综合原文档约定与源码实现一份SSS 友好的 HTML 应满足每页有且仅有一个main或[rolemain]容器正文全部位于其中若无显式主节点第一个h1的父节点能覆盖全部正文导航、菜单、搜索框使用nav标签或navigation/searchrole章节标题位于标题之下不超过两层嵌套内且标题或父容器带唯一id标题锚点用headerlink类包裹代码块行号用linenos/lineno类包裹Sphinx 项目使用兼容 basic 主题Search对象的渲染即可自动获得 SSS 覆盖与回退。若发现新的约定应被支持、内容应被忽略或有特殊处理或发现索引错误可以通过 Read the Docs 的 issue tracker 反馈见 search-integration.rst 末尾说明这些约定会随着 GenericParser 的演进持续完善。赞分享后端文档【免费下载链接】readthedocs.orgThe source code that powers readthedocs.org项目地址https://gitcode.com/gh_mirrors/re/readthedocs.org点击查看免费下载相关推荐CANN/ge模型描述获取接口aclmdlGetDesca nameZH CN_TOPIC_0000001312641337 /a 产品支持情况a namesection16人工智能深度学习模型编译模型优化编译器Ascend如何用Nix构建mermaid-asciiflake.nix可复现构建指南如何用Nix构建mermaid asciiflake.nix可复现构建指南 mermaid ascii 是一款能将 Mermaid 图表直接渲染为终端 ASCCLI开发工具pg_durable条件执行掌握if-else、case-when和switch模式的终极指南pg_durable条件执行掌握if else、case when和switch模式的终极指南 pg_durable是PostgreSQL的in databa上一篇Kubespray 如何启用 AWS EBS CSI Driver 并验证 StorageClass 与 PVC 动态供给下一篇3分钟搞定国家中小学智慧教育平台电子课本PDF下载终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考