【问题标题】:Trouble web scraping Google Sheets in R using RSelenium and RCurl使用 RSelenium 和 RCurl 在 R 中抓取 Google 表格时遇到问题
【发布时间】:2019-04-26 23:14:51
【问题描述】:

我的最终目标是为Montreal 抓取The Puzzled PintStandings 页面。

我认为我需要动态抓取(例如使用RSelenium),因为我感兴趣的表格是 JavaScript iframe - 网页的一部分,它显示的内容独立于其容器。

有些人建议直接从这些iframes 的源中抓取是要走的路。我在firefox 浏览器中使用网络开发人员Inspector 工具找到src=,恰好是Google Sheets

首先,使用robots.txt 确保我们可以从Google Sheets 抓取它:

library(robotstxt)
paths_allowed("https://docs.google.com/spreadsheets/d/1o1PlLIQS8v-XSuEz1eqZB80kcJk9xg5lsbueB7mTg1U/pub?output=html&widget=true#gid=203220308")

现在我知道我有权限,我尝试了RCurl 包。获取第一页很简单:

library(RCurl)
sheet <- getForm("https://docs.google.com/spreadsheet/pub", hl = "en_US", key = "1o1PlLIQS8v-XSuEz1eqZB80kcJk9xg5lsbueB7mTg1U", output = "csv", .opts = list(followlocation = TRUE, verbose = TRUE, ssl.verifypeer = FALSE))
df <- read.csv(textConnection(sheet))
head(df)

但是,当您单击此Google Sheet 上的任何其他Month/Year 链接时,网址的gid= 会发生变化。例如,对于 2018 年 10 月,现在是:

https://docs.google.com/spreadsheets/d/1o1PlLIQS8v-XSuEz1eqZB80kcJk9xg5lsbueB7mTg1U/pub?output=html&widget=true#gid=1367583807

我不确定是否可以用RCurl 抓取widget 的内容?如果是的话,我很想听听如何。

所以看起来我很可能需要使用RSelenium 来执行此操作。

library(RSelenium)
# connect to a running server
remDr <- remoteDriver(
  remoteServerAddr = "192.168.99.100",
  port = 4445L
)
remDr$open()
# navigate to the site of interest
remDr$navigate("https://docs.google.com/spreadsheets/d/1o1PlLIQS8v-XSuEz1eqZB80kcJk9xg5lsbueB7mTg1U/pub?output=html&widget=true#gid=203220308")

我的问题是尝试获取此页面上表格的HTML,以下是suggested on SO,但对我不起作用(它不会返回预期的输出,只是来自Month/Year 元数据链接/元素)?

library(XML)
doc <- htmlParse(remDr$getPageSource()[[1]])
readHTMLTable(doc)

我认为我需要导航到内部框架,但不知道该怎么做? 例如,当在chrome 中查找带有SelectorGadget 的该表的CSS 标记时,它会警告我它是一个iframe,并且能够在其中进行选择,我需要单击一个链接。

当我将此链接与readHTMLTable() 一起使用时,我得到了我想要的正确信息:

remDr$navigate("https://docs.google.com/spreadsheets/d/1o1PlLIQS8v-XSuEz1eqZB80kcJk9xg5lsbueB7mTg1U/pubhtml/sheet?headers=false&gid=203220308")
doc <- htmlParse(remDr$getPageSource()[[1]])
readHTMLTable(doc)

这带来了一个问题,因为我需要使用 RSelenium 来浏览上一个链接的不同页面/表格(iframe 小部件):

remDr$navigate("https://docs.google.com/spreadsheets/d/1o1PlLIQS8v-XSuEz1eqZB80kcJk9xg5lsbueB7mTg1U/pub?output=html&widget=true#gid=203220308")

要浏览不同的页面/表格,我使用SelectorGadget 来查找CSS 标签

# find all elements/links
webElems <- remDr$findElements(using = "css", ".switcherItem")
# Select the first link (October 2018)
webElem_01 <- webElems[[1]]

然后使用TightVNC viewer,我确认我突出显示了正确的元素,然后“单击”该元素(在本例中为October 2018 链接)。

webElem_01$highlightElement()

webElem_01$clickElement()

由于我可以看到页面在 TightVNC 上发生了变化,我认为在此处捕获/抓取之前不需要更多步骤,但如上所述,我需要一种以编程方式导航到每个页面的内部 iframe 的方法.

更新

好的,我知道如何使用remDr$switchToFrame() 命令导航到内框,但我似乎不知道如何导航回外框以“单击”下一个链接并重复该过程。我当前的 hacky 尝试将涉及我导航回主页并多次重复此过程:

# navigate to the main page
remDr$navigate("https://docs.google.com/spreadsheets/d/1o1PlLIQS8v-XSuEz1eqZB80kcJk9xg5lsbueB7mTg1U/pub?output=html&widget=true#gid=690408156")
# look for table
tableElem <- remDr$findElement(using = "id", "pageswitcher-content")
# switch to table
remDr$switchToFrame(tableElem)
# parse html
doc <- htmlParse(remDr$getPageSource()[[1]])
readHTMLTable(doc)

# how do I switch back to the outer frame?
# the remDr$goBack() command doesn't seem to do this

