1 Goal

Hands-on webscraping

It is time to do some webscraping ourselves. In what follows is a short first tutorial on webscraping where we will be collecting data from webpages on the internet. We will use the specific use case of the political science department staff of the university of Leiden.

What do they publish? Where? And with whom do they collaborate? We assume you have at least some experience with coding in R. In the rest of this part of the tutorial, we will switch between base R and Tidyverse (just a bit), whatever is most convenient. (Note that this will happen often if you become an applied computational sociologist.) We also offer Python code.

There are different strategies in scraping. There is often a trade-off between complex scraping techniques versus complex string manipulations. In this first example, we will use quite a lot of string manipulations.

For even more info see our SNASS book - Chapter 11


2 Preparation

2.1 clean up

rm(list = ls())
gc()

2.2 Custom functions

  • fpackage.check: Check if packages are installed (and install if not) in R.
  • fsave: Save to processed data in repository
  • fload: To load the files back after an fsave
  • fshowdf: To print objects (tibbles / data.frame) nicely on screen in .rmd
rm(list = ls())  #clean up your environment

fpackage.check <- function(packages) {
    lapply(packages, FUN = function(x) {
        if (!require(x, character.only = TRUE)) {
            install.packages(x, dependencies = TRUE)
            library(x, character.only = TRUE)
        }
    })
}

fsave <- function(x, file = NULL, location = "./data/processed/") {
    ifelse(!dir.exists("data"), dir.create("data"), FALSE)
    ifelse(!dir.exists("data/processed"), dir.create("data/processed"), FALSE)
    if (is.null(file))
        file = deparse(substitute(x))
    datename <- substr(gsub("[:-]", "", Sys.time()), 1, 8)
    totalname <- paste(location, datename, file, ".rda", sep = "")
    save(x, file = totalname)  #need to fix if file is reloaded as input name, not as x. 
}

fload <- function(filename) {
    load(filename)
    get(ls()[ls() != "filename"])
}

fshowdf <- function(x, ...) {
    knitr::kable(x, digits = 2, "html", ...) %>%
        kableExtra::kable_styling(bootstrap_options = c("striped", "hover")) %>%
        kableExtra::scroll_box(width = "100%", height = "300px")
}

colorize <- function(x, color) {
    sprintf("<span style='color: %s;'>%s</span>", color, x)
}

2.3 Packages

2.3.1 R

  • tidyverse: for piping etc.
  • httr: Tools for Working with URLs and HTTP
  • xml2: Work with XML files using a simple, consistent interface.
  • rvest: Wrappers around the ‘xml2’ and ‘httr’ packages to make it easy to download, then manipulate, HTML and XML.
  • reshape2: Flexibly Reshape Data
packages = c("tidyverse", "httr", "rvest", "reshape2", "xml2")
fpackage.check(packages)

2.3.2 Python

Make sure you have the required libraries by typing in the code below into your console

pip install requests beautifulsoup4 pandas


and import necessary modules

import requests
from bs4 import BeautifulSoup
import pandas as pd
import re


3 Leiden University: Political Science

What do we mean by anchor data? Our goal is to get to know:

  1. who the Political Science staff is at several universities,
  2. what they publish with respect to scientific work, and
  3. who they collaborate with.

So that means at least three data sources we need to collect from somewhere. What would be a nice starting (read: anchor) point be? First, we have to know who is staff. Let’s check out the Leiden political science staff website. Here we see a nice list on who is on the staff in several pages. How do we get that data? It is actually quite simple, the package rvest has a very nice function html_read() (actually this comes from the xml2 package) which simply derives the source html of a static webpage:

3.1 Staff pages

3.1.1 R

Let’s first simply get the staff pages. read_html() is a function that simply extracts html webpages and puts them in xml format.

lpol_staff <- read_html("https://www.universiteitleiden.nl/en/social-behavioural-sciences/political-science/staff#tab-1")
head(lpol_staff)
#> $node
#> <pointer: 0x000001ba7b7fedd0>
#> 
#> $doc
#> <pointer: 0x000001ba87cfd050>

That looks kinda weird. What type of object did we store it by putting the html into lpol_staff1?

class(lpol_staff)
#> [1] "xml_document" "xml_node"

So it is is stored in something that’s called an xml object. Not important for now what that is. But it is important to extract the relevant table that we saw on the staff website. How do we do that? Go to one of the links above in a browser and then press “Inspect” on the webpage (usually: right click–>Inspect). In the html code we extracted, we need to go to one of the nodes first. If you move your cursor over “div” in the html code on the screen, the entire “body” of the page should become some shade of blue. This means that the elements encapsulated in the “body” node captures everything that turned blue.

3.1.2 Python

# Fetch the webpage
url = "https://www.universiteitleiden.nl/en/social-behavioural-sciences/political-science/staff#tab-1"

response = requests.get(url)
webpage = response.content

# Parse the webpage
soup = BeautifulSoup(webpage, 'html.parser')

#print(soup.prettify())

#or to print just a few lines
result = soup.prettify().splitlines()
print('\n'.join(result[:10]))
<!DOCTYPE html>
<html data-version="1.178.00" lang="en">
 <head>
  <!-- standard page html head -->
  <title>
   Staff - Leiden University
  </title>
  <meta content="o8KYuFAiSZi6QWW1wxqKFvT1WQwN-BxruU42si9YjXw" name="google-site-verification"/>
  <meta content="hRUxrqIARMinLW2dRXrPpmtLtymnOTsg0Pl3WjHWQ4w" name="google-site-verification"/>
  <link href="https://www.universiteitleiden.nl/en/social-behavioural-sciences/political-science/staff" rel="canonical"/>

3.2 Nodes and tags

Next, we need to look at the specific elements on the page that we need to extract. Somewhat by informed trial and error, looking for the correct code, we can select the elements we want. So we need code that looks for the node “main” and the “td” elements in the xml object and then extract those elements in it. Note that you can click on the arrows once you are in the “Inspect” mode in the web browser to trial-and-error to get at the correct elements.

There are many ways to find the CSS or Xpath of the elements you want. The package rvest also has a SelectorGadget tool. See here

Wo we need to find WHERE the table is located in the html.

Assignment:
- use firefox ‘inspect’
- use rstudio
- use rvest SelectorGadget

3.2.1 R

lpol_staff <- lpol_staff %>%
    html_nodes("body") %>%
    html_nodes(xpath = "//a") %>%
    html_text()

Let us for now assume you are satisfied with the above output.

3.2.2 Python

lpol_staff = soup.body.find_all('a')

lpol_staff = [tags.prettify() for tags in lpol_staff]
print(lpol_staff[:10])
['<a class="active track-event" data-event-category="external-site" data-event-label="Topmenu external-site" href="/en">\n Leiden University\n</a>\n', '<a class="track-event" data-event-category="student-site" data-event-label="Topmenu external-site" href="https://www.student.universiteitleiden.nl/en">\n Students\n</a>\n', '<a class="track-event" data-event-category="staffmember-site" data-event-label="Topmenu external-site" href="https://www.staff.universiteitleiden.nl/">\n Staff members\n</a>\n', '<a class="track-event" data-event-category="org-site" data-event-label="Topmenu external-site" href="https://www.organisatiegids.universiteitleiden.nl/en">\n Organisational structure\n</a>\n', '<a class="track-event" data-event-category="library-site" data-event-label="Topmenu external-site" href="https://www.library.universiteitleiden.nl/">\n Library\n</a>\n', '<a href="/en">\n <img alt="Universiteit Leiden" height="64" src="/en/design-1.0/assets/images/zegel.png" width="151"/>\n</a>\n', '<a data-hidden="" data-hint="Search for subject or person" href="/en/search">\n All categories\n</a>\n', '<a data-hidden=\'{"content-category":"staffmember"}\' data-hint="Search for\xa0persons" href="/en/search">\n Persons\n</a>\n', '<a data-hidden=\'{"content-category":"education"}\' data-hint="Search for\xa0education" href="/en/search">\n Education\n</a>\n', '<a data-hidden=\'{"content-category":"research"}\' data-hint="Search for\xa0research" href="/en/search">\n Research\n</a>\n']

3.3 Cleaning

3.3.1 R

Seems like more useful data now. But can we improve by deleting some elements we do not need? Let’s first delete some of the useless information.

