Session 6: Introduction to the Web

Introduction to Web Scraping and Data Management for Social Scientists

Johannes B. Gruber

2026-07-27

Introduction

This Course

Day Session
1 Introduction
2 Data Structures and Wrangling
3 Working with Files
4 Linking and joining data & SQL
5 Scaling, Reporting and Database Software
6 Introduction to the Web
7 Static Web Pages
8 Application Programming Interface (APIs)
9 Interactive Web Pages
10 Building a Reproducible Research Project

The Plan for Today

In this session, we learn how to scout data in the wild. We will:

  • discuss web scraping from a theoretical point of view:
    • What is web scraping?
    • Why should you learn it?
  • learn a bit more about how the Internet works
    • What is HTML
    • What is CSS
  • What legal, ethical, institutional and scientific implications should you keep in mind?

Angie Gade via unsplash.com

What is Web Scraping

Forms to get data from the web

  • Download data from a website
  • Retrieve data via an API
  • Scrape the (unstructured) Data

Image Source: daveberesford.co.uk

Web Scraping

  • Used when other means are unavailable
  • Scrape the (unstructured) Data from a website
    • E.g: get the author, title, date and body of an online news article
    • E.g: get a table from a website (like Wikipedia)
    • E.g: get all entries from a blog
    • E.g: get all press statements from a political party website
    • E.g: get all hyperlinks to files on a website
  • A web-scraper is a program (or robot) that:
    • goes to a web page
    • downloads its content
    • extracts data from the content
    • then saves the data to a file or a database

Web Scraping: A Three-Step Process

  1. Request & Collect:
    • you send an HTTP request to the webpage
    • server responds to the request by returning HTML content
  2. Parse the HTML content
    • extract the information you want from the nested structure of HTML code
  3. Wrangle the data into a useful format
    • usually a tidy table

Hurdles

  • Unfortunately no one-size-fits-all solution
    • Lots of different techniques, tools, tricks
    • Websites change (some more frequently than others)
  • Some web pages are easier to scrape than others (by accident or on purpose!):

Difficulty levels

  1. Well behaved static HTML with recurring patterns
  2. Haphazard HTML not clearly differentiating between different types of information
  3. Interactive web sites loading content by executing code (usually JavaScript or PHP)
  4. Interactive web sites with mechanisms against data extraction (rate limits, captchas etc.)
  5. Server requests fail without the correct (frequently changing) token

Why Should You Learn Web Scraping?

  • The internet is a data gold mine!
  • Data was not created for research, but are often traces of what people are actually doing on the internet
  • Reproducible and renewable data collection (e.g., rehydrate data that is copyrighted)
  • Web Scraping let’s you automate data retrieval (as opposed to using tedious copy & paste on some web site)
  • It’s one of the most fun tasks to learn R and programming!
    • It’s engaging and satisfying to find repeating patterns that you can employ to structure data (every website becomes a little puzzle)
    • It touches on many important computational skills
    • The return is good data to further your career (unlike sudokus or video games)

Found vs Designed Data

Designed Data

  • Collected for research
  • Full control of shape and form
  • Problems of validity due to social desirability and imperfect estimation problems

Found Data

  • Traces of human behavior
  • Comes in all shapes and forms
  • Problems of validity as not representative and incomplete access

What are HTML and CSS

What is HTML

  • HTML (HyperText Markup Language) is the standard markup language for documents designed to be displayed in a web browser
  • Contains the raw data (text, URLs to pictures and videos) plus defines the layout and some of the styling of text

Image Source: Wikipedia.org

Example: Simple

Code:

<!DOCTYPE html>
<html>
<head>
    <title>My Simple HTML Page</title>
</head>
<body>
    <p>This is the body of the text.</p>
</body>
</html>

Browser View:

Example: With headline and author

Code:

<!DOCTYPE html>
<html>
<head>
    <title>My Simple HTML Page</title>
</head>
<body>
    <h1>My Headline</h1>
    <p class="author" href="https://www.johannesbgruber.eu/">Me</p>
    <p>This is the body of the text.</p>
</body>
</html>

Browser View:

Example: With some data

Code:

<!DOCTYPE html>
<html>
<head>
    <title>My Simple HTML Page</title>
</head>
<body>
    <h1>My Headline</h1>
    <p class="author">Me</p>
    <p>This is the body of the text.</p>
    <p>Consider this data:</p>
    <table>
        <tr>
            <th>Name</th>
            <th>Age</th>
        </tr>
        <tr>
            <td>John</td>
            <td>25</td>
        </tr>
        <tr>
            <td>Mary</td>
            <td>26</td>
        </tr>
    </table>
