Как я могу очистить CGI-Bin с Rvest и R?

Я пытаюсь использовать rvest для очистки результатов веб-формы, которая появляется в cgi-bin. Однако когда я запускаю скрипт, я получаю 0 результатов в пределах 200 миль в результате. Ниже мой код, я ценю любые отзывы и помощь. Основной веб-сайт http://www.zmax.com/ котором есть окно поиска, запускающее cgi-bin.

library(rvest); 
library(purrr) ;
library(plyr) ;
library(dplyr) ;

x<-read_html('http://www.nearestoutlet.com/cgi-bin/smi/findsmi.pl') 

y<-x%>% html_node('table')%>% html_table(fill=true)

также я попробовал

y<-x%>% 
html_node('td div td, p')
%>% html_text()

Я не уверен, в чем я ошибаюсь, возвращая данные, которые есть в форме.

1 ответ

Решение

Как ни странно, ни основной сайт, ни провайдер, которого они используют для поиска розеток, не предотвращают очистку T&C или REP. ¯\_(ツ)_/¯

Вы должны действительно ознакомиться с инструментами разработчика браузера, так как вы могли бы видеть, что основной сайт делает HTTP POST запрос на поисковый сайт против GET запрос браузеры нормально делают и что read_html() делает. Вот что вам нужно сделать, чтобы получить успешные запросы (мы выберем почтовый индекс рядом с вами):

library(httr)
library(rvest)

POST(
  url = "http://www.nearestoutlet.com/cgi-bin/smi/findsmi.pl", 
  body = list(zipcode = "48127"), 
  encode = "form"
) -> res

res является httrresponse объект и обычно делают просто:

content(res, as="parsed")

чтобы подготовить разобранный объект к анализу XML/HTML. Но есть странные проблемы с кодированием (по крайней мере для меня) на этом сайте, которые заставляют нас делать следующее:

content(res, as="raw") %>% read_html() -> pg

Вам следует cat(as.character(pg)) чтобы увидеть, насколько уродлив HTML. Это вложенные таблицы, но не в хорошем смысле. Записи, которые вы видите, есть все <tr> элементы без <table> брейки. К счастью? есть только единственное число <td> элементы в каждом из этих <tr> элементы. Таким образом, мы можем схватить их всех одним махом, выбрав правильный <table>:

rows <- html_nodes(pg, "table[width='300'] > tr > td")
rows
## {xml_nodeset (60)}
##  [1] <td width="300" height="19" bgcolor="#8B0101"><p align="left"><font face="Tahoma" color="#FFFFFF" style="font-size: 11px"><b>O\u0092REILLY AUTO PARTS</b></fo ...
##  [2] <td width="300" height="2"><font face="Tahoma" style="font-size: 11px">6938 NORTH TELEGRAPH ROAD</font></td>
##  [3] <td width="300" height="2"><font face="Tahoma" style="font-size: 11px">Dearborn Heights, MI  48127</font></td>
##  [4] <td width="300" height="2"><font face="Tahoma" style="font-size: 11px">(313) 792-9134</font></td>
##  [5] <td width="300" height="2"><font face="Tahoma" style="font-size: 11px"><a href="#" onclick="window.open('http://maps.google.com/maps?q=6938+NORTH+TELEGRAPH+R ...
##  [6] <td width="300" height="6"></td>
##  [7] <td width="300" height="19" bgcolor="#8B0101"><p align="left"><font face="Tahoma" color="#FFFFFF" style="font-size: 11px"><b>Advance Auto Parts</b></font></p ...
##  [8] <td width="300" height="2"><font face="Tahoma" style="font-size: 11px">8120 North Telegraph Road</font></td>
##  [9] <td width="300" height="2"><font face="Tahoma" style="font-size: 11px">Dearborn Heights, MI  48127</font></td>
## [10] <td width="300" height="2"><font face="Tahoma" style="font-size: 11px">(313) 528-4920</font></td>
## [11] <td width="300" height="2"><font face="Tahoma" style="font-size: 11px"><a href="#" onclick="window.open('http://maps.google.com/maps?q=8120+North+Telegraph+R ...
## [12] <td width="300" height="6"></td>
## [13] <td width="300" height="19" bgcolor="#8B0101"><p align="left"><font face="Tahoma" color="#FFFFFF" style="font-size: 11px"><b>Pep Boys</b></font></p></td>
## [14] <td width="300" height="2"><font face="Tahoma" style="font-size: 11px">8955 TELEGRAPH RD</font></td>
## [15] <td width="300" height="2"><font face="Tahoma" style="font-size: 11px">Redford, MI  48239</font></td>
## [16] <td width="300" height="2"><font face="Tahoma" style="font-size: 11px">(313) 532-5750</font></td>
## [17] <td width="300" height="2"><font face="Tahoma" style="font-size: 11px"><a href="#" onclick="window.open('http://maps.google.com/maps?q=8955+TELEGRAPH+RD+Redf ...
## [18] <td width="300" height="6"></td>
## [19] <td width="300" height="19" bgcolor="#8B0101"><p align="left"><font face="Tahoma" color="#FFFFFF" style="font-size: 11px"><b>O\u0092REILLY AUTO PARTS</b></fo ...
## [20] <td width="300" height="2"><font face="Tahoma" style="font-size: 11px">27207 PLYMOUTH ROAD</font></td>
## ...

