← 返回 Day 127 正文

Day 127 · 图解

Day 127 — BeautifulSoup / lxml 解析:图解

1. 解析流程

┌──────────────────────────────────────────────────────────────┐
│                    HTML 解析流程                              │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐               │
│  │ 原始HTML  │───▶│ 解析器选择│───▶│ 构建DOM树 │               │
│  │ 字符串    │    │ BS4/lxml │    │          │               │
│  └──────────┘    └──────────┘    └────┬─────┘               │
│                                       │                      │
│                                       ▼                      │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐               │
│  │ 提取数据  │◀───│ 查找元素  │◀───│ 遍历DOM树│               │
│  │ text/attr│    │ find/xpath│    │          │               │
│  └────┬─────┘    └──────────┘    └──────────┘               │
│       │                                                      │
│       ▼                                                      │
│  ┌──────────┐    ┌──────────┐                                │
│  │ 清洗数据  │───▶│ 存储数据  │                                │
│  │ 去空格    │    │ JSON/CSV │                                │
│  └──────────┘    └──────────┘                                │
│                                                              │
└──────────────────────────────────────────────────────────────┘

2. BeautifulSoup 查找方法对比

┌──────────────────────────────────────────────────────────────┐
│              BeautifulSoup 查找方法对比                       │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  find() — 返回第一个匹配                                      │
│  ┌──────────────────────────────────────────────────────┐   │
│  │ soup.find("h1")          → <h1>标题</h1>              │   │
│  │ soup.find("h1", class_="main")                        │   │
│  │ soup.find("a", href="/about")                         │   │
│  │ soup.find("div", attrs={"id": "content"})            │   │
│  └──────────────────────────────────────────────────────┘   │
│                                                              │
│  find_all() — 返回所有匹配                                    │
│  ┌──────────────────────────────────────────────────────┐   │
│  │ soup.find_all("a")        → [<a>, <a>, <a>]          │   │
│  │ soup.find_all("a", limit=3)  → 最多3个                │   │
│  │ soup.find_all(["h1", "h2"])  → 所有h1和h2             │   │
│  │ soup.find_all("p", class_=re.compile("post"))        │   │
│  └──────────────────────────────────────────────────────┘   │
│                                                              │
│  CSS 选择器 — 更简洁的语法                                    │
│  ┌──────────────────────────────────────────────────────┐   │
│  │ soup.select("h1")         → [<h1>]                   │   │
│  │ soup.select(".main")      → [<h1 class="main">]      │   │
│  │ soup.select("div > p")    → div 的直接子 p             │   │
│  │ soup.select("div p")      → div 的所有后代 p           │   │
│  │ soup.select("a[href]")    → 有 href 的所有 a          │   │
│  └──────────────────────────────────────────────────────┘   │
│                                                              │
└──────────────────────────────────────────────────────────────┘

3. XPath 语法速查

┌──────────────────────────────────────────────────────────────┐
│                    XPath 语法速查                              │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  路径表达式                                                   │
│  ┌──────────────────────────────────────────────────────┐   │
│  │ //div           → 所有 div 元素                        │   │
│  │ /html/body      → 绝对路径                             │   │
│  │ //div/p         → div 的直接子 p                       │   │
│  │ //div//p        → div 的所有后代 p                     │   │
│  │ ..              → 父元素                               │   │
│  └──────────────────────────────────────────────────────┘   │
│                                                              │
│  谓语(条件)                                                 │
│  ┌──────────────────────────────────────────────────────┐   │
│  │ //div[@class="main"]      → class="main" 的 div       │   │
│  │ //a[@href]                → 有 href 的 a               │   │
│  │ //p[contains(text(),"Python")] → 包含 "Python" 的 p   │   │
│  │ //li[1]                   → 第一个 li                  │   │
│  │ //li[last()]              → 最后一个 li                │   │
│  │ //li[position()<3]        → 前两个 li                  │   │
│  └──────────────────────────────────────────────────────┘   │
│                                                              │
│  提取                                                        │
│  ┌──────────────────────────────────────────────────────┐   │
│  │ //h1/text()       → h1 的文本内容                     │   │
│  │ //a/@href         → 所有 a 的 href 属性               │   │
│  │ //div/@class      → 所有 div 的 class 属性            │   │
│  └──────────────────────────────────────────────────────┘   │
│                                                              │
│  组合                                                        │
│  ┌──────────────────────────────────────────────────────┐   │
│  │ //h1 | //h2       → h1 或 h2                         │   │
│  │ //div[p]          → 包含 p 的 div                     │   │
│  │ //div[p and a]    → 同时包含 p 和 a 的 div             │   │
│  └──────────────────────────────────────────────────────┘   │
│                                                              │
└──────────────────────────────────────────────────────────────┘

