Python爬虫零基础入门【第二章:网页基础·第2节】你要抓的到底是什么:HTML、CSS 选择器、XPath 入门?

🔥 本期专栏《Python爬虫实战》已收录,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~持续更新中!!

全文目录:

      • [🌟 开篇语](#🌟 开篇语)
      • [📌 上期回顾](#📌 上期回顾)
      • [🎯 本节目标](#🎯 本节目标)
      • [一、HTML 文档结构:理解网页的骨架](#一、HTML 文档结构:理解网页的骨架)
        • [1.1 一个简单的 HTML 示例](#1.1 一个简单的 HTML 示例)
        • [1.2 HTML 的核心概念](#1.2 HTML 的核心概念)
        • [1.3 DOM 树:理解文档的层级结构](#1.3 DOM 树:理解文档的层级结构)
      • [二、CSS 选择器:最常用的定位方式](#二、CSS 选择器:最常用的定位方式)
        • [2.1 基础选择器](#2.1 基础选择器)
        • [2.2 组合选择器(重点)](#2.2 组合选择器(重点))
        • [2.3 伪类选择器(实用技巧)](#2.3 伪类选择器(实用技巧))
        • [2.4 实战示例:提取新闻字段](#2.4 实战示例:提取新闻字段)
        • [2.5 选择器优先级(工程建议)](#2.5 选择器优先级(工程建议))
      • 三、XPath:更强大的定位语言
        • [3.1 XPath 基础语法](#3.1 XPath 基础语法)
        • [3.2 XPath 属性和文本筛选](#3.2 XPath 属性和文本筛选)
        • [3.3 XPath 实战示例](#3.3 XPath 实战示例)
        • [3.4 CSS vs XPath:如何选择?](#3.4 CSS vs XPath:如何选择?)
      • 四、浏览器实时测试选择器(必会技能)
        • [4.1 Chrome DevTools 控制台](#4.1 Chrome DevTools 控制台)
        • [4.2 Elements 面板右键菜单](#4.2 Elements 面板右键菜单)
        • [4.3 实战演练:分析真实网页](#4.3 实战演练:分析真实网页)
      • 五、选择器稳定性:工程化思维
        • [5.1 什么是"稳定"的选择器?](#5.1 什么是"稳定"的选择器?)
        • [5.2 选择器设计的五个原则](#5.2 选择器设计的五个原则)
      • 六、常见问题与调试技巧
        • [6.1 问题:选择器返回 None 或空列表](#6.1 问题:选择器返回 None 或空列表)
        • [6.2 问题:提取的文本包含多余空格和换行](#6.2 问题:提取的文本包含多余空格和换行)
        • [6.3 问题:元素包含子标签,只想要纯文本](#6.3 问题:元素包含子标签,只想要纯文本)
        • [6.4 问题:动态加载的内容抓不到](#6.4 问题:动态加载的内容抓不到)
      • 七、本节小结
      • [📝 课后作业(必做,验收进入下一节)](#📝 课后作业(必做,验收进入下一节))
      • [🔮 下期预告](#🔮 下期预告)
      • 🌟文末
        • [📌 专栏持续更新中|建议收藏 + 订阅](#📌 专栏持续更新中|建议收藏 + 订阅)
        • [✅ 互动征集](#✅ 互动征集)

🌟 开篇语

哈喽,各位小伙伴们你们好呀~我是【喵手】。

运营社区: C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO

欢迎大家常来逛逛,一起学习,一起进步~🌟

我长期专注 Python 爬虫工程化实战 ,主理专栏 👉 《Python爬虫实战》:从采集策略反爬对抗 ,从数据清洗分布式调度 ,持续输出可复用的方法论与可落地案例。内容主打一个"能跑、能用、能扩展 ",让数据价值真正做到------抓得到、洗得净、用得上

📌 专栏食用指南(建议收藏)

  • ✅ 入门基础:环境搭建 / 请求与解析 / 数据落库
  • ✅ 进阶提升:登录鉴权 / 动态渲染 / 反爬对抗
  • ✅ 工程实战:异步并发 / 分布式调度 / 监控与容错
  • ✅ 项目落地:数据治理 / 可视化分析 / 场景化应用

📣 专栏推广时间 :如果你想系统学爬虫,而不是碎片化东拼西凑,欢迎订阅/关注专栏《Python爬虫实战》

订阅后更新会优先推送,按目录学习更高效~

📌 上期回顾

在上一节《网页是怎么工作的:URL、请求、响应、状态码?》中,我们学习了 HTTP 请求响应机制,掌握了如何使用浏览器 Network 面板分析网络请求。现在,你已经知道数据是如何从服务器传输到浏览器的。

但问题来了:服务器返回的 HTML 代码有几千行,我们要的标题、时间、作者这些字段藏在哪里?如何精准提取出来?

这一节,我们将学习网页结构解析的核心技能------这是爬虫工程中最关键的环节之一!

🎯 本节目标

通过本节学习,你将能够:

  1. 理解 HTML 文档结构和 DOM 树的概念
  2. 掌握 CSS 选择器的基本语法和优先级
  3. 学会使用 XPath 表达式定位元素
  4. 在浏览器中实时测试选择器
  5. 建立"选择器稳定性"的工程思维
  6. 交付验收:从一个真实网页手动定位 5 个字段(标题/时间/作者/正文/链接)

一、HTML 文档结构:理解网页的骨架

1.1 一个简单的 HTML 示例
html 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>新闻详情页</title>
</head>
<body>
    <header>
        <nav class="navbar">
            <a href="/">首页</a>
            <a href="/news">新闻</a>
        </nav>
    </header>
    
    <main>
        <article class="news-article" id="article-12345">
            <h1 class="title">重大科技突破:量子计算新进展</h1>
            
            <div class="meta-info">
                <span class="author">张三</span>
                <span class="publish-time" data-timestamp="1705824000">
                    2025-01-21 10:00:00
                </span>
                <span class="category">科技</span>
            </div>
            
            <div class="content">
                <p>研究团队今日宣布...</p>
                <p>这一突破意味着...</p>
            </div>
            
            <div class="tags">
                <a href="/tag/quantum" class="tag">量子计算</a>
                <a href="/tag/science" class="tag">科学</a>
            </div>
        </article>
    </main>
    
    <footer>
        <p>版权所有 © 2025</p>
    </footer>
</body>
</html>
1.2 HTML 的核心概念

标签(Tag)

  • 成对出现:<div>内容</div>
  • 自闭合:<img src="photo.jpg" />
  • 嵌套关系:子标签在父标签内部

属性(Attribute)

html 复制代码
<div class="container" id="main" data-page="1">
  • class:样式类名(可多个,空格分隔)
  • id:唯一标识符(页面内唯一)
  • data-*:自定义数据属性

常见标签及作用

标签 作用 示例
<h1> - <h6> 标题 <h1>主标题</h1>
<p> 段落 <p>正文内容</p>
<a> 链接 <a href="/news">新闻</a>
<div> 块级容器 <div class="box">...</div>
<span> 行内容器 <span class="label">标签</span>
<ul> <li> 列表 <ul><li>项目1</li></ul>
<img> 图片 <img src="photo.jpg" alt="描述">
1.3 DOM 树:理解文档的层级结构

上面的 HTML 在浏览器中被解析为 DOM(文档对象模型)树:

json 复制代码
html
├── head
│   ├── meta
│   └── title
└── body
    ├── header
    │   └── nav.navbar
    │       ├── a
    │       └── a
    ├── main
    │   └── article.news-article#article-12345
    │       ├── h1.title
    │       ├── div.meta-info
    │       │   ├── span.author
    │       │   ├── span.publish-time
    │       │   └── span.category
    │       ├── div.content
    │       │   ├── p
    │       │   └── p
    │       └── div.tags
    │           ├── a.tag
    │           └── a.tag
    └── footer
        └── p

💡 核心理解:爬虫的本质就是在这棵树上"导航",找到目标节点,提取其文本或属性。

二、CSS 选择器:最常用的定位方式

2.1 基础选择器

1. 标签选择器

css 复制代码
p          /* 选择所有 <p> 标签 */
div        /* 选择所有 <div> 标签 */

2. 类选择器(最常用)

css 复制代码
.title              /* 选择 class="title" 的元素 */
.meta-info          /* 选择 class="meta-info" 的元素 */
.news-article       /* 选择 class="news-article" 的元素 */

3. ID 选择器

css 复制代码
#article-12345      /* 选择 id="article-12345" 的元素 */

4. 属性选择器

css 复制代码
[data-timestamp]           /* 有 data-timestamp 属性的元素 */
[href="/news"]             /* href 等于 "/news" 的元素 */
[class^="news"]            /* class 以 "news" 开头 */
[class$="info"]            /* class 以 "info" 结尾 */
[class*="meta"]            /* class 包含 "meta" */
2.2 组合选择器(重点)

1. 后代选择器(空格)

css 复制代码
article .title              /* article 内部的 .title(任意层级) */
.meta-info span             /* .meta-info 内部的所有 span */

2. 子选择器(>)

css 复制代码
.meta-info > span           /* .meta-info 的直接子元素 span */
article > h1                /* article 的直接子元素 h1 */

3. 相邻兄弟选择器(+)

css 复制代码
h1 + .meta-info             /* h1 后面紧邻的 .meta-info */

4. 通用兄弟选择器(~)

css 复制代码
h1 ~ div                    /* h1 后面所有同级的 div */
2.3 伪类选择器(实用技巧)
css 复制代码
li:first-child              /* 第一个 li */
li:last-child               /* 最后一个 li */
li:nth-child(2)             /* 第 2 个 li */
li:nth-child(odd)           /* 奇数位的 li */
li:nth-child(even)          /* 偶数位的 li */
p:not(.intro)               /* 不包含 intro 类的 p */
2.4 实战示例:提取新闻字段

基于前面的 HTML 示例:

python 复制代码
from bs4 import BeautifulSoup

html = """...(前面的HTML代码)..."""
soup = BeautifulSoup(html, 'lxml')

# 1. 提取标题
title = soup.select_one('article h1.title').text.strip()
# 结果:"重大科技突破:量子计算新进展"

# 2. 提取作者
author = soup.select_one('.meta-info .author').text.strip()
# 结果:"张三"

# 3. 提取时间
time_elem = soup.select_one('.publish-time')
publish_time = time_elem.text.strip()
timestamp = time_elem.get('data-timestamp')
# 结果:"2025-01-21 10:00:00", "1705824000"

# 4. 提取正文(所有段落)
paragraphs = soup.select('.content p')
content = '\n'.join([p.text.strip() for p in paragraphs])

# 5. 提取所有标签
tags = soup.select('.tags a.tag')
tag_list = [tag.text.strip() for tag in tags]
# 结果:['量子计算', '科学']
2.5 选择器优先级(工程建议)

优先级从高到低

  1. ID 选择器#article-12345(唯一性高)
  2. 类选择器.news-article(通用性好)
  3. 属性选择器[data-id="12345"](灵活)
  4. 标签选择器article(范围太宽)

⚠️ 工程原则 :优先使用 classid,避免过度依赖标签名和层级结构(网页改版时容易失效)。

三、XPath:更强大的定位语言

3.1 XPath 基础语法

XPath 是一种在 XML/HTML 文档中导航的语言,功能比 CSS 选择器更强大。

基本路径表达式

xpath 复制代码
/html/body/div              # 绝对路径(从根开始)
//div                       # 所有 div 元素(任意位置)
//div[@class="title"]       # 带 class="title" 的 div
//div[@id="main"]           # 带 id="main" 的 div

轴操作

xpath 复制代码
//article                   # 选择 article 元素
//article/h1                # article 的子元素 h1
//article//span             # article 内部所有 span(任意层级)
//h1/following-sibling::div # h1 后面的兄弟 div 元素
//div/parent::article       # div 的父元素 article
3.2 XPath 属性和文本筛选

属性匹配

xpath 复制代码
//div[@class="news"]                    # class 完全匹配
//div[contains(@class, "news")]         # class 包含 "news"
//div[starts-with(@class, "news")]      # class 以 "news" 开头
//a[@href="/news"]                      # href 等于 "/news"
//span[@data-timestamp]                 # 有 data-timestamp 属性

文本匹配

xpath 复制代码
//h1[text()="标题"]                     # 文本完全匹配
//p[contains(text(), "关键词")]         # 文本包含 "关键词"
//div[string-length(text()) > 10]      # 文本长度大于 10

位置索引

xpath 复制代码
//li[1]                     # 第 1 个 li(注意:XPath 索引从 1 开始)
//li[last()]                # 最后一个 li
//li[position() < 3]        # 前 2 个 li
3.3 XPath 实战示例
python 复制代码
from lxml import etree

html = """...(前面的HTML代码)..."""
tree = etree.HTML(html)

# 1. 提取标题
title = tree.xpath('//article[@class="news-article"]/h1[@class="title"]/text()')[0]
# 或更简洁:
title = tree.xpath('//h1[@class="title"]/text()')[0]

# 2. 提取作者
author = tree.xpath('//span[@class="author"]/text()')[0]

# 3.')[0]

# 3. 提取时间戳属性
timestamp = tree.xpath('//span[@class="publish-time"]/@data-timestamp')[0]

# 4. 提取正文所有段落
paragraphs = tree.xpath('//div[@class="content"]/p/text()')
content = '\n'.join(paragraphs)

# 5. 提取标签链接和文本
tags = tree.xpath('//div[@class="tags"]/a')
tag_data = [
    {'text': tag.text.strip(), 'href': tag.get('href')}
    for tag in tags
]

# 6. 复杂场景:提取包含特定关键词的段落
keyword_paras = tree.xpath('//p[contains(text(), "突破")]')
3.4 CSS vs XPath:如何选择?
特性 CSS 选择器 XPath
学习难度 简单,前端常用 稍复杂,但更强大
文本筛选 ✅ 支持
属性获取 ⚠️ 需额外方法 ✅ 直接获取 @href
父节点访问 ❌ 不支持 ✅ 支持 parent::
性能 稍快 稍慢(可忽略)
浏览器测试 ✅ 直接支持 ✅ 支持($x()

💡 建议

  • 简单场景用 CSS(代码更简洁)
  • 复杂筛选用 XPath(功能更强大)
  • 两者结合使用(根据具体场景选择)

四、浏览器实时测试选择器(必会技能)

4.1 Chrome DevTools 控制台

步骤

  1. F12 打开开发者工具
  2. 切换到 Console 标签页
  3. 使用以下命令测试

CSS 选择器测试

javascript 复制代码
// 返回第一个匹配元素
document.querySelector('.title')

// 返回所有匹配元素(NodeList)
document.querySelectorAll('.meta-info span')

// 简写形式(Chrome 提供)
$('.title')              // 等同于 querySelector
$$('.meta-info span')    // 等同于 querySelectorAll

XPath 测试

javascript 复制代码
// Chrome 提供的 XPath 测试函数
$x('//h1[@class="title"]')             // 返回数组
$x('//span[@class="author"]/text()')   // 提取文本节点
4.2 Elements 面板右键菜单

复制选择器

  1. 在 Elements 面板右键点击目标元素
  2. 选择 "Copy" → "Copy selector"(自动生成 CSS 选择器)
  3. 选择 "Copy" → "Copy XPath"(自动生成 XPath)

注意:浏览器生成的选择器通常过于具体,需要手动简化:

css 复制代码
/* 浏览器生成(过于复杂)*/
body > div.wrapper > main > article:nth-child(1) > h1.title

/* 简化后(更稳定)*/
article h1.title
/* 或 */
.news-article .title
4.3 实战演练:分析真实网页

任务 :打开 https://news.sina.com.cn/ 并提取新闻标题

javascript 复制代码
// 1. 在控制台测试
$$('.news-item h2 a')  // 假设这是标题所在位置

// 2. 遍历提取文本
$$('.news-item h2 a').forEach(el => console.log(el.innerText))

// 3. 提取链接
$$('.news-item h2 a').forEach(el => console.log(el.href))

五、选择器稳定性:工程化思维

5.1 什么是"稳定"的选择器?

不稳定的示例

css 复制代码
/* ❌ 依赖层级和顺序,改版易失效 */
body > div:nth-child(3) > div:nth-child(2) > p:first-child

/* ❌ 依赖样式类,可能被随机化 */
.css-1xw2kqp-Title

/* ❌ 过于泛化,容易误匹配 */
div span

稳定的示例

css 复制代码
/* ✅ 语义化类名 */
.article-title
.publish-date
.author-name

/* ✅ 数据属性 */
[data-article-id]
[data-role="title"]

/* ✅ 适度组合 */
article.news .title
.meta-info .author
5.2 选择器设计的五个原则

原则1:优先使用语义化的 class 和 id

python 复制代码
# ✅ 好
soup.select_one('.article-title')

# ❌ 差
soup.select_one('div > div > h1')

原则2:避免过度依赖层级结构

python 复制代码
# ✅ 好(适度层级)
soup.select('.article-content p')

# ❌ 差(过深层级)
soup.select('body > main > section > article > div > div > p')

原则3:用属性选择器应对动态类名

python 复制代码
# 某些网站的类名是动态生成的
# <div class="css-1ab2cd3-Title">标题</div>

# ❌ 不稳定
soup.select_one('.css-1ab2cd3-Title')

# ✅ 用属性或结构
soup.select_one('[data-testid="title"]')
soup.select_one('article > h1')

原则4:多重选择器作为 fallback

python 复制代码
def extract_title(soup):
    """提取标题,支持多种选择器"""
    selectors = [
        'h1.article-title',      # 首选
        '[data-role="title"]',   # 备选1
        'article > h1',          # 备选2
        'h1'                     # 兜底
    ]
    
    for selector in selectors:
        element = soup.select_one(selector)
        if element:
            return element.text.strip()
    
    return None  # 所有选择器都失效

原则5:留下注释说明选择逻辑

python 复制代码
# 提取发布时间
# 网站结构:<span class="time" data-timestamp="...">2025-01-21</span>
# 优先获取 data-timestamp 属性(Unix 时间戳)
# 如果没有,则解析显示文本
time_elem = soup.select_one('.meta-info .time')
if time_elem and time_elem.get('data-timestamp'):
    timestamp = int(time_elem['data-timestamp'])
else:
    time_text = time_elem.text.strip()  # 需要进一步解析

六、常见问题与调试技巧

6.1 问题:选择器返回 None 或空列表

排查步骤

python 复制代码
# 1. 检查 HTML 是否正确获取
print(response.text[:500])  # 查看前 500 字符

# 2. 保存 HTML 到文件,用浏览器打开对比
with open('debug.html', 'w', encoding='utf-8') as f:
    f.write(response.text)

# 3. 在保存的文件中测试选择器
soup = BeautifulSoup(open('debug.html', encoding='utf-8'), 'lxml')
result = soup.select('.title')
print(f"找到 {len(result)} 个元素")

# 4. 逐步简化选择器
soup.select('h1')              # 是否能找到任何 h1?
soup.select('.title')          # 是否有这个类?
soup.select('article .title')  # 组合是否正确?
6.2 问题:提取的文本包含多余空格和换行

清洗方法

python 复制代码
import re

# 原始文本
raw_text = "  \n  这是标题  \n  "

# 方法1:strip() 去除首尾空白
clean1 = raw_text.strip()
# 结果:"这是标题"

# 方法2:正则替换多余空白
clean2 = re.sub(r'\s+', ' ', raw_text).strip()
# 结果:"这是标题"

# 方法3:处理 HTML 实体
from html import unescape
text_with_entity = "这是&nbsp;标题&lt;test&gt;"
clean3 = unescape(text_with_entity)
# 结果:"这是 标题<test>"
6.3 问题:元素包含子标签,只想要纯文本
python 复制代码
# HTML: <div class="content">正文<span>(来源:网站)</span></div>

# ❌ 错误做法(会包含 span 的文本)
text = soup.select_one('.content').text
# 结果:"正文(来源:网站)"

# ✅ 方法1:只取直接文本节点
element = soup.select_one('.content')
text = ''.join(element.find_all(text=True, recursive=False))
# 结果:"正文"

# ✅ 方法2:移除特定子元素后提取
element = soup.select_one('.content')
for span in element.select('span'):
    span.decompose()  # 删除 span 标签
text = element.text.strip()
6.4 问题:动态加载的内容抓不到
python 复制代码
# 症状:浏览器能看到,代码获取的 HTML 里没有

# 原因:内容是 JavaScript 渲染的
# 解决方案1:找到数据接口(推荐)
# 在 Network 面板找 XHR/Fetch 请求,直接请求 JSON 接口

# 解决方案2:使用动态渲染工具(下一章学习)
# 如 Playwright、Selenium

七、本节小结

本节我们系统学习了网页解析的核心技能:

HTML 结构 :理解标签、属性、DOM 树的概念

CSS 选择器 :掌握类、ID、属性、组合选择器的用法

XPath 表达式 :学会更强大的定位和筛选语法

浏览器调试 :使用控制台实时测试选择器

稳定性原则 :编写不易失效的选择器

问题排查:调试技巧和常见错误处理

核心原则

  • 选择器要"刚刚好"------不过度具体,不过度泛化
  • 语义化类名 > 层级结构 > 标签名
  • 多准备 fallback 选择器,提高鲁棒性
  • 遇到问题先保存 HTML,对比浏览器渲染结果

📝 课后作业(必做,验收进入下一节)

任务1:选择器练习

针对以下 HTML 片段,写出选择器并验证:

html 复制代码
<div class="news-list">
    <div class="news-item" data-id="1001">
        <h3 class="title"><a href="/news/1001">新闻标题A</a></h3>
        <div class="info">
            <span class="author">作者A</span>
            <span class="date">2025-01-21</span>
            <span class="views">1.2万</span>
        </div>
    </div>
    <div class="news-item" data-id="1002">
        <h3 class="title"><a href="/news/1002">新闻标题B</a></h3>
        <div class="info">
            <span class="author">作者B</span>
            <span class="date">2025-01-20</span>
            <span class="views">8500</span>
        </div>
    </div>
</div>

要求:写出提取以下内容的选择器(CSS 和 XPath 各一种)

  1. 所有新闻标题
  2. 第一条新闻的作者
  3. data-id 属性为 1002 的新闻链接
  4. 所有发布日期
  5. 阅读量超过 1 万的新闻标题
任务1:爬虫代码示例

如下为任务1的爬虫代码,仅供参加:

1)所有新闻标题

CSS

css 复制代码
.news-item h3.title > a

XPath

xpath 复制代码
//div[@class="news-item"]//h3[@class="title"]/a/text()

2)第一条新闻的作者

CSS

css 复制代码
.news-item:first-child .info .author

XPath

xpath 复制代码
(//div[@class="news-item"])[1]//span[@class="author"]/text()

3)data-id=1002 的新闻链接

CSS

css 复制代码
.news-item[data-id="1002"] h3.title > a

XPath

xpath 复制代码
//div[@class="news-item" and @data-id="1002"]//h3[@class="title"]/a/@href

4)所有发布日期

CSS

css 复制代码
.news-item .info .date

XPath

xpath 复制代码
//div[@class="news-item"]//span[@class="date"]/text()

5)阅读量超过 1 万的新闻标题(工程化答案)

CSS(先选出每条新闻节点,后续用 Python 过滤)

css 复制代码
.news-item

XPath(同理,先取每条 news-item 节点,再过滤)

xpath 复制代码
//div[@class="news-item"]
任务2:真实网站练习

选择一个真实网站(推荐:新浪新闻、知乎、豆瓣),完成:

  1. 打开浏览器开发者工具

  2. 定位列表页的 5 个字段:

    • 标题
    • 发布时间
    • 作者/来源
    • 链接
    • (可选)摘要/标签
  3. 为每个字段编写 CSS 选择器和 XPath 表达式

  4. 在控制台测试验证

  5. 截图保存测试结果

任务2:爬虫代码示例

如下为任务2的爬虫代码,仅供参加:

我用这篇示例文章页来写(你也可以换成你自己选的页面,替换选择器即可):

2.1(浏览器控制台)字段定位"通用兜底选择器"

这类移动文章页通常 h1 就是标题 ,正文段落多为 p,你可以先用"兜底选择器"跑通,然后再在 Elements 里 Copy selector 精准化 ✨

js 复制代码
// 1) 标题(兜底:第一条 h1)
document.querySelector("h1")?.innerText

// 2) 来源/作者(兜底:页面里第一个 h2 或者包含"账号/作者"的块)
// 如果 h2 就是来源:
document.querySelector("h2")?.innerText

// 3) 发布时间(兜底:从页面文本里用正则抽"MM.DD HH:MM")
document.body.innerText.match(/\b\d{2}\.\d{2}\s+\d{2}:\d{2}\b/)?.[0]

// 4) 链接(当前页 URL)
location.href

// 5) 正文(兜底:取所有 p 的文本)
[...document.querySelectorAll("p")].map(p => p.innerText.trim()).filter(Boolean).join("\n")

2.2(Python 抓取)真实网站提取脚本源码:real_site_demo.py

你本地运行需要联网;如遇到"页面需要开启 JavaScript",就换页面或改用 Playwright(下一章会学)。这个示例页正文是直出的,适合 requests。 ([手机科技频道][1])

python 复制代码
# real_site_demo.py
import re
import requests
from bs4 import BeautifulSoup
from lxml import etree

URL = "https://tech.sina.cn/2026-01-21/detail-inhiaktr5379581.d.html?cid=79649&node_id=79649&vt=4"

def pick_first_text(soup, css_list):
    """多套 CSS 选择器兜底:找到第一个就返回 text"""
    for css in css_list:
        el = soup.select_one(css)
        if el and el.get_text(strip=True):
            return el.get_text(strip=True)
    return None

def main():
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
                      "(KHTML, like Gecko) Chrome/120.0 Safari/537.36"
    }
    r = requests.get(URL, headers=headers, timeout=15)
    r.raise_for_status()
    r.encoding = r.apparent_encoding

    html = r.text
    soup = BeautifulSoup(html, "lxml")
    tree = etree.HTML(html)

    # 1) 标题:兜底优先 h1
    title = pick_first_text(soup, ["h1", "header h1", ".article-title", ".art_tit_h1"])

    # 2) 来源/作者:兜底 h2 或常见 class
    source = pick_first_text(soup, ["h2", ".source", ".article-source", ".art_info .source"])

    # 3) 时间:页面里通常出现 "01.21 19:44" 这类格式,直接正则抽
    time_text = None
    m = re.search(r"\b\d{2}\.\d{2}\s+\d{2}:\d{2}\b", soup.get_text(" ", strip=True))
    if m:
        time_text = m.group(0)

    # 4) 链接:就是 URL
    link = URL

    # 5) 正文:兜底抓所有 p(过滤太短的段落)
    ps = [p.get_text(" ", strip=True) for p in soup.select("p")]
    content = "\n".join([t for t in ps if len(t) >= 10])

    # 若 p 抓不到,再用 XPath 兜底抓全文(不够精准但能交付)
    if not content.strip():
        text_nodes = tree.xpath("//body//text()")
        content = "\n".join([t.strip() for t in text_nodes if t and t.strip()])

    print("=== REAL SITE PARSE RESULT ===")
    print("Title:", title)
    print("Time:", time_text)
    print("Source:", source)
    print("Link:", link)
    print("\nContent preview:\n", content[:400], "...\n")

if __name__ == "__main__":
    main()
任务3:编写提取脚本

创建 parser_demo.py

python 复制代码
from bs4 import BeautifulSoup
from lxml import etree

html = """(任务1的HTML代码)"""

# 用 BeautifulSoup + CSS 选择器
soup = BeautifulSoup(html, 'lxml')
print("=== BeautifulSoup + CSS ===")
# 实现提取逻辑...

# 用 lxml + XPath
tree = etree.HTML(html)
print("\n=== lxml + XPath ===")
# 实现提取逻辑...
任务3:爬虫代码示例

下面这份脚本直接用你作业里的 HTML 片段,分别用 BeautifulSoup+CSSlxml+XPath 提取 5 个要求字段,并对"阅读量 > 1 万"做了工程化数值解析。

python 复制代码
# parser_demo.py
import re
from bs4 import BeautifulSoup
from lxml import etree

HTML = """
<div class="news-list">
    <div class="news-item" data-id="1001">
        <h3 class="title"><a href="/news/1001">新闻标题A</a></h3>
        <div class="info">
            <span class="author">作者A</span>
            <span class="date">2025-01-21</span>
            <span class="views">1.2万</span>
        </div>
    </div>
    <div class="news-item" data-id="1002">
        <h3 class="title"><a href="/news/1002">新闻标题B</a></h3>
        <div class="info">
            <span class="author">作者B</span>
            <span class="date">2025-01-20</span>
            <span class="views">8500</span>
        </div>
    </div>
</div>
"""

def parse_views_to_int(s: str) -> int:
    """
    把 '1.2万' / '8500' / '12,345' 统一转成整数
    """
    if not s:
        return 0
    s = s.strip().replace(",", "")
    # 形如 1.2万
    m = re.fullmatch(r"(\d+(?:\.\d+)?)万", s)
    if m:
        return int(float(m.group(1)) * 10000)
    # 纯数字
    m = re.fullmatch(r"\d+", s)
    if m:
        return int(s)
    return 0

def bs4_css_demo():
    soup = BeautifulSoup(HTML, "lxml")
    print("=== BeautifulSoup + CSS ===")

    # 1. 所有新闻标题
    titles = [a.get_text(strip=True) for a in soup.select(".news-item h3.title > a")]
    print("1) Titles:", titles)

    # 2. 第一条新闻的作者
    first_author = soup.select_one(".news-item:first-child .info .author").get_text(strip=True)
    print("2) First author:", first_author)

    # 3. data-id=1002 的新闻链接
    link_1002 = soup.select_one('.news-item[data-id="1002"] h3.title > a')["href"]
    print("3) Link of data-id=1002:", link_1002)

    # 4. 所有发布日期
    dates = [s.get_text(strip=True) for s in soup.select(".news-item .info .date")]
    print("4) Dates:", dates)

    # 5. 阅读量超过 1 万的新闻标题(工程化过滤)
    over_10k = []
    for item in soup.select(".news-item"):
        views_text = item.select_one(".views").get_text(strip=True)
        views = parse_views_to_int(views_text)
        if views > 10000:
            over_10k.append(item.select_one("h3.title > a").get_text(strip=True))
    print("5) Titles with views > 10k:", over_10k)

def lxml_xpath_demo():
    tree = etree.HTML(HTML)
    print("\n=== lxml + XPath ===")

    # 1. 所有新闻标题
    titles = tree.xpath('//div[@class="news-item"]//h3[@class="title"]/a/text()')
    titles = [t.strip() for t in titles]
    print("1) Titles:", titles)

    # 2. 第一条新闻的作者
    first_author = tree.xpath('(//div[@class="news-item"])[1]//span[@class="author"]/text()')[0].strip()
    print("2) First author:", first_author)

    # 3. data-id=1002 的新闻链接
    link_1002 = tree.xpath('//div[@class="news-item" and @data-id="1002"]//h3[@class="title"]/a/@href')[0]
    print("3) Link of data-id=1002:", link_1002)

    # 4. 所有发布日期
    dates = tree.xpath('//div[@class="news-item"]//span[@class="date"]/text()')
    dates = [d.strip() for d in dates]
    print("4) Dates:", dates)

    # 5. 阅读量超过 1 万:XPath 抓文本 + Python 数值过滤(最稳)
    items = tree.xpath('//div[@class="news-item"]')
    over_10k = []
    for item in items:
        title = item.xpath('.//h3[@class="title"]/a/text()')[0].strip()
        views_text = item.xpath('.//span[@class="views"]/text()')[0].strip()
        if parse_views_to_int(views_text) > 10000:
            over_10k.append(title)
    print("5) Titles with views > 10k:", over_10k)

if __name__ == "__main__":
    bs4_css_demo()
    lxml_xpath_demo()

最终验收方式:在留言区提交:

  • 任务1的选择器答案(CSS + XPath)
  • 任务2的分析截图和选择器代码
  • 任务3的脚本运行截图
  • 遇到的困难和解决方法

🔮 下期预告

下一节《接口数据基础:JSON 是什么?分页是什么?》,我们将学习:

  • JSON 数据格式的结构和解析
  • 列表接口的三种分页模式(page/offset/cursor)
  • 如何用 Postman 测试接口
  • 接口参数的逆向分析技巧
  • 编写第一个接口采集脚本

预习建议

打开你常访问的网站,在 Network 面板筛选 XHR 类型的请求,观察哪些返回的是 JSON 格式数据。尝试理解 JSON 的结构(对象、数组、嵌套关系)。


💬 选择器是爬虫的基本功,多练习才能熟练!加油!

记住:没有完美的选择器,只有适合当前场景的选择器。保持灵活,准备 plan B,你的爬虫会更稳定。

🌟文末

好啦~以上就是本期 《Python爬虫实战》的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥

📌 专栏持续更新中|建议收藏 + 订阅

专栏 👉 《Python爬虫实战》,我会按照"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一篇都做到:

✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)

📣 想系统提升的小伙伴:强烈建议先订阅专栏,再按目录顺序学习,效率会高很多~

✅ 互动征集

想让我把【某站点/某反爬/某验证码/某分布式方案】写成专栏实战?

评论区留言告诉我你的需求,我会优先安排更新 ✅


⭐️ 若喜欢我,就请关注我叭~(更新不迷路)

⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)

⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)


免责声明:本文仅用于学习与技术研究,请在合法合规、遵守站点规则与 Robots 协议的前提下使用相关技术。严禁将技术用于任何非法用途或侵害他人权益的行为。

相关推荐
幻云20102 小时前
Python机器学习:筑基与实践
前端·人工智能·python
飞天小蜈蚣2 小时前
python-django_ORM的十三个查询API接口
开发语言·python·django
人工智能AI技术2 小时前
【Agent从入门到实践】18 脚本化编程:批量执行、自动化逻辑
人工智能·python
摘星编程2 小时前
React Native + OpenHarmony:MapView自定义标注样式
python
向量引擎2 小时前
[硬核架构] 2026 企业级 AI 网关落地指南:从“连接超时”到“秒级响应”的架构演进(附 Python/Java 源码)
人工智能·python·gpt·ai作画·架构·aigc·api调用
0思必得02 小时前
[Web自动化] Selenium模拟用户的常见操作
前端·python·selenium·自动化
凡客丶2 小时前
Windows版Miniconda打包环境迁移到内网离线环境【详解】
windows·python
AI大佬的小弟2 小时前
【详细步骤】大模型基础知识(4)---ollama模型调用-多轮对话体验
python·ollama·大模型基础·ai 聊天机器人·简单的大模型部署·实现ollama模型调用·零基础上手 ollama体验
AndrewHZ2 小时前
【Python与生活】怎么用python画出好看的分形图?
开发语言·python·生活·可视化·递归·分形