{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# ā HTML Detective ā COMPLETE SOLUTION\n", "\n", "> **You're looking at the fully finished version.** Every code cell already works. There are no missions to solve ā this is the answer key plus bonus extensions.\n", ">\n", "> š” **Try the student version first!** You'll learn way more by writing the code yourself. Open the file `CodeKids_HTML_Detective_Student.ipynb` and work through it. Only come here when you're stuck, or when you've finished and want to see what advanced moves are possible.\n", "\n", "This notebook includes:\n", "\n", "- ā Every mission solved\n", "- ā A reusable `scan_page()` function that bundles all the regexes into one tool\n", "- ā A live URL example using `https://books.toscrape.com`\n", "- ā Bonus patterns to extend the script (links, emails, API keys, scripts)\n", "- ā Teacher notes at the end\n", "\n", "Press ā¶ on each cell from top to bottom to run the whole detective at once." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setup\n", "\n", "Load Python's HTTP and regex tools." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import requests, re\n", "\n", "print(\"ā Tools loaded.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Step 1 ā Practice page (hardcoded HTML)\n", "\n", "A tiny fake shop with planted secrets. We'll use this for Missions 1ā3 before switching to real websites." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "target_html = '''\n", "\n", "
\n", "Today's special: gummy bears
\n", "\n", " \n", "\n", " \n", " \n", "\n", " \n", "\n", "'''\n", "\n", "print(f\"ā Loaded {len(target_html)} characters of HTML.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ā Mission 1 ā Find every HTML comment\n", "\n", "Pattern explanation:\n", "- `` are the literal start and end of a comment\n", "- `(.*?)` captures anything in between ā the `?` makes it **lazy** (shortest possible match), so we don't gobble across multiple comments\n", "- `re.DOTALL` lets `.` match newlines, so multi-line comments work" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "comments = re.findall(r\"\", target_html, re.DOTALL)\n", "\n", "print(f\"š Found {len(comments)} HTML comments:\\n\")\n", "for i, c in enumerate(comments, 1):\n", " print(f\" {i}. {c.strip()}\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Mini-challenge ā only show comments that contain \"FLAG\":\n", "for c in comments:\n", " if \"FLAG\" in c:\n", " print(\"š©\", c.strip())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ā Mission 2 ā Find hidden form inputs\n", "\n", "Pattern explanation:\n", "- `]*` matches anything that isn't `>` (so we don't accidentally swallow other tags)\n", "- `type=\"hidden\"` is the literal text that flags this input as hidden\n", "- `[^>]*>` finishes off the tag" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "hidden = re.findall(r']*type=\"hidden\"[^>]*>', target_html)\n", "\n", "print(f\"š» Found {len(hidden)} hidden inputs:\\n\")\n", "for i, h in enumerate(hidden, 1):\n", " print(f\" {i}. {h}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ā Mission 3 ā Find every FLAG{...}\n", "\n", "Pattern explanation:\n", "- `FLAG\\{` matches the literal `FLAG{` ā note the `\\{` escapes the curly brace (it has a special meaning in regex)\n", "- `[^}]+` matches one or more characters that aren't `}`\n", "- `\\}` matches the literal `}`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "flags = re.findall(r\"FLAG\\{[^}]+\\}\", target_html)\n", "\n", "print(f\"š© Found {len(flags)} flags!\\n\")\n", "for i, f in enumerate(flags, 1):\n", " print(f\" {i}. {f}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ā Bonus 1 ā A reusable `scan_page()` function\n", "\n", "Wrap all three regexes into one function so you can scan any page in a single call." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def scan_page(html):\n", " \"\"\"\n", " Take a chunk of HTML, return a dict with every secret we can find.\n", " \"\"\"\n", " return {\n", " 'comments': [c.strip() for c in re.findall(r\"\", html, re.DOTALL)],\n", " 'hidden_inputs': re.findall(r']*type=\"hidden\"[^>]*>', html),\n", " 'flags': re.findall(r\"FLAG\\{[^}]+\\}\", html),\n", " }\n", "\n", "# Test on the practice page:\n", "results = scan_page(target_html)\n", "print(\"=== SCAN COMPLETE ===\\n\")\n", "for kind, items in results.items():\n", " print(f\"{kind.upper().replace('_', ' ')} ({len(items)} found):\")\n", " for item in items:\n", " print(f\" ⢠{item}\")\n", " print()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ā Mission 4 ā Point the detective at a real website\n", "\n", "Same regex code, real data. We'll use `https://books.toscrape.com` ā a fake bookstore built specifically for scraping practice." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "url = \"https://books.toscrape.com\"\n", "target_html = requests.get(url).text\n", "\n", "print(f\"ā Downloaded {len(target_html)} characters from {url}\\n\")\n", "\n", "# Run our scanner on the real page.\n", "results = scan_page(target_html)\n", "for kind, items in results.items():\n", " print(f\"{kind}: {len(items)} found\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ā Bonus 2 ā Extract book titles\n", "\n", "Books on `books.toscrape.com` sit inside `