Getting data from the web: APIs and scraping
After this lesson you will read a web API call part by part — method, host, path, parameters — together with the JSON it returns, and decide for any website whether you can ask an API, simply download and parse the page, or must drive a real browser, and which of BeautifulSoup, Scrapy and Selenium does what.
The exercise session on APIs prints this request to Trenitalia's ViaggiaTreno service (p. 1517):
http://www.viaggiatreno.it/infomobilita/resteasy/viaggiatreno/partenze/S00219/Wed%20Nov%2005%202025%2020:43:00%20GMT+0100%20(Central%20European%20Standard%20Time)
What came back was not a web page but a list of twelve departures, each one a record with named fields. Now look at the tail of the URL. Decoded, it reads "Wed Nov 05 2025 20:43:00 GMT+0100 (Central European Standard Time)". Nobody types a date like that into an address bar: the weekday, the seconds, the offset from Greenwich, the time zone spelled out in brackets, and a %20 for every space.
So who wrote this URL? The answer is also the reason why a script that downloads a web page sometimes finds nothing in a page that your browser shows full of data. Keep the question in mind; it is answered at the end of the lesson.
An API is a counter where programs ask for data
The course notes call data ingestion "the first and fundamental step of any Data Analysis Pipeline" (§5.1.1) — the bottom layer of the architecture of lesson 4, and the step that a ready-made dataset does for you in most courses. From the web there are two ways in: ask an API, or scrape the page.
Picture a take-away counter: a form with fixed fields goes in, a labelled box comes out, and you never see the kitchen. The first slide defines this counter for software (p. 1510):
Application Programming Interface (API) — "a set of rules and protocols that allows different software applications to communicate with each other and exchange data. Often can be called remotely." "APIs act as an intermediary layer, enabling developers to use existing functions and services from other applications, which simplifies development."
The definition is general: the functions of a Python library are an API too. "Remotely" narrows it to the web API, called over the network with HTTP — in the course notes, "A Web API is a API which is based on the HTTP protocol, and it's usually used to give programmatic access to data and platforms" (§5.1.2). "API without Libraries", the title of the first two slides, is not explained on them; read it as calling the API directly — a URL in the browser, in Postman or with curl — rather than through a client library that wraps the calls.
Every call names an HTTP method and a URL: "APIs can be invoked through HTTP methods (e.g., GET, POST, etc.) depending on the kind of operations to be performed", and "As an end-user, the most used requests for APIs are GET requests" (p. 1511). GET reads, and whoever collects data mostly reads; it also fits in one URL, which is why every example of the session is a line you could paste into a browser.
To develop and test APIs, "Postman is one of the most renowned ones" (p. 1511). The next slide is a screenshot of it with four red callouts, redrawn in Fig. 1.
postman-echo.com, answers with the request it received: the two parameters reappear as args, and two headers that nobody typed — host and the client's user-agent — travelled with them.Read the four parts in the order you use them. The API Collection keeps saved requests; "API 101" has one HTTP method per operation on customers — GET to read all or one, POST to add, PUT to update, DEL (DELETE) to remove. The Query callout points at the request line, method and URL. Request Parameters lists the key=value pairs after the ?. The Result holds status 200 OK, time, size and a JSON body — and since the server is an echo, the body shows what really travelled: your parameters as args, plus headers the client added on its own.
Why one method per operation? The REST convention behind "API 101" (course notes, not the slides)
Most web APIs follow the REST paradigm, which gives URLs a predictable shape, METHOD http://domain/collection/item — for example GET http://example.com/user/1234 — and fixes what each method does on a collection or on one item (course notes, §5.1.2):
| Verb | On a collection | On an item |
|---|---|---|
POST | Create a new item | not used |
GET | Get list of elements | Get the selected item |
PUT | not used | Update the selected item |
DELETE | not used | Delete the selected item |
The names in "API 101" read like this table: "Get all customers" is GET on the collection, "Get one customer" GET on an item, "Add new customer" POST on the collection (the screenshot shows the names, not the URLs).
A web API answers a method and a URL with data for programs — usually JSON — not with a page for people.
Reading a request: the iTunes Store examples
The session's first worked example asks Apple's iTunes Store to "Retrieve the first 10 artists whose name contains "maroon"" (p. 1513). No app, no login: the whole question is one URL.
GET https://itunes.apple.com/search?term=maroon&entity=allArtist&attribute=allArtistTerm&limit=10
Read it left to right: https is the scheme, the protocol; itunes.apple.com the host, the server that answers; /search the path, the service you call. After the ? comes the query string: key=value pairs separated by &, each an instruction for the search. The course notes print the general shape (§5.1.2); the bracketed parts are optional:
URL format (RFC 2396):
scheme:[//[user:password@]host[:port]][/]path[?query][#fragment]
| Key | p. 1513 | p. 1514 | What it asks for |
|---|---|---|---|
term | maroon | Stallone | the text searched for |
entity | allArtist | movieArtist | which kind of object to return |
attribute | allArtistTerm | — | which field the term is matched against: the artist's name |
limit | 10 | — | at most this many results; without it the service's default applies |
The answer is not a page but a JSON document: an object with resultCount and results, an array of artist objects. Its start, as the slide prints it:
{
"resultCount": 10,
"results": [
{
"wrapperType": "artist",
"artistType": "Artist",
"artistName": "Maroon 5",
"artistLinkUrl": "https://music.apple.com/us/artist/maroon-5/1798556?uo=4",
"artistId": 1798556,
"amgArtistId": 529962,
"primaryGenreName": "Rock",
"primaryGenreId": 21
},
… nine more artist objects ("Maroon" is the second)
]
}
resultCount is 10 because limit=10 allowed at most ten; each element of results is a record with named fields — structure an API gives you for free, and scraping must rebuild from a page layout.
The second call, "Retrieve all the artist whose surname is "Stallone"" (p. 1514), is GET https://itunes.apple.com/search?term=Stallone&entity=movieArtist: no attribute, no limit. Its answer has "resultCount": 6, and its first two records do not have the same fields:
"artistType": "Artist",
"artistName": "Frank Stallone",
"artistLinkUrl": "https://music.apple.com/…",
"artistId": 91734703,
"amgArtistId": 5513,
"primaryGenreName": "Pop",
"primaryGenreId": 14
"artistType": "Movie Artist",
"artistName": "Sylvester Stallone",
"artistLinkUrl": "https://itunes.apple.com/…",
"artistId": 186052826,
"primaryGenreName": "Drama",
"primaryGenreId": 4406
Frank Stallone has an amgArtistId and a link on music.apple.com; Sylvester Stallone has no amgArtistId and a link on itunes.apple.com. Records of one answer need not share their fields — the freedom a document store gives the documents of one collection (lesson 13) — so a program must check that a field exists before it uses it.
"The URL filters artists by surname." It does not: there is no surname key, term=Stallone is a free-text search on names, and the caption gives the intention, not the mechanism. The screenshot shows more: although the call asks for entity=movieArtist, its first result, Frank Stallone, is an "Artist" with genre Pop. A likely reason, not given on the slide: Apple's documentation lists movieArtist as an entity of the movie media, and this call sets no media key. Read the answer, not the caption.
A URL cannot carry a space, and it reads &, ? and # as separators. Inside a value such characters are written with percent-encoding: % plus the character's code in two hexadecimal digits, so a space is %20 and an & is %26. Otherwise the server would read an & inside a value as the start of a new pair. The warm-up URL has nine %20.
In Python (background), requests builds and encodes the query string from a dictionary, as Postman does from its table:
# the request of p. 1513, in Python
import requests
url = "https://itunes.apple.com/search"
params = {"term": "maroon",
"entity": "allArtist",
"attribute": "allArtistTerm",
"limit": 10}
r = requests.get(url, params=params)
data = r.json() # a dict
data["resultCount"] # 10
Parameters in the path, days in milliseconds: ViaggiaTreno
Trenitalia's ViaggiaTreno service works differently: its parameters travel inside the path, one per segment, and it takes two calls — the first returns the key the second needs. First, the autocompletion of train number 798 (p. 1515):
GET http://www.viaggiatreno.it/infomobilita/resteasy/viaggiatreno/cercaNumeroTrenoTrenoAutocomplete/798
798 - SALERNO - 04/11/25|798-S09818-1762210800000
798 - SALERNO - 05/11/25|798-S09818-1762297200000
Two lines of plain text, one per run of train 798: the run that departs on 4 November and the one that departs on 5 November. Left of the | is a label for people; right of it, a key for programs — train number, station code (S09818 is SALERNO, as the line says) and departure day, joined by dashes. The second call puts the key's parts into the path (p. 1516; its answer is cut down here to a few of its fields):
GET http://www.viaggiatreno.it/infomobilita/resteasy/viaggiatreno/andamentoTreno/S09818/798/1762210800000
{tipoTreno: "PG", orientamento: null, codiceCliente: 4, fermateSoppresse: [], …}
arrivato: true
categoria: "ICN"
circolante: true
compClassRitardoLine: "regolare_line"
compDurata: "14:35"
compImgCambiNumerazione: " "
compImgRitardo: "/vt_static/img/legenda/icone_legenda/regolare.png"
…
The second answer is the train's status, with Italian keys: categoria: "ICN", compDurata: "14:35" (a duration), arrivato: true. The comp keys carry pieces of the website — CSS class names such as "regolare_line", image paths, the HTML entity — the opposite of the "Separation between model and presentation" that the course notes list as the first advantage of a web API (§5.1.2).
What 1762210800000 means
The day is a number: epoch time in milliseconds, the count of milliseconds since 1 January 1970, 00:00:00 UTC — the usual way computers store an instant. The steps check that 1762210800000 is the midnight of 4 November 2025 in Italy.
Fix the instant: in November Italy is on CET, UTC + 1, so local midnight of 4 November is 23:00 UTC on 3 November.
Whole years 1970–2024: 55 years of 365 days, plus one day for each of the 14 leap years 1972 … 2024.
\[ 55 \times 365 + 14 = 20\,089 \text{ days} \]In 2025, January to October have 304 days; 1 and 2 November make 306.
\[ 20\,089 + 306 = 20\,395 \text{ days} \]A day has \(24 \times 3600 = 86\,400\) s; convert, then add the 23 hours of 3 November.
\[ 20\,395 \times 86\,400 = 1\,762\,128\,000 \] \[ +\ 82\,800 = 1\,762\,210\,800 \text{ s} \]The API counts milliseconds: multiply by 1000 and you get the key of p. 1515, digit for digit.
\[ 1\,762\,210\,800\,000 \text{ ms} \]One day later is 86 400 000 ms more — the second key of p. 1515.
\[ 1\,762\,297\,200\,000 \text{ ms} \]
"The train leaves at 1762210800000." The caption of p. 1516 says "at", as if the number were a time. It is a day, named by its first millisecond: a run of a train is identified by train number, origin station and departure day — the three parts of the key.
The third call asks for all departures from a station at a given moment (p. 1517) — the long date of the warm-up. Decoded, the URL reads:
http://www.viaggiatreno.it/infomobilita/resteasy/viaggiatreno/partenze/S00219/Wed Nov 05 2025 20:43:00 GMT+0100 (Central European Standard Time)
The answer is a JSON array of twelve departures. Element 0, compNumeroTreno: "REG 3143", has codOrigine: "S00219" and, side by side, dataPartenzaTrenoAsDate: "2025-11-05" and dataPartenzaTreno: 1762297200000 — the same day for people and for programs, and the number the autocompletion gave for 5 November.
Providers and their rules
Three more slides present data providers reached through APIs.
| Provider | What the slide says |
|---|---|
| Twitter API (p. 1518) | "enables programmatic access to Twitter in unique and advanced ways"; two versions: v1 "is free and open to everyone" and "only provides the last week of tweets"; v2 "requires the developer to submit an authorization for each application they develop" and "also includes an access to past tweets" |
| OpenStreetMap (p. 1519) | "a collaborative project to create a free editable geographic database of the world"; "Basically, it is a crowdsourced map of the world"; users collect data by "manual survey, GPS devices, aerial photography, and other free sources", or "their own local knowledge of the area" |
| OpenWeatherMap (p. 1520) | "provides a set of weather-related products, including short-term and long-term forecasting, history and observations for any location"; "Most of their services are provided through APIs." |
The Twitter slide shows the Twitter logo crossed out by a red no-entry sign: read it as a warning that this API no longer works as described. Indeed (background), the version the slide calls v1 was v1.1, and since 2023 X, as Twitter is now called, sells read access to its API in paid tiers. At the exam, describe the two versions as the slide prints them; what lasts is the idea — an API is a contract its owner can change (who may call it, which data it reaches), and every pipeline built on it depends on that contract.
What the slides leave out: why providers offer APIs, keys, pagination, rate limits, status codes (background from the course notes and standard HTTP)
- Why providers offer APIs. The course notes list the advantages of a web API (§5.1.2): "Separation between model and presentation"; regulated access to the data (traceable accounts, only a portion of the data); no direct access to the platform website; request throttling; no server congestion; protection against DoS and DDoS attacks; paid access to the full data, or to higher volumes.
- Keys and tokens. "Almost all the APIs require a kind of user authentication": you register at the provider's developer portal, get a key and a secret, and exchange them for a token sent with every request, usually through OAuth (course notes, §5.1.2).
- Pagination. "most APIs support data pagination to split huge chunks of data into smaller set of data" (§5.1.2):
limitis half of it, a page number or a cursor for the next chunk the other half. - Rate limits. Providers "put a limit on the number of requests a single account can make" (§5.1.2), so a large crawler spreads its requests over several accounts, in round robin.
- Status codes.
200 OK(p. 1512) is success; the others you will meet first are 401 and 403 (no or wrong key, forbidden), 404 (not found), 429 (too many requests: the rate limit) and 5xx (the server failed).
No API? Scrape the page — and meet the DOM
An API hands you records with named fields; a web page hands you text in tags, laid out for human eyes. When a site has no API, you take the page itself. The scraping deck opens with the definition (p. 1523):
Web scraping — "the automated process of extracting data from websites and organizing it into a structured format, such as a spreadsheet or database. It uses specialized software or scripts, called scrapers, to pull information like text, prices, or images from the HTML code of web pages, which can then be used for analysis."
The slide sets scraping apart from web crawling, "which is used by search engines to index web content": a crawler moves from page to page along links, a scraper extracts data from them — and Scrapy, below, does both.
What can be scraped (p. 1527)? "Social networks: Facebook, Instagram, etc.", "Retailers: Amazon, Tripadvisor, Google Maps, etc.", and "Any website with valuable contents and no official API (e.g. events, train schedules, etc.)". The last item is the rule — scrape when there is no API; "a last resort", say the course notes (§5.1.3). Train schedules are on the list, yet the first deck reached them through an API: when one exists, it is the better door.
The page is a tree
What is a page to a program? The slides answer with the W3C definition (p. 1524):
Document Object Model (DOM) — "a platform and language-neutral interface that allows programs and scripts to dynamically access and update the content, structure, and style of a document." "In other words it is a standard for how to get, change, add, or delete HTML elements."
"When a web page is loaded, the browser creates a Document Object Model of the page" (p. 1525), which defines "The HTML elements as objects", "The properties of all HTML elements", "The methods to access all HTML elements" and "The events for all HTML elements". The third DOM slide is a picture (p. 1526), redrawn in Fig. 3; this is the HTML behind it (the slide names the attribute, not its value):
<html>
<head>
<title>My title</title>
</head>
<body>
<a href="page2.html">My link</a>
<h1>My header</h1>
</body>
</html>
href hangs beside its element <a> on a horizontal line — drawn dashed here: it belongs to the element but is not one of its children.Why a scraper cares: a parser such as BeautifulSoup builds this same tree from the HTML text; the patterns that pick data out of a page are paths in it; and the page's own scripts can change it after loading — "dynamically access and update", the subject of the next section.
Pointing at a node: three kinds of pattern
Once the page is in hand, "Another basic step is parsing the page, extracting data that respects specific patterns" (p. 1530): CSS selectors, regular expressions and XPath. All three reach the link of Fig. 3 (background: the slides name the techniques, not the calls):
# html holds the page of Fig. 3 as text
import re
from bs4 import BeautifulSoup
from lxml import html as lh
# 1. CSS selector, with BeautifulSoup
soup = BeautifulSoup(html, "html.parser")
link = soup.select_one("body > a")
link.get_text() # 'My link'
link["href"] # 'page2.html'
# 2. XPath, with lxml
lh.fromstring(html).xpath("//a/@href")
# ['page2.html']
# 3. regular expression, on the raw text
re.findall(r'<a href="([^"]*)">', html)
# ['page2.html']
CSS selectors and XPath walk the tree: body > a is "an a that is a child of body", //a/@href "the href attribute of any a" — reached with @ or ["href"], not one level down (Fig. 3). A regular expression reads the raw text, blind to nesting, and works only while the HTML is written exactly as the pattern expects. Hence p. 1529: "Since HTML code is tagged text, any tool that allows to manipulate and process text could be potentially used to parse a page" — potentially; a tree-building parser is sturdier.
Scraping rebuilds structure from layout: the DOM is the tree it walks, and a selector is a path in that tree.
Static or dynamic: when downloading the page is not enough
For a static page, scraping takes two steps (p. 1530): "retrieving a static page from the Web" — with "curl, using command line" or "requests, using Python scripts" — then parsing it with the patterns above (the other group's deck lists only XPath and regular expressions, p. 1542). Take a small restaurant page whose reviews you want — Trattoria Rossi, invented for this lesson:
# step 1, from the command line: download the page into a file
curl -s "https://example.com/trattoria-rossi" -o page.html
# steps 1 and 2 in Python: download, then parse
import requests
from bs4 import BeautifulSoup
r = requests.get("https://example.com/trattoria-rossi")
soup = BeautifulSoup(r.text, "html.parser")
# one element per review in the page
reviews = soup.select("div#reviews > p.review")
Then comes the sentence on which the second half of the session turns: "Most websites have dynamic-generated content: downloading and parsing the HTML page could not be sufficient" (p. 1529). The browser shows you the DOM after the page's JavaScript has run; requests downloads the text the server sent, before any script ran — it is not a browser and runs nothing. When a script fills the page after loading, the reviews you see are simply not in the download (Fig. 4).
Before: what requests.get downloads
After: what the browser builds — and Selenium hands over
div#reviews and a script; once the page has loaded, the script fetches the reviews and inserts three <p class="review"> nodes (blue). requests stops at the first tree, a browser reaches the second, and BeautifulSoup parses whichever tree it is given."A dynamic page is one generated by a program on the server." Not for a scraper: a page the server writes before sending it is static from the scraper's point of view — the content is in the download. The trouble is content built in the browser, by JavaScript, after loading. The deck says "dynamic" in three senses: "dynamic-generated content" (p. 1529), content loaded on click or scroll (p. 1531), and "Dynamic Websites" whose HTML "changes over time" (p. 1537). And "could not be sufficient" means "may not be sufficient".
The next slide asks the two questions that a plain download cannot answer (p. 1531): "What happens if we want to obtain pages in sequence (e.g.: pages of reviews from Amazon or TripAdvisor)?" and "What we need to do if the page loads the content we need only after clicking a specific field or while scrolling the page itself (e.g.: Facebook and Instagram posts)?"
Three modules, three jobs
"Python is one of the best languages to build scrapers: it is easy to learn, fast to code and it has plenty of libraries to get anything done efficiently" (p. 1532). The deck presents three of them.
| Module | The slide's words | Its job in a scraper | Runs the page's JavaScript? |
|---|---|---|---|
| BeautifulSoup (p. 1533) | "an HTML (and XML) parser"; it lets you "navigate through an HTML document, accessing each tag and hierarchy easily"; "the main source to extract data after the page is fully loaded" | parse: turn HTML text into a tree and pick data out of it; it downloads nothing | no |
| Scrapy (p. 1534) | "a Python framework for web crawling and data extraction", for "web scrapers and spiders"; "it bith [both] crawls a website and extracts data" | fetch many pages by following links, and extract from them | no, not by itself |
| Selenium (p. 1535) | "developed for automating websites testing"; it "simulates clicks, drags, scrolls"; "used to retrieve data that is not loaded with the source page (Javascript) or to navigate several pages of the site" | drive a real browser: load, click, scroll, then hand over the rendered page | yes — the browser runs it |
Selenium's origin explains why it answers both questions of p. 1531: a testing tool must do what a user does, so it drives a real browser — the browser runs the scripts, Selenium clicks "next" or scrolls, and the DOM it reads at the end is the rendered one. The handover to BeautifulSoup is one line, driver.page_source:
# the dynamic page: a real browser loads it, BeautifulSoup parses
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from bs4 import BeautifulSoup
# start a real browser and open the page
driver = webdriver.Chrome()
driver.get("https://example.com/trattoria-rossi")
# wait (at most 10 s) until the script has added a review
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "p.review")))
# hand the rendered page to the parser
soup = BeautifulSoup(driver.page_source, "html.parser")
driver.quit()
reviews = soup.select("div#reviews > p.review")
The pipeline, and why scrapers break
"The image below represents the interactions between the three presented modules: their simple combination is the basis for a web scraper" (p. 1536). Fig. 5 redraws it, with the two return loops of the other group's version (p. 1545).
</>), from there to Beautiful Soup, and out as parsed data. The orange loops are new requests: back to the browser for dynamic elements, back to the web for another page.Read it as fetch, parse, store: Selenium fetches when the page needs a browser, Scrapy when many pages must be crawled, and BeautifulSoup turns the HTML page into parsed data. The loops answer the two questions of p. 1531 by name: "get dynamic elements" (click or scroll, then read again) and "get another page". Two simplifications: the static path of p. 1530 (requests, then BeautifulSoup) is not drawn, and Scrapy's page goes through BeautifulSoup although Scrapy can extract by itself.
Fragile by design
An API is a contract with named fields; a page is a layout that its owner may redesign any night. "Most of the times, the HTML changes over time, especially the classes and sometimes the structure of the HTML. Therefore, it is really important to keep the scraper updated" (p. 1537, "Dynamic Websites" — the third sense of the word). Rename the class in div#reviews > p.review and the scraper returns an empty list without any error, which is why the course notes call scrapers very difficult to maintain (§5.1.3).
Sites also defend themselves: "Some websites have some protections against scraping. They are able to detect weird behaviours (e.g., repeated scrolling) and prevent you from scraping" (p. 1528). And Selenium needs a "chromedriver", the program through which it drives the browser, so the two versions must match: "Check out your Google Chrome/EDGE version", then "Download and put the correct chromedriver version within the folder" (p. 1538). Since version 4.6, Selenium fetches the matching driver itself (background).
The slides list protections but no rules; the course notes warn that "many website providers don't allow scraping, so you could run into legal problems if you do" (§5.1.3). Good practice: read the terms of use and the site's robots.txt, keep requests slow, identify your client honestly, and handle personal data as privacy law (in the EU, the GDPR) requires.
Who wrote the URL of the warm-up
Back to the warm-up. Its decoded tail is exactly the text JavaScript produces for a date — try new Date().toString() in the console of a browser that runs in English on Italian time — and a %20 for every space, with : and + left bare, is exactly what JavaScript's encodeURI makes of it. So a program running JavaScript wrote this URL: most likely the ViaggiaTreno web page itself, asking its server for the departures after it had loaded. The session shows the call, not how it was found; this reading is ours.
If this reading is right, it is the dynamic page of p. 1529 seen from inside: a board filled this way reaches the browser as an empty frame, and requests.get receives only the frame. Two ways out: drive a browser with Selenium and parse what it renders, or call the API that the page itself calls, as the session did. When you can see the call, the second is simpler and sturdier — named fields instead of a layout, and nothing breaks when a CSS class is renamed.
Prefer an API when one exists; when you scrape, fetch with the simplest tool that sees the data, parse with BeautifulSoup, and expect to maintain the scraper.
Exam-style questions
APIs and scraping are not among the topics of the practical part (Neo4j, MongoDB, Elasticsearch, Redis and SparkQL), and none of the nine past papers has such an exercise. The theory part covers "Content explained during the theoretical and practical lectures" (Exam Rules 2026/27), and this session is a practical lecture: expect closed questions (multiple choice, true or false — wrong answers may carry a penalty, announced just before the exam) and short open answers.
True or false: "As an end-user, the most used requests for APIs are GET requests."
Show model answer
True. APIs are invoked through HTTP methods depending on the kind of operation; an end-user mostly retrieves data, and retrieval is a GET request, which can be written as a single URL (as in the iTunes and ViaggiaTreno examples).
The sentence is quoted from the slide, so a true/false item can reuse it as it stands. If it comes back as an open question, add the reason: the method carries the intent of the operation.
Consider the request https://itunes.apple.com/search?term=maroon&entity=allArtist&attribute=allArtistTerm&limit=10, shown in the exercise session on APIs. Identify the scheme, the host, the path and the query parameters, explain what limit=10 does, and describe the structure of the answer.
Show model answer
It is a GET request to a web API. The scheme is https, the host is itunes.apple.com (the server that answers) and the path is /search (the service called). After the question mark comes the query string: four key=value pairs separated by ampersands — term=maroon, the text searched for; entity=allArtist, the kind of object returned (artists); attribute=allArtistTerm, the field the term is matched against (the artist's name); limit=10, the maximum number of results. The answer is a JSON object with resultCount, here 10 because of the limit, and results, an array of artist objects with named fields such as artistName, artistId and primaryGenreName — the first two are Maroon 5 and Maroon. Records of the same answer may have different fields. Special characters inside a value must be percent-encoded, for example %20 for a space.
Name all four parts of the URL with their terms, then show that you understand each parameter as an instruction to the server. Saying that the answer is "a web page" loses the point of an API: it returns data, JSON, for programs.
In the Trenitalia ViaggiaTreno example, the autocompletion for train 798 returns the lines 798 - SALERNO - 04/11/25|798-S09818-1762210800000 and 798 - SALERNO - 05/11/25|798-S09818-1762297200000. Explain the structure of these lines, what the number 1762210800000 represents, why the two numbers differ by 86400000, and how the result is used in the next request.
Show model answer
Left of the vertical bar is a label for people: train number, origin station and date. Right of it is a key for programs: train number, station code (S09818, Salerno) and departure day, separated by dashes. The day is written as epoch time in milliseconds, the number of milliseconds since 1 January 1970, 00:00 UTC: 1762210800000 is the midnight of 4 November 2025 in Italian time (23:00 UTC on 3 November). One day lasts 24 × 3600 × 1000 = 86 400 000 ms, so the second key is the next day, 5 November. The parts of the key are then placed in the path of the second call, andamentoTreno/S09818/798/1762210800000 (station, train, day), which returns the status of that run of the train in JSON — for example category ICN, duration 14:35, arrived. It is a two-step workflow in which the first call provides the identifiers needed by the second, and the parameters travel in the path rather than in a query string.
The three things to get right: the number is a day, not a time of departure; the unit is milliseconds, not seconds; and the key feeds the next call. The slides print the numbers without explaining them — the derivation in this lesson is ours, and it checks digit for digit.
What is web scraping? How does it differ from web crawling, and when is scraping the appropriate way to collect data?
Show model answer
Web scraping is the automated process of extracting data from websites and organizing it into a structured format, such as a spreadsheet or a database. Programs called scrapers pull information such as text, prices or images from the HTML code of web pages, for later analysis. Web crawling, used by search engines to index web content, is about visiting pages by following links; scraping is about extracting data from them. The two can be combined: Scrapy both crawls a website and extracts data. Scraping is appropriate for websites with valuable content and no official API — social networks, retailers, events, train schedules — and it should be a last resort: when an API exists it provides structured, documented data, while scrapers break when the HTML changes and websites may detect and block them.
The definition of p. 1523 is the core; the contrast with crawling is the classic follow-up. The "last resort" argument (maintenance, protections) shows that you know why an API is preferred.
What is the Document Object Model (DOM), what does it define, and why does it matter for web scraping?
Show model answer
The W3C DOM is a platform- and language-neutral interface that allows programs and scripts to dynamically access and update the content, structure and style of a document — a standard for how to get, change, add or delete HTML elements. When a web page is loaded, the browser creates the DOM of the page. It defines the HTML elements as objects, the properties of all HTML elements, the methods to access them and their events. It is a tree: the document, the root element html, the elements head and body, their child elements, and text nodes as leaves; attributes such as href are attached to their element. For scraping it matters because parsers such as BeautifulSoup build the same tree from the HTML, and selectors (CSS selectors, XPath) are paths in it; and because scripts can update the DOM after loading, the HTML that is downloaded can differ from what the browser shows.
Quote the definition, list the four things the DOM defines (p. 1525), and add the link to scraping — the last sentence is what turns a memorised definition into understanding.
Why may downloading and parsing the HTML of a page not be sufficient to extract its content? Which tool does the exercise session propose, and why that tool?
Show model answer
Most websites have dynamically generated content: the HTML returned by the server (and downloaded with curl or requests) is the page before its JavaScript runs, and the content may be loaded by scripts only after the page has loaded, after a click on a specific field, or while scrolling (for example Facebook and Instagram posts); content may also be split over pages in sequence (pages of reviews on Amazon or TripAdvisor). A plain download does not contain that content and cannot click or scroll. The session proposes Selenium, a module developed for automating website testing: it drives a real browser and simulates clicks, drags, scrolls and any other interaction, so it retrieves data that is not loaded with the source page and navigates several pages. The rendered page is then passed to BeautifulSoup, the parser that extracts the data after the page is fully loaded.
The decisive word is "browser": Selenium works because the browser runs the scripts. An answer that says only "use Selenium" without saying why the download is empty misses the mechanism the examiner is checking.
Which of the following statements about the scraping tools presented in the exercise session are correct? Select all that apply.
Show model answer
The first, third and fifth statements are correct. In the pipeline of the session, Selenium or Scrapy obtains the HTML page from the Internet, BeautifulSoup parses it, and the result is the parsed data.
Two wrong options swap the roles of two tools or of two steps — browser automation versus parsing, retrieve versus parse; the last one forgets that the HTML changes and that sites detect scrapers. Wrong answers may carry a penalty, so tick only what you can justify with a slide.
Check yourself
In the request of p. 1513 you change limit=10 into limit=3 and nothing else. What comes back?
The call of p. 1514 asks for entity=movieArtist, yet its first result, Frank Stallone, has "artistType": "Artist" and genre Pop, and it has an amgArtistId that Sylvester Stallone lacks. What is the right conclusion?
Which of these pages can you scrape with requests and BeautifulSoup alone? Select all that apply.
The test is not how the page was produced but whether the content is in the text the server sends.
In the DOM tree of p. 1526, the box "Attribute: "href"" is joined to "Element: <a>" by a horizontal line instead of hanging below it. What does that tell you?
ViaggiaTreno identifies a day by the epoch milliseconds of its midnight, Italian time. 5 November 2025 is 1762297200000. Which number identifies 6 November 2025?
A scraper ran fine for months; now it returns empty lists, while the page still shows the data in your browser. Which cause does the slide "Dynamic Websites" point to?
Which of the three modules never downloads anything by itself?
Key take-aways
- An API is a set of rules and protocols that lets applications communicate and exchange data; a web API is called over HTTP with a method and a URL, and answers with data for programs, usually JSON. End-users mostly send GET requests, and Postman shows a request's four parts: collection, request line, parameters, result.
- A URL reads scheme://host/path?key=value&key=value: each pair is one instruction to the server, reserved characters inside a value are percent-encoded (
%20for a space,%26for an ampersand), and some APIs, such as ViaggiaTreno, put their parameters in the path instead. - Read the answer, not the caption: iTunes returns
resultCountand an arrayresultswhose records need not share their fields; ViaggiaTreno returns a key that feeds the next call, with the day as epoch milliseconds (one day = 86 400 000 ms). - APIs come with rules that change: Twitter v1 was free and open with the last week of tweets, v2 asks for an authorization per application and reaches past tweets. Keys, pagination, rate limits and status codes are part of using real APIs.
- Web scraping is the automated extraction of data from the HTML of web pages into a structured format — crawling follows links, scraping extracts — and it is the way in when a site has valuable content and no API. The DOM is the browser's tree of the page, and CSS selectors and XPath are paths in it.
- A static page needs two steps, retrieve (curl, requests) and parse (CSS selectors, regular expressions, XPath). Content rendered by JavaScript or loaded on click or scroll needs Selenium, which drives a real browser and can also walk through pages in sequence; BeautifulSoup parses, Scrapy crawls and extracts.
- Scrapers are fragile: HTML classes and structure change, so they must be kept updated, and sites detect behaviour such as repeated scrolling. Prefer an API when one exists — often the very API that the page itself calls.
What to watch and read
-
Overview of HTTP
MDN Web Docs10 min
The request and response behind every call of this lesson: method, path and headers going out; status code, headers and body coming back.
-
iTunes Search API
Apple
Apple's documentation of the service; its page "Construct a Search" lists the keys of p. 1513 — term, entity, attribute, limit — with their defaults. Then paste the slide's URL into your browser: it still answers, with a resultCount of 10.
-
CSS Diner
Luke Pacholski
A game of 32 levels on CSS selectors — element, class, id, child, attribute — exactly what
soup.selecttakes. Play it before you write your first scraper. -
Web Scraping Sandbox
toscrape.com, by Zyte
Sites built to be scraped, in a static version and in versions with JavaScript-generated content and infinite scroll: the widget's three pages, for real, with nobody's rules to break.
-
Tutorial: Legality and Ethics of Web Scraping
Krotov, Johnson, Silva · Communications of the AIS, 2020
The questions the slides leave out: terms of use, copyright, privacy law, harm to the site, and the purpose the data is used for.