Markdown 生成基础

Crawl4AI 的核心功能之一是从网页生成简洁、结构化的 Markdown 代码。Crawl4AI 的 Markdown 系统最初旨在解决如何只提取“实际”内容并丢弃样板或噪音的问题,如今它仍然是 AI 工作流程中最大的吸引力之一。

在本教程中,您将学习:

  1. 如何配置默认 Markdown 生成器
  2. 内容过滤器(BM25 或 Pruning)如何帮助您优化 Markdown 并丢弃垃圾
  3. 原始降价之间的差异(result.markdown ) 和过滤的 markdown (fit_markdown )
先决条件 - 您已完成或阅读AsyncWebCrawler 基础知识,了解如何运行简单的爬网。 - 您知道如何配置CrawlerRunConfig

1. 快速示例

下面是使用 DefaultMarkdownGenerator 且不带额外过滤的最小代码片段:

import asyncio
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig
from crawl4ai.markdown_generation_strategy import DefaultMarkdownGenerator

async def main():
    config = CrawlerRunConfig(
        markdown_generator=DefaultMarkdownGenerator()
    )
    async with AsyncWebCrawler() as crawler:
        result = await crawler.arun("https://example.com", config=config)

        if result.success:
            print("Raw Markdown Output:\n")
            print(result.markdown)  # The unfiltered markdown from the page
        else:
            print("Crawl failed:", result.error_message)

if __name__ == "__main__":
    asyncio.run(main())

发生什么事了?CrawlerRunConfig( markdown_generator = DefaultMarkdownGenerator() )指示 Crawl4AI 在每次爬取结束时将最终的 HTML 转换为 Markdown。- 生成的 Markdown 可通过以下方式访问result.markdown


2. Markdown 生成工作原理

2.1 HTML 到文本的转换(分叉和修改)

在底层,DefaultMarkdownGenerator 使用一种专门的 HTML 到文本方法:

  • 保留标题、代码块、项目符号等。
  • 删除不添加有意义内容的无关标签(脚本、样式)。
  • 可以选择生成链接的引用或完全跳过它们。

一组选项(以字典形式传递)允许您精确自定义 HTML 转换为 Markdown 的方式。这些选项映射到标准的类似 html2text 的配置,并可根据您的需求进行增强(例如,忽略内部链接、逐字保留某些标签或调整行宽)。

默认情况下,生成器可以转换<a href="...">元素进入[text][1]引用,然后将实际链接放在文档底部。这对于需要结构化引用的研究工作流程非常方便。

2.3 可选内容过滤器

在 HTML 转 Markdown 的步骤之前或之后,您可以应用内容过滤器(例如 BM25 或 Pruning)来减少噪音,并生成一个“fit_markdown”——一个高度精简、专注于页面主要文本的版本。我们稍后会介绍这些过滤器。


3. 配置默认 Markdown 生成器

您可以通过传递options听写DefaultMarkdownGenerator。 例如:

from crawl4ai.markdown_generation_strategy import DefaultMarkdownGenerator
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig

async def main():
    # Example: ignore all links, don't escape HTML, and wrap text at 80 characters
    md_generator = DefaultMarkdownGenerator(
        options={
            "ignore_links": True,
            "escape_html": False,
            "body_width": 80
        }
    )

    config = CrawlerRunConfig(
        markdown_generator=md_generator
    )

    async with AsyncWebCrawler() as crawler:
        result = await crawler.arun("https://example.com/docs", config=config)
        if result.success:
            print("Markdown:\n", result.markdown[:500])  # Just a snippet
        else:
            print("Crawl failed:", result.error_message)

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

一些常用options

  • (bool): 是否删除最终 markdown 中的所有超链接。
  • (bool): 删除所有![image]()参考。
  • (bool): 将 HTML 实体转换为文本(默认值通常是True)。
  • (int):在 N 个字符处换行。0或者None表示不包装。
  • (布尔值):如果True,省略#localAnchors或引用同一页面的内部链接。
  • (bool): 尝试处理<sup>/<sub>以更易读的方式。

