Jsoup 提取 href 需用 abs:href 并指定 base URI,过滤伪协议、校验 URL 合法性、去重标准化,死链检测优先 HEAD+降级 GET,限制递归深度与并发,区分处理各类异常。

如何用 Jsoup 提取所有 href 并避免重复请求
直接遍历 HTML 里的 a 标签不难,但真实页面里常有相对路径、锚点、javascript:void(0)、mailto: 这类无效链接,硬塞进 HTTP 请求会立刻报错或超时。Jsoup 的 abs:href 选择器能自动补全为绝对 URL,但前提是文档本身带 <base href="..."> 或解析时指定 base URI。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 用
Document.parse(html, baseUrl)初始化文档,baseUrl必须是原始页面完整 URL(如"https://example.com/page.html"),否则abs:href生成的链接可能错乱 - 过滤掉常见伪协议:
href.startsWith("javascript:") || href.startsWith("mailto:") || href.startsWith("#") - 对提取出的每个
href做一次URL解析校验,捕获MalformedURLException,跳过非法格式 - 用
Set<string></string>缓存已检测过的 URL(注意标准化:统一转小写、去掉末尾/、忽略 URL 参数顺序差异),防止同一资源被反复请求
HTTP 状态码校验该用 HEAD 还是 GET
死链检测本质是判断资源是否存在,不是下载内容。用 GET 会拉下整个响应体,浪费带宽和时间,尤其遇到大文件或重定向链时更明显;但有些服务器对 HEAD 返回 405 Method Not Allowed,或干脆不支持 HEAD(比如某些静态托管服务)。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 默认发
HEAD请求,设置超时connectTimeout(3000)和readTimeout(5000),避免卡住 - 若响应状态码是
405,降级为GET,但加.header("Range", "bytes=0-0")或.ignoreContentType(true)防止解析失败 - 注意重定向:
HttpClient默认跟随,但你要记录原始 URL 和最终状态码,否则无法区分「301 跳转后 404」和「原始 URL 直接 404」 - 别依赖
Connection: close或响应体长度判断——有些服务对HEAD返回空体但状态码是200,有些则返回204
递归抓取时怎么控制深度与并发,又不炸掉目标站
递归检测不是爬虫,目标是验证链接有效性,不是镜像网站。无节制递归会导致:IP 被封、对方日志暴增、自己 OOM(大量 Document 对象驻留堆中)。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 深度限制设为
maxDepth = 2:只检查当前页的外链(depth=1),以及这些外链页面里的链接(depth=2)。再深基本属于无关路径 - 用
ExecutorService控制并发数,newFixedThreadPool(3)足够,超过 5 个线程在多数场景下反而因 DNS 解析/连接竞争拖慢整体速度 - 每次请求前加
Thread.sleep(100)(非必须,但友好);更稳妥的是用Semaphore实现每秒请求数(QPS)限流,比如acquireUninterruptibly()配合定时释放 - 跳过非 HTML 响应:检查响应头
Content-Type是否含text/html或application/xhtml+xml,否则不递归解析
IOException 和 HttpStatusException 怎么区分处理
网络请求失败原因五花八门:ConnectException 是连不上服务器,SocketTimeoutException 是卡在握手或读响应,HttpStatusException(来自 Jsoup)代表收到了 HTTP 错误码(如 404/503),而 UnsupportedMimeTypeException 可能只是对方返回了 PDF。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 把
HttpStatusException当作正常业务响应处理——它有getStatusCode()方法,可直接归类为「死链」或「服务不可用」 -
IOException子类要拆开看:UnknownHostException说明域名解析失败(DNS 问题或拼写错),ConnectException多半是目标关站或防火墙拦截,这类应记为「无法连接」而非「死链」 - 别 catch 通用
Exception后吞掉——至少 log 出e.getClass().getSimpleName()和e.getMessage(),否则排查时只能看到「请求失败」四个字 - 对临时性错误(如
SocketTimeoutException)可重试 1 次,但不要对 4xx 错误重试,它们是客户端问题,重试没意义
真正麻烦的是那些返回 200 却渲染出「页面找不到了」文字的站点,或者用 JS 跳转的伪 404。这种得结合正文文本匹配或 DOM 结构判断,已经超出纯 HTTP 校验范畴了。











