Crawl4AI 懒加载图片抓取实战:wait_for_images 与 scan_full_page 全解析
在 Crawl4AI 中,很多现代网站会随滚动懒加载(lazy-load) 图片——页面初次渲染时只有少量真实 <img>,其余是占位符。如果直接抓取,这些图片不会出现在 result.media 里。本文基于仓库文档 懒加载指南,讲透三个核心配置项 wait_for_images、scan_full_page、scroll_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_delay:scan_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"]获取,每个条目含src与score字段,可与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",
)
从源码结构看,有三个值得注意的实现细节:
- 判定标准是
img.complete:并非等待所有图片字节 100% 下载完成,而是轮询检查页面上每个<img>元素的complete属性,1 秒超时会发出Some images failed to load within timeout警告。这意味着它适合"图片是否开始加载"的场景,而极慢网络下的超大图仍可能被跳过; text_mode下会被跳过:BrowserConfig(text_mode=True)的纯文本模式不做图片等待,这是合理的资源优化;- 与
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 有明确描述):
- 获取视口高度
viewport_height; - 先滚动一个视口高度,触发首屏外的懒加载;
- 通过
get_page_dimensions获取页面总高度total_height; - 循环滚动
viewport_height步,每步滚动后都会重新测量页面高度——如果new_height > total_height则更新total_height,这正是它能"追上"动态增长页面的关键; - 到底后先滚回顶部、再滚到最底部,确保首尾区域的懒加载元素都进入过视口。
# 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_steps的scan_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.media 与 result.links 中只保留主域图片和非排除域名的链接。这些过滤参数的构造入口同样在 CrawlerRunConfig(exclude_external_images、exclude_all_images、exclude_social_media_domains、exclude_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_SCAN、SCRAPE 等 tag(见上文源码片段),能直接定位是"图片超时未加载"还是"全页扫描超时"。
七、小结
Crawl4AI 对懒加载图片的处理是一个"三层配合"机制:wait_for_images 负责加载完成判定(基于 img.complete 轮询),scan_full_page 负责触发加载(分步滚动 + 动态高度重测),scroll_delay 控制触发节奏。理解 执行层实现 后可以避开的最大陷阱是:不显式设置 max_scroll_steps 时实际只滚 10 步,超长页面务必显式放大该值。再叠加 exclude_external_images、exclude_domains 等过滤参数与 CacheMode.BYPASS,就构成了一套完整的链接与媒体抓取策略。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00