4. 选择用于生成 Markdown 的 HTML 源

content_source参数允许您控制哪些 HTML 内容将用作 Markdown 生成的输入。这让您能够灵活地控制在转换为 Markdown 之前如何处理 HTML。

from crawl4ai.markdown_generation_strategy import DefaultMarkdownGenerator
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig

async def main():
    # Option 1: Use the raw HTML directly from the webpage (before any processing)
    raw_md_generator = DefaultMarkdownGenerator(
        content_source="raw_html",
        options={"ignore_links": True}
    )

    # Option 2: Use the cleaned HTML (after scraping strategy processing - default)
    cleaned_md_generator = DefaultMarkdownGenerator(
        content_source="cleaned_html",  # This is the default
        options={"ignore_links": True}
    )

    # Option 3: Use preprocessed HTML optimized for schema extraction
    fit_md_generator = DefaultMarkdownGenerator(
        content_source="fit_html",
        options={"ignore_links": True}
    )

    # Use one of the generators in your crawler config
    config = CrawlerRunConfig(
        markdown_generator=raw_md_generator  # Try each of the generators
    )

    async with AsyncWebCrawler() as crawler:
        result = await crawler.arun("https://example.com", config=config)
        if result.success:
            print("Markdown:\n", result.markdown.raw_markdown[:500])
        else:
            print("Crawl failed:", result.error_message)

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

HTML 源选项

  • (默认):使用经过抓取策略处理后的 HTML。这种 HTML 通常更简洁,更注重内容,并且删除了一些样板代码。
  • :直接使用网页中的原始 HTML,无需任何清理或处理。这样可以保留更多原始内容,但可能包含导航栏、广告、页脚以及其他与主要内容不相关的元素。
  • :使用预处理的 HTML 进行架构提取。此 HTML 已针对结构化数据提取进行了优化,并且可能简化或删除了某些元素。

何时使用每个选项

  • 使用"cleaned_html"(默认)适用于大多数需要在内容保存和噪音消除之间取得平衡的情况。
  • 使用"raw_html"当您需要保留所有原始内容时,或者当清理过程删除您实际想要保留的内容时。
  • 使用"fit_html"当处理结构化数据或需要针对模式提取进行优化的 HTML 时。

5.内容过滤器

内容过滤器会选择性地移除或排序部分文本,然后再将其转换为 Markdown 格式。如果您的页面包含广告、导航栏或其他您不想要的杂乱内容,此功能尤其有用。

5.1 BM25内容过滤器

如果您有搜索查询,BM25 是一个不错的选择:

from crawl4ai.markdown_generation_strategy import DefaultMarkdownGenerator
from crawl4ai.content_filter_strategy import BM25ContentFilter
from crawl4ai import CrawlerRunConfig

bm25_filter = BM25ContentFilter(
    user_query="machine learning",
    bm25_threshold=1.2,
    language="english"
)

md_generator = DefaultMarkdownGenerator(
    content_filter=bm25_filter,
    options={"ignore_links": True}
)

config = CrawlerRunConfig(markdown_generator=md_generator)
  • :您想要关注的术语。BM25 会尝试仅保留与该查询相关的内容块。
  • :升高它可以保留较少的块;降低它可以保留较多的块。
  • (默认True): 是否将词干提取应用于查询和内容。
  • :词干提取的语言(默认值:'英语')。

没有提供查询?BM25 会尝试从页面元数据中收集上下文,或者您可以简单地将其视为一种焦土政策,丢弃通用分数较低的文本。实际上,您需要提供查询才能获得最佳结果。

5.2 修剪内容过滤器

如果你没有特定的查询,或者你只是想要一个强大的“垃圾清除器”,请使用PruningContentFilter。它分析文本密度、链接密度、HTML 结构和已知模式(如“导航”、“页脚”),以系统地修剪无关或重复的部分。

