首页
/ Crawl4AI 懒加载图片抓取实战:wait_for_images 与 scan_full_page 全解析

Crawl4AI 懒加载图片抓取实战:wait_for_images 与 scan_full_page 全解析

2026-09-04 20:48:47作者:伍霜盼Ellen

在 Crawl4AI 中,很多现代网站会随滚动懒加载(lazy-load) 图片——页面初次渲染时只有少量真实 <img>,其余是占位符。如果直接抓取,这些图片不会出现在 result.media 里。本文基于仓库文档 懒加载指南,讲透三个核心配置项 wait_for_imagesscan_full_pagescroll_delay 的用法与底层实现,读完你可以稳定抓取整个图库/信息流中的懒加载图片,并了解每个参数在源码中的真实执行路径。

一、三个核心配置项及其默认值

参数 类型 默认值 作用
wait_for_images bool False 等待所有 <img> 元素加载完成后再提取内容
scan_full_page bool False 让爬虫从页面顶部滚动到底部,触发懒加载
scroll_delay float 0.2(秒) 每一步滚动之间的暂停时间,给站点留出加载图片的时间
max_scroll_steps int | None None 全页扫描的最大滚动步数,防止无限滚动页卡死

这四个参数的定义可以在 CrawlerRunConfig 构造函数 中直接确认:

# crawl4ai/async_configs.py(节选)
wait_until: str = "domcontentloaded",
page_timeout: int = PAGE_TIMEOUT,
wait_for: str = None,
wait_for_images: bool = False,
delay_before_return_html: float = 0.1,
...
scan_full_page: bool = False,
scroll_delay: float = 0.2,
max_scroll_steps: Optional[int] = None,

官方参数说明 对这三个参数给出了与本文一致的语义:

  • wait_for_images:为 True 时在提取内容前等待图片加载;
  • scan_full_page:为 True 时滚动整个页面以加载所有内容;
  • scroll_delayscan_full_page=True 时每一步滚动之间的秒级延迟,默认 0.2
  • max_scroll_steps:全页扫描期间的最大滚动步数,None 表示一直滚动直到整个页面加载完(默认 None)。

二、完整示例:确保懒加载图片出现在结果中

下面是文档给出的可运行示例,配合注释说明每一步的意图:

import asyncio
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig, BrowserConfig
from crawl4ai.async_configs import CacheMode

async def main():
    config = CrawlerRunConfig(
        # 强制等待图片完全加载后再收尾
        wait_for_images=True,

        # 方案 1:自动滚动整页以触发懒加载
        scan_full_page=True,  # 让爬虫尝试滚动整个页面
        scroll_delay=0.5,     # 每步滚动之间的延迟(秒)

        # 方案 2:如果站点用 "Load More" 或 JS 事件触发图片,
        # 还可以在此处指定 js_code 或 wait_for 逻辑。

        cache_mode=CacheMode.BYPASS,  # 绕过缓存,保证抓到最新图片
        verbose=True
    )

    async with AsyncWebCrawler(config=BrowserConfig(headless=True)) as crawler:
        result = await crawler.arun("https://www.example.com/gallery", config=config)

        if result.success:
            images = result.media.get("images", [])
            print("Images found:", len(images))
            for i, img in enumerate(images[:5]):
                print(f"[Image {i}] URL: {img['src']}, Score: {img.get('score','N/A')}")
        else:
            print("Error:", result.error_message)

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

关键点解释:

  • wait_for_images=True:爬虫在最终确定 HTML 前会尝试确保图片加载完毕(源码行为见下一节);
  • scan_full_page=True:爬虫会从顶部向底部逐步滚动,每一步滚动都会触发视口内懒加载逻辑;
  • scroll_delay=0.5:每步滚动后暂停 0.5 秒,帮助站点在下载/渲染图片后再继续;
  • 结果通过 result.media["images"] 获取,每个条目含 srcscore 字段,可与 image_score_threshold 配合做质量过滤。

三、源码解析:wait_for_images 到底做了什么

AsyncCrawlerStrategy 的内容加载阶段,可以清楚看到 wait_for_images 的执行路径:

# crawl4ai/async_crawler_strategy.py(节选)
if not self.browser_config.text_mode and (
    config.wait_for_images or config.adjust_viewport_to_content
):
    await page.wait_for_load_state("domcontentloaded")
    await asyncio.sleep(0.1)

    # Check for image loading with improved error handling
    images_loaded = await self.csp_compliant_wait(
        page,
        "() => Array.from(document.getElementsByTagName('img')).every(img => img.complete)",
        timeout=1000,
    )

    if not images_loaded and self.logger:
        self.logger.warning(
            message="Some images failed to load within timeout",
            tag="SCRAPE",
        )

从源码结构看,有三个值得注意的实现细节:

  1. 判定标准是 img.complete:并非等待所有图片字节 100% 下载完成,而是轮询检查页面上每个 <img> 元素的 complete 属性,1 秒超时会发出 Some images failed to load within timeout 警告。这意味着它适合"图片是否开始加载"的场景,而极慢网络下的超大图仍可能被跳过;
  2. text_mode 下会被跳过BrowserConfig(text_mode=True) 的纯文本模式不做图片等待,这是合理的资源优化;
  3. adjust_viewport_to_content 共用同一入口:两者任一为 True 都会先进入 domcontentloaded 等待,说明该分支是整个"渲染等待"的总闸口。

四、源码解析:scan_full_page 的滚动算法

