HTML table 未在源文件中显示

HTML table does not show on source file

我正在尝试使用 R(程序包 rvest)在网页上抓取 table 数据。为此,数据需要位于 html 源文件中(显然是 rvest 查找它的地方),但在本例中并非如此。

但是,数据元素显示在检查面板的元素视图中:

源文件显示为空 table:

为什么数据显示在inspect element 上而不是源文件上? 如何访问 html 格式的 table 数据? 如果我无法通过 html 访问,我该如何更改我的网页抓取策略?

*网页是 https://si3.bcentral.cl/siete/secure/cuadros/cuadro_dinamico.aspx?idMenu=IPC_VAR_MEN1_HIST&codCuadro=IPC_VAR_MEN1_HIST

源文件: view-source:https://si3.bcentral.cl/siete/secure/cuadros/cuadro_dinamico.aspx?idMenu=IPC_VAR_MEN1_HIST&codCuadro=IPC_VAR_MEN1_HIST


编辑:感谢使用 R 的解决方案

数据很可能是从数据源或 API 动态加载的。您可以通过向网页发送 GET 请求并在数据加载后抓取页面来抓取填充的 table!

数据很可能是通过JavaScript框架加载的,因此原始来源已被JavaScript更改。

您需要一个可以执行 JavaScript 然后将结果废弃为数据的工具。或者您可以直接调用数据 API 并在 JSON 中获得结果。

编辑:我在使用 Microsoft PowerBI 抓取 Web 表方面取得了一些成功,这里有一个 link 示例,如果它适合您的话。 https://www.poweredsolutions.co/2018/05/14/new-web-scraping-experience-in-power-bi-power-query-using-css-selectors/

正如其他人所说,table 数据可能是由 javascript 动态加载的。

  • 您可以在开发人员工具中搜索 network 选项卡,也许可以找到请求 returns 您需要的数据。然后,您可能不会分析主文档的 html,而是从其他带有参数的 URL 中得到一些 JSON。 XML/HTML 和其他格式也是可能的。如果需要授权,您可能还必须重新创建所有 http 请求 headers。
  • 或者尝试将诸如 Selenium 之类的东西集成到您的脚本中 - 这将使用执行 js 脚本的真实浏览器。它主要用于测试,但也应该用于抓取数据。如果不欢迎打开新的 window,可能还可以选择使用一些无头浏览器:) 显然已经有将 selenium 与 R 集成的库 - 祝你好运:) Scraping with Selenium - R-bloggers

您的目标是一个复杂的动态网站,这就是您无法轻松抓取它的原因。要访问我认为您要询问的页面,我必须先转到 home page,然后单击左侧菜单上的 "Cuentas Nacionales"。该单击导致 POST 请求发送表单数据,显然指示要呈现的下一个视图,该视图显然存储在服务器端的会话中。这就是为什么你不能直接访问目标URL;对于几个不同的显示器,它是相同的 URL。

为了抓取页面,您需要编写一个浏览器脚本来完成访问页面的步骤,然后将呈现的页面保存到 HTML 文件,此时您应该能够使用 rvest 从文件中提取数据。 (@hrbrmstr 指出您绝对 需要 编写浏览器脚本来获取数据,因为您不需要通过抓取呈现的页面来获取数据。稍后会详细介绍。 )

此时(2018 年 12 月),PhantomJS 已被弃用,最好的建议是使用 headless chrome。为了充分编写脚本以在多页站点中导航,您可以使用 Selenium WebDriver with ChromeDriver to control headless chrome. See 来获得关于如何使用 Python 脚本的完整解释。 Selenium 文档包含有关如何使用其他编程语言的信息,包括 Java、C#、Ruby、Perl、PHP 和 JavaScript,因此请使用您使用的任何语言舒适 table 与。

