Это дополнительный вопрос к тому, который я задавал ранее (Как создать фрейм данных из очищенного веб-сайта, сохраняя вложенную структуру данных ) и ответ от @stefan. Этот ответ идеально подходит для этого вопроса.
Но что, если есть дополнительные уровни вложенности?
library(rvest)
library(dplyr, warn = FALSE)
books <- minimal_html('
<div class = "entry">
<div class = "collection">Collection 1</div>
<div class = "book">
<div class = "booktitle">Book 1</div>
<div class = "year">1999</div>
<div class = "author">
<div class = "name">Author 1</div>
<div class = "city">Austin</div>
</div>
<div class = "author">
<div class = "name">Author 2</div>
<div class = "city">Dallas</div>
</div>
<div class = "author">
<div class = "name">Author 3</div>
<div class = "city">Memphis</div>
</div>
</div>
<div class = "book">
<div class = "booktitle">Book 2</div>
<div class = "year">2022</div>
<div class = "author">
<div class = "name">Author 4</div>
<div class = "city">Houston</div>
</div>
</div>
</div>
<div class = "entry">
<div class = "collection">Collection 2</div>
<div class = "book">
<div class = "booktitle">Book 3</div>
<div class = "year">1845</div>
<div class = "author">
<div class = "name">Author 5</div>
</div>
<div class = "author">
<div class = "name">Author 6</div>
<div class = "city">Dayton</div>
</div>
<div class = "author">
<div class = "name">Author 7</div>
<div class = "city">Philadelphia</div>
</div>
</div>
</div>')
Как и раньше, я бы хотел, чтобы все было на уровне автора, но имя и город автора должны быть в одной строке. Также есть дополнительный внешний слой collection. Все авторы книг в entry должны иметь номер коллекции entry. Таким образом, строк должно быть семь, а Author 7 должен иметь следующие значения: Collection 2, Book 3, 1845, Author 7 и Philadelphia.
Также обратите внимание, что для каждого автора будет только одно (или ни одного) имя и один (или ни одного — у автора 5 нет города) город. А у entry всегда будет ровно один collection.
Как я могу расширить этот код из предыдущего ответа, чтобы получить желаемое решение?
data0 <- books %>%
html_elements(".book") |>
lapply(\(x) {
tibble(
title = x |> html_element(".booktitle") |> html_text2(),
year = x |> html_element(".year") |> html_text2(),
authors = x |> html_elements(".author") |> html_text2(),
)
}) |>
bind_rows()





Поскольку между автором и городом есть новый символ строки, вы можете попробовать отдельную функцию из пакета tidyr.
library(tidyr)
tidyr::separate(data0, authors, into=c("authors", "city"), sep = "\\n")
Для более сложных случаев, скорее всего, понадобится пара циклов или рекурсия.
Если есть вложенные слои HTML, вы можете сначала перейти на верхний уровень, используя html_nodes, а затем перейти на нижние уровни.
Глядя на предоставленный HTML-код, можно увидеть слой высшего класса под названием .entry. Сначала мы используем это в html_nodes:
books %>%
html_nodes(".entry") %>%
lapply(\(x) {
tibble(
collection = x |> html_element(".collection") |> html_text2(),
title = x |> html_element(".booktitle") |> html_text2(),
year = x |> html_element(".year") |> html_text2(),
author = x |> html_elements(".author") |> html_text2(),
city = x |> html_elements(".city") |> html_text2()
)
})
Приведенный выше код будет искать классы .author и .city и извлекать соответствующие тексты, но поскольку в одной из строк отсутствует город, функция tibble выдаст сообщение:
Error in `tibble()`:
! Tibble columns must have compatible sizes.
• Size 3: Existing data.
• Size 2: Column `city`.
ℹ Only values of size one are recycled.
Затем мы активизируемся (так же, как в вашем коде):
books %>%
html_nodes(".entry") %>%
lapply(\(x) {
tibble(
collection = x |> html_element(".collection") |> html_text2(),
title = x |> html_element(".booktitle") |> html_text2(),
year = x |> html_element(".year") |> html_text2(),
author = x |> html_elements(".author") |> html_text2()
)
})
В результате автор и город будут объединены, и мы сможем разделить их:
books %>%
html_nodes(".entry") %>%
lapply(\(x) {
tibble(
collection = x |> html_element(".collection") |> html_text2(),
title = x |> html_element(".booktitle") |> html_text2(),
year = x |> html_element(".year") |> html_text2(),
author = x |> html_elements(".author") |> html_text2()
) %>%
separate(
col = author
,into = c("authors", "city")
,sep = "\n"
,fill = "right"
)
}) %>%
bind_rows()
Спасибо за ответ!
Чтобы опираться на связанный ответ, вы можете добавить несколько xpath для доступа к одноуровневым книгам, здесь это будет элемент коллекции одного и того же родителя.
separate() действительно хороший ярлык в этом случае. В качестве альтернативы обработки сведений об авторе вы можете добавить еще один итератор либо в текущий lapply(), либо переместить его на следующий шаг обработки. Лично я предпочитаю последнее: здесь я сначала добавляю списки узлов авторов в тиббл при переборе всех книг, затем преобразовываю их в подтиблы в mutate() и, наконец, удаляю вложение.
library(rvest)
library(dplyr, warn = FALSE)
books |>
html_elements(".book") |>
lapply(\(x) {
tibble(
collection = x |> html_element(xpath = "../div[@class='collection']") |> html_text2(),
title = x |> html_element(".booktitle") |> html_text2(),
year = x |> html_element(".year") |> html_text2(),
authors = x |> html_elements(".author") |> list()
)
}) |>
bind_rows() |>
mutate(authors = lapply(authors, \(x) tibble(name = html_element(x, ".name") |> html_text2(),
city = html_element(x, ".city") |> html_text2()))) |>
tidyr::unnest(authors, names_sep = ".")
#> # A tibble: 7 × 5
#> collection title year authors.name authors.city
#> <chr> <chr> <chr> <chr> <chr>
#> 1 Collection 1 Book 1 1999 Author 1 Austin
#> 2 Collection 1 Book 1 1999 Author 2 Dallas
#> 3 Collection 1 Book 1 1999 Author 3 Memphis
#> 4 Collection 1 Book 2 2022 Author 4 Houston
#> 5 Collection 2 Book 3 1845 Author 5 <NA>
#> 6 Collection 2 Book 3 1845 Author 6 Dayton
#> 7 Collection 2 Book 3 1845 Author 7 Philadelphia
Пример данных:
books <- minimal_html('
<div class = "entry">
<div class = "collection">Collection 1</div>
<div class = "book">
<div class = "booktitle">Book 1</div>
<div class = "year">1999</div>
<div class = "author">
<div class = "name">Author 1</div>
<div class = "city">Austin</div>
</div>
<div class = "author">
<div class = "name">Author 2</div>
<div class = "city">Dallas</div>
</div>
<div class = "author">
<div class = "name">Author 3</div>
<div class = "city">Memphis</div>
</div>
</div>
<div class = "book">
<div class = "booktitle">Book 2</div>
<div class = "year">2022</div>
<div class = "author">
<div class = "name">Author 4</div>
<div class = "city">Houston</div>
</div>
</div>
</div>
<div class = "entry">
<div class = "collection">Collection 2</div>
<div class = "book">
<div class = "booktitle">Book 3</div>
<div class = "year">1845</div>
<div class = "author">
<div class = "name">Author 5</div>
</div>
<div class = "author">
<div class = "name">Author 6</div>
<div class = "city">Dayton</div>
</div>
<div class = "author">
<div class = "name">Author 7</div>
<div class = "city">Philadelphia</div>
</div>
</div>
</div>')
Created on 2024-08-08 with reprex v2.1.1
Очень мило спасибо!
Спасибо за это предложение!