【问题标题】:HTML table does not show on source fileHTML 表格未显示在源文件中
【发布时间】:2019-05-10 01:29:07
【问题描述】:

我正在尝试使用 R(包rvest)在网页上抓取表格数据。为此,数据需要位于 html 源文件中(rvest 显然是在其中寻找它),但在这种情况下它不是。

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

源文件显示一个空表:

为什么数据显示在检查元素上而不是源文件上? 如何访问 html 格式的表格数据? 如果我无法通过 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 的解决方案

【问题讨论】:

  • codementor.io/codementorteam/… 如何使用 Python 抓取 AJAX 网站
  • 谢谢,但我正在寻找 R 工具
  • 您发布的页面网址无效:La funcionalidad Excel dinámico será descontinuada a partir del 31 de Octubre de 2018。翻译:“动态 Excel 功能将于 2018 年 10 月 31 日停用。”
  • @OldPro 我不知道为什么它会把你扔掉......虽然你可以输入:si3.bcentral.cl/siete/secure/cuadros/arboles.aspx 并在左侧菜单中选择“Información histórica”->“Variación mensual”。这就是我想要的桌子。

标签: javascript html r web-scraping rvest


【解决方案1】:

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

【讨论】:

  • GET 请求只会加载原始页面源,作为文本。如果表是动态构建的,它将不存在。您还必须在页面上执行 Javascript 以加载该数据并且您必须构建生成的 DOM。
  • @StephenP 我认为你是最接近目标的,你知道在 R 中有什么技术可以做到这一点吗?
  • @DavidJorquera - 不,我根本不知道 R...而且我不知道我知道知道的任何语言的包。您基本上必须构建一个 Web 浏览器,减去渲染和用户控件;您需要 HTML 解析器、DOM 构建器和完全兼容浏览器的 Javascript 环境。
  • @DavidJorquera 请注意,Brady Ward 给您的链接是关于使用 PhantomJS。虽然它是 2016 年的最佳选择,但 PhantomJS 支持在 2018 年初是 abandoned(最后支持的版本是 2016 年 1 月的 2.1.1),因为到那时 headless chrome 是一个更好的选择,可以更好地支持正在进行的开发和支持。所以不要在新项目中使用 PhantomJS。
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

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

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

      此时(2018 年 12 月),PhantomJS 已被弃用,最好的建议是使用 headless chrome。为了充分编写脚本以在多页站点中导航,您可以使用Selenium WebDriverChromeDriver 来控制headless chrome。请参阅this answer,以获得有关如何使用 Python 脚本进行此操作的完整说明。 Selenium 文档包含有关如何使用其他编程语言的信息,包括 Java、C#、Ruby、Perl、PHP 和 JavaScript,因此请使用您熟悉的任何语言。

      脚本的大致轮廓(使用 Python sn-ps)将是

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

      看起来可以在 R 中使用seleniumPipes 完成所有这些操作。请参阅其文档以了解如何完成上述步骤。使用findElement("link text", "Cuentas Nacionales") %>% elementClick 查找并单击链接。然后使用getPageSource() 获取页面源并将其输入rvestXML 或查找和解析表格的内容。

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

      【讨论】:

      • 谢谢你。实际上从家里,你必须去“Precios”,然后是“Información Histórica”,最后是“Variación mensual”。但你的解释当然也适用于此。我试试看。
      • 您无需编写浏览器脚本即可获取数据。这是一个非常不准确的断言。
      • @hrbrmstr 好的,“需要”这个词太强了,因为它是绝对的,但我的意思是“唯一实用的替代方案”的正常对话意义,我支持从通过 JavaScript 和 AJAX 在浏览器中生成的网页。此外,SO旨在为原始海报之外的广大受众提供有用的信息,因此我想提供一个广泛适用的答案。如果 OP 使用 seleniumPipes 来解决这个问题,OP 会发现将解决方案扩展到该站点上的其他表或其他站点是微不足道的。你的回答不能这么说。
      【解决方案5】:

      我非常希望“专家”停止使用“您需要 Selenium/Headless Chrome”,因为它几乎永远不会是真的,并且会在数据科学工作流程中引入不必要的重量级第三方依赖。 p>

      该站点是一个 ASP.NET 站点,因此它大量使用会话,并且该特定会话背后的程序员强制该会话在家中启动(“您好,2000 调用并希望他们的会话状态保留模型回来。”

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

      我们还可以从 ?? 看到该网站返回了可爱的 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 提供许多隐藏值,因为这是大脑死机的 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… 元素中。

      【讨论】:

      • 虽然这在今天可能有效,但这种脚本非常脆弱,这意味着很容易破坏,并且可能在站点更改时破坏。如果它确实坏了,除非您是 HTML 和 AJAX 专家,否则很难理解原因并且很难修复。编写无头浏览器的脚本更加健壮(在站点更改时可能不会中断),并且当它失败时,更容易理解它失败的原因并进行修复,因为它紧跟用户使用网站的体验。
      • Selenium 不是吗?严重地?不错的反对票。并且 ASP.NET 多年来一直以同样的方式工作。详细的网页抓取很脆弱。时期。而且,依赖是可怕的事情。
      • 通过让 Selenium 跟随 Chrome 中的命名链接来导航网站与屏幕抓取一样强大,这既是因为网站不喜欢通过更改链接名称来混淆用户,而且因为这就是大多数自动化 QA 测试的方式完毕。一旦您完成了一次,就很容易扩展以执行站点上的所有其他表以及您需要客户端渲染的任何其他站点。我宁愿直接从我的浏览器手动保存页面(或 AJAX 调用的 JSON 响应)并解析它,而不是使用您的解决方案。
      • 我们赢了!考虑到所有缺点,这很有效。
      【解决方案6】:

      这可以通过 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)
      

      【讨论】:

      • 考虑到这一点,我意识到您可能一次只能提交一个下拉选项。在这种情况下,您将设置一个选项并提交。然后设置另一个选项并提交。但我不确定这里是否需要。
      • 这不会渲染表格 b/c 说渲染是用 JavaScript 完成的。
      猜你喜欢
      • 2017-07-28
      • 2014-06-08
      • 1970-01-01
      • 2020-01-14
      • 2013-02-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-07
      相关资源
      最近更新 更多