</body>
</html>

Browser View:

Example: With an image

Code:

<!DOCTYPE html>
<html>
<head>
    <title>My Simple HTML Page</title>
</head>
<body>
    <h1>My Headline</h1>
    <p class="author">Me</p>
    <p>This is the body of the text.</p>
    <p>Consider this image:</p>
    <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/11/3_month_old_Corgi_puppy.jpg/960px-3_month_old_Corgi_puppy.jpg" alt="About The Dog.">
</body>
</html>

Browser View:

What is CSS

  • CSS (Cascading Style Sheets) is very often used in addition to HTML to control the presentation of a document
  • Designed to enable the separation of content from things concerning the look, such as layout, colours, and fonts.
  • The reason it is interesting for web scraping is that certain information often get the same styling

Example: CSS

HTML:

<!DOCTYPE html>
<html>
<head>
    <title>My Simple HTML Page</title>
    <link rel="stylesheet" type="text/css" href="example.css">
</head>
<body>
  <h1 class="headline">My Headline</h1>
  <p class="author">Me</p>
  <div class="content">
    <p>This is the body of the text.</p>
    <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/11/3_month_old_Corgi_puppy.jpg/960px-3_month_old_Corgi_puppy.jpg" alt="About The Dog.">
    <p>Consider this data:</p>
    <table>
      <tr class="top-row">
          <th>Name</th>
          <th>Age</th>
      </tr>
      <tr>
          <td>John</td>
          <td>25</td>
      </tr>
      <tr>
          <td>Mary</td>
          <td>26</td>
      </tr>
    </table>
  </div>
</body>
</body>
</html>

CSS:

/* CSS file */

.headline {
  color: red;
}

.author {
  color: grey;
  font-style: italic;
  font-weight: bold;
}

.top-row {
  background-color: lightgrey;
}

.content img {
  border: 2px solid black;
}

table, th, td {
  border: 1px solid black;
}

Browser View:

Exercises 1

  1. Add another paragraph to data/example.html and display it in your browser
  2. Add a second level headline to the page
  3. Add another image to the page
  4. Manipulate the files data/example.html and/or data/example.css so that “content” is displayed in italics

HTMl and CSS in Web Scraping: first steps

Using HTML tags:

You can select HTML elements by their tags

library(rvest)
read_html("data/example.html") |>
  html_elements("p") |>
  html_text2()
[1] "Me"                            "This is the body of the text."
[3] "Consider this image:"          "Consider this data:"          
  • to select them, tags are written without the <>
  • in theory, arbitrary tags are possible, but commonly people use <p> (paragraph), <br> (line break), <h1>, <h2>, <h3>, … (first, second, third, … level headline), <b> (bold), <i> (italic), <img> (image), <a> (hyperlink), and a couple more.

Using attributes

You can select elements by an attribute, including the class:

read_html("data/example.html") |>
  html_element("[class=\"headline\"]") |>
  html_text()
[1] "My Headline"

For class, there is also a shorthand:

read_html("data/example.html") |>
  html_element(".headline") |>
  html_text()
[1] "My Headline"

Another important shorthand is #, which selects the id attribute:

read_html("data/example.html") |>
  html_element("#table-1") |>
  html_table()
# A tibble: 2 × 2
  Name    Age
  <chr> <int>
1 John     25
2 Mary     26
read_html("data/example.html") |>
  html_element("#table-1 > tr")
{html_node}
<tr class="top-row">
[1] <th>Name</th>
[2] <th>Age</th>

Extracting attributes

Instead of selecting by attribute, you can also extract one or all attributes:

read_html("data/example.html") |>
  html_elements("a") |>
  html_attr("href")
[1] "https://www.johannesbgruber.eu/"   "https://en.wikipedia.org/wiki/Dog"
read_html("data/example.html") |>
  html_elements("a") |>
  html_attrs()
[[1]]
                             href 
"https://www.johannesbgruber.eu/" 

[[2]]
                               href 
"https://en.wikipedia.org/wiki/Dog" 

Chaining selectors

If there is more than one element that fits your selector, but you only want one of them, see if you can make your selection more specific by chaining selectors with > (for the immediate children) or an empty space (for any children and descendants of an element):

read_html("data/example.html") |>
  html_elements(".author>a") |>
  html_attr("href")
[1] "https://www.johannesbgruber.eu/"
read_html("data/example.html") |>
  html_elements(".author a") |>
  html_attr("href")
[1] "https://www.johannesbgruber.eu/"