head(lpol_staff)
#> [1] "Leiden University"        "Students"                 "Staff members"           
#> [4] "Organisational structure" "Library"                  "\n"
lpol_staff <- lpol_staff[-c(1:39)]
lpol_staff <- lpol_staff[-c(133:length(lpol_staff))]
head(lpol_staff)
#> [1] "\n\n\n\n\n                    \n        Adina Akbik\n            Senior Assistant Professor\n    \n"                               
#> [2] "\n\n\n\n\n                    \n        Femke Bakker\n            Senior assistant professor\n    \n"                              
#> [3] "\n\n\n\n\n                    \n        Ingrid van Biezen\n            Professor of Comparative Politics\n    \n"                  
#> [4] "\n\n\n\n\n                    \n        Nicolas Blarel\n            Associate Professor\n    \n"                                   
#> [5] "\n\n\n\n\n                    \n        Arjen Boin\n            Professor of Public Institutions and Governance\n    \n"           
#> [6] "\n\n\n\n\n                    \n        Theo Brinkel\n            Professor by Special Appointment Military-social studies\n    \n"
fshowdf(lpol_staff)
x
Adina Akbik Senior Assistant Professor
Femke Bakker Senior assistant professor
Ingrid van Biezen Professor of Comparative Politics
Nicolas Blarel Associate Professor
Arjen Boin Professor of Public Institutions and Governance
Theo Brinkel Professor by Special Appointment Military-social studies
Manuel Cabal Lopez Assistant professor
Valentina Carraro Assistant Professor in Global Transformations and Governance Challenges
Stefan Cetkovic Assistant Professor
Leila Demarest Associate Professor
Matthew di Giuseppe Director of Studies / Associate Professor
Roos van der Haer Assistant Professor
Gisela Hirschmann Senior Assistant Professor
Joop van Holsteijn Professor Political Behaviour and Research Methods
Corinna Jentzsch Assistant Professor
Petr Kopecky Professor of Comparative Studies Political Parties and Party Systems
Matthew Longo Senior Assistant Professor
Tom Louwerse Director of Research / Associate Professor
Floris Mansvelt Beck Assistant Professor
Juan Masullo Jimenez Assistant Professor
Hilde van Meegdenburg Assistant Professor
Michael Meffert Assistant Professor
Frits Meijerink Assistant professor
Tim Mickler Senior assistant professor
Martijn Mos Assistant Professor
Katharina Natter Senior assistant professor
Paul Nieuwenburg Professor Political Philosophy
Simon Otjes Senior Assistant Professor
Hans Oversloot Senior Assistant Professor
Jonathan Phillips Assistant Professor
Rebecca Ploof Assistant Professor
Karolina Pomorska Associate professor
Francesco Ragazzi Associate professor
Babak Rezaeedaryakenari Senior Assistant Professor
Josh Robison Assistant Professor
Michael Sampson Senior Assistant Professor
Jan Aart Scholte Professor Global Transformations and Governance Challenges
Jonah Schulhofer-Wohl Senior assistant professor
Maria Spirova Associate Professor
Tom Theuns Senior Assistant Professor
Daniel Thomas Professor of International Relations
Christina Luise Toenshoff Assistant professor
Vasiliki (Billy) Tsagkroni Senior assistant professor
Wouter Veenendaal Professor by Special Appointment Kingdom Relations
Claire Vergerio Guest
Marco Verschoor Assistant Professor
Cynthia van Vonno Assistant Professor
Niels van Willigen Director of Studies/Associate Professor
Nikoleta Yordanova Associate professor
Yuan Yi Zhu Assistant professor
Frank de Zwart Guest
Alessia Aspide PhD candidate
Cyan Bae PhD candidate
Kathleen Brown PhD candidate
Mateo Cohen External PhD candidate
Josette Daemen Postdoc
Jesse Doornenbal Lecturer
Eleftherios Karchimakis Lecturer
Aleksandra Khokhlova PhD candidate
Stijn Koenraads PhD candidate
Hannah Kuhn PhD candidate
Alex Schilin Guest researcher
Pawan Sen PhD candidate
Ruben van de Ven PhD candidate
Anouk van Vliet Lecturer
Denny van der Vlist PhD candidate
Thijs Vos PhD candidate
Rick van Well PhD candidate
Daan van den Wollenberg PhD candidate / self funded
Elina Zorina PhD candidate
Rudy Andeweg Professor emeritus of Empirical Political Science
Ivan Bakalov Lecturer
Agha Bayramov Lecturer
Jelena Belic Lecturer
Jelke Bethlehem Professor emeritus Survey Methodology
Peter Castenmiller Lecturer
Diana Davila Gordillo Guest - researcher
Henk Dekker Emeritus professor of Political Socialization and Integration
Katerina Galanopoulou Lecturer
Rutger Hagen Lecturer
Henriëtte van den Heuvel Scientific Director ad interim
Galen Irwin
Devrim Kabasakal Badamchi- Guest researcher
Eleftherios Karchimakis Lecturer
Müge Kinacioglu Lecturer
Ruud Koole Professor emeritus Politicologie
Amber Lauwers Lecturer
José Lourenço Lecturer
Gjovalin Macaj Assistant professor
Jan Meijer Lecturer
Marijn Nagtzaam Lecturer
Christoph Niessen Guest
Alexandros Ntaflos Lecturer
Joyce Outshoorn Emeritus professor vrouwenstudies
Jimena Pacheco Miranda Lecturer
Julia Puente Duyn Lecturer
Ellen van Reuler Lecturer
Thomas Scarff Lecturer
Radostina Sharenkova-Toshkova Guest
Justin Spruit Lecturer
Vishwesh Sundar Lecturer
Harmen van der Veer Lecturer
Amy Verdun Guest professor
Ruben Verheul Web editor
Anouk van Vliet Lecturer
Carina van de Wetering Lecturer
Gul-i-Hina van der Zwan Postdoc / guest
Wencke Appelman Study adviser
Ester Blom Study Adviser
Anna van Dijk Management/office-assistent
Nathalie van Dooren Coordinator Marketing & Student Recruitment
Desiree van Drongelen Staff Member Studentregistrations
Nynke Heegstra Study Advisor
Ingrid van Heeringen-Göbbels Institute manager
Marit van der Heide Student member institute board
Katie Hudson Data Steward
Lianne Janssen Policy Officer Education and Quality Assurance
Eline Joor Internationalisation Officer
Ian Lau Study adviser
Daniëlle Lovink Study Adviser
Carien Nelissen Education manager
Caroline Remmerswaal Secretary board of examiners
Marjan Rijnja Teaching Coordinator
Elka Smith Research Project Manager
Judy Spruit Management/office-assistent
Tessa Thomas Management assistant
Debbie Tromper Management assistant
Gerard van der Veer Secretary board of examiners
Ruben Verheul Web editor
Jeanne Viet Communication staff member
Denise Zeeuw-van Veen Management/office assistant
Bachelor’s programmes

Still looks a bit messy. Can we get it into a dataframe and split the column into useful columns?

