── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.1.3 ✔ readr 2.1.4
✔ forcats 1.0.0 ✔ stringr 1.5.0
✔ ggplot2 3.4.3 ✔ tibble 3.2.1
✔ lubridate 1.9.3 ✔ tidyr 1.3.0
✔ purrr 1.0.2
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(rvest)
Attaching package: 'rvest'
The following object is masked from 'package:readr':
guess_encoding
#Specifying the url for desired website to be scrapedurl <-'https://www.imdb.com/search/title/?count=100&release_date=2016,2016&title_type=feature'
#Reading the HTML code from the websitewebpage <-read_html(url)
Step 1: Now, we will start by scraping the Rank field. For that, we’ll use the selector gadget to get the specific CSS selectors that encloses the rankings. You can click on the extension in your browser and select the rankings field with the cursor. Make sure that all the rankings are selected. You can select some more ranking sections in case you are not able to get all of them and you can also de-select them by clicking on the selected section to make sure that you only have those sections highlighted that you want to scrape for that go.
Step 2: Once you are sure that you have made the right selections, you need to copy the corresponding CSS selector that you can view in the bottom center.
Step 3: Once you know the CSS selector that contains the rankings, you can use this simple R code to get all the rankings:
#Using CSS selectors to scrape the rankings sectionrank_data <-html_nodes (webpage,'.text-primary')#Converting the ranking data to textrank_data <-html_text (rank_data)#Let's have a look at the rankingshead(rank_data)
[1] "1." "2." "3." "4." "5." "6."
length(rank_data)
[1] 100
Step 4: Once you have the data, make sure that it looks in the desired format. I am preprocessing my data to convert it to numerical format.
#Data-Preprocessing: Converting rankings to numericalrank_data<-as.numeric(rank_data)#Let's have another look at the rankingshead(rank_data)
[1] 1 2 3 4 5 6
Step 5: Now you can clear the selector section and select all the titles. You can visually inspect that all the titles are selected. Make any required additions and deletions with the help of your curser. I have done the same here.
Step 6: Again, I have the corresponding CSS selector for the titles – .lister-item-header a. I will use this selector to scrape all the titles using the following code.
#Using CSS selectors to scrape the title sectiontitle_data_html <-html_nodes(webpage,'.lister-item-header a')#Converting the title data to texttitle_data <-html_text(title_data_html)#Let's have a look at the titlehead(title_data)
Step 7: In the following code, I have done the same thing for scraping – Description, Runtime, Genre, Rating, Metascore, Votes, Gross_Earning_in_Mil , Director and Actor data.
#Using CSS selectors to scrape the description sectiondescription_data_html <-html_nodes(webpage,'.ratings-bar+ .text-muted')#Converting the description data to textdescription_data <-html_text(description_data_html)#Let's have a look at the description datahead(description_data)
[1] "\nOn Halloween night, Tara Heyes finds herself as the obsession of a sadistic murderer known as Art the Clown."
[2] "\nA secret government agency recruits some of the most dangerous incarcerated super-villains to form a defensive task force. Their first mission: save the world from the apocalypse."
[3] "\nIn the 17th century, two Portuguese Jesuit priests travel to Japan in an attempt to locate their mentor, who is rumored to have committed apostasy, and to propagate Catholicism."
[4] "\nA deaf and mute writer who retreated into the woods to live a solitary life must fight for her life in silence when a masked killer appears at her window."
[5] "\nEd and Lorraine Warren travel to North London to help a single mother raising four children alone in a house plagued by a supernatural spirit."
[6] "\nThree girls are kidnapped by a man with a diagnosed 23 distinct personalities. They must try to escape before the apparent emergence of a frightful new 24th."
#Data-Preprocessing: removing '\n'description_data<-gsub("\n","",description_data)#Let's have another look at the description data head(description_data)
[1] "On Halloween night, Tara Heyes finds herself as the obsession of a sadistic murderer known as Art the Clown."
[2] "A secret government agency recruits some of the most dangerous incarcerated super-villains to form a defensive task force. Their first mission: save the world from the apocalypse."
[3] "In the 17th century, two Portuguese Jesuit priests travel to Japan in an attempt to locate their mentor, who is rumored to have committed apostasy, and to propagate Catholicism."
[4] "A deaf and mute writer who retreated into the woods to live a solitary life must fight for her life in silence when a masked killer appears at her window."
[5] "Ed and Lorraine Warren travel to North London to help a single mother raising four children alone in a house plagued by a supernatural spirit."
[6] "Three girls are kidnapped by a man with a diagnosed 23 distinct personalities. They must try to escape before the apparent emergence of a frightful new 24th."
length(description_data)
[1] 100
#Using CSS selectors to scrape the Movie runtime sectionruntime_data_html <-html_nodes(webpage,'.text-muted .runtime')#Converting the runtime data to textruntime_data <-html_text(runtime_data_html)#Let's have a look at the runtimehead(runtime_data)
#Data-Preprocessing: removing mins and converting it to numericalruntime_data<-gsub(" min","",runtime_data)runtime_data<-as.numeric(runtime_data)#Let's have another look at the runtime datahead(runtime_data)
[1] 85 123 161 82 134 117
#Using CSS selectors to scrape the Movie genre sectiongenre_data_html <-html_nodes(webpage,'.genre')#Converting the genre data to textgenre_data <-html_text(genre_data_html)#Let's have a look at the genrehead(genre_data)
#Data-Preprocessing: removing \ngenre_data<-gsub("\n","",genre_data)#Data-Preprocessing: removing excess spacesgenre_data<-gsub(" ","",genre_data)#taking only the first genre of each moviegenre_data<-gsub(",.*","",genre_data)#Convering each genre from text to factorgenre_data<-as.factor(genre_data)#Let's have another look at the genre datahead(genre_data)
[1] Horror Action Drama Horror Horror Horror
9 Levels: Action Adventure Animation Biography Comedy Crime Drama ... Horror
length(genre_data)
[1] 100
#Using CSS selectors to scrape the IMDB rating sectionrating_data_html <-html_nodes(webpage,'.ratings-imdb-rating strong')#Converting the ratings data to textrating_data <-html_text(rating_data_html)#Let's have a look at the ratingshead(rating_data)
[1] "5.6" "5.9" "7.2" "6.6" "7.3" "7.3"
length(rating_data)
[1] 100
#Data-Preprocessing: converting ratings to numericalrating_data<-as.numeric(rating_data)#Let's have another look at the ratings datahead(rating_data)
[1] 5.6 5.9 7.2 6.6 7.3 7.3
#Using CSS selectors to scrape the votes sectionvotes_data_html <-html_nodes(webpage,'.sort-num_votes-visible span:nth-child(2)')#Converting the votes data to textvotes_data <-html_text(votes_data_html)#Let's have a look at the votes datahead(votes_data)
#Data-Preprocessing: removing commasvotes_data<-gsub(",","",votes_data)#Data-Preprocessing: converting votes to numericalvotes_data<-as.numeric(votes_data)#Let's have another look at the votes datahead(votes_data)
[1] 47757 710243 119470 149286 292337 532941
length(votes_data)
[1] 100
#Using CSS selectors to scrape the directors sectiondirectors_data <-html_nodes(webpage,'.text-muted+ p a:nth-child(1)')#Converting the directors data to textdirectors_data <-html_text(directors_data)#Let's have a look at the directors datahead(directors_data)
#Data-Preprocessing: converting directors data into factorsdirectors_data<-as.factor(directors_data)head(directors_data)
[1] Damien Leone David Ayer Martin Scorsese Mike Flanagan
[5] James Wan M. Night Shyamalan
97 Levels: Alessandro Carloni Alex Proyas Ana Lily Amirpour ... Zack Snyder
#Using CSS selectors to scrape the actors sectionactors_data_html <-html_nodes(webpage,'.lister-item-content .ghost+ a')#Converting the gross actors data to textactors_data <-html_text(actors_data_html)#Let's have a look at the actors datahead(actors_data)
#Data-Preprocessing: converting actors data into factorsactors_data<-as.factor(actors_data)head(actors_data)
[1] Jenna Kanell Will Smith Andrew Garfield John Gallagher Jr.
[5] Vera Farmiga James McAvoy
89 Levels: Aaron Poole Alexander Skarsgård Amy Adams ... Will Smith
But, I want you to closely follow what happens when I do the same thing for Metascore data.
#Using CSS selectors to scrape the metascore sectionmetascore_data_html <-html_nodes(webpage,'.metascore')#Converting the runtime data to textmetascore_data <-html_text(metascore_data_html)#Let's have a look at the metascore head(metascore_data)
[1] "40 " "79 " "67 " "65 " "63 "
[6] "71 "
#Data-Preprocessing: removing extra space in metascoremetascore_data<-gsub(" ","",metascore_data)head(metascore_data)
[1] "40" "79" "67" "65" "63" "71"
#Lets check the length of metascore datalength(metascore_data)
[1] 95
Step 8: The length of the metascore data is 95 while we are scraping the data for 100 movies. The reason this happened is that there are 5 movies that don’t have the corresponding Metascore fields.
Step 9: It is a practical situation which can arise while scraping any website. Unfortunately, if we simply add NA’s to last 5 entries, it will map NA as Metascore for movies 96 to 100 while in reality, the data is missing for some other movies. After a visual inspection, I found that the Metascore is missing for movies 1, 40, 44, 65 and 97. I have written the following function to get around this problem.
#Data-Preprocessing: converting metascore to numericalmetascore_data<-as.numeric(metascore_data_with_na)#Let's have another look at length of the metascore datalength(metascore_data)
[1] 100
#Let's look at summary statisticssummary(metascore_data)
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
21.00 47.00 62.00 60.36 72.50 99.00 5
Step 10: The same thing happens with the Gross variable which represents gross earnings of that movie in millions. I have use the same solution to work my way around:
#Using CSS selectors to scrape the gross revenue sectiongross_data_html <-html_nodes(webpage,'.ghost~ .text-muted+ span')#Converting the gross revenue data to textgross_data <-html_text(gross_data_html)#Let's have a look at the votes datahead(gross_data)
#Data-Preprocessing: removing '$' and 'M' signsgross_data<-gsub("M","",gross_data)gross_data<-substring(gross_data,2,6)#Let's check the length of gross datalength(gross_data)
#Data-Preprocessing: converting gross to numericalgross_data<-as.numeric(gross_data_with_na)#Let's have another look at the length of gross datalength(gross_data)
[1] 100
summary(gross_data)
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
0.01 15.50 55.87 93.09 122.05 532.10 10
Step 11: Now we have successfully scraped all the 11 features for the 100 most popular feature films released in 2016. Let’s combine them to create a dataframe and inspect its structure.
#Combining all the lists to form a data framemovies_df<-data.frame(Rank = rank_data, Title = title_data,Description = description_data, Runtime = runtime_data,Genre = genre_data, Rating = rating_data,Metascore = metascore_data, Votes = votes_data, Gross_Earning_in_Mil = gross_data,Director = directors_data, Actor = actors_data)#Structure of the data framestr(movies_df)
'data.frame': 100 obs. of 11 variables:
$ Rank : num 1 2 3 4 5 6 7 8 9 10 ...
$ Title : chr "Terrifier" "Suicide Squad" "Silence" "Hush" ...
$ Description : chr "On Halloween night, Tara Heyes finds herself as the obsession of a sadistic murderer known as Art the Clown." "A secret government agency recruits some of the most dangerous incarcerated super-villains to form a defensive "| __truncated__ "In the 17th century, two Portuguese Jesuit priests travel to Japan in an attempt to locate their mentor, who is"| __truncated__ "A deaf and mute writer who retreated into the woods to live a solitary life must fight for her life in silence "| __truncated__ ...
$ Runtime : num 85 123 161 82 134 117 139 145 128 108 ...
$ Genre : Factor w/ 9 levels "Action","Adventure",..: 9 1 7 9 9 9 4 7 5 3 ...
$ Rating : num 5.6 5.9 7.2 6.6 7.3 7.3 8.1 8.1 8 7.1 ...
$ Metascore : num NA 40 79 67 65 63 71 85 94 59 ...
$ Votes : num 47757 710243 119470 149286 292337 ...
$ Gross_Earning_in_Mil: num NA 325.1 7.1 NA 102.4 ...
$ Director : Factor w/ 97 levels "Alessandro Carloni",..: 19 22 61 65 44 59 63 71 18 34 ...
$ Actor : chr "39" "89" "4" "42" ...
Warning: `qplot()` was deprecated in ggplot2 3.4.0.
Question 1: Based on the above data, which movie from which Genre had the longest runtime?
# Find the maximum runtimemax_runtime <-max(movies_df$Runtime)# Subset the dataset to get the movie(s) with the longest runtimemovies_with_max_runtime <- movies_df[movies_df$Runtime == max_runtime, ]# Get the genre(s) of the movie(s) with the longest runtimelongest_runtime_genres <-unique(movies_with_max_runtime$Genre)# Print the resultcat("Movie(s) with the longest runtime:", movies_with_max_runtime$Movie, "\n")
Movie(s) with the longest runtime:
cat("Genre(s) of the movie(s) with the longest runtime:", longest_runtime_genres, "\n")
Genre(s) of the movie(s) with the longest runtime: 7
Genre of the movie with the longest runtime: Drama
Question 2: Based on the above data, in the Runtime of 130-160 mins, which genre has the highest votes?
# Filter the data for movies with runtime between 130 and 160 minsfiltered_movies <- movies_df[movies_df$Runtime >=130& movies_df$Runtime <=160, ]# Find the genre with the highest votes in the filtered datagenre_with_highest_votes <-with(filtered_movies, Genre[which.max(Votes)])# Print the resultcat("Genre with the highest votes in the runtime range of 130-160 mins:", genre_with_highest_votes, "\n")
Genre with the highest votes in the runtime range of 130-160 mins: 1
Genre with the highest votes in the runtime range of 130-160 mins: Action
Question 3: Based on the above data, across all genres which genre has the highest average gross earnings in runtime 100 to 120.
### Filter the data for movies with runtime between 100 and 120 minsfiltered_movies <- movies_df |>filter(Runtime >=100& Runtime <=120)# Calculate the average gross earnings for each genregenre_avg_gross_earnings <- filtered_movies |>group_by(Genre) |>summarize(Avg_Gross_Earnings =mean(Gross_Earning_in_Mil, na.rm =TRUE))# Find the genre with the highest average gross earningsgenre_with_highest_avg_earnings <- genre_avg_gross_earnings |>filter(Avg_Gross_Earnings ==max(Avg_Gross_Earnings)) |>pull(Genre)# Print the resultcat("Genre with the highest average gross earnings in the runtime range of 100-120 mins:", genre_with_highest_avg_earnings, "\n")
Genre with the highest average gross earnings in the runtime range of 100-120 mins: 3
Genre with the highest average gross earnings in the runtime range of 100-120 mins: Animation