from crawl4ai.content_filter_strategy import PruningContentFilter

prune_filter = PruningContentFilter(
    threshold=0.5,
    threshold_type="fixed",  # or "dynamic"
    min_word_threshold=50
)
  • :分数边界。低于此分数的方块将被移除。
  • "fixed" :直接比较(score >= threshold保持块)。"dynamic" :过滤器以数据驱动的方式调整阈值。
  • :丢弃 N 个单词以下的块,因为它们可能太短或没有帮助。

何时使用 PruningContentFilter - 您想要进行没有用户查询的广泛清理。 - 页面有大量重复的侧边栏、页脚或免责声明,妨碍了文本提取。

5.3 LLM内容过滤器

为了实现智能内容过滤和高质量 Markdown 生成,您可以使用 LLMContentFilter。此过滤器利用 LLM 生成相关的 Markdown,同时保留原始内容的含义和结构:

from crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig, LLMConfig, DefaultMarkdownGenerator
from crawl4ai.content_filter_strategy import LLMContentFilter

async def main():
    # Initialize LLM filter with specific instruction
    filter = LLMContentFilter(
        llm_config = LLMConfig(provider="openai/gpt-4o",api_token="your-api-token"), #or use environment variable
        instruction="""
        Focus on extracting the core educational content.
        Include:
        - Key concepts and explanations
        - Important code examples
        - Essential technical details
        Exclude:
        - Navigation elements
        - Sidebars
        - Footer content
        Format the output as clean markdown with proper code blocks and headers.
        """,
        chunk_token_threshold=4096,  # Adjust based on your needs
        verbose=True
    )
    md_generator = DefaultMarkdownGenerator(
        content_filter=filter,
        options={"ignore_links": True}
    )
    config = CrawlerRunConfig(
        markdown_generator=md_generator,
    )

    async with AsyncWebCrawler() as crawler:
        result = await crawler.arun("https://example.com", config=config)
        print(result.markdown.fit_markdown)  # Filtered markdown content

主要特点: - 智能过滤:使用 LLM 理解和提取相关内容,同时保持上下文 - 可定制指令:使用特定指令定制过滤过程 - 块处理:通过分块处理来处理大型文档(由chunk_token_threshold) - 并行处理:为了获得更好的性能,使用较小的chunk_token_threshold(例如 2048 或 4096)以实现内容块的并行处理

两个常见用例:

  1. 精确内容保存:
    filter = LLMContentFilter(
        instruction="""
        Extract the main educational content while preserving its original wording and substance completely.
        1. Maintain the exact language and terminology
        2. Keep all technical explanations and examples intact
        3. Preserve the original flow and structure
        4. Remove only clearly irrelevant elements like navigation menus and ads
        """,
        chunk_token_threshold=4096
    )
    
  2. 重点内容提取:
    filter = LLMContentFilter(
        instruction="""
        Focus on extracting specific types of content:
        - Technical documentation
        - Code examples
        - API references
        Reformat the content into clear, well-structured markdown
        """,
        chunk_token_threshold=4096
    )
    
性能提示:设置较小的chunk_token_threshold(例如 2048 或 4096)以启用内容块的并行处理。默认值为 infinity,表示将整个内容作为单个块进行处理。

6. 使用 Fit Markdown

当内容过滤器处于活动状态时,库会在内部生成两种形式的 markdownresult.markdown

1.raw_markdown :完整未过滤的降价。2.fit_markdown :过滤器已移除或修剪噪声部分的“适合”版本。

import asyncio
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig
from crawl4ai.markdown_generation_strategy import DefaultMarkdownGenerator
from crawl4ai.content_filter_strategy import PruningContentFilter

