Fall 2026
  • Discord
  • Gradescope
  • Syllabus

On this page

  • HTML 1
  • Question 1
  • Question 2
  • Question 3
  • Question 4
  • Question 5
  • HTML 2
  • Question 6
  • Question 7
  • Question 8
  • Question 9
  • Question 10
  • Question 11
  • Question 12
  • Question 13
  • Question 14
  • Question 15
  • Question 16
  • Question 17
  • Question 18
  • Question 19
  • Question 20
  • Question 21
  • Question 22
  • Question 23
  • Question 24
  • Question 25
  • Question 26
  • Question 27
  • Question 28
  • Question 29

Web Scraping: Practice Questions

HTML 1

<nav><a href="/help">Help</a></nav>
<div class="product">
  <a href="/item/duck">Debugging Duck</a>
  <span class="price">$6.00</span>
</div>
<div class="product">
  <a href="/item/mug">404 Coffee Not Found</a>
  <span class="price">$12.00</span>
</div>
<footer><span class="price">Delivery: $3.00</span></footer>

For each question, soup is a fresh BeautifulSoup object created from this HTML using the html.parser parser.

Question 1

link = soup.select('div.product > a')[0]
result = link.text
print(result)
Answer
Debugging Duck

The selector skips the navigation link. Its first match is the duck link; .text extracts its label.

Question 2

link = soup.select('div.product > a')[0]
result = link['href']
print(result)
Answer
/item/duck

The attribute contains /item/duck, a path on the store’s site. It needs the site address before a downloader can request it.

Question 3

result = []
for tag in soup.select('div.product .price'):
    result.append(tag.text)
print(result)
Answer
['$6.00', '$12.00']

.price also matches the delivery charge. Selecting price tags inside product containers excludes the footer.

Question 4

links = soup.select('div.product > a')
result = links[0].text
print(result)
Answer
Debugging Duck

select returns a list. Indexing selects one tag, which has a .text attribute. Loop over the list when you want every name.

Question 5

product = soup.select('div.product')[1]
link = product.select('a')[0]
result = {'name': link.text, 'price': product.select('.price')[0].text, 'href': link['href']}
print(result)
Answer
{'name': '404 Coffee Not Found', 'price': '$12.00', 'href': '/item/mug'}

Searching within the mug container keeps its name, price, and link together.

HTML 2

<main id="catalog">
  <div class="product featured">
    <a href="/item/duck">Debugging <em>Duck</em></a>
    <span class="price"> $6.00 </span>
    <span class="badge">Sale</span>
  </div>
  <div class="product">
    <a href="/item/mug">404 Coffee Not Found</a>
    <span class="sold-out">Sold out</span>
  </div>
  <section class="bundle">
    <div class="product">
      <a href="/item/stickers">Works on My Machine Stickers</a>
      <span class="price">$4.00</span>
    </div>
  </section>
</main>
<footer><span class="price">Delivery: $3.00</span></footer>

For each question, soup is a fresh BeautifulSoup object created from this HTML using the html.parser parser.

Question 6

link = soup.select('div.product a')[0]
result = link.text
print(result)
Answer
Debugging Duck

.text includes text inside nested tags. The em markup does not appear in the string.

Question 7

price = soup.select('div.product .price')[0]
result = price.text.strip()
print(result)
Answer
$6.00

.strip() removes whitespace at the ends. The dollar sign remains; the value is still a string.

Question 8

mug = soup.select('div.product')[1]
result = []
for tag in mug.select('.price'):
    result.append(tag.text)
print(result)
Answer
[]

The mug has no .price tag. select returns an empty list, so the loop body runs zero times.

Question 9

mug = soup.select('div.product')[1]
prices = mug.select('.price')
if prices:
    result = prices[0].text.strip()
else:
    result = None
print(result)
Answer
None

An empty list is false in an if. Accessing its first element would raise IndexError; checking it first lets the record keep an explicit missing price.

Question 10

mug = soup.select('div.product')[1]
prices = mug.select('.price')
result = None
if prices:
    result = prices[0].text.strip()
print(result)
Answer
None

The original lookup searches the whole document and takes its first price. mug.select searches only the mug. This product has no price.

Question 11

result = []
for product in soup.select('#catalog div.product'):
    result.append(product.select('a')[0].text)
print(result)
Answer
['Debugging Duck', '404 Coffee Not Found', 'Works on My Machine Stickers']

The stickers are inside a section, so their container is not a direct child of #catalog. A descendant selector reaches it.

Question 12

result = []
for price in soup.select('div.product > a + .price'):
    result.append(price.text.strip())
print(result)
Answer
['$6.00', '$4.00']

The duck and stickers have a price directly after their link. The mug has a sold-out tag there. The footer price has no matching product link before it.

Question 13

grouped = soup.select('div.product, .featured')
added = soup.select('div.product') + soup.select('.featured')
result = [len(grouped), len(added)]
print(result)
Answer
[3, 4]

The grouped selector returns each matching tag once. Adding two lists keeps the duck twice because it matches both selectors.

Question 14

result = []
for product in soup.select('div.product'):
    prices = product.select('.price')
    if not prices:
        continue
    name = product.select('a')[0].text
    result.append({'name': name, 'price': prices[0].text.strip()})
print(result)
Answer
[{'name': 'Debugging Duck', 'price': '$6.00'}, {'name': 'Works on My Machine Stickers', 'price': '$4.00'}]

