4 Webscraping with R

Nowadays, we manage a very huge part of our life online. This has an important side-effect: we can collect enormous data from the web for our researches. You can access data about shops, blogs, social media etc. The target of this chapter is to give a brief introduction how you can collect this data effectively. We will need a new package for this purpose: rvest

We will scrape the data from hasznaltauto, which is the online second hand car market of Hungary. Lets navigate to the page in our browser and lets click on search.

www.hasznaltauto.hu/

Figure 4.1: www.hasznaltauto.hu/

Now we have to copy and paste the new url from the browser to Rstudio. This will be the first link we want to visit while scraping. Lets assign this url as url in R.

url <- "https://www.hasznaltauto.hu/talalatilista/PCOG2VGRR3RDAD [...]" # long url
Click on the search button.

Figure 4.2: Click on the search button.

The next step is load the website into your R session. This can be done by the read_html function from the rvest package.

page <- read_html(url)

page
#> {html_document}
#> <html lang="hu-HU">
#> [1] <head>\n<meta http-equiv="Content-Type" content="text/html; charset=UTF-8 ...
#> [2] <body>\n    <script type="text/javascript">var utag_data = {"website":"ha ...

4.3 Data from tables

A random example of car ad.

Figure 4.6: A random example of car ad.

You can see that the data is tabulated on the page. This is the best option for us, as you don’t have to search for the item ID on the page one by one (as before with the ad title). You can simply apply the html_table function to a loaded page, which collects all the tables on the it.

car_url <- "https://www.hasznaltauto.hu/szemelyauto/renault/zoe/renault_zoe_ze_q90_41_kwh_intens_aut_kmgari-szkonyv-17447302"

info_tables <- read_html(car_url) %>% 
  html_table(fill = TRUE)

info_tables
#> [[1]]
#> # A tibble: 31 x 2
#>    `Ár, költségek`                        `Ár, költségek`                       
#>    <chr>                                  <chr>                                 
#>  1 "Vételár:"                             "5 199 000 Ft"                        
#>  2 "Vételár EUR:"                         "€ 14 265"                            
#>  3 "Finanszírozás kalkulátor            ~ "Finanszírozás kalkulátor            ~
#>  4 "Általános adatok"                     "Általános adatok"                    
#>  5 "Évjárat:"                             "2018/4"                              
#>  6 "Állapot:"                             "Megkímélt"                           
#>  7 "Kivitel:"                             "Ferdehátú"                           
#>  8 "Alvázszám:"                           "Elérhető autó-előélet alvázszám alap~
#>  9 "Ellenőrzöm"                           "Ellenőrzöm"                          
#> 10 "Finanszírozás"                        "Finanszírozás"                       
#> # ... with 21 more rows

What is the type of info_tables? Since it collects all the tables it can find from the page, it is a list of data frames.

I will reveal that in some cases we will see that there are multiple tables on a page (certain types of info are taken separately) and we want to avoid an irrelevant single column table causing an error (step 1).

Our goal is to be able to gather all the data about the car into a single two-column table. To join all two column tables (step 3), they must also have the same name (step 2).

info_tables %>% 
  keep(~ ncol(.) == 2) %>%  # keep tables that have 2 columns
  map(set_names, "x", "y") %>%  # set the names to x and y for each table
  bind_rows() # join the tables to one sinle table
#> # A tibble: 31 x 2
#>    x                                      y                                     
#>    <chr>                                  <chr>                                 
#>  1 "Vételár:"                             "5 199 000 Ft"                        
#>  2 "Vételár EUR:"                         "€ 14 265"                            
#>  3 "Finanszírozás kalkulátor            ~ "Finanszírozás kalkulátor            ~
#>  4 "Általános adatok"                     "Általános adatok"                    
#>  5 "Évjárat:"                             "2018/4"                              
#>  6 "Állapot:"                             "Megkímélt"                           
#>  7 "Kivitel:"                             "Ferdehátú"                           
#>  8 "Alvázszám:"                           "Elérhető autó-előélet alvázszám alap~
#>  9 "Ellenőrzöm"                           "Ellenőrzöm"                          
#> 10 "Finanszírozás"                        "Finanszírozás"                       
#> # ... with 21 more rows

We see that this works, the output now is one single table. Lets use this method on all the links. To do this, we need to write a function.

