在数据分析工作中,我们常常需要从网页上获取数据。作为一名长期使用R语言的数据分析师,我发现rvest包绝对是R生态中最优雅的网页抓取解决方案。这个由Hadley Wickham开发的包,完美体现了R语言"让数据操作变得简单"的哲学理念。
rvest基于xml2和httr这两个强大的底层包构建,提供了一套简洁直观的函数集,专门用于HTML网页的解析和数据提取。与Python的BeautifulSoup相比,rvest在R环境中的集成度更高,学习曲线更为平缓。它采用了管道操作符(%>%)的风格,让代码读起来就像是在描述数据处理流程。
在开始之前,我们需要确保rvest包已经安装并加载。如果你还没有安装,可以通过以下命令安装:
r复制install.packages("rvest")
安装完成后,每次使用前需要加载包:
r复制library(rvest)
提示:我强烈建议同时安装dplyr和magrittr包,因为它们提供的管道操作符能让rvest的代码更加清晰易读。
rvest中最基础的函数是read_html(),它可以从URL或本地HTML文件读取内容。让我们以一个实际的图书网站为例:
r复制url <- "http://books.toscrape.com/"
html <- read_html(url)
这个简单的操作背后,rvest实际上完成了以下工作:
我们可以通过打印html对象来查看网页的基本结构:
r复制html
输出会显示网页的
和部分,让我们对网页结构有个初步了解。网页中的标题通常位于
r复制h1_text <- html %>% html_element("h1") %>% html_text2()
print(h1_text)
这里使用了管道操作符,代码从左到右清晰地表达了:
html_text2()函数相比基础的html_text(),能更好地处理空白字符和特殊符号。
图书网站上通常有多个商品,它们的标题往往使用相同的标签(如
r复制book_titles <- html %>% html_elements("h3") %>% html_text2()
print(book_titles)
html_elements()(注意复数形式)会返回所有匹配的元素,而不是像html_element()只返回第一个匹配项。
当我们需要提取特定class或id的元素时,CSS选择器就派上用场了。例如提取图书价格(它们都有price_color类):
r复制book_prices <- html %>% html_elements(".price_color") %>% html_text2()
print(book_prices)
CSS选择器的使用技巧:
从网页提取的文本数据往往需要进一步清洗。比如价格数据带有货币符号:
r复制# 去除英镑符号并转换为数值
prices_numeric <- gsub("£", "", book_prices) %>% as.numeric()
除了文本内容,我们经常需要提取链接或其他属性:
r复制book_links <- html %>% html_elements("h3 a") %>% html_attr("href")
这里我们:
对于网页中的表格,rvest提供了特别方便的函数:
r复制tables <- html %>% html_table()
这会返回页面中所有表格的列表,每个表格都是一个整洁的data.frame。
许多网站的数据分布在多个页面。我们需要编写循环来抓取所有页面:
r复制base_url <- "http://books.toscrape.com/catalogue/page-%d.html"
all_books <- list()
for (page in 1:5) { # 假设我们只抓取前5页
page_url <- sprintf(base_url, page)
page_html <- read_html(page_url)
# 提取当前页的数据并存入列表
all_books[[page]] <- page_html %>% html_elements(".product_pod") %>%
map_df(~{
tibble(
title = html_text2(html_element(.x, "h3 a")),
price = html_text2(html_element(.x, ".price_color"))
)
})
Sys.sleep(1) # 礼貌性延迟,避免给服务器造成负担
}
# 合并所有页面的数据
final_data <- bind_rows(all_books)
对于通过JavaScript动态加载的内容,rvest可能无法直接获取。这时可以考虑:
有些网站需要登录才能访问数据。rvest可以通过html_form()和submit_form()处理:
r复制login_page <- read_html("http://example.com/login")
login_form <- html_form(login_page)[[1]]
filled_form <- set_values(login_form,
username = "your_username",
password = "your_password")
session <- submit_form(filled_form)
在抓取任何网站前,务必检查其robots.txt文件(通常在网站根目录下)。这能告诉你哪些页面允许抓取,哪些不允许。
快速连续请求可能导致你的IP被封锁。建议:
r复制Sys.sleep(runif(1, 1, 3)) # 随机等待1-3秒
网络请求可能失败,良好的代码应该能处理这些情况:
r复制safe_read <- safely(read_html)
result <- safe_read(url)
if (!is.null(result$error)) {
message("请求失败,正在重试...")
result <- safe_read(url)
}
有些网站会阻止默认的R用户代理。可以这样设置:
r复制html <- read_html(url, config = httr::user_agent("Mozilla/5.0"))
对于大规模抓取任务,建议:
让我们看一个完整的例子,抓取图书网站的标题、价格和评分:
r复制library(rvest)
library(tidyverse)
scrape_books <- function(url) {
html <- read_html(url)
tibble(
title = html_elements(html, "h3 a") %>% html_text2(),
price = html_elements(html, ".price_color") %>% html_text2() %>%
str_remove("£") %>% as.numeric(),
rating = html_elements(html, ".star-rating") %>%
html_attr("class") %>% str_remove("star-rating "),
link = html_elements(html, "h3 a") %>% html_attr("href") %>%
url_absolute(url)
)
}
# 使用函数抓取多页数据
map_df(1:3, ~{
url <- sprintf("http://books.toscrape.com/catalogue/page-%d.html", .x)
scrape_books(url) %>%
mutate(page = .x)
})
这个例子展示了如何:
对于大规模抓取任务,可以使用furrr包实现并行:
r复制library(furrr)
plan(multisession)
urls <- sprintf("http://example.com/page-%d", 1:100)
results <- future_map_dfr(urls, scrape_books)
避免重复请求相同URL:
r复制library(memoise)
scrape_memoised <- memoise(scrape_books)
对于持续更新的网站,可以只抓取新内容:
r复制last_run <- readRDS("last_run.rds") # 加载上次抓取的最新记录
new_data <- scrape_books(url) %>%
filter(!title %in% last_run$title)
当遇到乱码时,可以指定编码:
r复制html <- read_html(url, encoding = "ISO-8859-1")
对于需要登录的网站,保持会话很重要:
r复制session <- session(url)
logged_in <- session %>% session_submit(login_form)
r复制book_details <- html_elements(html, ".product_pod") %>%
map_df(~{
tibble(
title = html_element(.x, "h3 a") %>% html_text2(),
price = html_element(.x, ".price_color") %>% html_text2()
)
})
r复制library(httr)
api_resp <- GET("https://api.example.com/data")
content <- content(api_resp, as = "parsed")
r复制library(DBI)
con <- dbConnect(RSQLite::SQLite(), "scraped_data.db")
dbWriteTable(con, "books", final_data, overwrite = TRUE)
通过rvest与其他强大R包的结合,我们可以构建出非常复杂且高效的数据采集管道,满足各种实际业务需求。