R语言rvest批量提取多类class网页卡片元素及属性方法
fotocasa.es房源爬虫全量卡片抓取方案
场景说明
抓取fotocasa.es房源列表页的链接、标题等信息时,最初通过匹配re-CardPackMinimal-info-container类提取链接仅返回27条结果,与页面实际30条房源的数量不符。排查发现列表内的卡片分属多个class:re-CardPackMinimal(普通房源)、re-AdvertisingNativeListing-container/re-AdvertisingTop2Listing(广告)、re-CardPackPremium(Premium房源)、re-CardPackAdvance(Advance房源)。手动逐个指定class选节点的方式维护成本高,需要实现两个目标:
- 无需手动枚举class,自动抓取所有卡片的全量字段
- 提取结果可区分普通房源、广告、不同等级付费房源类型
现有基础代码
已通过rvest、tidyverse、xml2、RSelenium实现页面打开、cookie同意、排序、滚动加载全流程,可拿到完整渲染后的页面源码:
library(rvest) library(tidyverse) library(xml2) library(stringr) library(RSelenium) openAndScrollPage <- function(link){ driver = rsDriver(browser = c("firefox")) remDr <- driver[["client"]] remDr$navigate(link) # 同意cookie remDr$findElement(using = "xpath", '/html/body/div[1]/div[4]/div/div/div/footer/div/button[2]')$clickElement() # 按最新排序 remDr$findElement(using = 'xpath', '//*[@id="App"]/div[2]/div[1]/main/div/div[3]/div/div/select/option[3]')$clickElement() # 逐段滚动加载全部内容 for(i in 1:30){ print(paste("Scrolling for: ", i, " seconds", sep = "")) remDr$executeScript("window.scrollBy(0,500);") Sys.sleep(1) } # 读取完整页面源码 html_full_page = remDr$getPageSource()[[1]] %>% read_html() return(html_full_page) } link2 = "https://www.fotocasa.es/es/comprar/viviendas/barcelona-capital/la-nova-esquerra-de-l-eixample/l" page2 = openAndScrollPage(link2)
已验证可通过如下代码拿到所有卡片的class属性,共返回30+节点(含2个广告节点):
page2 %>% html_nodes(".re-SearchResult") %>% html_children() %>% html_attr(name = "class")
返回结果示例:
[1] "re-CardPackMinimal" "re-CardPackMinimal" "re-CardPackMinimal" [4] "re-CardPackMinimal" "re-AdvertisingTop2Listing" "re-CardPackMinimal" [7] "re-CardPackMinimal" "re-CardPackMinimal" "re-CardPackMinimal" [10] "re-AdvertisingNativeListing-container" "re-CardPackMinimal" "re-CardPackMinimal" [13] "re-CardPackMinimal" "re-CardPackMinimal" "re-CardPackMinimal" [16] "re-CardPackMinimal" "re-CardPackMinimal" "re-CardPackMinimal" [19] "re-CardPackMinimal" "re-CardPackMinimal" "re-CardPackMinimal" [22] "re-CardPackMinimal" "re-CardPackMinimal" "re-CardPackPremium" [25] "re-CardPackPremium" "re-CardPackMinimal" "re-CardPackMinimal" [28] "re-CardPackMinimal" "re-CardPackAdvance" "re-CardPackMinimal" [31] "re-CardPackMinimal" "re-CardPackMinimal"
解决方案
不用提前枚举class做批量匹配,直接选中.re-SearchResult下所有子节点就是全部房源卡片,提取单卡片信息时同步记录节点class作为类型标识即可,不会漏数。
可直接运行的实现代码
# 提取搜索结果容器下所有卡片节点,覆盖所有class类型 all_cards <- page2 %>% html_element(".re-SearchResult") %>% html_children() # 批量提取全字段,自动标记卡片类型 house_data <- tibble( # 记录卡片原始class值 raw_card_class = html_attr(all_cards, "class"), # 归类卡片类型 card_type = case_when( str_detect(raw_card_class, "Advertising") ~ "广告", str_detect(raw_card_class, "Premium") ~ "Premium付费房源", str_detect(raw_card_class, "Advance") ~ "Advance付费房源", str_detect(raw_card_class, "Minimal") ~ "普通房源", TRUE ~ "未知类型" ), # 提取房源详情链接,兼容所有卡片的链接结构 detail_url = map_chr(all_cards, function(node){ node %>% html_node("a[href^='/es/comprar/vivienda/'], a[href^='/vivienda/']") %>% html_attr("href") %>% paste0("https://www.fotocasa.es", .) }), # 提取房源标题,兼容不同卡片的标题class title = map_chr(all_cards, function(node){ node %>% html_node("[class*='title']") %>% html_text(trim = T) }) )
逻辑说明
- 选节点时直接取搜索结果容器的所有直接子节点,不需要提前收集class再逐个匹配,天然覆盖所有卡片类型,不会漏数
- 类型判断直接基于卡片自身的class属性,自动区分广告、不同等级付费房源、普通房源
- 单卡片内的字段提取用模糊匹配的选择器,兼容不同类型卡片的内部结构差异,不需要给每类卡片单独写提取规则
- 如果后续需要提取价格、面积、房间数等其他字段,直接在tibble里加对应列,用同样的节点内匹配逻辑即可
内容的提问来源于stack exchange,提问作者user113156
相关产品推荐
相关产品推荐