lpol_staff <- data.frame(lpol_staff)
lpol_staff <- colsplit(lpol_staff$lpol_staff, "          ", names = c("v1", "v2", "v3", "v4", "v5"))
fshowdf(lpol_staff, caption = "lpol_staff")
Table 3.1: lpol_staff
v1 v2 v3 v4 v5
NA Adina Akbik Senior Assistant Professor NA
NA Femke Bakker Senior assistant professor NA
NA Ingrid van Biezen Professor of Comparative Politics NA
NA Nicolas Blarel Associate Professor NA
NA Arjen Boin Professor of Public Institutions and Governance NA
NA Theo Brinkel Professor by Special Appointment Military-social studies NA
NA Manuel Cabal Lopez Assistant professor NA
NA Valentina Carraro Assistant Professor in Global Transformations and Governance Challenges NA
NA Stefan Cetkovic Assistant Professor NA
NA Leila Demarest Associate Professor NA
NA Matthew di Giuseppe Director of Studies / Associate Professor NA
NA Roos van der Haer Assistant Professor NA
NA Gisela Hirschmann Senior Assistant Professor NA
NA Joop van Holsteijn Professor Political Behaviour and Research Methods NA
NA Corinna Jentzsch Assistant Professor NA
NA Petr Kopecky Professor of Comparative Studies Political Parties and Party Systems NA
NA Matthew Longo Senior Assistant Professor NA
NA Tom Louwerse Director of Research / Associate Professor NA
NA Floris Mansvelt Beck Assistant Professor NA
NA Juan Masullo Jimenez Assistant Professor NA
NA Hilde van Meegdenburg Assistant Professor NA
NA Michael Meffert Assistant Professor NA
NA Frits Meijerink Assistant professor NA
NA Tim Mickler Senior assistant professor NA
NA Martijn Mos Assistant Professor NA
NA Katharina Natter Senior assistant professor NA
NA Paul Nieuwenburg Professor Political Philosophy NA
NA Simon Otjes Senior Assistant Professor NA
NA Hans Oversloot Senior Assistant Professor NA
NA Jonathan Phillips Assistant Professor NA
NA Rebecca Ploof Assistant Professor NA
NA Karolina Pomorska Associate professor NA
NA Francesco Ragazzi Associate professor NA
NA Babak Rezaeedaryakenari Senior Assistant Professor NA
NA Josh Robison Assistant Professor NA
NA Michael Sampson Senior Assistant Professor NA
NA Jan Aart Scholte Professor Global Transformations and Governance Challenges NA
NA Jonah Schulhofer-Wohl Senior assistant professor NA
NA Maria Spirova Associate Professor NA
NA Tom Theuns Senior Assistant Professor NA
NA Daniel Thomas Professor of International Relations NA
NA Christina Luise Toenshoff Assistant professor NA
NA Vasiliki (Billy) Tsagkroni Senior assistant professor NA
NA Wouter Veenendaal Professor by Special Appointment Kingdom Relations NA
NA Claire Vergerio Guest NA
NA Marco Verschoor Assistant Professor NA
NA Cynthia van Vonno Assistant Professor NA
NA Niels van Willigen Director of Studies/Associate Professor NA
NA Nikoleta Yordanova Associate professor NA
NA Yuan Yi Zhu Assistant professor NA
NA Frank de Zwart Guest NA
NA Alessia Aspide PhD candidate NA
NA Cyan Bae PhD candidate NA
NA Kathleen Brown PhD candidate NA
NA Mateo Cohen External PhD candidate NA
NA Josette Daemen Postdoc NA
NA Jesse Doornenbal Lecturer NA
NA Eleftherios Karchimakis Lecturer NA
NA Aleksandra Khokhlova PhD candidate NA
NA Stijn Koenraads PhD candidate NA
NA Hannah Kuhn PhD candidate NA
NA Alex Schilin Guest researcher NA
NA Pawan Sen PhD candidate NA
NA Ruben van de Ven PhD candidate NA
NA Anouk van Vliet Lecturer NA
NA Denny van der Vlist PhD candidate NA
NA Thijs Vos PhD candidate NA
NA Rick van Well PhD candidate NA
NA Daan van den Wollenberg PhD candidate / self funded NA
NA Elina Zorina PhD candidate NA
NA Rudy Andeweg Professor emeritus of Empirical Political Science NA
NA Ivan Bakalov Lecturer NA
NA Agha Bayramov Lecturer NA
NA Jelena Belic Lecturer NA
NA Jelke Bethlehem Professor emeritus Survey Methodology NA
NA Peter Castenmiller Lecturer NA
NA Diana Davila Gordillo Guest - researcher NA
NA Henk Dekker Emeritus professor of Political Socialization and Integration NA
NA Katerina Galanopoulou Lecturer NA
NA Rutger Hagen Lecturer NA
NA Henriëtte van den Heuvel Scientific Director ad interim NA
NA Galen Irwin NA
NA Devrim Kabasakal Badamchi- Guest researcher NA
NA Eleftherios Karchimakis Lecturer NA
NA Müge Kinacioglu Lecturer NA
NA Ruud Koole Professor emeritus Politicologie NA
NA Amber Lauwers Lecturer NA
NA José Lourenço Lecturer NA
NA Gjovalin Macaj Assistant professor NA
NA Jan Meijer Lecturer NA
NA Marijn Nagtzaam Lecturer NA
NA Christoph Niessen Guest NA
NA Alexandros Ntaflos Lecturer NA
NA Joyce Outshoorn Emeritus professor vrouwenstudies NA
NA Jimena Pacheco Miranda Lecturer NA
NA Julia Puente Duyn Lecturer NA
NA Ellen van Reuler Lecturer NA
NA Thomas Scarff Lecturer NA
NA Radostina Sharenkova-Toshkova Guest NA
NA Justin Spruit Lecturer NA
NA Vishwesh Sundar Lecturer NA
NA Harmen van der Veer Lecturer NA
NA Amy Verdun Guest professor NA
NA Ruben Verheul Web editor NA
NA Anouk van Vliet Lecturer NA
NA Carina van de Wetering Lecturer NA
NA Gul-i-Hina van der Zwan Postdoc / guest NA
NA Wencke Appelman Study adviser NA
NA Ester Blom Study Adviser NA
NA Anna van Dijk Management/office-assistent NA
NA Nathalie van Dooren Coordinator Marketing & Student Recruitment NA
NA Desiree van Drongelen Staff Member Studentregistrations NA
NA Nynke Heegstra Study Advisor NA
NA Ingrid van Heeringen-Göbbels Institute manager NA
NA Marit van der Heide Student member institute board NA
NA Katie Hudson Data Steward NA
NA Lianne Janssen Policy Officer Education and Quality Assurance NA
NA Eline Joor Internationalisation Officer NA
NA Ian Lau Study adviser NA
NA Daniëlle Lovink Study Adviser NA
NA Carien Nelissen Education manager NA
NA Caroline Remmerswaal Secretary board of examiners NA
NA Marjan Rijnja Teaching Coordinator NA
NA Elka Smith Research Project Manager NA
NA Judy Spruit Management/office-assistent NA
NA Tessa Thomas Management assistant NA
NA Debbie Tromper Management assistant NA
NA Gerard van der Veer Secretary board of examiners NA
NA Ruben Verheul Web editor NA
NA Jeanne Viet Communication staff member NA
NA Denise Zeeuw-van Veen Management/office assistant NA
Bachelor’s programmes NA NA

Nice! I think we only need column 4 and 5? And let’s name them nicely and delete any trailing or leading whitespace.

lpol_staff <- lpol_staff[, c("v3", "v4")]
names(lpol_staff) <- c("name", "func")

lpol_staff$name <- trimws(lpol_staff$name, which = c("both"), whitespace = "[ \t\r\n]")
lpol_staff$func <- trimws(lpol_staff$func, which = c("both"), whitespace = "[ \t\r\n]")
fshowdf(lpol_staff, caption = "lpol_staff")
Table 3.2: lpol_staff
name func
Adina Akbik Senior Assistant Professor
Femke Bakker Senior assistant professor
Ingrid van Biezen Professor of Comparative Politics
Nicolas Blarel Associate Professor
Arjen Boin Professor of Public Institutions and Governance
Theo Brinkel Professor by Special Appointment Military-social studies
Manuel Cabal Lopez Assistant professor
Valentina Carraro Assistant Professor in Global Transformations and Governance Challenges
Stefan Cetkovic Assistant Professor
Leila Demarest Associate Professor
Matthew di Giuseppe Director of Studies / Associate Professor
Roos van der Haer Assistant Professor
Gisela Hirschmann Senior Assistant Professor
Joop van Holsteijn Professor Political Behaviour and Research Methods
Corinna Jentzsch Assistant Professor
Petr Kopecky Professor of Comparative Studies Political Parties and Party Systems
Matthew Longo Senior Assistant Professor
Tom Louwerse Director of Research / Associate Professor
Floris Mansvelt Beck Assistant Professor
Juan Masullo Jimenez Assistant Professor
Hilde van Meegdenburg Assistant Professor
Michael Meffert Assistant Professor
Frits Meijerink Assistant professor
Tim Mickler Senior assistant professor
Martijn Mos Assistant Professor
Katharina Natter Senior assistant professor
Paul Nieuwenburg Professor Political Philosophy
Simon Otjes Senior Assistant Professor
Hans Oversloot Senior Assistant Professor
Jonathan Phillips Assistant Professor
Rebecca Ploof Assistant Professor
Karolina Pomorska Associate professor
Francesco Ragazzi Associate professor
Babak Rezaeedaryakenari Senior Assistant Professor
Josh Robison Assistant Professor
Michael Sampson Senior Assistant Professor
Jan Aart Scholte Professor Global Transformations and Governance Challenges
Jonah Schulhofer-Wohl Senior assistant professor
Maria Spirova Associate Professor
Tom Theuns Senior Assistant Professor
Daniel Thomas Professor of International Relations
Christina Luise Toenshoff Assistant professor
Vasiliki (Billy) Tsagkroni Senior assistant professor
Wouter Veenendaal Professor by Special Appointment Kingdom Relations
Claire Vergerio Guest
Marco Verschoor Assistant Professor
Cynthia van Vonno Assistant Professor
Niels van Willigen Director of Studies/Associate Professor
Nikoleta Yordanova Associate professor
Yuan Yi Zhu Assistant professor
Frank de Zwart Guest
Alessia Aspide PhD candidate
Cyan Bae PhD candidate
Kathleen Brown PhD candidate
Mateo Cohen External PhD candidate
Josette Daemen Postdoc
Jesse Doornenbal Lecturer
Eleftherios Karchimakis Lecturer
Aleksandra Khokhlova PhD candidate
Stijn Koenraads PhD candidate
Hannah Kuhn PhD candidate
Alex Schilin Guest researcher
Pawan Sen PhD candidate
Ruben van de Ven PhD candidate
Anouk van Vliet Lecturer
Denny van der Vlist PhD candidate
Thijs Vos PhD candidate
Rick van Well PhD candidate
Daan van den Wollenberg PhD candidate / self funded
Elina Zorina PhD candidate
Rudy Andeweg Professor emeritus of Empirical Political Science
Ivan Bakalov Lecturer
Agha Bayramov Lecturer
Jelena Belic Lecturer
Jelke Bethlehem Professor emeritus Survey Methodology
Peter Castenmiller Lecturer
Diana Davila Gordillo Guest - researcher
Henk Dekker Emeritus professor of Political Socialization and Integration
Katerina Galanopoulou Lecturer
Rutger Hagen Lecturer
Henriëtte van den Heuvel Scientific Director ad interim
Galen Irwin
Devrim Kabasakal Badamchi- Guest researcher
Eleftherios Karchimakis Lecturer
Müge Kinacioglu Lecturer
Ruud Koole Professor emeritus Politicologie
Amber Lauwers Lecturer
José Lourenço Lecturer
Gjovalin Macaj Assistant professor
Jan Meijer Lecturer
Marijn Nagtzaam Lecturer
Christoph Niessen Guest
Alexandros Ntaflos Lecturer
Joyce Outshoorn Emeritus professor vrouwenstudies
Jimena Pacheco Miranda Lecturer
Julia Puente Duyn Lecturer
Ellen van Reuler Lecturer
Thomas Scarff Lecturer
Radostina Sharenkova-Toshkova Guest
Justin Spruit Lecturer
Vishwesh Sundar Lecturer
Harmen van der Veer Lecturer
Amy Verdun Guest professor
Ruben Verheul Web editor
Anouk van Vliet Lecturer
Carina van de Wetering Lecturer
Gul-i-Hina van der Zwan Postdoc / guest
Wencke Appelman Study adviser
Ester Blom Study Adviser
Anna van Dijk Management/office-assistent
Nathalie van Dooren Coordinator Marketing & Student Recruitment
Desiree van Drongelen Staff Member Studentregistrations
Nynke Heegstra Study Advisor
Ingrid van Heeringen-Göbbels Institute manager
Marit van der Heide Student member institute board
Katie Hudson Data Steward
Lianne Janssen Policy Officer Education and Quality Assurance
Eline Joor Internationalisation Officer
Ian Lau Study adviser
Daniëlle Lovink Study Adviser
Carien Nelissen Education manager
Caroline Remmerswaal Secretary board of examiners
Marjan Rijnja Teaching Coordinator
Elka Smith Research Project Manager
Judy Spruit Management/office-assistent
Tessa Thomas Management assistant
Debbie Tromper Management assistant
Gerard van der Veer Secretary board of examiners
Ruben Verheul Web editor
Jeanne Viet Communication staff member
Denise Zeeuw-van Veen Management/office assistant

