使用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
相关产品推荐
相关产品推荐