# workaround is to navigate back to the main page then navigate back to the second page and repeat process
remDr$navigate("https://docs.google.com/spreadsheets/d/1o1PlLIQS8v-XSuEz1eqZB80kcJk9xg5lsbueB7mTg1U/pub?output=html&widget=true#gid=690408156")
webElems <- remDr$findElements(using = "css", ".switcherItem")
webElem_01 <- webElems[[1]]
webElem_01$clickElement()
tableElem <- remDr$findElement(using = "id", "pageswitcher-content")
# switch to table
remDr$switchToFrame(tableElem)
# parse html
doc2 <- htmlParse(remDr$getPageSource()[[1]])
readHTMLTable(doc2)

【问题讨论】:

  • 可以请求访问 Google 表格。大多数网站所有者都非常友好,如果你解释你的用例,你可能根本不需要做任何这种抓取。所有这些 URL 的非 HTML 视图都被锁定(我检查了:-),但所有内容都是 CCA-NCSA-3.0,因此它们不限制数据的使用。 feedback@puzzledpint.com 可能会比网络探索要快得多(尽管您已经完成了 ???????? 工作分类。
  • 感谢您提出请求访问权限的建议。我同意这将是最简单的方法,但这也主要是为了我自己学习 RSelenium :)

标签: r iframe web-scraping rcurl rselenium


【解决方案1】:

注意到我在评论中所做的,这适用于大约 19 个 URL:

library(googlesheets4)
library(rvest)

pg <- read_html("http://www.puzzledpint.com/standings/")

html_nodes(pg, xpath=".//iframe[contains(@src, 'sheet')]") %>% 
  html_attr("src") -> gsheet_urls

gsheet_urls[grepl("output=html", gsheet_urls, fixed=TRUE)] %>% 
  lapply(function(x) {
    read.csv(
      file =gsub("=true", "=false", gsub("=html", "=csv", x)),
      stringsAsFactors = FALSE
    )
  }) -> nineteen

str(nineteen, 1)
## List of 19
##  $ :'data.frame': 8 obs. of  6 variables:
##  $ :'data.frame': 37 obs. of  7 variables:
##  $ :'data.frame': 35 obs. of  6 variables:
##  $ :'data.frame': 62 obs. of  6 variables:
##  $ :'data.frame': 34 obs. of  6 variables:
##  $ :'data.frame': 30 obs. of  11 variables:
##  $ :'data.frame': 24 obs. of  6 variables:
##  $ :'data.frame': 11 obs. of  6 variables:
##  $ :'data.frame': 9 obs. of  6 variables:
##  $ :'data.frame': 13 obs. of  6 variables:
##  $ :'data.frame': 36 obs. of  6 variables:
##  $ :'data.frame': 9 obs. of  6 variables:
##  $ :'data.frame': 13 obs. of  6 variables:
##  $ :'data.frame': 29 obs. of  6 variables:
##  $ :'data.frame': 45 obs. of  6 variables:
##  $ :'data.frame': 34 obs. of  6 variables:
##  $ :'data.frame': 22 obs. of  6 variables:
##  $ :'data.frame': 3 obs. of  6 variables:
##  $ :'data.frame': 14 obs. of  6 variables:

所有pubhtml 都曾在未经许可的情况下明确拒绝 CSV 导出。

【讨论】:

  • 谢谢鲍勃!作为splashr 的作者(RSelenium 的替代品,我还没有尝试过)我想知道你为什么不建议使用该工具的解决方案?
  • Rvest + googlesheets4 方法仅适用于约 30% 的表 (19/63),并且仅适用于当月。请求从 PuzzledPint 访问谷歌表格(IMO 最不好玩方式)或我用RSelenium 更新的解决方案似乎是目前最好的解决方案
  • 是的,我在回答中注意到“~19”。没有splashr 答案的原因是我没有编写执行此操作所需的所有 Lua 代码 :-) 表组存在细微差别,每个表都需要自定义 Selenium 处理和/或自定义 splashr Splash Lua 代码。这要求相当多的 SO 贡献者 IMO。
  • 这很公平。我想知道这是它需要的代码的长度/复杂性还是出于其他原因(例如,类似于使用铲子杀死苍蝇)。感谢您抽出宝贵时间回复。
【解决方案2】:

您不需要 selenium,只需从页面源中提取 gidpageUrl

https://docs.google.com/spreadsheets/d/1o1PlLIQS8v-XSuEz1eqZB80kcJk9xg5lsbueB7mTg1U/pub?output=html&widget=true

示例代码

items.push(
{
  name: "November 2018",
  pageUrl: "https:\/\/docs.google.comm\/.....&gid=690408156",
  gid: "690408156",
  initialSheet: ("690408156" == gid)
});
items.push(
{
  name: "October 2018",
  pageUrl: "https:\/\/docs.google.com\/.....&gid=1367583807",
  gid: "1367583807",
  initialSheet: ("1367583807" == gid)
});

结果网址:

https://docs.google.com/spreadsheets/d/1o1PlLIQS8v-XSuEz1eqZB80kcJk9xg5lsbueB7mTg1U/pubhtml/sheet?headers=false&gid=690408156
https://docs.google.com/spreadsheets/d/1o1PlLIQS8v-XSuEz1eqZB80kcJk9xg5lsbueB7mTg1U/pubhtml/sheet?headers=false&gid=1367583807

【讨论】:

  • 这在我看来不像是 R 代码,你用的是终端吗?您是如何从页面源programmatically 中找到这些gid 号码的?比如说,下个月生成了一个新的gidregex 搜索会抓取它并将其附加到您的items.push() 代码中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多