async def main():
    config = CrawlerRunConfig(
        markdown_generator=DefaultMarkdownGenerator(
            content_filter=PruningContentFilter(threshold=0.6),
            options={"ignore_links": True}
        )
    )
    async with AsyncWebCrawler() as crawler:
        result = await crawler.arun("https://news.example.com/tech", config=config)
        if result.success:
            print("Raw markdown:\n", result.markdown)

            # If a filter is used, we also have .fit_markdown:
            md_object = result.markdown  # or your equivalent
            print("Filtered markdown:\n", md_object.fit_markdown)
        else:
            print("Crawl failed:", result.error_message)

if __name__ == "__main__":
    asyncio.run(main())

7.MarkdownGenerationResult目的

如果你的库将详细的 markdown 输出存储在类似MarkdownGenerationResult,您将看到如下字段:

  • :直接 HTML 到 markdown 的转换(无过滤)。
  • :将链接移动到参考样式脚注的版本。
  • :包含收集的引用的单独字符串或部分。
  • :如果您使用了内容过滤器,则过滤后的 markdown。
  • :用于生成相应的 HTML 代码片段fit_markdown(有助于调试或高级用法)。

例子:

md_obj = result.markdown  # your library’s naming may vary
print("RAW:\n", md_obj.raw_markdown)
print("CITED:\n", md_obj.markdown_with_citations)
print("REFERENCES:\n", md_obj.references_markdown)
print("FIT:\n", md_obj.fit_markdown)

为什么这很重要? - 您可以提供raw_markdown如果你想要完整的文本,可以申请法学硕士。- 或者fit_markdown放入矢量数据库以减少令牌的使用。 -references_markdown可以帮助您追踪链接来源。


以下是“组合过滤器(BM25 + 剪枝)”下修订版的部分,演示了如何在不重新抓取内容的情况下运行两遍内容过滤,方法是将第一遍的内容过滤中的 HTML(或文本)输入到第二遍过滤器中。它使用了您为 BM25ContentFilter 提供的代码片段中的真实代码模式,该代码片段直接接受 HTML 字符串(并且只需进行少量调整即可处理纯文本)。


8. 两次传递合并滤波器(BM25 + 剪枝)

你可能想先删除嘈杂的样板代码(使用PruningContentFilter),然后根据用户查询对剩余内容进行排名(使用BM25ContentFilter)。您无需两次抓取该页面。相反,您可以:

1. 第一阶段:应用PruningContentFilter直接到原始 HTMLresult.html (爬虫下载的 HTML)。2. 第二遍:从步骤 1 中获取修剪后的 HTML(或文本),并将其输入到BM25ContentFilter,重点关注用户查询。

两次传递示例

import asyncio
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig
from crawl4ai.content_filter_strategy import PruningContentFilter, BM25ContentFilter
from bs4 import BeautifulSoup

async def main():
    # 1. Crawl with minimal or no markdown generator, just get raw HTML
    config = CrawlerRunConfig(
        # If you only want raw HTML, you can skip passing a markdown_generator
        # or provide one but focus on .html in this example
    )

    async with AsyncWebCrawler() as crawler:
        result = await crawler.arun("https://example.com/tech-article", config=config)

        if not result.success or not result.html:
            print("Crawl failed or no HTML content.")
            return

        raw_html = result.html

        # 2. First pass: PruningContentFilter on raw HTML
        pruning_filter = PruningContentFilter(threshold=0.5, min_word_threshold=50)

        # filter_content returns a list of "text chunks" or cleaned HTML sections
        pruned_chunks = pruning_filter.filter_content(raw_html)
        # This list is basically pruned content blocks, presumably in HTML or text form

        # For demonstration, let's combine these chunks back into a single HTML-like string
        # or you could do further processing. It's up to your pipeline design.
        pruned_html = "\n".join(pruned_chunks)

        # 3. Second pass: BM25ContentFilter with a user query
        bm25_filter = BM25ContentFilter(
            user_query="machine learning",
            bm25_threshold=1.2,
            language="english"
        )

        # returns a list of text chunks
        bm25_chunks = bm25_filter.filter_content(pruned_html)  

        if not bm25_chunks:
            print("Nothing matched the BM25 query after pruning.")
            return

        # 4. Combine or display final results
        final_text = "\n---\n".join(bm25_chunks)

        print("==== PRUNED OUTPUT (first pass) ====")
        print(pruned_html[:500], "... (truncated)")  # preview

        print("\n==== BM25 OUTPUT (second pass) ====")
        print(final_text[:500], "... (truncated)")