get_data <- function(url_to_car) {
  url_to_car %>% 
    read_html() %>% 
    html_table(fill = TRUE) %>% 
    keep(~ ncol(.) == 2) %>% 
    map(~ set_names(., "x", "y")) %>% 
    bind_rows()
}
get_data(car_url)
#> # A tibble: 31 x 2
#>    x                                      y                                     
#>    <chr>                                  <chr>                                 
#>  1 "Vételár:"                             "5 199 000 Ft"                        
#>  2 "Vételár EUR:"                         "€ 14 265"                            
#>  3 "Finanszírozás kalkulátor            ~ "Finanszírozás kalkulátor            ~
#>  4 "Általános adatok"                     "Általános adatok"                    
#>  5 "Évjárat:"                             "2018/4"                              
#>  6 "Állapot:"                             "Megkímélt"                           
#>  7 "Kivitel:"                             "Ferdehátú"                           
#>  8 "Alvázszám:"                           "Elérhető autó-előélet alvázszám alap~
#>  9 "Ellenőrzöm"                           "Ellenőrzöm"                          
#> 10 "Finanszírozás"                        "Finanszírozás"                       
#> # ... with 21 more rows

The resulting table can all be collected in a single column of our table. Each cell will be a table that we all collected from a given link.

This can also take a serious amount of time if we want to retrieve data from hundreds of cars at once. You should always use only a few. Use the sample_n function to randomly select a few lines to test if everything works as expected.

cars_add_df %>% 
  sample_n(size = 3) %>% # remove this line at the end if everything is fine
  mutate(
    data = map(url_to_cars, get_data)
  )
#> # A tibble: 3 x 3
#>   url_to_cars                         ad_title                         data     
#>   <chr>                               <chr>                            <list>   
#> 1 https://www.hasznaltauto.hu/szemel~ OPEL MOKKA 1.2 T Edition CÉGEKN~ <tibble ~
#> 2 https://www.hasznaltauto.hu/szemel~ MERCEDES-BENZ E 200 d 9G-TRONIC~ <tibble ~
#> 3 https://www.hasznaltauto.hu/szemel~ MERCEDES-BENZ GLC-OSZTÁLY GLC 4~ <tibble ~

Currently, our small table is also nested in a cell. Expand it!

cars_add_df %>% 
  sample_n(size = 3) %>% # remove this line at the end if everything is fine
  mutate(
    data = map(url_to_cars, get_data)
  ) %>% 
  unnest()
#> # A tibble: 107 x 4
#>    url_to_cars                  ad_title                  x        y            
#>    <chr>                        <chr>                     <chr>    <chr>        
#>  1 https://www.hasznaltauto.hu~ MERCEDES-AMG E 43 4Matic~ Vételár: 14 290 000 Ft
#>  2 https://www.hasznaltauto.hu~ MERCEDES-AMG E 43 4Matic~ Vételár~ € 39 209     
#>  3 https://www.hasznaltauto.hu~ MERCEDES-AMG E 43 4Matic~ Általán~ Általános ad~
#>  4 https://www.hasznaltauto.hu~ MERCEDES-AMG E 43 4Matic~ Évjárat: 2017/6       
#>  5 https://www.hasznaltauto.hu~ MERCEDES-AMG E 43 4Matic~ Állapot: Kitűnő       
#>  6 https://www.hasznaltauto.hu~ MERCEDES-AMG E 43 4Matic~ Kivitel: Sedan        
#>  7 https://www.hasznaltauto.hu~ MERCEDES-AMG E 43 4Matic~ Alvázsz~ Elérhető aut~
#>  8 https://www.hasznaltauto.hu~ MERCEDES-AMG E 43 4Matic~ Ellenőr~ Ellenőrzöm   
#>  9 https://www.hasznaltauto.hu~ MERCEDES-AMG E 43 4Matic~ Jármű a~ Jármű adatok 
#> 10 https://www.hasznaltauto.hu~ MERCEDES-AMG E 43 4Matic~ Kilomét~ 112 651 km   
#> # ... with 97 more rows

Now that we have a lot of rows instead of 3, the url in the first row is repeated as many times as the number of rows in the table next to it so far.

Since the x column now has the variable name and the y column has the value of the variable, we need another super useful function we’ve seen before: pivot_wider!