Not bad.

3.4 Love Scraping?

Suppose you do not like datawrangling but simply love scraping.

3.4.1 R

lpol_staff_names <- read_html("https://www.universiteitleiden.nl/en/social-behavioural-sciences/political-science/staff#tab-1") %>%
    html_element("section.tab.active") %>%
    html_elements("ul.table-list") %>%
    html_elements("li") %>%
    html_elements("a") %>%
    html_elements("div") %>%
    html_elements("strong") %>%
    html_text()

lpol_staff_functions <- read_html("https://www.universiteitleiden.nl/en/social-behavioural-sciences/political-science/staff#tab-1") %>%
    html_element("section.tab.active") %>%
    html_elements("ul.table-list") %>%
    html_elements("li") %>%
    html_elements("a") %>%
    html_elements("div") %>%
    html_elements("span") %>%
    html_text()

lpol_staff2 <- data.frame(name = lpol_staff_names, funct = lpol_staff_functions)
fshowdf(lpol_staff2)
name funct
Adina Akbik Senior Assistant Professor
Femke Bakker Senior assistant professor
Ingrid van Biezen Professor of Comparative Politics
Nicolas Blarel Associate Professor
Arjen Boin Professor of Public Institutions and Governance
Theo Brinkel Professor by Special Appointment Military-social studies
Manuel Cabal Lopez Assistant professor
Valentina Carraro Assistant Professor in Global Transformations and Governance Challenges
Stefan Cetkovic Assistant Professor
Leila Demarest Associate Professor
Matthew di Giuseppe Director of Studies / Associate Professor
Roos van der Haer Assistant Professor
Gisela Hirschmann Senior Assistant Professor
Joop van Holsteijn Professor Political Behaviour and Research Methods
Corinna Jentzsch Assistant Professor
Petr Kopecky Professor of Comparative Studies Political Parties and Party Systems
Matthew Longo Senior Assistant Professor
Tom Louwerse Director of Research / Associate Professor
Floris Mansvelt Beck Assistant Professor
Juan Masullo Jimenez Assistant Professor
Hilde van Meegdenburg Assistant Professor
Michael Meffert Assistant Professor
Frits Meijerink Assistant professor
Tim Mickler Senior assistant professor
Martijn Mos Assistant Professor
Katharina Natter Senior assistant professor
Paul Nieuwenburg Professor Political Philosophy
Simon Otjes Senior Assistant Professor
Hans Oversloot Senior Assistant Professor
Jonathan Phillips Assistant Professor
Rebecca Ploof Assistant Professor
Karolina Pomorska Associate professor
Francesco Ragazzi Associate professor
Babak Rezaeedaryakenari Senior Assistant Professor
Josh Robison Assistant Professor
Michael Sampson Senior Assistant Professor
Jan Aart Scholte Professor Global Transformations and Governance Challenges
Jonah Schulhofer-Wohl Senior assistant professor
Maria Spirova Associate Professor
Tom Theuns Senior Assistant Professor
Daniel Thomas Professor of International Relations
Christina Luise Toenshoff Assistant professor
Vasiliki (Billy) Tsagkroni Senior assistant professor
Wouter Veenendaal Professor by Special Appointment Kingdom Relations
Claire Vergerio Guest
Marco Verschoor Assistant Professor
Cynthia van Vonno Assistant Professor
Niels van Willigen Director of Studies/Associate Professor
Nikoleta Yordanova Associate professor
Yuan Yi Zhu Assistant professor
Frank de Zwart Guest

3.4.2 Python

url = "https://www.universiteitleiden.nl/en/social-behavioural-sciences/political-science/staff#tab-1"
response = requests.get(url)
webpage = response.content

# Parse the webpage
soup = BeautifulSoup(webpage, 'html.parser')

#save the tags 'li' in a list
soup2 = soup.select_one('section.tab.active').select_one('ul.table-list').find_all("li")

#loop through the list to find what we want
lpol_staff_names = [taga.select_one("strong").get_text() for taga in soup2]
lpol_staff_functions = [taga.select_one("span").get_text() for taga in soup2]

# Combine data into a DataFrame
lpol_staff_df = pd.DataFrame({
    'name': lpol_staff_names,
    'funct': lpol_staff_functions
})

# Display the DataFrame
print(lpol_staff_df.head())  
                name                                            funct
