使用 rvest(或其他 R 包)检测 HTML 段落的开头何时是不同的格式(例如 emboldened)

ks1*_*321 5 html r dataframe web-scraping rvest

我正在使用 R 包 edgarWebR 来解析 SEC 文件,例如https://www.sec.gov/Archives/edgar/data/1060224/000090480206000008/sa10k306.htm。它返回一个数据框,其中一列(称为“原始”)是 HTML。它将 HTML 页面分解为段落,每段一行:

其他栏目 生的 文本
第一排 <p id="PARA339" style="TEXT-ALIGN: left; MARGIN: 0pt; LINE-HEIGHT: 1.25"><font style="FONT-SIZE: 10pt; FONT-FAMILY: Times New Roman, Times, serif"><i>We had a net loss of $1.</i><i><b>55</b></i><i> million for the year ended December 31, 201</i><i>6</i><i> and have an accumulated deficit of $</i><i>61.5</i><i> million as of December 31, 201</i><i>6</i><i>. To achieve sustainable profitability, we must generate increased revenue.</i></font></p> 截至 2016 年 12 月 31 日止年度,我们的净亏损为 155 万美元,截至 2016 年 12 月 31 日的累计亏损为 6150 万美元。为了实现可持续盈利,我们必须增加收入。
第二排 <div style="line-height:174%;text-align:left;font-size:9pt;"><font style="font-family:inherit;font-size:9pt;font-style:italic;font-weight:bold;">We have a history of losses, and we cannot assure you that we will achieve profitability.</font></div> 我们有亏损的历史,我们不能向您保证我们会实现盈利。

您可以通过运行轻松复制示例数据帧

library(edgarWebR)
df <- parse_filing("https://www.sec.gov/Archives/edgar/data/1060224/000090480206000008/sa10k306.htm",include.raw=TRUE)
Run Code Online (Sandbox Code Playgroud)

我的目标是解析 HTML 以确定哪些段落代表标题,通过计算哪些格式(例如粗斜体)在整个文档中出现的频率较低。

一个问题是有些段落将是描述性的(即不是标题级别),但包含一个或几个在段落中间加粗的单词以进行强调。为此,我有一个函数,对于每个段落,它将获取 css 选择器列表(例如粗体或斜体的选择器)并告诉我该样式的字符比例,感谢@QHarr:

## the function
paragraph_style_proportion <- Vectorize(function(html, css_selector) {
  html_content <- tryCatch(read_html(html), error=function(err) "NOT HTML")
  if (html_content == "NOT HTML") {
    style_proportion <- -1
  }
  else {
    whole_paragraph_length <- nchar(str_squish(html_content %>% html_text()))
    style_text_length <- sum(nchar(str_squish(html_content %>% html_nodes(css_selector) %>% html_text())))
    style_proportion <- round(style_text_length/whole_paragraph_length, 2)
  }
  return(style_proportion)
})

## to apply the function to the html-containing column "raw" of a dataframe "df"
df <- df %>% 
  mutate(bold_proportion = paragraph_style_proportion(raw, 'b, strong, [style*="font-weight:bold"]'))
Run Code Online (Sandbox Code Playgroud)

从那里,如果至少 40% 的字符是粗体,我可以轻松应用规则,例如将段落记录为粗体。从那里,我继续计算每种样式组合的频率(例如斜体大写文本)并分配数字标题级别。

但是,我现在面临的问题是以下情况 - 其中段落的前几个词具有不同的风格:

在此处输入图片说明

在上面的例子中,我的算法会将这些归类为“正常”段落——即不是粗体或斜体或任何东西——因为它远低于 40% 的斜体。但显然它代表了某种标题——因为不同的风格在段落的开头

这个问题的一些例子可以在https://www.sec.gov/Archives/edgar/data/1376067/000137606711000002/vll51231201010k.htmhttps://www.sec.gov/Archives/edgar/data/1060224找到/000090480206000008/sa10k306.htm

