Получение текста после слова - R Webscraping - PullRequest
5 голосов
/ 08 апреля 2019

Несколько недель назад кто-то здесь помог мне безмерно получить список всех ссылок в базе данных Notable Names. я смог запустить этот код и получить следующий вывод

library(purrr)
library(rvest)
url_base <- "https://www.nndb.com/lists/494/000063305/"    
## Gets A-Z links
all_surname_urls <- read_html(url_base) %>%
      html_nodes(".newslink") %>%
      html_attrs() %>%
      map(pluck(1, 1))

all_ppl_urls <- map(
      all_surname_urls, 
      function(x) read_html(x) %>%
        html_nodes("a") %>%
        html_attrs() %>%
        map(pluck(1, 1))
    ) %>% 
      unlist()

all_ppl_urls <- setdiff(
      all_ppl_urls[!duplicated(all_ppl_urls)], 
      c(all_surname_urls, "http://www.nndb.com/")
    )

all_ppl_urls[1] %>%
      read_html() %>%
      html_nodes("p") %>%
      html_text()

# [1] "AKA Lee William Aaker"
# [2] "Born: 25-Sep-1943Birthplace: Los Angeles, CA"
# [3] "Gender: MaleRace or Ethnicity: WhiteOccupation: Actor"
# [4] "Nationality: United StatesExecutive summary: The Adventures of Rin Tin Tin"
# ...

Мое первоначальное намерение состояло в том, чтобы получить фрейм данных, в котором я получу имя человека, его пол , раса , род занятий и национальность в одном кадре данных.

Многие вопросы, которые я видел здесь и на других сайтах, были полезны, если ваши данные попали в таблицу html, а это не относится к базе данных заметных имен. Я знаю, что цикл должен быть задействован для всех 40К сайтов, но после уик-энда поиска ответов я не могу понять, как это сделать. Может ли кто-нибудь помочь?

Отредактировано, чтобы добавить Я попытался следовать некоторым правилам здесь, но этот запрос был немного более сложным

## I tried to run list <- all_ppl_urls%>% map(read_html) but that was taking a LONG time so I decided to just get the first ten links for the sake of showing my example:

example <- head(all_ppl_urls, 10)

list  <- example %>% map(read_html)

