多级分类爬取应基于URL模板预生成目标地址,用f-string或str.format()安全拼接;采用httpx+asyncio实现高并发;按正则路由分发解析函数;结果暂存后批量写入或通过队列+线程安全落库。

怎么用 URL 模板生成多级分类页地址
多级分类爬取的核心不是“递归发现链接”,而是提前知道所有目标 URL 的结构规律。比如电商商品页常是 https://example.com/category/{level1}/{level2}/page-{n},其中 {level1}、{level2} 是已知的分类 slug(如 electronics、smartphones),{n} 是页码。手动拼接容易出错,推荐用 str.format() 或 f-string 构建,避免字符串拼接漏斜杠或编码问题。
关键点:
- 先人工抽样 3–5 个分类路径,确认层级数和变量命名规则(比如是否含中文、是否需 URL 编码)
- 分类 ID 列表不能硬编码在爬虫主逻辑里,应从一级列表页解析后缓存为
list[dict],再传入 URL 构建函数 - 页码范围别盲目设 1–100;先对每个二级分类请求第 1 页,解析
total_pages字段或翻页控件文本,再动态生成后续页 URL
为什么不用 requests + ThreadPoolExecutor 而选 httpx + asyncio
多级分类本质是大量短连接、高并发、低计算的 I/O 密集任务。requests 同步阻塞,即使配 ThreadPoolExecutor,线程数超过 20 就容易触发 DNS 解析阻塞或连接池争抢;而 httpx.AsyncClient 基于 asyncio,单线程可轻松维持 100+ 并发连接,内存占用更低,响应更可控。
注意几个实际坑:
立即学习“Python免费学习笔记(深入)”;
-
httpx.AsyncClient必须用async with创建,不能复用跨async def函数——否则会抛RuntimeError: Event loop is closed - 别在异步函数里混用
time.sleep(),改用await asyncio.sleep(),否则整个协程被阻塞 - 设置
limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),防止目标站封 IP 或返回 429
如何让每级分类的解析逻辑不互相污染
一级页(分类导航)和二级页(商品列表)结构完全不同,强行用同一套 BeautifulSoup 选择器会崩溃。正确做法是按 URL 模式分发解析函数,而不是按“深度”或“层级编号”。
示例逻辑:
url_patterns = [
(r"/category/$", parse_level1),
(r"/category/[^/]+/$", parse_level2),
(r"/product/\d+", parse_detail),
]
for pattern, parser in url_patterns:
if re.match(pattern, url):
data = await parser(response.text)
break
这样新增三级页只需加一条正则规则,无需改主循环。同时,每个 parse_* 函数只处理本层字段,不向上透传原始 HTML——避免某层解析异常导致整条链路中断。
异步爬取中怎么安全写入 CSV / SQLite
多个协程并发调用 csv.writer.writerow() 或直接 conn.execute() 会触发文件锁冲突或数据库 busy 错误。不要试图用 asyncio.Lock 包裹写操作——它只锁协程,不锁底层系统调用。
可靠方案只有两种:
- 把采集结果暂存在内存列表(如
results: list[dict]),等当前分类所有页爬完,再统一写入一次文件 - 用线程安全的队列 + 单独的写入线程:启动一个
threading.Thread监听asyncio.Queue,所有协程await queue.put(item),写入线程queue.get_nowait()持续消费
SQLite 特别要注意:isolation_level=None(即开启 autocommit)才能避免 OperationalError: database is locked,但此时需手动控制 BEGIN IMMEDIATE 事务边界。
多级分类真正难的不是并发量,而是分类树结构变动时的容错——比如某二级分类突然下线,URL 返回 404,这时候解析函数必须能识别状态码并跳过,而不是让整个 asyncio.gather() 报错中断。


















