MIL-002: parsing HTML and making soup #36
@@ -0,0 +1,15 @@
|
||||
"""@brief The Link value object: the text and the address of an anchor."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Link:
|
||||
"""@brief A hyperlink found on a page.
|
||||
|
||||
@param text The visible text of the anchor.
|
||||
@param url The value of its `href` attribute.
|
||||
"""
|
||||
|
||||
text: str
|
||||
url: str
|
||||
@@ -0,0 +1,107 @@
|
||||
"""@brief BeautifulSoup basics from the "Parsing HTML and Making Soup" lectures."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from bs4 import BeautifulSoup, Tag
|
||||
|
||||
from scrape_movies.constants import HTML_PARSER
|
||||
from scrape_movies.domain.link import Link
|
||||
|
||||
|
||||
def load_html(path: Path) -> str:
|
||||
"""@brief Read an HTML file as text.
|
||||
|
||||
@param path The file to read.
|
||||
@return The content of the file.
|
||||
"""
|
||||
return path.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def make_soup(html: str) -> BeautifulSoup:
|
||||
"""@brief Parse HTML text into a soup object.
|
||||
|
||||
@param html The HTML document as text.
|
||||
@return The parsed document.
|
||||
"""
|
||||
return BeautifulSoup(html, HTML_PARSER)
|
||||
|
||||
|
||||
def get_title(soup: BeautifulSoup) -> str | None:
|
||||
"""@brief Return the text of the `title` tag.
|
||||
|
||||
@param soup The parsed document.
|
||||
@return The title text, or None when the page has no title.
|
||||
"""
|
||||
if soup.title is None or soup.title.string is None:
|
||||
return None
|
||||
return str(soup.title.string)
|
||||
|
||||
|
||||
def prettify_html(soup: BeautifulSoup) -> str:
|
||||
"""@brief Return the document indented one tag per line.
|
||||
|
||||
@param soup The parsed document.
|
||||
@return The indented HTML.
|
||||
"""
|
||||
return str(soup.prettify())
|
||||
|
||||
|
||||
def find_all_anchors(soup: BeautifulSoup) -> list[Link]:
|
||||
"""@brief Find every anchor tag with its text and address (`find_all`).
|
||||
|
||||
@param soup The parsed document.
|
||||
@return One Link per anchor that has an `href` attribute.
|
||||
"""
|
||||
links: list[Link] = []
|
||||
for anchor in soup.find_all("a"):
|
||||
url = anchor.get("href")
|
||||
if isinstance(url, str):
|
||||
links.append(Link(text=str(anchor.get_text()), url=url))
|
||||
return links
|
||||
|
||||
|
||||
def find_text_by_id(soup: BeautifulSoup, element_id: str) -> str | None:
|
||||
"""@brief Return the text of the first tag with the given id (`find`).
|
||||
|
||||
@param soup The parsed document.
|
||||
@param element_id The value of the `id` attribute.
|
||||
@return The text, or None when no tag has that id.
|
||||
"""
|
||||
tag = soup.find(id=element_id)
|
||||
return str(tag.get_text()) if isinstance(tag, Tag) else None
|
||||
|
||||
|
||||
def find_all_texts_by_class(
|
||||
soup: BeautifulSoup, tag_name: str, class_name: str
|
||||
) -> list[str]:
|
||||
"""@brief Return the text of every tag with the given class (`find_all`).
|
||||
|
||||
The keyword is `class_` because `class` is a reserved word in Python.
|
||||
|
||||
@param soup The parsed document.
|
||||
@param tag_name The tag to look for, for example `h3`.
|
||||
@param class_name The CSS class the tag must have.
|
||||
@return The texts in page order.
|
||||
"""
|
||||
return [str(tag.get_text()) for tag in soup.find_all(tag_name, class_=class_name)]
|
||||
|
||||
|
||||
def select_one_text(soup: BeautifulSoup, selector: str) -> str | None:
|
||||
"""@brief Return the text of the first tag matching a CSS selector.
|
||||
|
||||
@param soup The parsed document.
|
||||
@param selector A CSS selector such as `p a` or `#name`.
|
||||
@return The text, or None when nothing matches.
|
||||
"""
|
||||
tag = soup.select_one(selector)
|
||||
return str(tag.get_text()) if tag is not None else None
|
||||
|
||||
|
||||
def select_texts(soup: BeautifulSoup, selector: str) -> list[str]:
|
||||
"""@brief Return the text of every tag matching a CSS selector.
|
||||
|
||||
@param soup The parsed document.
|
||||
@param selector A CSS selector such as `.heading`.
|
||||
@return The texts in page order.
|
||||
"""
|
||||
return [str(tag.get_text()) for tag in soup.select(selector)]
|
||||
Reference in New Issue
Block a user