我将如何开始解决这个问题?之前的函数无法解决这个问题;我需要某种方式来逐一解析段落中每个包含文本的 HTML 部分,而不是仅仅拉出所有粗体文本并除以段落文本的总长度。特别困难的是“段落的第一部分”的长度会有所不同(通常甚至不存在),第一部分的样式以及段落其余部分的样式也会有所不同。

理想情况下,如果段落的第一部分是不同的格式,我想将段落分成两行 - 使不同格式的部分成为自己的行,然后可以按照我的方式对其标题级别进行分类已经描述过。如果没有,我要么想将整个段落标记为前几个单词的样式,要么创建一个列来标记前几个单词具有不同样式的段落。

谢谢

QHa*_*arr 1

我还没有对此进行足够的测试,但原则是通过查看给定行的 $raw 中的子标签来捕获您提到的情况。

目前,它被设置为查看孩子的标签/类型是否在给定的向量中c("b", "strong", "i");此外,还有对 style 属性的额外测试html_attr("style") %>% str_detect("italic|bold")。如果未找到属性匹配,将输出 Not N/A。

这两个测试的结果是该行是否应该被认为有标题(并且需要稍后拆分);使用布尔逻辑并包装if_else以在未找到子项的情况下输出 false。if_else(is.na(node), F, as.logical(style_flag | tag_flag)。结果写入名为 的新列header

为了稍微巧妙地处理这一点,然后对此新列进行比较,并bold_proportion_column确定最终是否应将其视为包含要处理的标题行。当前的确定取决于是否bold_proportion_column >= .5 OR header == T then T else F。显然,人们需要在某个时刻考虑到italic_proportion_column,或者重新考虑以将两者都包含在比例函数中(将是我的选择)。该确定被写入一outcome列。

我认为在完善这种方法和保证质量方面还有很多工作要做,例如1)我想重新工作,所以我们只解析一次html,2)将子html从其余部分返回到满足您将标题部分与其余部分拆分到不同行的要求;3)我还想通过不同页面的预先确定的测试用例列表来确定这种方法的有效性;4)我会重构以使功能get_child更通用,例如传递CSS选择器模式以获得更多的多功能性;在其他地方,在结果确定中传递 >= 的比例。

但它可能作为 10 的入门有用......如果可以的话,我可能会重新访问并完善它。


library(rvest)
library(tidyverse)
library(edgarWebR)
library(stringr)

paragraph_style_proportion <- Vectorize(function(html, css_selector) {
  missing_html_flag <- tryCatch(read_html(html), error = function(err) "NOT HTML")
  if (missing_html_flag == "NOT HTML") {
    style_proportion <- -1
  }
  else {
    page <- read_html(html)
    whole_paragraph_length <- nchar(str_squish(page %>% html_text()))
    style_text_length <- sum(nchar(str_squish(page %>% html_nodes(css_selector) %>% html_text())))
    style_proportion <- round(style_text_length / whole_paragraph_length, 2)
  }
  return(style_proportion)
})

get_child <- function(html) {
  child <- html %>%
    read_html() %>%
    html_node("*:nth-child(2)")
  return(child)
}

is_header <- function(node) {
  style_flag <- node %>%
    html_attr("style") %>%
    str_detect("italic|bold") %>% 
    if_else(is.na(.), F, .)
  tag_type <- node %>% html_name() %>% tolower()
  tag_flag <- tag_type %in% c("b", "strong", "i") # test if of certain type then classify
  flag <- if_else(is.na(node), F, as.logical(style_flag | tag_flag))
  return(flag)
}

df <- parse_filing("https://www.sec.gov/Archives/edgar/data/1070154/000114036109028731/form10k.htm", include.raw = TRUE)

df <- df %>%
  mutate(bold_proportion = paragraph_style_proportion(raw, "b, strong, [style*=bold]"))

df$header <- lapply(df$raw, function(x){is_header(get_child(x))})
df$outcome <-  with(df, ifelse(bold_proportion >=.5 | header == T, T, F))
Run Code Online (Sandbox Code Playgroud)