cars_add_df %>% 
  sample_n(size = 3) %>% # remove this line at the end if everything is fine
  mutate(
    data = map(url_to_cars, get_data)
  ) %>% 
  unnest() %>% 
  pivot_wider(names_from = "x", values_from = "y")
#> # A tibble: 3 x 42
#>   url_to_cars   ad_title   `Vételár:` `Vételár EUR:` `Általános adat~ `Évjárat:`
#>   <chr>         <chr>      <chr>      <chr>          <chr>            <chr>     
#> 1 https://www.~ MERCEDES-~ 14 290 00~ € 39 209       Általános adatok 2017/6    
#> 2 https://www.~ BMW 530e ~ 19 700 00~ € 54 053       Általános adatok 2021/2    
#> 3 https://www.~ VOLVO XC6~ 20 490 00~ € 56 220       Általános adatok 2020/7    
#> # ... with 36 more variables: Állapot: <chr>, Kivitel: <chr>, Alvázszám: <chr>,
#> #   Ellenőrzöm <chr>, Jármű adatok <chr>, Kilométeróra állása: <chr>,
#> #   Szállítható szem. száma: <chr>, Ajtók száma: <chr>, Szín: <chr>,
#> #   Saját tömeg: <chr>, Teljes tömeg: <chr>, Csomagtartó: <chr>,
#> #   Klíma fajtája: <chr>, Tető: <chr>, Motor adatok <chr>, Üzemanyag: <chr>,
#> #   Hengerűrtartalom: <chr>, Teljesítmény: <chr>, Henger-elrendezés: <chr>,
#> #   Hajtás: <chr>, Sebességváltó fajtája: <chr>, Okmányok <chr>, ...

The last thing that causes this headache for this task is the special characters used in the Hungarian language. The janitor, on the other hand, handles this smoothly as well.

We can now download all the cars if we wish!

cars_data_df <- cars_add_df %>% 
  mutate(
    data = map(url_to_cars, get_data)
  ) %>% 
  unnest() %>% 
  pivot_wider(names_from = "x", values_from = "y") %>% 
  janitor::clean_names()
cars_data_df
#> # A tibble: 239 x 63
#>    url_to_cars  ad_title   vetelar vetelar_eur finanszirozas_k~ altalanos_adatok
#>    <chr>        <chr>      <chr>   <chr>       <chr>            <chr>           
#>  1 https://www~ SSANGYONG~ 6 999 ~ € 19 204    "Finanszírozás ~ Általános adatok
#>  2 https://www~ CITROEN G~ 7 780 ~ € 21 347    "Finanszírozás ~ Általános adatok
#>  3 https://www~ SUZUKI SW~ <NA>    € 13 283     <NA>            Általános adatok
#>  4 https://www~ PEUGEOT 2~ <NA>    € 15 873    "Finanszírozás ~ Általános adatok
#>  5 https://www~ OPEL COMB~ <NA>    € 11 483     <NA>            Általános adatok
#>  6 https://www~ OPEL INSI~ <NA>    € 18 271     <NA>            Általános adatok
#>  7 https://www~ OPEL ZAFI~ <NA>    € 22 879     <NA>            Általános adatok
#>  8 https://www~ SUZUKI VI~ <NA>    € 16 271     <NA>            Általános adatok
#>  9 https://www~ SUZUKI IG~ <NA>    € 13 307     <NA>            Általános adatok
#> 10 https://www~ CITROEN C~ 8 600 ~ € 23 597    "Finanszírozás ~ Általános adatok
#> # ... with 229 more rows, and 57 more variables: atveheto <chr>, evjarat <chr>,
#> #   allapot <chr>, kivitel <chr>, finanszirozas <chr>, finanszirozas_2 <chr>,
#> #   finanszirozas_tipusa_casco_val <chr>,
#> #   finanszirozas_tipusa_casco_nelkul <chr>, kezdoreszlet_casco_nelkul <chr>,
#> #   havi_reszlet_casco_nelkul <chr>, futamido_casco_nelkul <chr>,
#> #   garancia <chr>, garancia_2 <chr>, atrozsdasodasi_garancia <chr>,
#> #   szavatossagi_garancia <chr>, jarmu_adatok <chr>, ...