用requests+beautifulsoup递归抓取需明确层级关系:先提取同源子路径并控制深度,再精准提取有效图片url(过滤base64、补全相对路径、校验后缀),结合url归一化与轻量哈希去重;高并发推荐asyncio+aiohttp替代多线程。

如何用 requests + BeautifulSoup 实现多层页面递归抓取
深度爬取不是靠“多开几个线程”硬扫,而是要明确页面层级关系和入口发现逻辑。比如从首页开始,提取所有 <a href="..."></a> 链接,过滤出属于同一域名的子路径(如 /blog/、/product/123),再逐个请求并继续提取图片链接——这个过程必须控制深度,否则容易陷入无限跳转或爬取静态资源目录。
实操建议:
- 用
urlparse判断是否为同源 URL,排除外链、mailto、javascript: 等伪协议 - 维护一个已访问集合
visited_urls = set(),每次生成新 URL 前先检查是否已存在 - 限制最大递归深度(例如
max_depth=3),避免进入分页嵌套或评论加载页 - 对响应状态码做判断:只处理
200,跳过404、503、302(除非你主动处理重定向)
怎么准确提取页面中所有有效图片 URL
很多新手直接搜 <img src="...">,结果抓到大量占位图、SVG、base64 内联图、懒加载的 data-src,甚至 CSS 背景图。真正要下载的,是能被 requests.get() 直接获取的、有明确扩展名(.jpg、.png、.webp)且非相对路径或空值的 URL。
实操建议:
- 同时检查
src、data-src、data-original属性,但优先级要分清(例如data-src存在时忽略src) - 用正则过滤掉 base64 字符串:
^data:image/开头的直接跳过 - 将相对路径补全为绝对 URL:用
urllib.parse.urljoin(current_url, img_src) - 对补全后的 URL 做简单校验:包含常见图片后缀、不包含
icon、sprite、logo-small等关键词(按实际站点调整)
去重不只是比对 URL 字符串
同一个图片可能通过不同 URL 访问(带参数、大小写差异、CDN 域名不同),只靠字符串相等判断会漏去重;而全量下载后再算 MD5 又太耗资源。更可行的是“URL 归一化 + 关键字段哈希”双保险。
实操建议:
- 归一化步骤:转小写、移除
utm_*和ref=类追踪参数、标准化 CDN 域名(如把img1.example.com全替换成cdn.example.com) - 对归一化后的 URL 取
hashlib.md5(url.encode()).hexdigest()[:8]作为轻量指纹 - 内存中用
set()存指纹,文件落地前再对首 1KB 做一次快速校验(防极小概率碰撞) - 注意:不要依赖
Content-Disposition或filename,很多服务器根本不返回这个头
为什么用 asyncio + aiohttp 比多线程更稳
图片请求本质是大量短连接 I/O 密集型任务,Python 多线程受 GIL 限制,实际并发数上不去,还容易因 DNS 解析阻塞卡死;而 aiohttp 复用连接池、自带超时和重试,配合 asyncio.Semaphore 控制并发数(如限 10 个并发),稳定性高得多。
实操建议:
- 设置
timeout=aiohttp.ClientTimeout(total=10),避免单个请求拖垮整个协程池 - 启用连接池:
connector = aiohttp.TCPConnector(limit=100, limit_per_host=30),防止对单域名打爆 - 遇到
ClientConnectorError或ServerDisconnectedError时自动重试 2 次,别让一个失败中断全部流程 - 别在协程里直接写文件,用
loop.run_in_executor()扔给线程池处理保存逻辑
playwright 或手动分析 network 面板确认真实请求来源。Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











