R语言rvest批量请求多页面时出现HTTP error 403如何解决
问题概述
- 开发目标:编写同站点多页面遍历爬虫,可实现两类功能之一:① 下载每个页面的HTML/CSS资源;② 提取带特定class的列表项内包含的链接,当前已完成第一类需求的基础代码
- 技术选型:选用R语言开发,此前使用Python向该站点发起首次GET请求即返回403,因此排除BeautifulSoup、Selenium实现方案
- 当前故障:R版代码仅能短时间正常运行,随后抛出403错误,报错信息为:
Error in open.connection(x, "rb") : HTTP error 403. - 已考虑方案:曾计划在循环中加入
Sys.sleep()设置请求间隔,但因需遍历近1000个页面,认为该方案实用性不足 - 已知潜在解决方向:配置代理、修改请求头,但对相关技术掌握程度有限,可尝试学习落地
- 现有参考代码:遍历存储待爬URL、对应保存文件名的dataframe逐一下载HTML,代码如下:
for (i in 1:length(data1$Search)) { url = data1$Search[i] name = data1$Name[i] download.file(url, destfile = paste(name, ".html", sep = ""), quiet = TRUE) }
其中data1为包含Search、Name两列的dataframe:Search列存储待请求URL,Name列对应下载HTML文件的命名。
可行解决建议
1. 先补全合法请求头,绕过基础反爬校验
你当前使用的download.file()默认请求头特征极明显,绝大多数站点的基础反爬规则都会直接拦截这类请求,这也是短时间运行就报403的核心原因之一:
- 弃用基础
download.file(),改用httr包发起请求,手动配置和真实浏览器一致的请求头,至少要带上正常的User-Agent(打开任意网页在浏览器控制台输入navigator.userAgent就能拿到自己当前浏览器的UA,直接复制使用即可),可补充Referer字段(填写站点首页地址)、Accept字段,模拟普通用户的浏览器访问行为 - 增加状态码判断、失败日志逻辑,遇到报错不要直接中断整个循环,把失败URL记录下来后续补爬即可
- 修改后的参考代码:
library(httr) # 替换成你自己浏览器的UA my_ua <- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36" # 替换成目标站点的首页地址 site_referer <- "https://替换成目标站点域名首页/" for (i in 1:nrow(data1)) { url <- data1$Search[i] file_name <- paste0(data1$Name[i], ".html") # 带请求头发起GET请求,设置10秒超时 resp <- GET( url = url, user_agent(my_ua), add_headers(Referer = site_referer), timeout(10) ) # 状态码200才写入文件,否则记录失败日志 if (status_code(resp) == 200) { writeBin(content(resp, "raw"), file_name) } else { cat(paste0("爬取失败|URL:", url, "|状态码:", status_code(resp), "\n"), file = "crawl_fail_log.txt", append = TRUE) } }
2. 搭配随机短间隔+重试逻辑,成本极低
完全没必要因为要爬1000个页面就放弃间隔设置:
- 不要用固定长间隔,改成0.5-2秒的随机短间隔即可,每次请求完执行
Sys.sleep(runif(1, 0.5, 2)),爬完1000个页面总耗时仅十几分钟,远低于触发403后等待IP解封、重新排查问题的时间成本 - 增加简单重试逻辑:遇到403状态码时,等待5-10秒后重新发起请求,重试2-3次仍失败再记入日志,绝大多数临时触发的频率限制都能通过这个方式绕过
3. 代理IP配置方案(上述方案无效时再使用)
如果补全请求头、加了随机间隔仍然短时间触发403,说明站点做了IP维度的频率限制:
- 准备可用的代理IP资源,在
httr的GET请求中加入use_proxy()参数即可,格式为use_proxy("代理IP地址", 端口, username = "认证用户名(若有)", password = "认证密码(若有)") - 每爬取30-50页切换一次代理IP,避免单个IP请求频率过高被封禁
4. 第二类提取需求的适配方法
后续如果要切换为“提取特定class列表项内链接”的功能,不需要把所有HTML都下载到本地,直接用rvest包解析请求返回的响应内容即可,参考逻辑:
library(rvest) # 解析响应内容为HTML结构 page_doc <- read_html(resp) # 替换成你要定位的class名称 target_links <- page_doc %>% html_elements(".替换成目标class名") %>% html_element("a") %>% html_attr("href") # 将提取到的相对路径链接转为绝对路径 target_links <- url_absolute(target_links, url)
内容的提问来源于stack exchange,提问作者scoopfaze
相关产品推荐
相关产品推荐

