0Pricing
R Academy · 课时

处理分页与多页面

遍历分页结果,并将抓取的数据合并为单个数据集

处理分页与多页面 是 CoddyKit 上的免费 R Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 R Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 R Academy 课程共包含 4 节课。

了解分页

许多网站会将内容分布在多个页面中。基于 URL 的分页会在 URL 中附加页码(例如 ?page=2 或 /page/2/)。请识别其中的模式,以便通过程序构造 URL。

library(rvest)

# Common URL pagination patterns:
# Pattern 1: query parameter
# https://site.com/products?page=1
# https://site.com/products?page=2

# Pattern 2: path segment
# https://site.com/products/page/1/
# https://site.com/products/page/2/

# Build URLs for pages 1 to 5
base_url <- 'https://books.toscrape.com/catalogue/page-'
page_urls <- paste0(base_url, 1:5, '.html')
page_urls
# [1] 'https://books.toscrape.com/catalogue/page-1.html'
# [2] 'https://books.toscrape.com/catalogue/page-2.html' ...

使用 sprintf 构建 URL 模式

sprintf() 和 paste0() 都可以构建 URL 字符串,但 sprintf() 能更灵活地控制零填充和格式。请根据网站的 URL 结构选择合适的函数。

# Various URL building approaches

# paste0 for simple concatenation
pages <- 1:5
urls_v1 <- paste0('https://site.com/list?p=', pages)
urls_v1[1]  # 'https://site.com/list?p=1'

# sprintf for formatted strings
urls_v2 <- sprintf('https://site.com/page/%02d/', pages)
urls_v2[1]  # 'https://site.com/page/01/'

# With multiple parameters
urls_v3 <- sprintf('https://site.com/items?page=%d&size=20', pages)
urls_v3[3]  # 'https://site.com/items?page=3&size=20'

# glue package alternative
# glue('https://site.com/page/{pages}')

使用 map() 抓取多个页面

purrr::map() 会对 URL 向量中的每个 URL 应用抓取函数,并返回结果列表。结合 dplyr::bind_rows(),可以将结果合并为一个数据框。

library(rvest)
library(purrr)

# Define a function to scrape one page
scrape_page <- function(url) {
  # In real code: page <- read_html(url)
  # Here we simulate with local HTML
  page <- read_html(sprintf(
    '<ul><li class="item">Item %d-A</li><li class="item">Item %d-B</li></ul>',
    url, url
  ))
  data.frame(
    title = html_text2(html_elements(page, '.item')),
    page  = url
  )
}

# Apply over pages 1 and 2 (simulated with numbers)
results <- map(1:2, scrape_page)
do.call(rbind, results)

礼貌地抓取:Sys.sleep()

向服务器快速连续发送请求是不礼貌的行为,还可能触发速率限制或 IP 封禁。请在请求之间加入 Sys.sleep(),以尊重服务器。通常延迟 1 到 2 秒就足够了。

library(rvest)

# Polite scraping with delays between requests
scrape_with_delay <- function(urls, delay = 1.5) {
  results <- vector('list', length(urls))
  for (i in seq_along(urls)) {
    cat(sprintf('Scraping page %d of %d\n', i, length(urls)))
    # results[[i]] <- read_html(urls[[i]])
    Sys.sleep(delay)  # Wait before next request
  }
  results
}

# Also: randomize delay to appear more human-like
random_delay <- function(min = 1, max = 3) {
  Sys.sleep(runif(1, min = min, max = max))
}
random_delay()
cat('Always be polite to servers you scrape')

设置用户代理

请设置描述性的 User-Agent 标头,以礼貌地标识您的抓取程序。服务器会利用它识别机器人。优秀的 User-Agent 应包含您的姓名和联系信息,以便网站所有者与您联系。

library(rvest)

# Default user agent reveals R/rvest
# Better: set a descriptive, honest UA
ua <- paste0(
  'MyResearchBot/1.0 ',
  '(Academic research; ',
  'contact: researcher@university.edu)'
)

# With httr/httr2 for UA control:
# library(httr2)
# response <- request(url) |>
#   req_headers('User-Agent' = ua) |>
#   req_perform()
# html <- read_html(resp_body_string(response))

# Or use polite package for full compliance:
# library(polite)
# session <- bow('https://example.com', user_agent = ua)
cat('Honest UA: better for everyone')

遵守 robots.txt

robots.txt 会告知爬虫哪些路径禁止访问。robotstxt 包可以让您在抓取 URL 之前,以编程方式检查是否允许访问。

library(rvest)
# library(robotstxt)  # uncomment to use

# Check if scraping is allowed:
# rtxt <- robotstxt(domain = 'books.toscrape.com')
# rtxt$check(paths = '/catalogue/', bot = '*')
# TRUE means allowed

# Robots.txt typically looks like:
# User-agent: *
# Disallow: /admin/
# Disallow: /private/
# Allow: /catalogue/
# Crawl-delay: 2

# The polite package combines UA + robots.txt + delays:
# library(polite)
# session <- bow('https://books.toscrape.com')
# page <- scrape(session)
cat('Always check robots.txt before large-scale scraping')

跟随分页链接

与其猜测 URL,不如跟随每个页面上的实际“下一页”链接。html_attr(html_element(page, 'a.next'), 'href') 可以动态查找下一页链接,从而处理非连续的分页模式。

library(rvest)