0        Adina Akbik                       Senior Assistant Professor
1       Femke Bakker                       Senior assistant professor
2  Ingrid van Biezen                Professor of Comparative Politics
3     Nicolas Blarel                              Associate Professor
4         Arjen Boin  Professor of Public Institutions and Governance
LS0tDQp0aXRsZTogIldlYnNjcmFwaW5nIHNjaG9sYXJzIg0KYmlibGlvZ3JhcGh5OiByZWZlcmVuY2VzLmJpYg0KbGluay1jaXRhdGlvbnM6IHllcw0KLS0tDQoNCg0KYGBge3IsIGdsb2JhbHNldHRpbmdzLCBlY2hvPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30NCmxpYnJhcnkoa25pdHIpDQpvcHRzX2NodW5rJHNldCh0aWR5Lm9wdHM9bGlzdCh3aWR0aC5jdXRvZmY9MTAwKSx0aWR5PVRSVUUsICB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSxjb21tZW50ID0gIiM+IiwgY2FjaGU9VFJVRSwgY2xhc3Muc291cmNlPWMoInRlc3QiKSwgY2xhc3Mub3V0cHV0PWMoInRlc3QyIiksIGNhY2hlLmxhenkgPSBGQUxTRSkNCm9wdGlvbnMod2lkdGggPSAxMDApIA0KcmdsOjpzZXR1cEtuaXRyKCkNCmBgYA0KDQpgYGB7ciBrbGlwcHksIGVjaG89RkFMU0UsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgZXZhbD1UUlVFfQ0KIyBpbnN0YWxsLnBhY2thZ2VzKCJyZW1vdGVzIikNCiNyZW1vdGVzOjppbnN0YWxsX2dpdGh1Yigicmxlc3VyL2tsaXBweSIpDQprbGlwcHk6OmtsaXBweShwb3NpdGlvbiA9IGMoJ3RvcCcsICdyaWdodCcpKQ0KI2tsaXBweTo6a2xpcHB5KGNvbG9yID0gJ2RhcmtyZWQnKQ0KI2tsaXBweTo6a2xpcHB5KHRvb2x0aXBfbWVzc2FnZSA9ICdDbGljayB0byBjb3B5JywgdG9vbHRpcF9zdWNjZXNzID0gJ0RvbmUnKQ0KYGBgDQoNCiMgR29hbCAgDQoNCioqSGFuZHMtb24gd2Vic2NyYXBpbmcqKiAgDQoNCkl0IGlzIHRpbWUgdG8gZG8gc29tZSB3ZWJzY3JhcGluZyBvdXJzZWx2ZXMuIEluIHdoYXQgZm9sbG93cyBpcyBhIHNob3J0IGZpcnN0IHR1dG9yaWFsIG9uIHdlYnNjcmFwaW5nIHdoZXJlIHdlIHdpbGwgYmUgY29sbGVjdGluZyBkYXRhIGZyb20gd2VicGFnZXMgb24gdGhlIGludGVybmV0LiBXZSB3aWxsIHVzZSB0aGUgc3BlY2lmaWMgdXNlIGNhc2Ugb2YgdGhlIHBvbGl0aWNhbCBzY2llbmNlIGRlcGFydG1lbnQgc3RhZmYgb2YgdGhlIHVuaXZlcnNpdHkgb2YgTGVpZGVuLiANCg0KV2hhdCBkbyB0aGV5IHB1Ymxpc2g/IFdoZXJlPyBBbmQgd2l0aCB3aG9tIGRvIHRoZXkgY29sbGFib3JhdGU/IFdlIGFzc3VtZSB5b3UgaGF2ZSBhdCBsZWFzdCBzb21lIGV4cGVyaWVuY2Ugd2l0aCBjb2RpbmcgaW4gUi4gSW4gdGhlIHJlc3Qgb2YgdGhpcyBwYXJ0IG9mIHRoZSB0dXRvcmlhbCwgd2Ugd2lsbCBzd2l0Y2ggYmV0d2VlbiBiYXNlIFIgYW5kIFRpZHl2ZXJzZSAoanVzdCBhIGJpdCksIHdoYXRldmVyIGlzIG1vc3QgY29udmVuaWVudC4gKE5vdGUgdGhhdCB0aGlzIHdpbGwgaGFwcGVuIG9mdGVuIGlmIHlvdSBiZWNvbWUgYW4gYXBwbGllZCBjb21wdXRhdGlvbmFsIHNvY2lvbG9naXN0LikgV2UgYWxzbyBvZmZlciBQeXRob24gY29kZS4gDQoNClRoZXJlIGFyZSBkaWZmZXJlbnQgc3RyYXRlZ2llcyBpbiBzY3JhcGluZy4gVGhlcmUgaXMgb2Z0ZW4gYSB0cmFkZS1vZmYgYmV0d2VlbiBjb21wbGV4IHNjcmFwaW5nIHRlY2huaXF1ZXMgdmVyc3VzIGNvbXBsZXggc3RyaW5nIG1hbmlwdWxhdGlvbnMuIEluIHRoaXMgZmlyc3QgZXhhbXBsZSwgd2Ugd2lsbCB1c2UgcXVpdGUgYSBsb3Qgb2Ygc3RyaW5nIG1hbmlwdWxhdGlvbnMuIA0KDQpGb3IgZXZlbiBtb3JlIGluZm8gc2VlIG91ciBbU05BU1MgYm9vayAtIENoYXB0ZXIgMTFdKGh0dHBzOi8vc25hc3MubmV0bGlmeS5hcHAvd2ViaW50cm8uaHRtbCkgIA0KDQotLS0gIA0KDQojIFByZXBhcmF0aW9uDQoNCiMjIGNsZWFuIHVwDQpgYGB7ciwgY2xlYW51cCwgcmVzdWx0cz0naGlkZSd9DQpybShsaXN0PWxzKCkpDQpnYygpDQpgYGANCg0KIyMgQ3VzdG9tIGZ1bmN0aW9ucw0KDQotIGBmcGFja2FnZS5jaGVja2A6IENoZWNrIGlmIHBhY2thZ2VzIGFyZSBpbnN0YWxsZWQgKGFuZCBpbnN0YWxsIGlmIG5vdCkgaW4gUi4gIA0KLSBgZnNhdmVgOiBTYXZlIHRvIHByb2Nlc3NlZCBkYXRhIGluIHJlcG9zaXRvcnkgIA0KLSBgZmxvYWRgOiBUbyBsb2FkIHRoZSBmaWxlcyBiYWNrIGFmdGVyIGFuIGBmc2F2ZWAgIA0KLSBgZnNob3dkZmA6IFRvIHByaW50IG9iamVjdHMgKHRpYmJsZXMgLyBkYXRhLmZyYW1lKSBuaWNlbHkgb24gc2NyZWVuIGluIC5ybWQgIA0KDQoNCmBgYHtyIGN1c3RvbWZ1bmN0aW9ucywgcmVzdWx0cz0naGlkZScsIGV2YWwgPSBUUlVFfQ0Kcm0obGlzdCA9IGxzKCkpICNjbGVhbiB1cCB5b3VyIGVudmlyb25tZW50DQoNCmZwYWNrYWdlLmNoZWNrIDwtIGZ1bmN0aW9uKHBhY2thZ2VzKSB7DQogIGxhcHBseShwYWNrYWdlcywgRlVOID0gZnVuY3Rpb24oeCkgew0KICAgIGlmICghcmVxdWlyZSh4LCBjaGFyYWN0ZXIub25seSA9IFRSVUUpKSB7DQogICAgICBpbnN0YWxsLnBhY2thZ2VzKHgsIGRlcGVuZGVuY2llcyA9IFRSVUUpDQogICAgICBsaWJyYXJ5KHgsIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkNCiAgICB9DQogIH0pDQp9DQoNCmZzYXZlIDwtIGZ1bmN0aW9uKHgsIGZpbGU9TlVMTCwgbG9jYXRpb249Ii4vZGF0YS9wcm9jZXNzZWQvIikgew0KICBpZmVsc2UoIWRpci5leGlzdHMoImRhdGEiKSwgZGlyLmNyZWF0ZSgiZGF0YSIpLCBGQUxTRSkNCiAgaWZlbHNlKCFkaXIuZXhpc3RzKCJkYXRhL3Byb2Nlc3NlZCIpLCBkaXIuY3JlYXRlKCJkYXRhL3Byb2Nlc3NlZCIpLCBGQUxTRSkNCiAgaWYgKGlzLm51bGwoZmlsZSkpIGZpbGU9IGRlcGFyc2Uoc3Vic3RpdHV0ZSh4KSkNCiAgZGF0ZW5hbWUgPC0gc3Vic3RyKGdzdWIoIls6LV0iLCAiIiwgU3lzLnRpbWUoKSksIDEsOCkgIA0KICB0b3RhbG5hbWUgPC0gcGFzdGUobG9jYXRpb24sIGRhdGVuYW1lLCBmaWxlLCAiLnJkYSIsIHNlcD0iIikNCiAgc2F2ZSh4LCBmaWxlID0gdG90YWxuYW1lKSAgI25lZWQgdG8gZml4IGlmIGZpbGUgaXMgcmVsb2FkZWQgYXMgaW5wdXQgbmFtZSwgbm90IGFzIHguIA0KfQ0KDQpmbG9hZCA8LSBmdW5jdGlvbihmaWxlbmFtZSkgew0KICBsb2FkKGZpbGVuYW1lKQ0KICBnZXQobHMoKVtscygpICE9ICJmaWxlbmFtZSJdKQ0KfQ0KDQpmc2hvd2RmIDwtICBmdW5jdGlvbih4LCAuLi4pIHsNCiAga25pdHI6OmthYmxlKHgsIGRpZ2l0cz0yLCAiaHRtbCIsIC4uLikgJT4lDQogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIikpICU+JQ0KICBrYWJsZUV4dHJhOjpzY3JvbGxfYm94KHdpZHRoPSIxMDAlIiwgaGVpZ2h0PSAiMzAwcHgiKQ0KfSANCg0KY29sb3JpemUgPC0gZnVuY3Rpb24oeCwgY29sb3IpIHtzcHJpbnRmKCI8c3BhbiBzdHlsZT0nY29sb3I6ICVzOyc+JXM8L3NwYW4+IiwgY29sb3IsIHgpIH0NCg0KYGBgDQoNCi0tLS0NCg0KIyMgUGFja2FnZXMgey50YWJzZXQgLnRhYnNldC1mYWRlfQ0KDQojIyMgUiANCg0KLSBgdGlkeXZlcnNlYDogZm9yIHBpcGluZyBldGMuIA0KLSBgaHR0cmA6IFRvb2xzIGZvciBXb3JraW5nIHdpdGggVVJMcyBhbmQgSFRUUCAgDQotIGB4bWwyYDogV29yayB3aXRoIFhNTCBmaWxlcyB1c2luZyBhIHNpbXBsZSwgY29uc2lzdGVudCBpbnRlcmZhY2UuICANCi0gYHJ2ZXN0YDogV3JhcHBlcnMgYXJvdW5kIHRoZSAneG1sMicgYW5kICdodHRyJyBwYWNrYWdlcyB0byBtYWtlIGl0IGVhc3kgdG8gZG93bmxvYWQsIHRoZW4gbWFuaXB1bGF0ZSwgSFRNTCBhbmQgWE1MLiAgDQotIGByZXNoYXBlMmA6IEZsZXhpYmx5IFJlc2hhcGUgRGF0YSAgDQoNCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30NCnBhY2thZ2VzID0gYygidGlkeXZlcnNlIiwgImh0dHIiLCAicnZlc3QiLCAicmVzaGFwZTIiLCAieG1sMiIpDQpmcGFja2FnZS5jaGVjayhwYWNrYWdlcykNCmBgYA0KDQotLS0gIA0KDQojIyMgUHl0aG9uDQoNCk1ha2Ugc3VyZSB5b3UgaGF2ZSB0aGUgcmVxdWlyZWQgbGlicmFyaWVzIGJ5IHR5cGluZyBpbiB0aGUgY29kZSBiZWxvdyBpbnRvIHlvdXIgY29uc29sZQ0KDQpgYGB7YmFzaCwgZXZhbCA9IEZBTFNFfQ0KcGlwIGluc3RhbGwgcmVxdWVzdHMgYmVhdXRpZnVsc291cDQgcGFuZGFzDQpgYGAgDQoNCjxicj4NCg0KYW5kIGltcG9ydCBuZWNlc3NhcnkgbW9kdWxlcw0KDQpgYGB7cHl0aG9uLCBldmFsID0gRkFMU0V9DQoNCmltcG9ydCByZXF1ZXN0cw0KZnJvbSBiczQgaW1wb3J0IEJlYXV0aWZ1bFNvdXANCmltcG9ydCBwYW5kYXMgYXMgcGQNCmltcG9ydCByZQ0KDQpgYGAgDQoNCjxicj4NCg0KDQojIExlaWRlbiBVbml2ZXJzaXR5OiBQb2xpdGljYWwgU2NpZW5jZQ0KDQpXaGF0IGRvIHdlIG1lYW4gYnkgYW5jaG9yIGRhdGE/IE91ciBnb2FsIGlzIHRvIGdldCB0byBrbm93OiAgDQoNCmkuIHdobyB0aGUgUG9saXRpY2FsIFNjaWVuY2Ugc3RhZmYgaXMgYXQgc2V2ZXJhbCB1bml2ZXJzaXRpZXMsICANCmlpLiB3aGF0IHRoZXkgcHVibGlzaCB3aXRoIHJlc3BlY3QgdG8gc2NpZW50aWZpYyB3b3JrLCBhbmQgIA0KaWlpLiB3aG8gdGhleSBjb2xsYWJvcmF0ZSB3aXRoLiAgDQoNClNvIHRoYXQgbWVhbnMgYXQgbGVhc3QgdGhyZWUgZGF0YSBzb3VyY2VzIHdlIG5lZWQgdG8gY29sbGVjdCBmcm9tIHNvbWV3aGVyZS4gV2hhdCB3b3VsZCBiZSBhIG5pY2Ugc3RhcnRpbmcgKHJlYWQ6IGFuY2hvcikgcG9pbnQgYmU/IEZpcnN0LCB3ZSBoYXZlIHRvIGtub3cgd2hvIGlzIHN0YWZmLiBMZXQncyBjaGVjayBvdXQgW3RoZSBMZWlkZW4gcG9saXRpY2FsIHNjaWVuY2Ugc3RhZmYgd2Vic2l0ZV0oaHR0cHM6Ly93d3cudW5pdmVyc2l0ZWl0bGVpZGVuLm5sL2VuL3NvY2lhbC1iZWhhdmlvdXJhbC1zY2llbmNlcy9wb2xpdGljYWwtc2NpZW5jZS9zdGFmZiN0YWItMSkuIEhlcmUgd2Ugc2VlIGEgbmljZSBsaXN0IG9uIHdobyBpcyBvbiB0aGUgc3RhZmYgaW4gc2V2ZXJhbCBwYWdlcy4gSG93IGRvIHdlIGdldCB0aGF0IGRhdGE/IEl0IGlzIGFjdHVhbGx5IHF1aXRlIHNpbXBsZSwgdGhlIHBhY2thZ2UgW2BydmVzdGBdKGh0dHBzOi8vcnZlc3QudGlkeXZlcnNlLm9yZy9pbmRleC5odG1sKSBoYXMgYSB2ZXJ5IG5pY2UgZnVuY3Rpb24gW2BodG1sX3JlYWQoKWBdKGh0dHBzOi8vcnZlc3QudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvcmVhZF9odG1sLmh0bWwpIChhY3R1YWxseSB0aGlzIGNvbWVzIGZyb20gdGhlIGB4bWwyYCBwYWNrYWdlKSB3aGljaCBzaW1wbHkgZGVyaXZlcyB0aGUgc291cmNlIGh0bWwgb2YgYSAqc3RhdGljKiB3ZWJwYWdlOg0KDQoNCjwhLS0gYHIgY29sb3JpemUoIkhvdyBpbmNyZWRpYmx5IGFubm95aW5nLCB3aGVuIHRyeWluZyB0byBkbyB0aGUgZmluYWwga25pdCBmb3IgdGhlIHdvcmtzaG9wLCBMZWlkZW4gdW5pdmVyc2l0eSBkZWNpZGVkIHRvIGRvIG1haW50YW5jZSB3b3JrIG9uIHRoZWlyIHdlYnNpdGUgb24gU2F0dXJkYXkgMjIgYW5kIFN1bmRheSAyMyBKdW5lISEhISEhISIsICJyZWQiKWAgLS0+DQoNCiMjIFN0YWZmIHBhZ2VzIHsudGFic2V0IC50YWJzZXQtZmFkZX0NCg0KIyMjIFIgDQoNCkxldCdzIGZpcnN0IHNpbXBseSBnZXQgdGhlIHN0YWZmIHBhZ2VzLg0KYHJlYWRfaHRtbCgpYCBpcyBhIGZ1bmN0aW9uIHRoYXQgc2ltcGx5IGV4dHJhY3RzIGh0bWwgd2VicGFnZXMgYW5kIHB1dHMgdGhlbSBpbiB4bWwgZm9ybWF0Lg0KDQpgYGB7cn0NCmxwb2xfc3RhZmYgPC0gcmVhZF9odG1sKCJodHRwczovL3d3dy51bml2ZXJzaXRlaXRsZWlkZW4ubmwvZW4vc29jaWFsLWJlaGF2aW91cmFsLXNjaWVuY2VzL3BvbGl0aWNhbC1zY2llbmNlL3N0YWZmI3RhYi0xIikNCmhlYWQobHBvbF9zdGFmZikNCmBgYA0KVGhhdCBsb29rcyBraW5kYSB3ZWlyZC4gV2hhdCB0eXBlIG9mIG9iamVjdCBkaWQgd2Ugc3RvcmUgaXQgYnkgcHV0dGluZyB0aGUgaHRtbCBpbnRvIGBscG9sX3N0YWZmMWA/DQoNCmBgYHtyfQ0KY2xhc3MobHBvbF9zdGFmZikNCmBgYA0KDQpTbyBpdCBpcyBpcyBzdG9yZWQgaW4gc29tZXRoaW5nIHRoYXQncyBjYWxsZWQgYW4geG1sIG9iamVjdC4gTm90IGltcG9ydGFudCBmb3Igbm93IHdoYXQgdGhhdCBpcy4gQnV0IGl0IGlzIGltcG9ydGFudCB0byBleHRyYWN0IHRoZSByZWxldmFudCB0YWJsZSB0aGF0IHdlIHNhdyBvbiB0aGUgc3RhZmYgd2Vic2l0ZS4gSG93IGRvIHdlIGRvIHRoYXQ/IEdvIHRvIG9uZSBvZiB0aGUgbGlua3MgYWJvdmUgaW4gYSBicm93c2VyIGFuZCB0aGVuIHByZXNzICJJbnNwZWN0IiBvbiB0aGUgd2VicGFnZSAodXN1YWxseTogcmlnaHQgY2xpY2stLVw+SW5zcGVjdCkuIEluIHRoZSBodG1sIGNvZGUgd2UgZXh0cmFjdGVkLCB3ZSBuZWVkIHRvIGdvIHRvIG9uZSBvZiB0aGUgbm9kZXMgZmlyc3QuIElmIHlvdSBtb3ZlIHlvdXIgY3Vyc29yIG92ZXIgKioiZGl2IioqIGluIHRoZSBodG1sIGNvZGUgb24gdGhlIHNjcmVlbiwgdGhlIGVudGlyZSAqKiJib2R5IioqIG9mIHRoZSBwYWdlIHNob3VsZCBiZWNvbWUgc29tZSBzaGFkZSBvZiBibHVlLiBUaGlzIG1lYW5zIHRoYXQgdGhlIGVsZW1lbnRzIGVuY2Fwc3VsYXRlZCBpbiB0aGUgKioiYm9keSIqKiBub2RlIGNhcHR1cmVzIGV2ZXJ5dGhpbmcgdGhhdCB0dXJuZWQgYmx1ZS4NCg0KDQojIyMgUHl0aG9uDQoNCmBgYHtweXRob24sIGV2YWwgPSBGQUxTRX0NCiMgRmV0Y2ggdGhlIHdlYnBhZ2UNCnVybCA9ICJodHRwczovL3d3dy51bml2ZXJzaXRlaXRsZWlkZW4ubmwvZW4vc29jaWFsLWJlaGF2aW91cmFsLXNjaWVuY2VzL3BvbGl0aWNhbC1zY2llbmNlL3N0YWZmI3RhYi0xIg0KDQpyZXNwb25zZSA9IHJlcXVlc3RzLmdldCh1cmwpDQp3ZWJwYWdlID0gcmVzcG9uc2UuY29udGVudA0KDQojIFBhcnNlIHRoZSB3ZWJwYWdlDQpzb3VwID0gQmVhdXRpZnVsU291cCh3ZWJwYWdlLCAnaHRtbC5wYXJzZXInKQ0KDQojcHJpbnQoc291cC5wcmV0dGlmeSgpKQ0KDQojb3IgdG8gcHJpbnQganVzdCBhIGZldyBsaW5lcw0KcmVzdWx0ID0gc291cC5wcmV0dGlmeSgpLnNwbGl0bGluZXMoKQ0KcHJpbnQoJ1xuJy5qb2luKHJlc3VsdFs6MTBdKSkNCmBgYA0KYGBgDQo8IURPQ1RZUEUgaHRtbD4NCjxodG1sIGRhdGEtdmVyc2lvbj0iMS4xNzguMDAiIGxhbmc9ImVuIj4NCiA8aGVhZD4NCiAgPCEtLSBzdGFuZGFyZCBwYWdlIGh0bWwgaGVhZCAtLT4NCiAgPHRpdGxlPg0KICAgU3RhZmYgLSBMZWlkZW4gVW5pdmVyc2l0eQ0KICA8L3RpdGxlPg0KICA8bWV0YSBjb250ZW50PSJvOEtZdUZBaVNaaTZRV1cxd3hxS0Z2VDFXUXdOLUJ4cnVVNDJzaTlZalh3IiBuYW1lPSJnb29nbGUtc2l0ZS12ZXJpZmljYXRpb24iLz4NCiAgPG1ldGEgY29udGVudD0iaFJVeHJxSUFSTWluTFcyZFJYclBwbXRMdHltbk9Uc2cwUGwzV2pIV1E0dyIgbmFtZT0iZ29vZ2xlLXNpdGUtdmVyaWZpY2F0aW9uIi8+DQogIDxsaW5rIGhyZWY9Imh0dHBzOi8vd3d3LnVuaXZlcnNpdGVpdGxlaWRlbi5ubC9lbi9zb2NpYWwtYmVoYXZpb3VyYWwtc2NpZW5jZXMvcG9saXRpY2FsLXNjaWVuY2Uvc3RhZmYiIHJlbD0iY2Fub25pY2FsIi8+DQpgYGANCg0KDQojIyBOb2RlcyBhbmQgdGFncyB7LnRhYnNldCAudGFic2V0LWZhZGV9DQoNCg0KTmV4dCwgd2UgbmVlZCB0byBsb29rIGF0IHRoZSBzcGVjaWZpYyBlbGVtZW50cyBvbiB0aGUgcGFnZSB0aGF0IHdlIG5lZWQgdG8gZXh0cmFjdC4gU29tZXdoYXQgYnkgaW5mb3JtZWQgdHJpYWwgYW5kIGVycm9yLCBsb29raW5nIGZvciB0aGUgY29ycmVjdCBjb2RlLCB3ZSBjYW4gc2VsZWN0IHRoZSBlbGVtZW50cyB3ZSB3YW50LiBTbyB3ZSBuZWVkIGNvZGUgdGhhdCBsb29rcyBmb3IgdGhlIG5vZGUgKioibWFpbiIqKiBhbmQgdGhlICoqInRkIioqIGVsZW1lbnRzIGluIHRoZSB4bWwgb2JqZWN0IGFuZCB0aGVuIGV4dHJhY3QgdGhvc2UgZWxlbWVudHMgaW4gaXQuIE5vdGUgdGhhdCB5b3UgY2FuIGNsaWNrIG9uIHRoZSBhcnJvd3Mgb25jZSB5b3UgYXJlIGluIHRoZSAiSW5zcGVjdCIgbW9kZSBpbiB0aGUgd2ViIGJyb3dzZXIgdG8gdHJpYWwtYW5kLWVycm9yIHRvIGdldCBhdCB0aGUgY29ycmVjdCBlbGVtZW50cy4gIA0KDQpUaGVyZSBhcmUgbWFueSB3YXlzIHRvIGZpbmQgdGhlIENTUyBvciBYcGF0aCBvZiB0aGUgZWxlbWVudHMgeW91IHdhbnQuIA0KVGhlIHBhY2thZ2UgYHJ2ZXN0YCBhbHNvIGhhcyBhIFNlbGVjdG9yR2FkZ2V0IHRvb2wuIFNlZSBbaGVyZV0oaHR0cHM6Ly9ydmVzdC50aWR5dmVyc2Uub3JnL2FydGljbGVzL3NlbGVjdG9yZ2FkZ2V0Lmh0bWwpDQoNCldvIHdlIG5lZWQgdG8gZmluZCBXSEVSRSB0aGUgdGFibGUgaXMgbG9jYXRlZCBpbiB0aGUgaHRtbC4gDQoNCkFzc2lnbm1lbnQ6ICANCi0gdXNlIGZpcmVmb3ggJ2luc3BlY3QnICANCi0gdXNlIHJzdHVkaW8gIA0KLSB1c2UgYHJ2ZXN0YCBTZWxlY3RvckdhZGdldA0KDQojIyMgUiANCg0KYGBge3J9DQpscG9sX3N0YWZmIDwtIGxwb2xfc3RhZmYgJT4lIA0KICBodG1sX25vZGVzKCJib2R5IikgJT4lDQogIGh0bWxfbm9kZXMoeHBhdGggPSAiLy9hIikgJT4lIA0KICBodG1sX3RleHQoKQ0KYGBgDQoNCkxldCB1cyBmb3Igbm93IGFzc3VtZSB5b3UgYXJlIHNhdGlzZmllZCB3aXRoIHRoZSBhYm92ZSBvdXRwdXQuDQoNCiMjIyBQeXRob24NCmBgYHtweXRob24sIGV2YWwgPSBGQUxTRX0NCmxwb2xfc3RhZmYgPSBzb3VwLmJvZHkuZmluZF9hbGwoJ2EnKQ0KDQpscG9sX3N0YWZmID0gW3RhZ3MucHJldHRpZnkoKSBmb3IgdGFncyBpbiBscG9sX3N0YWZmXQ0KcHJpbnQobHBvbF9zdGFmZls6MTBdKQ0KYGBgDQpgYGANClsnPGEgY2xhc3M9ImFjdGl2ZSB0cmFjay1ldmVudCIgZGF0YS1ldmVudC1jYXRlZ29yeT0iZXh0ZXJuYWwtc2l0ZSIgZGF0YS1ldmVudC1sYWJlbD0iVG9wbWVudSBleHRlcm5hbC1zaXRlIiBocmVmPSIvZW4iPlxuIExlaWRlbiBVbml2ZXJzaXR5XG48L2E+XG4nLCAnPGEgY2xhc3M9InRyYWNrLWV2ZW50IiBkYXRhLWV2ZW50LWNhdGVnb3J5PSJzdHVkZW50LXNpdGUiIGRhdGEtZXZlbnQtbGFiZWw9IlRvcG1lbnUgZXh0ZXJuYWwtc2l0ZSIgaHJlZj0iaHR0cHM6Ly93d3cuc3R1ZGVudC51bml2ZXJzaXRlaXRsZWlkZW4ubmwvZW4iPlxuIFN0dWRlbnRzXG48L2E+XG4nLCAnPGEgY2xhc3M9InRyYWNrLWV2ZW50IiBkYXRhLWV2ZW50LWNhdGVnb3J5PSJzdGFmZm1lbWJlci1zaXRlIiBkYXRhLWV2ZW50LWxhYmVsPSJUb3BtZW51IGV4dGVybmFsLXNpdGUiIGhyZWY9Imh0dHBzOi8vd3d3LnN0YWZmLnVuaXZlcnNpdGVpdGxlaWRlbi5ubC8iPlxuIFN0YWZmIG1lbWJlcnNcbjwvYT5cbicsICc8YSBjbGFzcz0idHJhY2stZXZlbnQiIGRhdGEtZXZlbnQtY2F0ZWdvcnk9Im9yZy1zaXRlIiBkYXRhLWV2ZW50LWxhYmVsPSJUb3BtZW51IGV4dGVybmFsLXNpdGUiIGhyZWY9Imh0dHBzOi8vd3d3Lm9yZ2FuaXNhdGllZ2lkcy51bml2ZXJzaXRlaXRsZWlkZW4ubmwvZW4iPlxuIE9yZ2FuaXNhdGlvbmFsIHN0cnVjdHVyZVxuPC9hPlxuJywgJzxhIGNsYXNzPSJ0cmFjay1ldmVudCIgZGF0YS1ldmVudC1jYXRlZ29yeT0ibGlicmFyeS1zaXRlIiBkYXRhLWV2ZW50LWxhYmVsPSJUb3BtZW51IGV4dGVybmFsLXNpdGUiIGhyZWY9Imh0dHBzOi8vd3d3LmxpYnJhcnkudW5pdmVyc2l0ZWl0bGVpZGVuLm5sLyI+XG4gTGlicmFyeVxuPC9hPlxuJywgJzxhIGhyZWY9Ii9lbiI+XG4gPGltZyBhbHQ9IlVuaXZlcnNpdGVpdCBMZWlkZW4iIGhlaWdodD0iNjQiIHNyYz0iL2VuL2Rlc2lnbi0xLjAvYXNzZXRzL2ltYWdlcy96ZWdlbC5wbmciIHdpZHRoPSIxNTEiLz5cbjwvYT5cbicsICc8YSBkYXRhLWhpZGRlbj0iIiBkYXRhLWhpbnQ9IlNlYXJjaCBmb3Igc3ViamVjdCBvciBwZXJzb24iIGhyZWY9Ii9lbi9zZWFyY2giPlxuIEFsbCBjYXRlZ29yaWVzXG48L2E+XG4nLCAnPGEgZGF0YS1oaWRkZW49XCd7ImNvbnRlbnQtY2F0ZWdvcnkiOiJzdGFmZm1lbWJlciJ9XCcgZGF0YS1oaW50PSJTZWFyY2ggZm9yXHhhMHBlcnNvbnMiIGhyZWY9Ii9lbi9zZWFyY2giPlxuIFBlcnNvbnNcbjwvYT5cbicsICc8YSBkYXRhLWhpZGRlbj1cJ3siY29udGVudC1jYXRlZ29yeSI6ImVkdWNhdGlvbiJ9XCcgZGF0YS1oaW50PSJTZWFyY2ggZm9yXHhhMGVkdWNhdGlvbiIgaHJlZj0iL2VuL3NlYXJjaCI+XG4gRWR1Y2F0aW9uXG48L2E+XG4nLCAnPGEgZGF0YS1oaWRkZW49XCd7ImNvbnRlbnQtY2F0ZWdvcnkiOiJyZXNlYXJjaCJ9XCcgZGF0YS1oaW50PSJTZWFyY2ggZm9yXHhhMHJlc2VhcmNoIiBocmVmPSIvZW4vc2VhcmNoIj5cbiBSZXNlYXJjaFxuPC9hPlxuJ10NCmBgYA0KDQojIyBDbGVhbmluZyANCg0KIyMjIFIgDQoNClNlZW1zIGxpa2UgbW9yZSB1c2VmdWwgZGF0YSBub3cuIEJ1dCBjYW4gd2UgaW1wcm92ZSBieSBkZWxldGluZyBzb21lIGVsZW1lbnRzIHdlIGRvIG5vdCBuZWVkPyBMZXQncyBmaXJzdCBkZWxldGUgc29tZSBvZiB0aGUgdXNlbGVzcyBpbmZvcm1hdGlvbi4NCg0KYGBge3J9DQpoZWFkKGxwb2xfc3RhZmYpDQpscG9sX3N0YWZmIDwtbHBvbF9zdGFmZlstYygxOjM5KV0NCmxwb2xfc3RhZmY8LSBscG9sX3N0YWZmWy1jKDEzMzpsZW5ndGgobHBvbF9zdGFmZikpXQ0KaGVhZChscG9sX3N0YWZmKQ0KDQpmc2hvd2RmKGxwb2xfc3RhZmYpDQpgYGANCg0KU3RpbGwgbG9va3MgYSBiaXQgbWVzc3kuIENhbiB3ZSBnZXQgaXQgaW50byBhIGRhdGFmcmFtZSBhbmQgc3BsaXQgdGhlIGNvbHVtbiBpbnRvIHVzZWZ1bCBjb2x1bW5zPw0KDQpgYGB7cn0NCmxwb2xfc3RhZmYgPC0gZGF0YS5mcmFtZShscG9sX3N0YWZmKQ0KbHBvbF9zdGFmZiA8LSBjb2xzcGxpdChscG9sX3N0YWZmJGxwb2xfc3RhZmYsICIgICAgICAgICAgIiwgbmFtZXMgPSBjKCJ2MSIsInYyIiwidjMiLCJ2NCIsInY1IikpDQpmc2hvd2RmKGxwb2xfc3RhZmYsIGNhcHRpb249Imxwb2xfc3RhZmYiKQ0KYGBgDQoNCk5pY2UhIEkgdGhpbmsgd2Ugb25seSBuZWVkIGNvbHVtbiA0IGFuZCA1PyBBbmQgbGV0J3MgbmFtZSB0aGVtIG5pY2VseSBhbmQgZGVsZXRlIGFueSB0cmFpbGluZyBvciBsZWFkaW5nIHdoaXRlc3BhY2UuDQoNCmBgYHtyfQ0KbHBvbF9zdGFmZiA8LSBscG9sX3N0YWZmWywgYygidjMiLCJ2NCIpXQ0KbmFtZXMobHBvbF9zdGFmZikgPC0gYygibmFtZSIsICJmdW5jIikNCg0KbHBvbF9zdGFmZiRuYW1lIDwtIHRyaW13cyhscG9sX3N0YWZmJG5hbWUsIHdoaWNoID0gYygiYm90aCIpLCB3aGl0ZXNwYWNlID0gIlsgXHRcclxuXSIpDQpscG9sX3N0YWZmJGZ1bmMgPC0gdHJpbXdzKGxwb2xfc3RhZmYkZnVuYywgd2hpY2ggPSBjKCJib3RoIiksIHdoaXRlc3BhY2UgPSAiWyBcdFxyXG5dIikNCmZzaG93ZGYobHBvbF9zdGFmZiwgY2FwdGlvbj0ibHBvbF9zdGFmZiIpDQpgYGANCg0KTm90IGJhZC4gDQoNCiMjIExvdmUgU2NyYXBpbmc/IHsudGFic2V0IC50YWJzZXQtZmFkZX0NCg0KU3VwcG9zZSB5b3UgZG8gbm90IGxpa2UgZGF0YXdyYW5nbGluZyBidXQgc2ltcGx5IGxvdmUgc2NyYXBpbmcuIA0KDQojIyMgUg0KDQpgYGB7cn0NCmxwb2xfc3RhZmZfbmFtZXMgPC0gcmVhZF9odG1sKCJodHRwczovL3d3dy51bml2ZXJzaXRlaXRsZWlkZW4ubmwvZW4vc29jaWFsLWJlaGF2aW91cmFsLXNjaWVuY2VzL3BvbGl0aWNhbC1zY2llbmNlL3N0YWZmI3RhYi0xIikgJT4lDQogIGh0bWxfZWxlbWVudCgic2VjdGlvbi50YWIuYWN0aXZlIikgJT4lDQogIGh0bWxfZWxlbWVudHMoInVsLnRhYmxlLWxpc3QiKSAlPiUNCiAgaHRtbF9lbGVtZW50cygibGkiKSAlPiUgDQogIGh0bWxfZWxlbWVudHMoImEiKSAlPiUgDQogIGh0bWxfZWxlbWVudHMoImRpdiIpICU+JQ0KICBodG1sX2VsZW1lbnRzKCJzdHJvbmciKSU+JQ0KICBodG1sX3RleHQoKQ0KDQpscG9sX3N0YWZmX2Z1bmN0aW9ucyA8LSByZWFkX2h0bWwoImh0dHBzOi8vd3d3LnVuaXZlcnNpdGVpdGxlaWRlbi5ubC9lbi9zb2NpYWwtYmVoYXZpb3VyYWwtc2NpZW5jZXMvcG9saXRpY2FsLXNjaWVuY2Uvc3RhZmYjdGFiLTEiKSAlPiUNCiAgaHRtbF9lbGVtZW50KCJzZWN0aW9uLnRhYi5hY3RpdmUiKSAlPiUNCiAgaHRtbF9lbGVtZW50cygidWwudGFibGUtbGlzdCIpICU+JQ0KICBodG1sX2VsZW1lbnRzKCJsaSIpICU+JSANCiAgaHRtbF9lbGVtZW50cygiYSIpICU+JSANCiAgaHRtbF9lbGVtZW50cygiZGl2IikgJT4lDQogIGh0bWxfZWxlbWVudHMoInNwYW4iKSU+JQ0KICBodG1sX3RleHQoKQ0KICANCiAgbHBvbF9zdGFmZjIgPC0gZGF0YS5mcmFtZShuYW1lID0gbHBvbF9zdGFmZl9uYW1lcywgZnVuY3QgPSBscG9sX3N0YWZmX2Z1bmN0aW9ucykNCiAgZnNob3dkZihscG9sX3N0YWZmMikNCg0KYGBgDQojIyMgUHl0aG9uDQoNCmBgYHtweXRob24sIGV2YWwgPSBGQUxTRX0NCnVybCA9ICJodHRwczovL3d3dy51bml2ZXJzaXRlaXRsZWlkZW4ubmwvZW4vc29jaWFsLWJlaGF2aW91cmFsLXNjaWVuY2VzL3BvbGl0aWNhbC1zY2llbmNlL3N0YWZmI3RhYi0xIg0KcmVzcG9uc2UgPSByZXF1ZXN0cy5nZXQodXJsKQ0Kd2VicGFnZSA9IHJlc3BvbnNlLmNvbnRlbnQNCg0KIyBQYXJzZSB0aGUgd2VicGFnZQ0Kc291cCA9IEJlYXV0aWZ1bFNvdXAod2VicGFnZSwgJ2h0bWwucGFyc2VyJykNCg0KI3NhdmUgdGhlIHRhZ3MgJ2xpJyBpbiBhIGxpc3QNCnNvdXAyID0gc291cC5zZWxlY3Rfb25lKCdzZWN0aW9uLnRhYi5hY3RpdmUnKS5zZWxlY3Rfb25lKCd1bC50YWJsZS1saXN0JykuZmluZF9hbGwoImxpIikNCg0KI2xvb3AgdGhyb3VnaCB0aGUgbGlzdCB0byBmaW5kIHdoYXQgd2Ugd2FudA0KbHBvbF9zdGFmZl9uYW1lcyA9IFt0YWdhLnNlbGVjdF9vbmUoInN0cm9uZyIpLmdldF90ZXh0KCkgZm9yIHRhZ2EgaW4gc291cDJdDQpscG9sX3N0YWZmX2Z1bmN0aW9ucyA9IFt0YWdhLnNlbGVjdF9vbmUoInNwYW4iKS5nZXRfdGV4dCgpIGZvciB0YWdhIGluIHNvdXAyXQ0KDQojIENvbWJpbmUgZGF0YSBpbnRvIGEgRGF0YUZyYW1lDQpscG9sX3N0YWZmX2RmID0gcGQuRGF0YUZyYW1lKHsNCiAgICAnbmFtZSc6IGxwb2xfc3RhZmZfbmFtZXMsDQogICAgJ2Z1bmN0JzogbHBvbF9zdGFmZl9mdW5jdGlvbnMNCn0pDQoNCiMgRGlzcGxheSB0aGUgRGF0YUZyYW1lDQpwcmludChscG9sX3N0YWZmX2RmLmhlYWQoKSkgIA0KDQpgYGANCmBgYA0KICAgICAgICAgICAgICAgIG5hbWUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0DQowICAgICAgICBBZGluYSBBa2JpayAgICAgICAgICAgICAgICAgICAgICAgU2VuaW9yIEFzc2lzdGFudCBQcm9mZXNzb3INCjEgICAgICAgRmVta2UgQmFra2VyICAgICAgICAgICAgICAgICAgICAgICBTZW5pb3IgYXNzaXN0YW50IHByb2Zlc3Nvcg0KMiAgSW5ncmlkIHZhbiBCaWV6ZW4gICAgICAgICAgICAgICAgUHJvZmVzc29yIG9mIENvbXBhcmF0aXZlIFBvbGl0aWNzDQozICAgICBOaWNvbGFzIEJsYXJlbCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFzc29jaWF0ZSBQcm9mZXNzb3INCjQgICAgICAgICBBcmplbiBCb2luICBQcm9mZXNzb3Igb2YgUHVibGljIEluc3RpdHV0aW9ucyBhbmQgR292ZXJuYW5jZQ0KYGBgDQoNCg0K


Copyright © 2024 Jochem Tolsma