if __name__ == "__main__":
    asyncio.run(main())

发生了什么事?

1. 原始 HTML:我们抓取一次并将原始 HTML 存储在result.html2. PruningContentFilter:接受 HTML 和可选参数。它会提取文本块或部分 HTML,移除被视为“噪音”的标题/章节。它会返回一个文本块列表。3. 合并或转换:我们将这些修剪后的块重新连接成一个类似 HTML 的字符串。(或者,您也可以将它们存储在列表中,以便进一步处理逻辑——只要适合您的管道即可。)4. BM25ContentFilter:我们将修剪后的字符串输入到BM25ContentFilter通过用户查询。第二遍进一步将内容范围缩小到与“机器学习”相关的块。

无需重新抓取:我们使用raw_html从第一遍开始,所以不需要运行arun()再次——没有第二个网络请求。

技巧与变化

  • 纯文本 vs. HTML:如果修剪后的输出大部分是文本,BM25 仍然可以处理;但请记住,它需要有效的字符串输入。如果您提供部分 HTML(例如"<p>some text</p>"),它会将其解析为 HTML。
  • 在单个管道中链接:如果您的代码支持,您可以自动链接多个过滤器。否则,手动进行两遍过滤(如图所示)很简单。
  • 调整阈值:如果您在第一步中看到文本过多或过少,请调整threshold=0.5或者min_word_threshold=50。 相似地,bm25_threshold=1.2可以在第二步中升高/降低以获得更多或更少的块。

一次通过的组合?

如果您的代码库或流水线设计允许一次性应用多个过滤器,您可以这样做。但通常更简单、更透明的做法是按顺序运行它们,并分析每个步骤的结果。

总结:通过手动将过滤逻辑串联成两遍,您可以对最终内容进行强大的增量控制。首先,使用剪枝移除“全局”杂乱内容,然后使用基于 BM25 的查询相关性进一步优化——无需再次进行网络爬取。


9. 常见陷阱与技巧

1. 没有 Markdown 输出? - 确保爬虫确实检索到了 HTML。如果网站大量使用 JS,您可能需要启用动态渲染或等待元素加载。 - 检查您的内容过滤器是否过于严格。降低阈值或禁用过滤器,看看内容是否会重新出现。

2. 性能考虑 - 包含多个过滤器的大型页面可能会比较慢。考虑cache_mode以避免重新下载。 - 如果您的最终用例是 LLM 摄取,请考虑进一步总结或分块大文本。

3. 利用fit_markdown- 非常适合 RAG 管道、语义搜索或任何不需要多余样板的场景。 - 仍然验证文本质量 - 一些网站在页脚或侧边栏中有关键数据。

4.调整html2text选项 - 如果您发现文本中混入大量原始 HTML,请打开escape_html. - 如果代码块看起来很乱,可以尝试mark_code或者handle_code_in_pre


10.总结及后续步骤

在本 Markdown 生成基础教程中,您学习了:

  • 使用 HTML 到文本选项配置 DefaultMarkdownGenerator。
  • 使用选择不同的 HTML 源content_source范围。
  • 使用 BM25ContentFilter 进行特定查询的提取或使用 PruningContentFilter 进行一般噪声消除。
  • 区分原始 Markdown 和过滤 Markdown(fit_markdown )。
  • 利用MarkdownGenerationResult对象来处理不同形式的输出(引用、参考等)。

现在,您可以从任何网站生成高质量的 Markdown,专注于您所需的内容 - 这是支持 AI 模型、摘要管道或知识库查询的重要步骤。

最后更新时间:2025-01-01


> Feedback