# Pattern: follow 'next' links dynamically
scrape_all_pages <- function(start_url, max_pages = 10) {
  all_data <- list()
  url <- start_url
  i <- 1
  while (!is.null(url) && i <= max_pages) {
    # page <- read_html(url)
    # data <- extract_data(page)
    # all_data[[i]] <- data
    # next_link <- html_attr(html_element(page, 'li.next a'), 'href')
    # url <- if (!is.na(next_link)) xml2::url_absolute(next_link, url)
    #        else NULL
    cat(sprintf('Would scrape page %d: %s\n', i, url))
    url <- NULL  # Stop in this demo
    i <- i + 1
  }
  do.call(rbind, all_data)
}
scrape_all_pages('https://books.toscrape.com')

在 rvest 中使用 session_follow_link()

rvest 的 session() 会创建一个有状态的浏览会话,维护 Cookie 和历史记录。session_follow_link() 可导航到某个链接,适用于需要登录或需要在页面之间保持状态的网站。

library(rvest)
# Stateful session with rvest
# s <- session('https://example.com/login')
# s <- session_submit(s,
#   html_form(read_html(s))[[1]],
#   list(username='me', password='pw')
# )
# After login, navigate:
# page2 <- session_follow_link(s, 'Products')
# page3 <- session_back(page2)   # go back
# session_history(page2)         # show history

# For simple cases, follow href directly:
# href <- html_attr(html_element(page, 'a.next'), 'href')
# next_page <- session_jump_to(s, href)
cat('session() maintains cookies across requests')

检测最后一页

请确定何时停止:检查“下一页”按钮是否存在、当前页面的项目数量是否少于预期,或页码是否超过已知总页数。请妥善处理边界情况。

library(rvest)
# Strategy 1: Check if 'Next' link exists
has_next <- function(page) {
  next_link <- html_element(page, 'li.next a')
  !is.na(html_attr(next_link, 'href'))
}

# Strategy 2: Compare item count to expected page size
page_size <- 20
check_done <- function(items_on_page) {
  items_on_page < page_size
}

# Strategy 3: Read total from pagination text
# e.g., 'Page 1 of 50' -> extract 50
extract_total_pages <- function(page) {
  text <- html_text2(html_element(page, '.current'))
  as.integer(gsub('.*of (\\d+).*', '\\1', text))
}
cat('Always plan your stopping condition')

处理抓取错误

网络错误、超时和 404 错误都可能发生。请将请求封装在 tryCatch() 中,以妥善处理失败情况,并记录哪些页面失败,以便稍后重试。

library(rvest)

safe_scrape <- function(url) {
  tryCatch({
    # page <- read_html(url)
    # data <- extract_data(page)
    # return(data)
    cat(sprintf('Scraping: %s\n', url))
    data.frame(url = url, status = 'ok')
  }, error = function(e) {
    cat(sprintf('FAILED: %s -> %s\n', url, e$message))
    data.frame(url = url, status = 'error')
  })
}

urls <- c('https://example.com/page/1',
          'https://example.com/page/2')
results <- lapply(urls, safe_scrape)
do.call(rbind, results)

缓存抓取的页面

解析之前,先将下载的 HTML 保存到磁盘。这样,如果解析失败,就不必重新下载。再次读取时直接从磁盘读取,而不是访问网络,因此在开发过程中既礼貌又快速。

library(rvest)

scrape_cached <- function(url, cache_dir = 'cache') {
  dir.create(cache_dir, showWarnings = FALSE)
  # Create a filename from URL hash
  fname <- file.path(cache_dir,
    paste0(digest::digest(url), '.html'))
  if (file.exists(fname)) {
    cat('Loading from cache\n')
    return(read_html(fname))
  }
  cat('Downloading...\n')
  # page <- read_html(url)
  # xml2::write_html(page, fname)
  # return(page)
  cat('Cached to:', fname, '\n')
}

# Without digest: use URLencode or gsub
url <- 'https://example.com/page/1'
fname <- gsub('[^a-z0-9]', '_', tolower(url))
cat(fname)

快速检查

测试您对礼貌且稳健地抓取多个页面的理解。

回顾:分页与礼貌抓取

要点: 使用 paste0() 或 sprintf() 构建页面 URL。使用 purrr::map() 对多个 URL 应用抓取操作。请始终在请求之间加入 Sys.sleep()。使用 robotstxt 包检查 robots.txt。使用 html_attr() 或 session_follow_link() 动态跟随“下一页”链接。通过检查下一页链接是否缺失来判断最后一页。将请求封装在 tryCatch() 中,以增强错误处理能力。在开发过程中将 HTML 缓存到磁盘。

# Multi-page scraping template:
# urls <- paste0(base, 1:n_pages)
# results <- purrr::map(urls, function(url) {
#   Sys.sleep(runif(1, 1, 2))  # polite delay
#   page <- read_html(url)
#   extract_data(page)          # your parser
# })
# df <- dplyr::bind_rows(results)

# Key principles:
# 1. Respect robots.txt
# 2. Add delays (Sys.sleep)
# 3. Set honest User-Agent
# 4. Cache HTML during development
# 5. Handle errors with tryCatch
cat('Polite scraping = sustainable scraping')

常见问题解答

「处理分页与多页面」课时是免费的吗?

是的 — 「处理分页与多页面」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 R Academy 课程的其余内容,请升级到 CoddyKit PRO。 R Academy 课程共包含 4 节课。

「处理分页与多页面」这节课中我会学到什么?

遍历分页结果,并将抓取的数据合并为单个数据集 你通过在浏览器中直接运行的动手代码来练习 R Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 R Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 R Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「处理分页与多页面」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 R Academy 课中编写并运行代码吗?

能。每节 R Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. HTML 结构与 CSS 选择器
  2. html_element() 与 html_text() 基础
  3. 抓取表格与链接
  4. 处理分页与多页面
← 返回 R Academy