HTML table 未在源文件中显示
HTML table does not show on source file
我正在尝试使用 R(程序包 rvest
)在网页上抓取 table 数据。为此,数据需要位于 html 源文件中(显然是 rvest
查找它的地方),但在本例中并非如此。
但是,数据元素显示在检查面板的元素视图中:
源文件显示为空 table:
为什么数据显示在inspect element 上而不是源文件上?
如何访问 html 格式的 table 数据?
如果我无法通过 html 访问,我该如何更改我的网页抓取策略?
编辑:感谢使用 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
- 点击link
link.click()
- 再次等待页面加载
- 使用
driver.getPageSource()
获取 HTML 并将其保存到文件中。
- 将该文件输入
rvest
看起来可以使用 seleniumPipes
在 R 中完成所有这些工作。请参阅其文档以了解如何完成上述步骤。使用findElement("link text", "Cuentas Nacionales") %>% elementClick
找到并点击link。然后使用 getPageSource()
获取页面源并将其提供给 rvest
或 XML
或其他东西来查找和解析 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)
我正在尝试使用 R(程序包 rvest
)在网页上抓取 table 数据。为此,数据需要位于 html 源文件中(显然是 rvest
查找它的地方),但在本例中并非如此。
但是,数据元素显示在检查面板的元素视图中:
源文件显示为空 table:
为什么数据显示在inspect element 上而不是源文件上? 如何访问 html 格式的 table 数据? 如果我无法通过 html 访问,我该如何更改我的网页抓取策略?
编辑:感谢使用 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 个片段)是
- 以无头模式启动chrome
- 获取主页
- 等待页面完全加载。在这种情况下,我不确定这样做的最佳方法,但您可以轮询页面以查找要填写的 table 数据,然后等到找到它。参见 Selenium explicit and implicit waits。
- 通过 Link 文本
link = driver.find_element_by_link_text("Cuentas Nacionales")
找到 link
- 点击link
link.click()
- 再次等待页面加载
- 使用
driver.getPageSource()
获取 HTML 并将其保存到文件中。 - 将该文件输入
rvest
看起来可以使用 seleniumPipes
在 R 中完成所有这些工作。请参阅其文档以了解如何完成上述步骤。使用findElement("link text", "Cuentas Nacionales") %>% elementClick
找到并点击link。然后使用 getPageSource()
获取页面源并将其提供给 rvest
或 XML
或其他东西来查找和解析 table。
旁注:@hrbrmstr
我真希望 '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)