脚本的大纲(包含 Python 个片段)是

  • 以无头模式启动chrome
  • 获取主页
  • 等待页面完全加载。在这种情况下,我不确定这样做的最佳方法,但您可以轮询页面以查找要填写的 table 数据,然后等到找到它。参见 Selenium explicit and implicit waits
  • 通过 Link 文本 link = driver.find_element_by_link_text("Cuentas Nacionales")
  • 找到 link
  • 点击linklink.click()
  • 再次等待页面加载
  • 使用 driver.getPageSource() 获取 HTML 并将其保存到文件中。
  • 将该文件输入 rvest

看起来可以使用 seleniumPipes 在 R 中完成所有这些工作。请参阅其文档以了解如何完成上述步骤。使用findElement("link text", "Cuentas Nacionales") %>% elementClick找到并点击link。然后使用 getPageSource() 获取页面源并将其提供给 rvestXML 或其他东西来查找和解析 table。

旁注:@hrbrmstr 你可以在浏览器中手动完成所有步骤,使用浏览器的开发工具提取相关请求和响应数据,而不是编写浏览器脚本来抓取页面,以便您最终可以编写一组 HTTPS 请求和响应解析器的脚本,这些解析器最终将生成一个请求,该请求将 return 您想要的数据。由于 hrbrmstr 已经为您完成了这项工作,因此在这个确切的例子中,您可以更轻松地剪切和粘贴他们的答案,但总的来说我不推荐这种方法,因为它很难设置,将来很可能会中断,而且当它坏了时很难修复。对于不关心长期可维护性的人来说,由于此 table 仅每月更改一次,您可以更轻松地手动导航到该页面并使用浏览器将其保存到 HTML文件并将该文件加载到 R 脚本中。

我真希望 'experts' 会停止使用 "you need Selenium/Headless Chrome" 因为它 几乎 永远不会是真的,并且会在数据科学中引入不必要的、重量级的第三方依赖工作流程。

该站点是一个 ASP.NET 站点,因此它大量使用会话,并且此特定会话背后的程序员强制该会话在家中开始("Hello, 2000 called and would like their session state preserving model back.")

无论如何,我们需要从这里开始并进入您的页面。在您的浏览器中显示如下:

我们还可以看到该网站 returns 很可爱 JSON 所以我们最终会抓住它。让我们开始像上面的会话一样对 R httr 工作流建模:

library(xml2)
library(httr)
library(rvest)

从,嗯,开始!

httr::GET(
  url = "https://si3.bcentral.cl/Siete/secure/cuadros/home.aspx",
  httr::verbose()
) -> res

现在,我们需要从该页面获取 HTML,因为我们需要向 POST 提供许多隐藏值,因为这是大脑如何- dead ASP.NET 工作流程有效(同样,按照上图中的要求):

pg <- httr::content(res)

hinput <- html_nodes(pg, "input")
hinput <- as.list(setNames(html_attr(hinput, "value"), html_attr(hinput, "name")))
hinput$`header$txtBoxBuscador` <- ""
hinput$`__EVENTARGUMENT` <- ""
hinput$`__EVENTTARGET` <- "lnkBut01"

httr::POST(
  url = "https://si3.bcentral.cl/Siete/secure/cuadros/home.aspx",
  httr::add_headers(
    `Referer` = "https://si3.bcentral.cl/Siete/secure/cuadros/home.aspx"
  ),
  encode = "form",
  body = hinput
) -> res

现在我们已经完成了让网站认为我们有一个正确的会话所需的操作,所以让我们请求 JSON 内容:

httr::GET(
  url = "https://si3.bcentral.cl/siete/secure/cuadros/actions.aspx",
  httr::add_headers(
    `X-Requested-With` = "XMLHttpRequest"
  ),
  query = list(
    Opcion = "1",
    idMenu = "IPC_VAR_MEN1_HIST",
    codCuadro = "IPC_VAR_MEN1_HIST",
    DrDwnAnioDesde = "",
    DrDwnAnioHasta = "",
    DrDwnAnioDiario = "",
    DropDownListFrequency = "",
    DrDwnCalculo = "NONE"
  )
) -> res

然后,繁荣:

str(
  httr::content(res), 1
)

