You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用R语言爬取多页连续网页表格的实现问题咨询

爬取SINEB网站BOGOTÁ D.C.地区全量分页表格数据方案

原代码失效原因

  • 目标站点基于JSF框架构建,表格分页通过AJAX POST请求触发,首次提交查询仅返回第一页内容,不会自动加载全部分页数据
  • 原代码未模拟分页请求逻辑,也未携带分页交互必需的动态校验参数,自然无法获取2-20页的内容

全量爬取实现代码

代码会自动识别总页数,逐页请求并合并数据,内置延时避免触发站点反爬:

library(tidyverse)
library(rvest)
library(httr)

# 初始化会话
base_url <- "https://sineb.mineducacion.gov.co/bcol/app"
init_session <- html_session(base_url)
init_page <- read_html(init_session)

# 提取BOGOTÁ D.C.对应的查询参数值
init_form <- html_form(init_page)[[1]]
dept_opts <- init_form$fields$departamento$options[-1]
bogota_code <- dept_opts[grep("D.C", names(dept_opts))][[1]]

# 提取JSF框架必需的动态校验参数,首次查询和后续分页都需要携带
current_viewstate <- init_page |> 
  html_element("input[name='javax.faces.ViewState']") |> 
  html_attr("value")

# 提交首次条件查询,获取第一页数据
first_query <- POST(
  url = base_url,
  body = list(
    `javax.faces.partial.ajax` = "true",
    `javax.faces.source` = "consultar",
    `javax.faces.partial.execute` = "@all",
    `javax.faces.partial.render` = "formResultado",
    `consultar` = "consultar",
    `departamento` = bogota_code,
    `municipio` = "",
    `nombreInstitucion` = "",
    `codigoDane` = "",
    `javax.faces.ViewState` = current_viewstate
  ),
  encode = "form",
  add_headers(`Faces-Request` = "partial/ajax")
)

first_resp_text <- content(first_query, as = "text")
# 提取返回内容里的表格模块
first_table_part <- str_extract(first_resp_text, "(?<=<update id=\"formResultado\">).*?(?=</update>)") |> read_html()
# 识别总页数
page_count <- first_table_part |> 
  html_elements(".ui-paginator-page") |> 
  html_attr("data-page") |> 
  as.numeric() |> 
  max(na.rm = T)

# 存储所有页数据的列表,先存入第一页
data_list <- list()
data_list[[1]] <- first_table_part |> 
  html_element("table") |> 
  html_table(header = T, convert = T)

# 循环爬取剩余分页
for (p in 2:page_count) {
  # 从上一次响应里更新动态校验参数,参数过期会导致请求失败
  current_viewstate <- str_extract(first_resp_text, "(?<=<update id=\"javax.faces.ViewState\"><!\\[CDATA\\[).*?(?=\\]\\]></update>)")
  
  page_resp <- POST(
    url = base_url,
    body = list(
      `javax.faces.partial.ajax` = "true",
      `javax.faces.source` = "formResultado:tablaResultado_paginator_bottom",
      `javax.faces.partial.execute` = "formResultado:tablaResultado",
      `javax.faces.partial.render` = "formResultado:tablaResultado",
      `formResultado:tablaResultado_paginator_bottom` = as.character(p),
      `formResultado:tablaResultado_rows` = "10",
      `formResultado` = "formResultado",
      `departamento` = bogota_code,
      `municipio` = "",
      `nombreInstitucion` = "",
      `codigoDane` = "",
      `javax.faces.ViewState` = current_viewstate
    ),
    encode = "form",
    add_headers(`Faces-Request` = "partial/ajax")
  )
  
  page_resp_text <- content(page_resp, as = "text")
  page_table_part <- str_extract(page_resp_text, "(?<=<update id=\"formResultado:tablaResultado\">).*?(?=</update>)") |> read_html()
  data_list[[p]] <- page_table_part |> 
    html_element("table") |> 
    html_table(header = T, convert = T)
  
  # 更新响应文本供下一轮提取参数
  first_resp_text <- page_resp_text
  # 加1秒延时,降低请求频率
  Sys.sleep(1)
  message("已完成第", p, "页爬取,总计", page_count, "页")
}

# 合并所有页为完整数据框
full_bogota_data <- bind_rows(data_list)

关键说明

  • JSF框架的javax.faces.ViewState是单次会话有效的动态参数,每次请求后都会更新,必须从上一次的响应里提取最新值传入下一次请求,否则会被服务端拦截
  • 分页请求必须携带Faces-Request: partial/ajax请求头,否则服务端不会返回分页对应的表格内容,会直接返回整页源码
  • 如果后续站点前端调整了分页组件的ID,可以打开浏览器开发者工具,点击分页按钮后查看网络请求里的POST参数,对应替换代码里的参数名即可
  • 爬取时不要删除延时逻辑,高频请求很容易触发站点反爬导致IP被临时封禁

内容的提问来源于stack exchange,提问作者Laura Villalobos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 19:12:18