Tip: there is also no rule against doing this instead:

read_html("data/example.html") |>
  html_elements(".author") |>
  html_elements("a") |>
  html_attr("href")
[1] "https://www.johannesbgruber.eu/"

Common Selectors

There are quite a lot of CSS selectors, but often you can stick to just a few:

Common CSS selectors
selector example Selects
element/tag table all <table> elements
class .someTable all elements with class="someTable"
id #table-1 unique element with id="table-1"
element.class tr.headerRow all <tr> elements with the headerRow class
class1.class2 .someTable.blue all elements with the someTable AND blue class
element,element tr,p all <tr> elements AND all <p> elements
class1 tag .table-1 tr all <tr> elements that are descendants of .table-1
class1 > tag .table-1 > tr all <tr> elements that are direct children of .table-1
class1 + tag .table-1 + a the <a> element directly following an element .table-1
[attribute] [required] all elements with a required attribute
[attr=value] [type="text"] all elements with type attribute equal to “text”
[attr^=value] [class^="btn"] all elements with class attribute starting with “btn”
[attr$=value] [src$=“.jpg”] all elements with src attribute ending with “.jpg”
[attr*=value] [class*="nav"] all elements with class attribute containing “nav”
:first-child tr:first-child all <tr> elements that are the first child of their parent
:last-child tr:last-child all <tr> elements that are the last child of their parent
:nth-child(n) tr:nth-child(2) all <tr> elements that are the 2nd child of their parent

Family Relations

Each html tag can contain other tags. To keep track of the relations we speak of ancestors, descendants, parents, children and siblings.

<book>
  <chapter>
    <section>
      <subsection>
        This is a subsection.
      </subsection>
      <subsection>
        This is another subsection.
      </subsection>
    </section>
    <section>
      This is a section.
    </section>
  </chapter>
  <chapter>
    <section>
      This is a section.
    </section>
    <section>
      This is a section.
    </section>
  </chapter>
  <chapter>
    This is a chapter without sections.
  </chapter>
</book>

Exercises 2

  1. Practice finding the right selector with the CSS Diner game (level 1-16 and 27-32): https://flukeout.github.io/
  2. Consider the toy HTML example below. Which selectors do you need to put into html_elements() (which extracts all elements matching the selector) to extract the information
library(rvest)
webpage <- "<html>
<body>
  <h1>Computational Research in the Post-API Age</h1>
  <div class='author'>Deen Freelon</div>
  <div>Keywords:
    <ul>
      <li>API</li>
      <li>computational</li>
      <li>Facebook</li>
    </ul>
  </div>
  <div class='text'>
    <p>Three pieces of advice on whether and how to scrape from Dan Freelon</p>
  </div>
  
  <ol class='advice'>
    <li id='one'> use authorized methods whenever possible </li>
    <li id='two'> do not confuse terms of service compliance with data protection </li>
    <li id='three'> understand the risks of violating terms of service </li>
  </ol>

</body>
</html>" |>
  read_html()
# the headline
headline <- html_elements(webpage, "")
headline
# the author
author <- html_elements(webpage, "")
author
# the ordered list
ordered_list <- html_elements(webpage, "")
ordered_list
# all bullet points
bullet_points <- html_elements(webpage, "")
bullet_points
# bullet points in unordered list
bullet_points_unordered <- html_elements(webpage, "")
bullet_points_unordered
# elements in ordered list
bullet_points_ordered <- html_elements(webpage, "")
bullet_points_ordered
# third bullet point in ordered list
bullet_point_three_ordered <- html_elements(webpage, "")
bullet_point_three_ordered

Implications of Web Scraping

Four Sets of Considerations

Before starting a scraping project, Brown et al. (2025) suggest working through four sets of questions:

  • Legal: what am I allowed to access, collect, store and share?
  • Ethical: what should I access, collect, store and share?
  • Institutional: who do I need to talk to – and who has my back if something goes wrong?
  • Scientific: is the data I end up with good enough to answer my question?

The legal and institutional answers are jurisdiction-specific (Brown et al. write for a US audience, we mostly look at the EU). The ethical and scientific ones travel everywhere.

ToS and Robots.txt

Twitter ToS

User-agent: *                                       # the rules apply to all user agents
Disallow: /episerver/                               # do not crawl any URLs that start with /episerver/
Disallow: /utils/                                   # do not crawl any URLs that start with /episerver/
Sitemap: https://www.parliament.uk/sitemapindex.xml # where to find the map of the site