4. 解析器选择决策树

┌──────────────────────────────────────────────────────────────┐
│              解析器选择决策树                                  │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  需要解析 HTML?                                              │
│       │                                                      │
│       ├── 需要最高性能?                                       │
│       │       │                                              │
│       │       ├── 是 → 使用 lxml XPath                       │
│       │       │                                              │
│       │       └── 否 → 需要简单 API?                         │
│       │               │                                      │
│       │               ├── 是 → 使用 BeautifulSoup            │
│       │               │                                      │
│       │               └── 否 → 使用 lxml                    │
│       │                                                      │
│       ├── HTML 很脏(未闭合标签等)?                           │
│       │       │                                              │
│       │       ├── 是 → 使用 html5lib 或 BeautifulSoup       │
│       │       │                                              │
│       │       └── 否 → lxml 或 html.parser 都可以            │
│       │                                                      │
│       ├── 需要提取文本节点?                                   │
│       │       │                                              │
│       │       ├── 是 → 使用 lxml XPath                      │
│       │       │    (XPath 的 /text() 是独有能力)              │
│       │       │                                              │
│       │       └── 否 → BeautifulSoup 也支持                  │
│       │                                                      │
│       └── 不能安装第三方库?                                   │
│               │                                              │
│               └── 使用 html.parser (Python 内置)              │
│                                                              │
└──────────────────────────────────────────────────────────────┘

5. Mermaid 流程图

graph TD A[获取 HTML 内容] --> B{选择解析器} B -->|简单场景| C[html.parser] B -->|性能优先| D[lxml] B -->|脏HTML| E[html5lib] C --> F[构建 DOM 树] D --> F E --> F F --> G{查找方式} G -->|CSS 选择器| H[select / select_one] G -->|方法调用| I[find / find_all] G -->|XPath| J[xpath] H --> K[提取数据] I --> K J --> K K --> L{数据类型} L -->|文本| M[.text / text()] L -->|属性| N[.get / @attr] L -->|HTML| O[.prettify] M --> P[清洗数据] N --> P O --> P P --> Q[存储数据] Q --> R[JSON / CSV / 数据库]
graph TD
    A[获取 HTML 内容] --> B{选择解析器}

    B -->|简单场景| C[html.parser]
    B -->|性能优先| D[lxml]
    B -->|脏HTML| E[html5lib]

    C --> F[构建 DOM 树]
    D --> F
    E --> F

    F --> G{查找方式}

    G -->|CSS 选择器| H[select / select_one]
    G -->|方法调用| I[find / find_all]
    G -->|XPath| J[xpath]

    H --> K[提取数据]
    I --> K
    J --> K

    K --> L{数据类型}

    L -->|文本| M[.text / text()]
    L -->|属性| N[.get / @attr]
    L -->|HTML| O[.prettify]

    M --> P[清洗数据]
    N --> P
    O --> P

    P --> Q[存储数据]
    Q --> R[JSON / CSV / 数据库]

6. CSS 选择器 vs XPath 对比

┌──────────────────────────────────────────────────────────────┐
│              CSS 选择器 vs XPath 对比                          │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  操作              CSS 选择器           XPath                  │
│  ──────────────────────────────────────────────────────────  │
│  选择所有 h1       h1                   //h1                  │
│                                                              │
│  按类查找          .classname           //*[@class="c"]       │
│                                                              │
│  按 ID 查找        #id                  //*[@id="id"]         │
│                                                              │
│  直接子元素        div > p              //div/p               │
│                                                              │
│  后代元素          div p                //div//p              │
│                                                              │
│  属性选择          a[href]              //a[@href]            │
│                                                              │
│  属性值匹配        [href="url"]         //a[@href="url"]      │
│                                                              │
│  模糊匹配          [class*="part"]      //*[contains(@c,"p")] │
│                                                              │
│  索引选择          :nth-child(2)        //li[2]               │
│                                                              │
│  最后一个          :last-child          //*[last()]           │
│                                                              │
│  提取文本          N/A (需 .text)       //a/text()            │
│                                                              │
│  提取属性          N/A (需 .get)        //a/@href             │
│                                                              │
│  父元素            N/A (不支持)         //a/..                │
│                                                              │
│  兄弟选择          + (相邻)             following-sibling::   │
│                                                              │
│  组合查询          .a, .b               //a | //b             │
│                                                              │
└──────────────────────────────────────────────────────────────┘