如何高效抓取美国输液中心定位网站的全部结果?
问题描述
我想从https://locator.infusioncenter.org/获取美国所有输液中心的完整列表(包含中心名称、地址)。该网站需要输入“城市, 州”或邮编,并设置5-100英里的搜索半径,美国共有超过3500家输液中心,我需要高效的抓取方法。
我发现搜索后URL不会更新,但还是写了一个R函数,传入批量邮编来尝试抓取,用了对应的CSS选择器:
get_infusion_centers <- function(zip) { url <- paste0("https://locator.infusioncenter.org/?zip=", zip) page <- read_html(url) locations <- page %>% html_nodes("#root > div > div > div.locator-container.flex.flex1 > div.locator-form.flex.column > div.flex1.scrollY > div.locator-results-container > div > div:nth-child(1) > a > div > div") %>% html_text() return(data.frame(zip = zip, locations = locations)) }
运行后出现错误:
Error in data.frame(zip = zip, locations = locations) : arguments imply differing number of rows: 1, 0
我猜测是位置信息抓取失败了,作为网页抓取新手,希望得到帮助。
解决方案
1. 代码失败原因
这个网站是单页应用(SPA),搜索结果是通过AJAX动态加载的,直接用read_html请求URL只能拿到静态页面框架,获取不到实际的搜索数据,所以locations会是空向量,和传入的zip行数(1)不匹配,才抛出了行数不一致的错误。另外你的CSS选择器只定位了第一个结果,就算能拿到数据也只能获取单条记录。
2. 两种可行抓取方案
方法一:直接调用API接口(高效推荐)
打开浏览器开发者工具(F12)切换到Network标签,输入邮编搜索后,能看到网站会向一个API接口发送POST请求获取数据,请求体包含邮编、半径等参数。用httr和jsonlite包直接调用接口,能拿到结构化的JSON数据:
library(httr) library(jsonlite) library(dplyr) get_infusion_api <- function(zip, radius = 50) { # 构造请求参数 body <- list( zip = zip, radius = radius, page = 1, pageSize = 100 # 单次请求获取最大数量结果 ) # 发送POST请求 response <- POST( url = "https://locator.infusioncenter.org/api/locations/search", body = body, encode = "json", add_headers( "Content-Type" = "application/json", "Accept" = "application/json" ) ) # 解析JSON结果并提取核心字段 data <- fromJSON(content(response, "text")) results <- data$results %>% select(name, address, city, state, zip) return(results) }
方法二:用RSelenium模拟浏览器操作(应对反爬)
如果API接口有反爬限制,就用RSelenium模拟真实浏览器的搜索操作:
library(RSelenium) library(rvest) library(dplyr) # 启动Chrome浏览器驱动(需提前安装对应版本的chromedriver) driver <- rsDriver(browser = "chrome", port = 4567L) remDr <- driver[["client"]] get_infusion_selenium <- function(zip, radius = 50) { # 打开目标页面 remDr$navigate("https://locator.infusioncenter.org/") # 输入邮编 zip_input <- remDr$findElement(using = "css", value = "input[placeholder='Enter City, State or ZIP']") zip_input$sendKeysToElement(list(zip)) # 设置搜索半径 radius_select <- remDr$findElement(using = "css", value = "select[name='radius']") radius_select$sendKeysToElement(list(as.character(radius))) # 点击搜索按钮 search_btn <- remDr$findElement(using = "css", value = "button[type='submit']") search_btn$clickElement() # 等待结果加载完成 Sys.sleep(3) # 解析页面结果 page_source <- remDr$getPageSource()[[1]] page <- read_html(page_source) locations <- page %>% html_nodes(".locator-result-item") %>% map_df(function(node) { name <- node %>% html_node(".result-name") %>% html_text(trim = TRUE) address <- node %>% html_node(".result-address") %>% html_text(trim = TRUE) tibble(name = name, address = address) }) return(locations) } # 使用完毕后关闭浏览器 # remDr$close() # driver$server$stop()
3. 批量获取与去重
遍历美国邮编列表调用上述函数后,需要对结果去重(不同邮编搜索会出现重复的输液中心):
# 假设zips是你的美国邮编向量 all_results <- purrr::map_dfr(zips, get_infusion_api) unique_centers <- all_results %>% distinct(name, address, .keep_all = TRUE)
注意事项
- 不要高频请求,建议添加
Sys.sleep(1)之类的延迟,避免触发反爬机制 - 提前查看网站的robots.txt,确保抓取行为符合网站使用条款
内容的提问来源于stack exchange,提问作者Russell Morgan
相关产品推荐
相关产品推荐