https://www.parliament.uk/robots.txt

  • Many companies have ToS that might prohibit you from scraping (see above: these are not laws, might not be binding and whether they can be enforced is a separate question)
  • /robots.txt is often where guidelines are communicated to automated crawlers (these are not legally binding either)

Ethical

What is legal and what is decent are two different questions.

  • Are there other means available to get to the data (e.g., via an API)?
  • robots.txt might not be legally binding, but it is not nice to ignore it
  • Scraping can put a heavy load on a website (if you make 1000s of requests) which costs the hosts money and might bring down a site (DDoS attack)
    • be a polite scraper: rate limit yourself, cache what you already downloaded so you don’t request it twice, and identify yourself in the user agent
  • Think twice before scraping personal data. You should ask yourself:
    • is it necessary for your research?
    • are you harming anyone by obtaining (or distributing) the data?
    • do you really need everything or are parts of the data sufficient (e.g., can you preselect cases or ignore variables)

Ethical: “But it’s Public!”

Public availability is where the ethical question starts, not where it ends (Brown et al. 2025)

  • People do not expect it: few users are aware that their public posts are used in research, and most think researchers should ask first (Fiesler and Proferes 2018)
  • Context matters: data was shared in one context (a support forum, a local Facebook group) – is your use appropriate to that context? (Zimmer 2018)
  • Third parties: replies, social graphs, images and tags drag in people who never posted anything themselves and cannot consent
  • Vulnerable groups and sensitive topics deserve extra care – minors in particular are unlikely to understand that their data is used for research
  • The whole lifecycle: your dataset can be repurposed for things you never intended (facial recognition, predictive policing, profiling)
    • limit what you share, document the intended use, add licence restrictions, and do not publish raw data when the risk of misuse is high

Two resources worth knowing: the AoIR Internet Research Ethics guidelines and the PERVADE data ethics tool

Institutional: Who to Talk To

You are not supposed to figure all of this out on your own – but the support structures are scattered and often do not talk to each other (Brown et al. 2025)

  • Ethics committee / IRB: engage them early and often, as a partner rather than as a gatekeeper
    • most treat publicly available online data as “not human subjects research” or exempt it
    • but boards differ enormously between institutions, and few have scraping-specific expertise
  • Legal counsel: your university’s legal office exists to protect the university, not you personally
  • Research IT and data stewards: secure storage, access control, deletion protocols
    • technical rigour is what actually lowers your ethical and legal risk
  • Data archives (e.g., GESIS, the Social Media Archive at ICPSR) can host data and advise on sharing

Warning

Early career researchers “experience heightened precarity… but can carry the bulk of the research labour” (Brown et al. 2025). If you are the PhD student or research assistant who actually runs the scraper, find out before you start what protection your institution offers you.

Scientific: Is the Data Any Good?

The most under-discussed set of considerations – scraped data has systematic quality problems (Brown et al. 2025)

  • Sampling frame: you usually cannot draw a random sample, and you often cannot tell what your sample represents
    • what a search box or a feed returns is ranked, filtered and moderated by the platform – not by you
    • “everything with the hashtag #x” is not a population, it is whatever the platform decided to show you
  • Missing data, missing non-randomly: rate limits and timeouts create gaps, and results usually arrive in the order they were created – so gaps cluster in time
    • platform metrics (“views”, “impressions”) are often estimated or sampled by the platform itself
  • Temporal instability: sites change layout, algorithms and A/B tests without notice
    • best case your scraper breaks, worst case it keeps running and silently collects something else
  • Construct validity: can you measure what you actually mean?
    • an impression is not a “read”; logged-out search results are not what a logged-in user sees
  • Reproducibility: posts get deleted, accounts get banned, sites get redesigned – your study may not be repeatable a year later

Scientific: What to Do About It

  • Ask first whether scraping is the right way to answer your question, or whether a better source exists
  • Define the population you can actually describe up front and sample from it – avoid convenience and snowball samples
    • a complete, well-defined subsample (all MPs, all national newspapers, all municipalities) often beats a “random” sample of everything
  • Test your pipeline: look for duplicates, missing pages and silent errors, scrape a few pages twice, and compare against something you can verify by hand
  • Caveat your claims to what the data can support – “posts we could retrieve through X’s search interface in March 2026” is a very different population from “public discourse”
  • Document everything: version control the scraper, publish code and metadata, and archive the raw responses where you are allowed to

Advice?

Legal and ethical advice is rare and complicated to give. A good opinion piece about it is Freelon (2018). It is worth reading, but can be summarised in three general pieces of advice

  • Use authorized methods whenever possible
  • Do not confuse terms of service compliance with data protection
  • Understand the risks of violating terms of service

