2.1 一单任务的完整旅程:爬虫基本原理 本节摘要:网络爬虫的原理可以压成五个工位的接力——下载、调度、渲染、解析、去重回流。本节用 Crawl4AI 的首次安装与 arun 调用走通全流程,看清任务单如何变成引擎里的工件流转,为后面四节的逐工位深化打底。 拆开一单任务 车间主任的第一课,不是学拧某颗螺丝,而是看完整车怎么从这头开到那头。把一单任务拆到最细,它在引擎里的旅程是这样的:URL 从调度队列出队,下载器建立连接取回初始响应;浏览器内核接手执行脚本,等 DOM 稳定;解析器按选择器或策略分拣出目标字段与新的链接;去重器筛掉见过的 URL,新面孔回流队列;产出的干净数据交给下游。
本节摘要:网络爬虫的原理可以压成五个工位的接力——下载、调度、渲染、解析、去重回流。本节用 Crawl4AI 的首次安装与 arun 调用走通全流程,看清任务单如何变成引擎里的工件流转,为后面四节的逐工位深化打底。
车间主任的第一课,不是学拧某颗螺丝,而是看完整车怎么从这头开到那头。把一单任务拆到最细,它在引擎里的旅程是这样的:URL 从调度队列出队,下载器建立连接取回初始响应;浏览器内核接手执行脚本,等 DOM 稳定;解析器按选择器或策略分拣出目标字段与新的链接;去重器筛掉见过的 URL,新面孔回流队列;产出的干净数据交给下游。五工位的接力里,任何一环卡住,外在症状都差不多——"没数据"或"数据不对",所以定位问题的第一动作永远是判断卡在哪个工位。

五工位视角的价值在排障时最明显。同一句"抓不到数据",可能是工位三的等待条件没满足(页面还在加载),也可能是工位四的选择器失效(站点改版),处理方式完全不同。先定位工位,再动手修,是车间的基本修养。
理论归位,先把引擎发动起来。安装与初始化分两步,第二步会下载浏览器内核,第一次约几百兆,值得单独跑:
# 第一步:安装框架本体 pip install crawl4ai # 第二步:初始化运行环境(下载 Chromium 内核与依赖) crawl4ai-setup # 可选体检:检查环境是否就绪 crawl4ai-doctor
环境就绪后,用最小的一次出车验证五个工位都在岗。注意 Crawl4AI 的主接口是异步的,入口统一走 asyncio:
import asyncio from crawl4ai import AsyncWebCrawler, BrowserConfig, CrawlerRunConfig, CacheMode async def first_run(url: str): # 浏览器级配置:影响整个浏览器实例 browser_cfg = BrowserConfig(headless=True, verbose=False) # 单次抓取级配置:影响这一轮任务的每个页面 run_cfg = CrawlerRunConfig( cache_mode=CacheMode.BYPASS, # 绕过缓存,教学环境保证拿到新内容 page_timeout=30000, # 单页 30 秒超时,防悬挂 ) async with AsyncWebCrawler(config=browser_cfg) as crawler: result = await crawler.arun(url, config=run_cfg) # 工位三四的产出都在 result 上: print("状态码:", result.status) # 输出:状态码: 200 print("Markdown 长度:", len(result.markdown.raw_markdown)) # 输出:Markdown 长度: 1256(随页面而异) print("内部链接数:", len(result.links.get("internal", []))) # 输出:内部链接数: 31 print("图片数:", len(result.media.get("images", []))) # 输出:图片数: 4 asyncio.run(first_run("https://example.com"))
四行输出各自对应一个工位的健康信号:状态码看下载、Markdown 长度看渲染加解析、链接数看解析加回流、媒体数看富媒体抽取。第一次出车建议永远先打印这四项,再谈字段抽取。
五个工位在时间轴上如何交接,时序图比文字直观。特别注意浏览器内核与脚本执行之间有个"等待条件"的回合——它是动态页面成败的关键,也是新手最常忽略的一跳:
时序图里藏着一个设计决策:为什么等待条件放在浏览器内核里而不是外层循环里轮询?因为内核能感知网络空闲与 DOM 变化,外层轮询只能靠猜。Crawl4AI 把 wait_for 参数直接接到内核事件上,这正是 2.3 节处理动态页面的技术底座。
为了不让理解寄生在框架上,最后用标准库裸写工位一和工位二,感受一下 Crawl4AI 替你省掉的部分:
import urllib.request, urllib.robotparser, time def bare_fetch(url: str, ua: str = "MyCrawler/1.0") -> str: """裸写工位一二:频控纪律 + HTTP 下载(无渲染能力)""" domain = "/".join(url.split("/")[:3]) rp = robotparser.RobotFileParser() rp.set_url(domain + "/robots.txt") rp.read() if not rp.can_fetch(ua, url): raise PermissionError("robots 协议不允许抓取该路径") req = urllib.request.Request(url, headers={"User-Agent": ua}) with urllib.request.urlopen(req, timeout=10) as resp: return resp.read().decode("utf-8", errors="ignore") html = bare_fetch("https://example.com") print(len(html)) # 输出:1256(原始 HTML 字节数,未渲染未解析) time.sleep(2) # 裸写时连频控都要自己记得睡
对比两段代码:裸写版本没有渲染、没有 Markdown、没有链接分组,但它的好处是每一步都摊在你眼前。真实项目的选择往往是混合的——探测阶段用轻量请求摸清站点结构,正式采集交给带浏览器内核的框架。
工位全貌清楚了,下一节把放大镜对准工位一:队列怎么排、策略怎么选、频控怎么落进代码。