library(tidyverse)  # Data wrangling
library(rvest)      # HTML scraping
library(httr2)      # HTTP requests with user-agent control
library(kableExtra) # Formatted tables

Overview

Following the same steps as the in-class SEO demo. Using read_html(), html_element() / html_elements(), html_text2(), and str_squish() this notebook scrapes the <title> tag, meta description, and H1/H2/H3 headings from two sites I chose: Target.com and IKEA.com.

I played with the colors a bit as well. Both red and blue as background/font combos were too difficult to read. I used the logo colors of Target and IKEA for the font and background of this report: red and blue for font and white for the background which made it more readable.
Code was produced using AI.

Step 1: Target Sites

sites <- tibble(
  source = c("Target", "IKEA"),
  url    = c("https://www.target.com", "https://www.ikea.com/us/en/")
)

Step 2: Scraping Function

# Scrapes title, meta description, and H1/H2/H3 headings from one URL
scrape_seo_elements <- function(url, source_name) {

  Sys.sleep(1.5)  # Polite delay between requests

  tryCatch({
    resp <- request(url) %>%
      req_headers(
        `User-Agent` = paste0(
          "Mozilla/5.0 (compatible; MSBA580-Research-Bot/1.0; ",
          "+https://github.com/utjimmyx)"
        )
      ) %>%
      req_timeout(15) %>%
      req_perform()

    page <- resp %>%
      resp_body_string() %>%
      read_html()

    title <- page %>%
      html_element("title") %>%
      html_text2() %>%
      str_squish()

    meta_desc <- page %>%
      html_element("meta[name='description']") %>%
      html_attr("content") %>%
      str_squish()

    h1 <- page %>% html_elements("h1") %>% html_text2() %>% str_squish()
    h2 <- page %>% html_elements("h2") %>% html_text2() %>% str_squish()
    h3 <- page %>% html_elements("h3") %>% html_text2() %>% str_squish()

    tibble(
      source        = source_name,
      url           = url,
      title         = if (length(title) == 0 || is.na(title)) NA_character_ else title,
      meta_description = if (length(meta_desc) == 0 || is.na(meta_desc)) NA_character_ else meta_desc,
      h1_count      = length(h1),
      h2_count      = length(h2),
      h3_count      = length(h3),
      h1_sample     = paste(head(h1, 3), collapse = " | "),
      h2_sample     = paste(head(h2, 5), collapse = " | "),
      h3_sample     = paste(head(h3, 5), collapse = " | ")
    )
  }, error = function(e) {
    # Reason is printed to the knitted output (not suppressed) so a failed
    # scrape is visible instead of silently turning into unexplained NAs.
    warning("Could not scrape ", source_name, ": ", conditionMessage(e), call. = FALSE)
    tibble(
      source = source_name, url = url, title = NA_character_,
      meta_description = NA_character_, h1_count = NA_integer_,
      h2_count = NA_integer_, h3_count = NA_integer_,
      h1_sample = NA_character_, h2_sample = NA_character_, h3_sample = NA_character_
    )
  })
}

Step 3: Run the Scrape

seo_results <- map2_dfr(sites$url, sites$source, ~ scrape_seo_elements(.x, .y))

Step 4: Title & Meta Description

seo_results %>%
  select(source, title, meta_description) %>%
  kbl(caption = "Title Tags and Meta Descriptions") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE)
Title Tags and Meta Descriptions
source title meta_description
Target Target : Expect More. Pay Less. Shop Target online and in-store for everything from groceries and essentials to clothing and electronics. Choose contactless pickup or delivery today.
IKEA Shop Affordable Home Furnishings & Home Goods - IKEA Find affordable furniture and home goods at IKEA! Discover furnishings and inspiration to create a better life at home. Shop online or in store!

Step 5: Heading Structure (H1/H2/H3)

seo_results %>%
  select(source, h1_count, h2_count, h3_count) %>%
  kbl(caption = "Heading Tag Counts") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Heading Tag Counts
source h1_count h2_count h3_count
Target 1 14 80
IKEA 1 17 32
seo_results %>%
  select(source, h1_sample, h2_sample, h3_sample) %>%
  kbl(caption = "Sample Heading Text") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE)
Sample Heading Text
source h1_sample h2_sample h3_sample
Target Homepage Endless summer style | Refresh your summer beauty routine | Discover 1000+ new eats & sips | Just in for summer | LoveShackFancy x Target Loading… | Loading… | Loading… | Loading… | Loading…
IKEA Welcome to IKEA USA College starts soon – save now for great sleep all semester | College must-haves at must-have prices | Up to 20% off college essentials in-store only thru 7/21 | Delicious deals on your favorite foods | Today’s best deals Sign in to IKEA Family or IKEA Business Network to save | 50% off entrées for college students | 50% off meatballs every Thursday | Kids eat free on Wednesdays | Last chance

Crawlability Issues

Several other sites I first tried (Amazon, Etsy, Best Buy, Costco) block automated requests with bot-detection (HTTP 403 errors or dropped connections), which is itself a relevant SEO/crawlability observation. A site that blocks well-behaved crawlers risks blocking search engine bots too if misconfigured. Target and IKEA both responded normally.

Findings Summary

IKEA’s homepage shows a more complete on-page SEO structure than Target’s: both carry a single <h1> and a clear, benefit-driven meta description, but IKEA returns 32 real, content-bearing <h3> tags (promotions, membership prompts, deals) compared to Target, whose <h3> elements are almost entirely “Loading…” placeholders because Target renders that content client-side with JavaScript that a static scraper cannot execute. Both sites have similarly rich <h2> sections (17 for IKEA, 14 for Target) built around seasonal promotions. Because search engine crawlers behave much like this static scraper (they see the initial HTML, not everything JavaScript renders afterward) IKEA’s static markup is giving Google more usable heading text to index than Target’s, making IKEA the stronger performer on these specific SEO elements.

References

Silge, J., & Robinson, D. (2017). Text Mining with R: A Tidy Approach. O’Reilly Media. https://www.tidytextmining.com/

Xu, J.Z. (2026). Text Analysis & NLP 3 - Word Co-Occurrences for SEO. MSBA 580 Course Materials.