Есть много подходов, которые можно использовать, чтобы создать фрейм данных из этого беспорядка. Один простой включает использование того факта, что названия магазинов имеют заданный цвет фона, а другие нет. Это делает код немного хрупким, но мы можем помочь сделать его менее хрупким, просто проверив наличие цвета фона. Зачем нам вообще нужно это делать? Что ж, нам нужно отметить начало и конец записей, и один из простых способов сделать это - использовать тот факт, что мы можем cumsum() логический вектор, зная, что это FALSE == 0. Почему это важно? Мы можем создать столбец неявной группировки следующим образом:

data_frame(
  record = !is.na(html_attr(rows, "bgcolor")),
  text = html_text(rows, trim=TRUE)
) %>% 
  mutate(record = cumsum(record)) -> xdf
#3 # A tibble: 60 x 2
#3    record                        text
#3     <int>                       <chr>
#3  1      1  "O\u0092REILLY AUTO PARTS"
#3  2      1   6938 NORTH TELEGRAPH ROAD
#3  3      1 Dearborn Heights, MI  48127
#3  4      1              (313) 792-9134
#3  5      1                0 miles away
#3  6      1                            
#3  7      2          Advance Auto Parts
#3  8      2   8120 North Telegraph Road
#3  9      2 Dearborn Heights, MI  48127
#3 10      2              (313) 528-4920
#3 # ... with 50 more rows

Теперь нам нужно удалить пустые строки с filter() и сделать некоторые манипулирования, чтобы получить данные в достойной форме для создания фрейма данных. Это очень хрупкий код, поскольку этот конкретный фрагмент может обрабатывать недостающие данные телефонного номера, но это все. Если есть вторая адресная строка, вам нужно изменить этот подход или использовать другой подход:

filter(xdf, text != "") %>% 
  group_by(record) %>% 
  summarise(x = paste0(text, collapse="|")) %>% 
  separate(x, c("store", "address1", "city_state_zip", "phone_and_or_distance"), sep="\\|", extra="merge")
## # A tibble: 10 x 5
##    record                      store                  address1              city_state_zip       phone_and_or_distance
##  *  <int>                      <chr>                     <chr>                       <chr>                       <chr>
##  1      1 "O\u0092REILLY AUTO PARTS" 6938 NORTH TELEGRAPH ROAD Dearborn Heights, MI  48127 (313) 792-9134|0 miles away
##  2      2         Advance Auto Parts 8120 North Telegraph Road Dearborn Heights, MI  48127 (313) 528-4920|0 miles away
##  3      3                   Pep Boys         8955 TELEGRAPH RD          Redford, MI  48239 (313) 532-5750|2 miles away
##  4      4 "O\u0092REILLY AUTO PARTS"       27207 PLYMOUTH ROAD          Redford, MI  48239 (313) 937-1787|2 miles away
##  5      5 "O\u0092REILLY AUTO PARTS"      14975 TELEGRAPH ROAD          Redford, MI  48239 (313) 538-3584|2 miles away
##  6      6                   AutoZone           24250 FIVE MILE          Redford, MI  48239 (313) 527-6877|2 miles away
##  7      7 "O\u0092REILLY AUTO PARTS"        5940 MIDDLEBELT RD      Garden City, MI  48135 (734) 525-1607|3 miles away
##  8      8                   AutoZone        6228 MIDDLEBELT RD      Garden City, MI  48135 (734) 513-2233|3 miles away
##  9      9         Advance Auto Parts       3845 S Telegraph Rd         Dearborn, MI  48124 (313) 274-6549|3 miles away
## 10     10 "O\u0092REILLY AUTO PARTS"     27565 MICHIGAN AVENUE          Inkster, MI  48141 (313) 724-8544|3 miles away 

На случай, если процесс был неочевиден, мы:

  • сгруппировать строки по нашему недавно созданному record колонка
  • смять весь текст в одну строку, каждая часть разделена |"s
  • выделить все отдельные биты

Надеюсь, это поможет объяснить хрупкость.

Конечно, вы хотели только часть "как добраться до контента", но, надеюсь, это сэкономило вам еще немного времени.

Другие вопросы по тегам