Python 现代开发学习笔记

(十)取得详情并还原完整 Book

1. 从页面属性逐步得到字段

继续把下面部分写入同一个 parsers.py。这次先确定详情根节点,再读取编号、书名、状态和标签:

# 续写同一文件:crawler_project/src/catalogcrawl/parsers.py
def parse_detail(html: str, source_url: str) -> CollectedBook:
    soup = BeautifulSoup(html, "html.parser")
    root = soup.select_one("article#book")
    if root is None:
        raise ParseError(f"{source_url}:没有找到详情 article#book")
    title = root.select_one("h1")
    source_id = root.get("data-id")
    flag = root.get("data-finished")
    if title is None or not isinstance(source_id, str) or not source_id.strip():
        raise ParseError(f"{source_url}:缺少书名或源站编号")
    if flag not in {"true", "false"}:
        raise ParseError(f"{source_url}:data-finished 不是 true 或 false")
    record = {
        "title": title.get_text(" ", strip=True),
        "finished": flag == "true",
        "tags": [tag.get_text(" ", strip=True) for tag in root.select("ul.tags > li")],
    }
    try:
        book = book_from_dict(record)
    except RecordError as cause:
        raise ParseError(f"{source_url}:{cause}") from cause
    return CollectedBook(source_id.strip(), book, source_url)

data-finished="false" 先得到字符串 false,比较 flag == "true" 才得到 False;标签节点被分别提取成列表。book_from_dict 再检查书名、布尔类型和标签,并清理重复标签。网络请求和记录检查之间保留了解析这一步,出错时可以知道具体停在哪里。

详情属性中的false文本先按约定转换成False,标签逐项提取,最终建立带来源的Book

2. 两个地址读到同一本书

# 项目文件:demos/details_sync.py
# 两个不同地址最终指向同一本书,URL 集合之外还要按源站编号识别记录。
import httpx
from crawlsite import running_site
from catalogcrawl.parsers import parse_detail


if __name__ == "__main__":
    with (
        running_site() as site,
        httpx.Client(timeout=1, trust_env=False, follow_redirects=True) as client,
    ):
        items = {}
        for path in ["/books/b101", "/alias/b101", "/books/b102"]:
            response = client.get(site.base_url + path)
            response.raise_for_status()
            item = parse_detail(response.text, str(response.url))
            items.setdefault(item.source_id, item)
        for item in items.values():
            print(item.source_id, item.as_record())

程序请求正常详情、它的跳转别名,以及第二本书,最后只留下 b101 和 b102 两条记录。b101 的 finished 为 False,标签是 ['基础', '语法'];b102 为 True,标签是 ['数值']。按编号执行 setdefault 保留第一次取得的记录。

实际采集时,来源 URL 也跟着记录保存。之后发现某个标题提取错了,可以按这个地址重新查看正文,而不是从最终 CSV 里的几个字倒猜出错页面。

笔记正文与全部题解 · Ctrl / ⌘ K