test <-list  %>% map_df(~{
   text_1 <- html_nodes(.x, 'p , b') %>% html_text

и я получил эту ошибку: Ошибка: Дополнительно: предупреждающее сообщение: закрытие неиспользуемого соединения 3 (http://www.nndb.com/people/965/000279128/)

Ответы [ 2 ]

3 голосов
/ 11 апреля 2019

Обновление

Включена процедура обработки ошибок для профилей, которые не могут быть проанализированы должным образом.Если есть какая-либо ошибка, вы получите строку NA (даже если некоторая информация может быть проанализирована правильно - это связано с тем, что мы читаем все поля одновременно и полагаемся, что все поля могут быть прочитаны).

Может быть, вы хотите доработать этот код для возврата частичной информации?Вы можете сделать это, читая поля одно за другим (а не один раз) и, если есть ошибка, вернуть NA для этого поля, а не для всей строки.Однако у этого есть недостаток: код должен анализировать документ не только один раз для каждого профиля, но и несколько раз.


Вот функция, которая использует Xpath для выбора соответствующих полей:

library(rvest)
library(glue)
library(tibble)
library(dplyr)
library(purrr)

scrape_profile <- function(url) {
   fields <- c("Gender:", "Race or Ethnicity:", "Occupation:", "Nationality:")
   filter <- glue("contains(text(), '{fields}')") %>%
                  paste0(collapse = " or ")
   xp_string <- glue("//b[{filter}]/following::text()[normalize-space()!=''][1]") 
   tryCatch({
      doc <- read_html(url)
      name <- doc %>%
                html_node(xpath = "(//b/text())[1]") %>% 
                as.character()
      doc %>%
         html_nodes(xpath = xp_string) %>%
         as.character() %>%
         gsub("^\\s|\\s$", "", .) %>%
         as.list() %>%
         setNames(c("Gender", "Race", "Occupation", "Nationality")) %>%
         as_tibble() %>%
         mutate(Name = name) %>%
         select(Name, everything())
   }, error = function(err) {
      message(glue("Profile <{url}> could not be parsed properly."))
      tibble(Name = ifelse(exists("name"), name, NA), Gender = NA,
             Race = NA, Occupation = NA,
             Nationality = NA)
   })
}

Все, что вам нужно сделать сейчас, это применить scrape_profile ко всем URL вашего профиля:

map_dfr(all_ppl_urls[1:5], scrape_profile)
# # A tibble: 5 x 5
#   Name                Gender Race  Occupation Nationality  
#   <chr>               <chr>  <chr> <chr>      <chr>        
# 1 Lee Aaker           Male   White Actor      United States
# 2 Aaliyah             Female Black Singer     United States
# 3 Alvar Aalto         Male   White Architect  Finland      
# 4 Willie Aames        Male   White Actor      United States
# 5 Kjetil André Aamodt Male   White Skier      Norway 

Объяснение

  1. Определение структуры сайта : просматривая исходный код сайта профиля, вы видите, что вся соответствующая информация, кроме названия, помечена жирным шрифтом (т. Е. <b> теги), иногда также имеетсятег ссылки (<a>).
  2. Селектор построения : С помощью этой информации теперь мы можем построить либо css, либо XPath селектор.Однако, поскольку мы хотим выбрать текстовые узлы , XPath представляется единственной (?) Опцией: //b[contains(text(), "Gender:")]/following::text()[normalize-space()!=' '][1] выбирает
    • первый непустой текстовый узел ::text()[normalize-space()!=' '][1], который
    • родной (/following) из
    • тег <b> (//b), который
    • содержит текст Gender: ([contains(text(), "Gender:")])
  3. множественный выбор : поскольку все теги построены одинаковым образом, мы можем создать Xpath, который соответствует более чем одному элементу, избегая явных циклов. Это мы делаем путем вставкинесколько contains(.) операторов вместе разделены or
  4. Дальнейшее форматирование : Наконец, мы удаляем пробелы и возвращаем его в tibble
  5. Поле имени : последний шаг - извлечь имя, которое, по сути, является первым жирным (<b>) текстом
2 голосов
/ 11 апреля 2019

Здесь у вас есть способ получить данные, просматривая каждый из ваших HTML-файлов. Это просто подход, который дает хорошие результаты ... но ... вы должны заметить, что эти функции gsub должны быть отредактированы, чтобы получить лучшие результаты. Это происходит потому, что этот список URL-адресов или, скажем, эта веб-страница не являются однородными в том, как отображаются данные. Это то, с чем вам приходится иметь дело. Например, вот только два скриншота, где вы можете найти эти различия в веб-презентации:

enter image description here

enter image description here

В любом случае, вы можете управлять этим, адаптируя этот код:

library(purrr)
library(rvest)

[...] #here is your data

all_ppl_urls[100] %>%
    read_html() %>%
    html_nodes("p") %>%
    html_text()
# [3] "Gender: MaleReligion: Eastern OrthodoxRace or Ethnicity: Middle EasternSexual orientation: StraightOccupation: PoliticianParty Affiliation: Republican"  

#-----------------------------------------------------------------------------------------------
# NEW WAY
toString(read_html(all_ppl_urls[100])) #get example of how html looks...
#><b>AKA</b> Edmund Spencer Abraham</p>\n<p><b>Born:</b> <a href=\"/lists/681/000106363/\" class=\"proflink\">12-Jun</a>-<a href=\"/lists/951/000105636/\" class=\"proflink\">1952</a><br><b>Birthplace:</b> <a href=\"/geo/604/000080364/\" class=\"proflink\">East Lansing, MI</a><br></p>\n<p><b>Gender:</b> Male<br><b>

#1. remove NA urls (avoid problems later on)
urls <- all_ppl_urls[!is.na(all_ppl_urls)]
length(all_ppl_urls)
length(urls)

#function that creates a list with your data
GetLife <- function (htmlurl) {
    htmltext <- toString(read_html(htmlurl))
    name <- gsub('^.*AKA</b>\\s*|\\s*</p>\n.*$', '', htmltext)
    gender <- gsub('^.*Gender:</b>\\s*|\\s*<br>.*$', '', htmltext)
    race <- gsub('^.*Race or Ethnicity:</b>\\s*|\\s*<br>.*$', '', htmltext)
    occupation <- gsub('^.*Occupation:</b>\\s*|\\s*<br>.*$|\\s*</a>.*$|\\s*</p>.*$', '', htmltext)
    #as occupation seems to have to many hyperlinks we are making another step
    occupation <- gsub("<[^>]+>", "",occupation)
    nationality <- gsub('^.*Nationality:</b>\\s*|\\s*<br>.*$', '', htmltext)
    res <- c(ifelse(nchar(name)>100, NA, name), #function that cleans weird results >100 chars
             ifelse(nchar(gender)>100, NA, gender),
             ifelse(nchar(race)>100, NA, race),
             ifelse(nchar(occupation)>100, NA, occupation),
             ifelse(nchar(nationality)>100, NA, nationality),
             htmlurl)
    return(res)
}

emptydf <- data.frame(matrix(ncol=6, nrow=0)) #creaty empty data frame
colnames(emptydf) <- c("name","gender","race","occupation","nationality","url") #set names in empty data frame
urls <- urls[2020:2030] #sample some of the urls
for (i in 1:length(urls)){
    emptydf[i,] <- GetLife(urls[i])
}
emptydf

Вот пример этих 10 проанализированных URL:

name gender     race occupation   nationality                                       url
1                        <NA>   Male    White   Business United States http://www.nndb.com/people/214/000128827/
2  Mark Alexander Ballas, Jr.   Male    White     Dancer United States http://www.nndb.com/people/162/000346121/
3       Thomas Cass Ballenger   Male    White Politician United States http://www.nndb.com/people/354/000032258/
4  Severiano Ballesteros Sota   Male Hispanic       Golf         Spain http://www.nndb.com/people/778/000116430/
5  Richard Achilles Ballinger   Male    White Government United States http://www.nndb.com/people/511/000168007/
6      Steven Anthony Ballmer   Male    White   Business United States http://www.nndb.com/people/644/000022578/
7        Edward Michael Balls   Male    White Politician       England http://www.nndb.com/people/846/000141423/
8                        <NA>   Male    White      Judge United States http://www.nndb.com/people/533/000168029/
9                        <NA>   Male    Asian   Engineer       England http://www.nndb.com/people/100/000123728/
10         Michael A. Balmuth   Male    White   Business United States http://www.nndb.com/people/635/000175110/
11        Aristotle N. Balogh   Male    White   Business United States http://www.nndb.com/people/311/000172792/
...