Brown et al. (2025) add a checklist logic: write down, before you start, your answers to

  1. how you access the data
  2. what exactly you collect and why you need it
  3. how you secure, minimise and eventually delete it
  4. who you talked to about it
  5. what you would say publicly if someone objected to your project

Exercises 3

Twitter made access to their API punishingly expensive and stopped free academic access for research in 2023. If you wanted to do research on Twitter data through web-scraping anyway what implications would that have:

  1. Legally

  2. Ethically

  3. Institutionally (who at your institution would you talk to, and in what order?)

  4. Scientifically (what would you not be able to claim with the data you get?)

  5. Practical

Discuss!

Wrap Up

Save some information about the session for reproducibility.

Show Session Info
sessionInfo()
R version 4.6.1 (2026-06-24)
Platform: x86_64-pc-linux-gnu
Running under: EndeavourOS

Matrix products: default
BLAS:   /usr/lib/libblas.so.3.12.0 
LAPACK: /usr/lib/liblapack.so.3.12.0  LAPACK version 3.12.0

locale:
 [1] LC_CTYPE=en_GB.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=de_DE.UTF-8        LC_COLLATE=en_GB.UTF-8    
 [5] LC_MONETARY=de_DE.UTF-8    LC_MESSAGES=en_GB.UTF-8   
 [7] LC_PAPER=de_DE.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=de_DE.UTF-8 LC_IDENTIFICATION=C       

time zone: Europe/Berlin
tzcode source: system (glibc)

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] rvest_1.0.5      lubridate_1.9.5  forcats_1.0.1    stringr_1.6.0   
 [5] dplyr_1.2.1      purrr_1.2.2      readr_2.2.0      tidyr_1.3.2     
 [9] tibble_3.3.1     ggplot2_4.0.3    tidyverse_2.0.0  tinytable_0.17.0

loaded via a namespace (and not attached):
 [1] utf8_1.2.6         generics_0.1.4     xml2_1.6.0         stringi_1.8.7     
 [5] hms_1.1.4          digest_0.6.39      magrittr_2.0.5     evaluate_1.0.5    
 [9] grid_4.6.1         timechange_0.4.0   RColorBrewer_1.1-3 fastmap_1.2.0     
[13] jsonlite_2.0.0     processx_3.9.0     chromote_0.5.1     ps_1.9.3          
[17] promises_1.5.0     httr_1.4.8         selectr_0.6-0      scales_1.4.0      
[21] codetools_0.2-20   cli_3.6.6          rlang_1.3.0        litedown_0.10     
[25] withr_3.0.3        yaml_2.3.12        otel_0.2.0         tools_4.6.1       
[29] tzdb_0.5.0         vctrs_0.7.3        R6_2.6.1           lifecycle_1.0.5   
[33] pkgconfig_2.0.3    pillar_1.11.1      later_1.4.8        gtable_0.3.6      
[37] glue_1.8.1         Rcpp_1.1.2         xfun_0.60          tidyselect_1.2.1  
[41] knitr_1.51         dichromat_2.0-1    farver_2.1.2       htmltools_0.5.9   
[45] websocket_1.4.4    rmarkdown_2.31     compiler_4.6.1     S7_0.2.2          

References

Brown, Megan A, Andrew Gruen, Gabe Maldoff, Solomon Messing, Zeve Sanderson, and Michael Zimmer. 2025. “Web Scraping for Research: Legal, Ethical, Institutional, and Scientific Considerations.” Big Data & Society 12 (4): 1–17. https://doi.org/10.1177/20539517251381686.
Fiesler, Casey, and Nicholas Proferes. 2018. “"Participant" Perceptions of Twitter Research Ethics.” Social Media + Society 4 (1). https://doi.org/10.1177/2056305118763366.
Freelon, Deen. 2018. “Computational Research in the Post-API Age.” Political Communication 35 (4): 665–68. https://doi.org/10.1080/10584609.2018.1477506.
Luscombe, Alex, Kevin Dick, and Kevin Walby. 2022. “Algorithmic Thinking in the Public Interest: Navigating Technical, Legal, and Ethical Hurdles to Web Scraping in the Social Sciences.” Quality & Quantity 56 (3): 1023–44. https://doi.org/10.1007/s11135-021-01164-0.
Zimmer, Michael. 2018. “Addressing Conceptual Gaps in Big Data Research Ethics: An Application of Contextual Integrity.” Social Media + Society 4 (2). https://doi.org/10.1177/2056305118768300.