虚拟卷轴
现代网站越来越多地使用虚拟滚动(也称为窗口渲染或视口渲染)来高效处理大型数据集。此技术仅渲染 DOM 中的可见项目,并在用户滚动时替换内容。常见示例包括 Twitter 的时间线、Instagram 的动态以及许多数据表。
Crawl4AI 的虚拟滚动功能会自动检测并处理这些情况,确保您捕获所有内容,而不仅仅是最初可见的内容。
了解虚拟滚动
问题
传统的无限滚动会将新内容附加到现有内容上。虚拟滚动会替换内容以保持性能:
Traditional Scroll: Virtual Scroll:
┌─────────────┐ ┌─────────────┐
│ Item 1 │ │ Item 11 │ <- Items 1-10 removed
│ Item 2 │ │ Item 12 │ <- Only visible items
│ ... │ │ Item 13 │ in DOM
│ Item 10 │ │ Item 14 │
│ Item 11 NEW │ │ Item 15 │
│ Item 12 NEW │ └─────────────┘
└─────────────┘
DOM keeps growing DOM size stays constant
如果没有适当的处理,爬虫只能捕获当前可见的项目,而错过其余内容。
三种滚动场景
Crawl4AI 的虚拟滚动可检测并处理三种情况:
- 无变化 - 滚动时内容不更新(静态页面或到达末尾)
- 附加内容 - 将新项目添加到现有项目(传统无限滚动)
- 内容替换 - 用新项目替换项目(真正的虚拟滚动)
只有场景 3 需要特殊处理,Virtual Scroll 可以自动执行。
基本用法
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig, VirtualScrollConfig
# Configure virtual scroll
virtual_config = VirtualScrollConfig(
container_selector="#feed", # CSS selector for scrollable container
scroll_count=20, # Number of scrolls to perform
scroll_by="container_height", # How much to scroll each time
wait_after_scroll=0.5 # Wait time (seconds) after each scroll
)
# Use in crawler configuration
config = CrawlerRunConfig(
virtual_scroll_config=virtual_config
)
async with AsyncWebCrawler() as crawler:
result = await crawler.arun(url="https://example.com", config=config)
# result.html contains ALL items from the virtual scroll
配置参数
虚拟滚动配置
范围 | 类型 | 默认 | 描述 |
---|---|---|---|
container_selector |
str |
必需的 | 可滚动容器的 CSS 选择器 |
scroll_count |
int |
10 |
执行的最大滚动次数 |
scroll_by |
或者int |
"container_height" |
每步滚动量 |
wait_after_scroll |
float |
0.5 |
每次滚动后等待的秒数 |
按选项滚动
- - 按容器的可见高度滚动
- - 按视口高度滚动
- (整数) - 按精确像素量滚动
现实世界的例子
类似 Twitter 的时间线
from crawl4ai import AsyncWebCrawler, CrawlerRunConfig, VirtualScrollConfig, BrowserConfig
async def crawl_twitter_timeline():
# Twitter replaces tweets as you scroll
virtual_config = VirtualScrollConfig(
container_selector="[data-testid='primaryColumn']",
scroll_count=30,
scroll_by="container_height",
wait_after_scroll=1.0 # Twitter needs time to load
)
browser_config = BrowserConfig(headless=True) # Set to False to watch it work
config = CrawlerRunConfig(
virtual_scroll_config=virtual_config
)
async with AsyncWebCrawler(config=browser_config) as crawler:
result = await crawler.arun(
url="https://twitter.com/search?q=AI",
config=config
)
# Extract tweet count
import re
tweets = re.findall(r'data-testid="tweet"', result.html)
print(f"Captured {len(tweets)} tweets")
Instagram网格
async def crawl_instagram_grid():
# Instagram uses virtualized grid for performance
virtual_config = VirtualScrollConfig(
container_selector="article", # Main feed container
scroll_count=50, # More scrolls for grid layout
scroll_by=800, # Fixed pixel scrolling
wait_after_scroll=0.8
)
config = CrawlerRunConfig(
virtual_scroll_config=virtual_config,
screenshot=True # Capture final state
)
async with AsyncWebCrawler() as crawler:
result = await crawler.arun(
url="https://www.instagram.com/explore/tags/photography/",
config=config
)
# Count posts
posts = result.html.count('class="post"')
print(f"Captured {posts} posts from virtualized grid")
混合内容(新闻提要)
一些网站混合了静态和虚拟化内容:
async def crawl_mixed_feed():
# Featured articles stay, regular articles virtualize
virtual_config = VirtualScrollConfig(
container_selector=".main-feed",
scroll_count=25,
scroll_by="container_height",
wait_after_scroll=0.5
)
config = CrawlerRunConfig(
virtual_scroll_config=virtual_config
)
async with AsyncWebCrawler() as crawler:
result = await crawler.arun(
url="https://news.example.com",
config=config
)
# Featured articles remain throughout
featured = result.html.count('class="featured-article"')
regular = result.html.count('class="regular-article"')
print(f"Featured (static): {featured}")
print(f"Regular (virtualized): {regular}")
虚拟滚动与 scan_full_page
这两个功能都可处理动态内容,但用途不同:
特征 | 虚拟卷轴 | 扫描全页 |
---|---|---|
目的 | 捕获滚动过程中替换的内容 | 加载滚动过程中附加的内容 |
用例 | Twitter、Instagram、虚拟桌子 | 传统的无限滚动、延迟加载的图像 |
DOM 行为 | 替换元素 | 添加元素 |
内存使用情况 | 高效(合并内容) | 可以长得很大 |
配置 | 需要容器选择器 | 整页显示 |
何时使用哪个?
在以下情况下使用虚拟滚动: - 滚动时内容消失(Twitter 时间线) - DOM 元素数量保持相对恒定 - 您需要虚拟列表中的所有项目 - 基于容器的滚动(非整页)
在以下情况下使用 scan_full_page: - 滚动时内容累积 - 图像延迟加载 - 简单的“加载更多”行为 - 整页滚动
结合萃取
虚拟滚动与提取策略无缝协作:
from crawl4ai import LLMExtractionStrategy, LLMConfig
# Define extraction schema
schema = {
"type": "array",
"items": {
"type": "object",
"properties": {
"author": {"type": "string"},
"content": {"type": "string"},
"timestamp": {"type": "string"}
}
}
}
# Configure both virtual scroll and extraction
config = CrawlerRunConfig(
virtual_scroll_config=VirtualScrollConfig(
container_selector="#timeline",
scroll_count=20
),
extraction_strategy=LLMExtractionStrategy(
llm_config=LLMConfig(provider="openai/gpt-4o-mini"),
schema=schema
)
)
async with AsyncWebCrawler() as crawler:
result = await crawler.arun(url="...", config=config)
# Extracted data from ALL scrolled content
import json
posts = json.loads(result.extracted_content)
print(f"Extracted {len(posts)} posts from virtual scroll")
性能提示
- 容器选择:选择器要具体。使用正确的容器可以提高性能。
- 滚动计数:从保守开始,然后根据需要增加:
# Start with fewer scrolls virtual_config = VirtualScrollConfig( container_selector="#feed", scroll_count=10 # Test with 10, increase if needed )
- 等待时间:根据网站速度调整:
# Fast sites wait_after_scroll=0.2 # Slower sites or heavy content wait_after_scroll=1.5
- 调试模式:设置
headless=False
观看滚动:browser_config = BrowserConfig(headless=False) async with AsyncWebCrawler(config=browser_config) as crawler: # Watch the scrolling happen
内部运作方式
- 检测阶段:滚动并比较 HTML 以检测行为
- 捕获阶段:对于替换的内容,在每个位置存储 HTML 块
- 合并阶段:合并所有块,根据文本内容删除重复项
- 结果:包含所有唯一项目的完整 HTML
重复数据删除使用规范化文本(小写,无空格/符号)来确保准确合并而不会出现误报。
错误处理
虚拟滚动可以优雅地处理错误:
# If container not found or scrolling fails
result = await crawler.arun(url="...", config=config)
if result.success:
# Virtual scroll worked or wasn't needed
print(f"Captured {len(result.html)} characters")
else:
# Crawl failed entirely
print(f"Error: {result.error_message}")
如果未找到容器,则爬行将继续正常进行,无需虚拟滚动。
完整示例
请参阅我们的综合示例,其中演示了: - 类似 Twitter 的 feed - Instagram 网格 - 传统的无限滚动 - 混合内容场景 - 性能比较
该示例包括一个具有不同滚动行为的本地测试服务器以供实验。