## List of 32
##  $ CodigoCuadro       : chr "IPC_VAR_MEN1_HIST"
##  $ Language           : chr "es-CL"
##  $ DescripcionCuadro  : chr "IPC, IPCX, IPCX1 e IPC SAE, variación mensual, información histórica"
##  $ AnioDesde          : int 1928
##  $ AnioHasta          : int 2018
##  $ FechaInicio        : chr "01-01-2010"
##  $ FechaFin           : chr "01-11-2018"
##  $ ListaFrecuencia    :List of 1
##  $ FrecuenciaDefecto  : NULL
##  $ DrDwnAnioDesde     :List of 3
##  $ DrDwnAnioHasta     :List of 3
##  $ DrDwnAnioDiario    :List of 3
##  $ hsDecimales        :List of 1
##  $ ListaCalculo       :List of 1
##  $ Metadatos          : chr " <img runat=\"server\" ID=\"imgButMetaDatos\" alt=\"Ver metadatos\" src=\"../../Images/lens.gif\" OnClick=\"jav"| __truncated__
##  $ NotasPrincipales   : chr ""
##  $ StatusTextBox      : chr ""
##  $ Grid               :List of 4
##  $ GridColumnNames    :List of 113
##  $ Paginador          : int 15
##  $ allowEmptyColumns  : logi FALSE
##  $ FechaInicioSelected: chr "2010"
##  $ FechaFinSelected   : chr "2018"
##  $ FrecuenciaSelected : chr "MONTHLY"
##  $ CalculoSelected    : chr "NONE"
##  $ AnioDiarioSelected : chr "2010"
##  $ UrlFechaBase       : chr "Indizar_fechaBase.aspx?codCuadro=IPC_VAR_MEN1_HIST"
##  $ FechaBaseCuadro    : chr "Ene 2010"
##  $ IsBoletin          : logi FALSE
##  $ CheckSelected      :List of 4
##  $ lnkButFechaBase    : logi FALSE
##  $ ShowFechaBase      : logi FALSE

在 JSON 中挖掘您需要的数据。我认为它在Grid…元素中。

这可能与 rvest 相关,因为最终的 iframe 使用标准格式。为了仅使用 rvest,您必须利用一个会话、一个用户代理字符串,以及您已经收集的有关 iframe 直接链接的信息。

library(rvest)
library(httr)

# Change the User Agent string to tell the website into believing this is a legitimate browser
uastring <- "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"

# Load the initial session so you don't get a timeout error
session <- html_session("https://si3.bcentral.cl/siete/secure/cuadros/home.aspx", user_agent(uastring))

session$url

# Go to the page that has the information we want
session <- session %>%
  jump_to("https://si3.bcentral.cl/Siete/secure/cuadros/arboles.aspx")

session$url

# Load only the iframe with the information we want
session <- session %>%
  jump_to("https://si3.bcentral.cl/siete/secure/cuadros/cuadro_dinamico.aspx?idMenu=IPC_VAR_MEN1_HIST&codCuadro=IPC_VAR_MEN1_HIST")

session$url
page_html <- read_html(session)

# Next step would be to change the form using html_form(), set_values(), and submit_form() if needed.
# Then the table is available and ready to scrape.
settings_form <- session %>% 
  html_form() %>%
  .[[1]] 

# Form on home page has no submit button,
# so inject a fake submit button or else rvest cannot submit it.
# When I do this, rvest gives a warning "Submitting with '___'", where "___" is
# often an irrelevant field item.
# This warning might be an rvest (version 0.3.2) bug, but the code works.
fake_submit_button <- list(name = NULL,
                           type = "submit",
                           value = NULL,
                           checked = NULL,
                           disabled = NULL,
                           readonly = NULL,
                           required = FALSE)
attr(fake_submit_button, "class") <- "input"
settings_form[["fields"]][["submit"]] <- fake_submit_button

settings_form <- settings_form %>%
  set_values(DrDwnAnioDesde = "2017",
             DrDwnAnioDiario = "2017")

session2 <- session %>%
  submit_form(settings_form)