scan_full_page 触发的滚动逻辑集中在 _handle_full_page_scan,调用点在内容处理流程 中,并受 page_timeout 约束:

# crawl4ai/async_crawler_strategy.py(节选)
if config.scan_full_page:
    scan_timeout = (config.page_timeout or 30000) / 1000  # ms to seconds
    try:
        await asyncio.wait_for(
            self._handle_full_page_scan(page, config.scroll_delay, config.max_scroll_steps),
            timeout=scan_timeout,
        )
    except asyncio.TimeoutError:
        self.logger.warning(
            message="Full page scan timed out after {timeout}s, continuing with partial scroll",
            tag="PAGE_SCAN",
            params={"timeout": scan_timeout},
        )

滚动算法本身分五步(源码 docstring 有明确描述):

  1. 获取视口高度 viewport_height
  2. 先滚动一个视口高度,触发首屏外的懒加载;
  3. 通过 get_page_dimensions 获取页面总高度 total_height
  4. 循环滚动 viewport_height 步,每步滚动后都会重新测量页面高度——如果 new_height > total_height 则更新 total_height,这正是它能"追上"动态增长页面的关键;
  5. 到底后先滚回顶部、再滚到最底部,确保首尾区域的懒加载元素都进入过视口。
# crawl4ai/async_crawler_strategy.py(节选)
scroll_step_count = 0
while current_position < total_height:
    if max_scroll_steps is not None and scroll_step_count >= max_scroll_steps:
        break
    current_position = min(current_position + viewport_height, total_height)
    await self.safe_scroll(page, 0, current_position, delay=scroll_delay)
    scroll_step_count += 1

    dimensions = await self.get_page_dimensions(page)
    new_height = dimensions["height"]
    if new_height > total_height:
        total_height = new_height

还有两处容易踩坑的边界行为:

  • max_scroll_steps 的"双默认值"陷阱配置层 的默认值是 None,docstring 写的是"一直滚动到整页加载完";但执行层 在收到 None 时会兜底为 10 步("Default to 10 steps to prevent infinite scroll on dynamic pages")。也就是说实际运行时,不传 max_scroll_stepsscan_full_page 最多只滚 10 个视口高度。对超长页面,需要显式传入更大的 max_scroll_steps,否则"扫全页"实际只扫了前 10 屏;
  • 步数上限有安全钳制:对不受信来源构造的配置,参数钳制逻辑 会把 max_scroll_steps 限制在 _MAX_SCROLL_STEPS = 1000 以内,防止外部请求制造超长滚动任务。

滚动是怎么执行的? 每一步滚动走 safe_scroll:先用 CSP 兼容的方式执行 window.scrollTo(不直接内联字符串注入),滚动成功后再 wait_for_timeout(delay * 1000)——所以 scroll_delay 直接决定了每次滚动后等待渲染的时长。滚动后源码还会校验实际落点与目标位置的 delta,滚动是否真正生效是有反馈的。

五、与媒体过滤、域名排除的组合

懒加载逻辑可以与常规的媒体/链接过滤参数自由叠加:

config = CrawlerRunConfig(
    wait_for_images=True,
    scan_full_page=True,
    scroll_delay=0.5,

    # 只保留主域图片,过滤外部图片
    exclude_external_images=True,

    # 从链接结果中排除特定域名
    exclude_domains=["spammycdn.com"],
)

这样爬虫会先物理滚动整页触发懒加载,最终 result.mediaresult.links 中只保留主域图片和非排除域名的链接。这些过滤参数的构造入口同样在 CrawlerRunConfigexclude_external_imagesexclude_all_imagesexclude_social_media_domainsexclude_domains 等)。

六、场景选择与排错清单

文档给出的排错建议(Tips & Troubleshooting)结合源码可以整理成一张决策表:

场景 建议做法
图片随滚动出现(普通懒加载) scan_full_page=True + scroll_delay=0.5 + wait_for_images=True
超长页面 / 无限滚动 scan_full_page 资源开销大;改用 hooks 页面交互 循环点击 "Load More",并注意显式传 max_scroll_steps 控制步数
Twitter/Instagram 风格虚拟滚动 Virtual Scroll 专项能力,仓库内有现成示例 virtual_scroll_example.py
分批加载导致漏图 增大 scroll_delay,或用 js_code/hooks 循环执行多次部分滚动
占位图在某个事件后才变真图 wait_for="css:img.loaded" 或自定义 JS wait_for,配合 js_code_before_wait 先触发加载
疑似缓存导致漏新图 设置 cache_mode=CacheMode.BYPASS 强制重新抓取

排错时还可以利用 verbose=True 观察日志标签:滚动相关警告会打出 PAGE_SCANSCRAPE 等 tag(见上文源码片段),能直接定位是"图片超时未加载"还是"全页扫描超时"。

七、小结

Crawl4AI 对懒加载图片的处理是一个"三层配合"机制:wait_for_images 负责加载完成判定(基于 img.complete 轮询),scan_full_page 负责触发加载(分步滚动 + 动态高度重测),scroll_delay 控制触发节奏。理解 执行层实现 后可以避开的最大陷阱是:不显式设置 max_scroll_steps 时实际只滚 10 步,超长页面务必显式放大该值。再叠加 exclude_external_imagesexclude_domains 等过滤参数与 CacheMode.BYPASS,就构成了一套完整的链接与媒体抓取策略。

登录后查看全文
热门项目推荐
相关项目推荐