直接用find_all("a")提取链接常出错,因href可能是相对路径、空值、javascript伪协议或锚点;应先用urljoin补全为绝对url,再过滤非http/https协议和锚点链接。

为什么直接用 find_all("a") 提取的链接经常是错的?
因为页面里 a 标签的 href 属性可能是相对路径(如 "./about"、"../images/logo.png")、空值、JavaScript 伪协议("javascript:void(0)"),甚至包含锚点("#section1")。不处理这些,后续请求会 404 或跳转失败。
正确做法是:先用 urllib.parse.urljoin(base_url, href) 补全为绝对 URL,再过滤掉非 HTTP/HTTPS 协议和锚点链接。
- 必须传入原始请求的
base_url(不是当前页面 URL 的字符串拼接,而是响应头中的url字段或requests.get()返回的真实 URL) - 用
urllib.parse.urlparse(link).scheme in ("http", "https")过滤协议 - 用
urllib.parse.urlparse(link).fragment == ""排除锚点 - 避免用
href.startswith("http")判断——会漏掉//example.com这类协议相对链接
递归爬取时如何防止无限循环和重复抓取?
网页之间常存在环形引用(A→B→C→A)或大量重复链接(分页参数不同但内容相同、带 utm 参数的营销链接等)。不做控制,程序会卡死或爆炸式增长。
最简但有效的方案是维护一个已访问集合(set)+ URL 规范化。
- 对每个待爬 URL 先做
urllib.parse.urlparse(),清除query中的无意义参数(如"utm_source"、"_t=123"),再用urlunparse()生成规范键 - 已访问集合只存规范后的 URL,不是原始字符串
- 队列(
collections.deque)用于 BFS 控制深度,避免 DFS 深度过大导致栈溢出 - 加个
max_depth参数硬限制递归层级,比如只爬 3 层以内
BeautifulSoup 解析时遇到编码乱码或解析失败怎么办?
常见现象是 soup.find_all("a") 返回空列表,或者中文变成 \xe4\xbd\xa0\xe5\xa5\xbd。根本原因不是 HTML 写错了,而是 BeautifulSoup 没猜对编码,或服务器返回了压缩内容但没解压。
- 用
requests.get(url, headers={"Accept-Encoding": "identity"})强制禁用 gzip,避免 BeautifulSoup 直接解析二进制流 - 显式指定编码:
BeautifulSoup(res.content, "html.parser", from_encoding="utf-8"),优先用res.content而非res.text(后者已被 requests 自动解码,可能出错) - 如果仍乱码,检查
res.apparent_encoding和res.encoding,手动覆盖:例如res.encoding = res.apparent_encoding or "gbk" - 不要依赖
html.parser处理严重 malformed HTML;换成"lxml"(需安装lxml)容错更强
实际跑起来后 CPU 飙高、请求被封,怎么调?
默认无节制并发 + 无延迟 + 无 UA,等于主动触发反爬。真实场景下必须模拟人行为,且兼顾效率与隐蔽性。
- 加
time.sleep(1)是最简单有效的方式——别信“0.1 秒就够了”,很多小站连 0.5 秒都会限流 - 设置合理
User-Agent,最好从真实浏览器中复制(如 Chrome 当前最新版),并定期轮换 - 复用
requests.Session(),自动管理 cookies 和连接池,比反复新建requests.get快且低调 - 用
try/except包住单次请求,捕获requests.exceptions.RequestException,失败就跳过,别让整个爬虫崩掉 - 把超时设成
timeout=(3, 7)(3 秒连通,7 秒读取),避免卡死在慢响应上
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