continue skips the mug because its price list is empty. The footer is outside every product container.

Question 15

prices = soup.select('div.product .price')
first = prices[0].text.strip()
second = prices[1].text.strip()
result = first + second
print(result)
Answer
$6.00$4.00

+ concatenates two strings. Remove each dollar sign and convert each remaining string with float before adding, for example float(first[1:]) + float(second[1:]) gives 10.0.

Question 16

result = 0.0
for tag in soup.select('div.product .price'):
    text = tag.text.strip()
    result += float(text[1:])
print(result)
Answer
10.0

The duck costs 6 dollars and the stickers cost 4. The sold-out mug has no listed price, and the selector excludes delivery.

Question 17

import json
jsontext = '[{"name": "Debugging Duck", "price": "$6.00"}]'
products = json.loads(jsontext)
result = [jsontext[0], products[0]['name']]
print(result)
Answer
['[', 'Debugging Duck']

Before parsing, index 0 selects the first character of a string. After parsing, index 0 selects the first dictionary in a list.

Question 18

products = [{'name': 'Debugging Duck'}, {'name': 'DUCK Socks'}, {'name': '404 Coffee Not Found'}]
result = []
for product in products:
    if 'duck' in product['name'].lower():
        result.append(product['name'])
print(result)
Answer
['Debugging Duck', 'DUCK Socks']

Lowercasing a name for the comparison finds both spellings. Appending the original name preserves its capitalization.

Question 19

import json
jsontext = '[{"name": "404 Coffee Not Found", "price": null}]'
products = json.loads(jsontext)
result = products[0]['price']
print(result)
Answer
None

JSON null becomes Python None. The record retains its price key and records that the value is missing.

Question 20

downloaded = 'Dracula’s diary'.encode('utf-8')
result = downloaded.decode('utf-8')
print(result)
Answer
Dracula’s diary

Encoding produces bytes; decoding with the same encoding recovers the original text, including the curly apostrophe.

Question 21

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--url', required=True)
parser.add_argument('--filename', required=True)
args = parser.parse_args([
    '--filename', 'duck report.html',
    '--url', 'https://store.example/duck',
])
result = [args.url, args.filename]
print(result)
Answer
['https://store.example/duck', 'duck report.html']

The list supplies the arguments that would normally follow the program name in the terminal. Named options work in either order, and duck report.html is one argument containing a space.

Question 22

pages = [('duck.html', 200), ('mug.html', 404), ('stickers.html', 200)]
result = []
for filename, status_code in pages:
    if status_code >= 400:
        continue
    result.append(filename)
print(result)
Answer
['duck.html', 'stickers.html']

The 404 entry is skipped, and the loop still reaches the successful stickers page. The reading’s batch downloader uses an exception handler and continue to skip failed requests.

Question 23

downloaded = 'Café'.encode('utf-8')
with open('page.html', 'wb') as f:
    f.write(downloaded)
with open('page.html', encoding='utf-8') as f:
    result = f.read()
print(result)
Answer
Café

Binary mode saves the bytes unchanged. Opening the file as UTF-8 text decodes them into Café.

Question 24

import json
with open('products.json', 'w', encoding='utf-8') as f:
    json.dump([{'name': 'Duck', 'price': 6}, {'name': 'Mug', 'price': 12}], f)
with open('products.json', encoding='utf-8') as f:
    products = json.load(f)
result = products[1]['price'] + 1
print(result)
Answer
13

json.load rebuilds a list of dictionaries from the file. The second price is the number 12, so adding 1 produces 13.

Question 25

import json
product = {'name': 'Duck', 'price': None}
with open('product.json', 'w', encoding='utf-8') as f:
    json.dump(product, f)
with open('product.json', encoding='utf-8') as f:
    result = f.read()
print(result)
Answer
{"name": "Duck", "price": null}

json.dump writes JSON text with double-quoted keys and null for Python None. Reading the file without json.load gives that text back as a string.

Question 26

import json
products = json.loads('[{"price": "6"}, {"price": 6}]')
result = [products[0]['price'] * 2, products[1]['price'] * 2]
print(result)
Answer
['66', 12]

JSON preserves the difference between a quoted string and an unquoted number. Multiplication repeats the string but doubles the number.

Question 27

import json
products = json.loads('[{"price": "$6.00"}, {"price": "$4.00"}]')
result = 0.0
for product in products:
    result += float(product['price'][1:])
print(result)
Answer
10.0

Loading JSON leaves the quoted prices as strings. Remove the dollar sign and convert each amount before adding them.

Question 28

import json
posts = json.loads('[{"text": "DUCK duck"}, {"text": "A mug"}, {"text": "Duck socks"}]')
result = 0
for post in posts:
    if 'duck' in post['text'].lower():
        result += 1
print(result)
Answer
2

Lowercasing matches both capitalizations. The loop counts each matching post once, so the two mentions in the first post contribute only one.

Question 29

import json
products = []
for product in soup.select('div.product'):
    link = product.select('a')[0]
    products.append({'name': link.text, 'href': link['href']})
with open('report.json', 'w', encoding='utf-8') as f:
    json.dump(products, f)
with open('report.json', encoding='utf-8') as f:
    saved = json.load(f)
result = saved[1]['href']
print(result)
Answer
/item/mug

The HTML extraction creates ordinary dictionaries. JSON preserves their fields and list order, so the second saved record still has the mug’s link.