系列文章

Python 从入门到精通

第 26 / 36 篇

从环境安装和基础语法出发,逐步学习工程实践、自动化、数据处理与 Web 开发。

  1. 01
    Python 怎么装、怎么用
  2. 02
    变量、数字和字符串
  3. 03
    列表:把一组数据放在一起
  4. 04
    元组、集合和字典
  5. 05
    条件判断:让程序做选择
  6. 06
    循环:重复的事交给程序
  7. 07
    函数:把代码整理成可复用的块
  8. 08
    模块与包:拆分你的程序
  9. 09
    输入、输出与字符串格式化
  10. 10
    文件读写:保存程序的数据
  11. 11
    异常处理:程序出错时怎么办
  12. 12
    基础阶段练习:命令行记账本
  13. 13
    类和对象:面向对象入门
  14. 14
    继承、组合与特殊方法
  15. 15
    迭代器与生成器
  16. 16
    列表推导式与生成器表达式
  17. 17
    装饰器:给函数增加能力
  18. 18
    上下文管理器与 with
  19. 19
    类型标注与 dataclass
  20. 20
    正则表达式:从文本中找规律
  21. 21
    日期、时间与时区
  22. 22
    日志与调试
  23. 23
    虚拟环境与依赖管理
  24. 24
    测试:让修改不再提心吊胆
  25. 25
    网络请求:用 Python 调用 API
  26. 26
    网页解析与合规采集正在阅读
  27. 27
    操作 Excel、CSV 与批量文件
  28. 28
    SQLite:给程序加一个数据库
  29. 29
    数据分析入门:NumPy 与 Pandas
  30. 30
    画图:把数据变得直观
  31. 31
    Flask 入门:做一个小网站
  32. 32
    异步编程:同时处理多项任务
  33. 33
    线程、进程与并发选择
  34. 34
    性能分析与优化
  35. 35
    项目结构、配置与发布
  36. 36
    综合项目:从需求到上线

查看整个系列 →

上一篇用 requests 拿到了网页的 HTML,但 HTML 是给人看的,一坨标签里找数据很痛苦。这一篇学网页解析:用 BeautifulSoup 把 HTML 变成能查询的结构,提取你要的数据。最后郑重讲一下采集的合规问题——这个必须放在前面。

合规第一:采集的规矩

会爬虫 ≠ 能随便爬。三条底线:

  • 只采公开数据:需要登录、需要付费、明确禁止采集的内容,别碰
  • 尊重 robots.txt你的域名/robots.txt 里写了哪些路径允许爬。查一眼不费事
  • 控制频率:别把人家服务器打挂。请求间隔至少 1-2 秒,小站更要注意

法律上,爬取并商用他人数据、绕过访问控制、造成对方损失都可能违法。学技术是好事,别用在歪路上。

安装和基本用法

pip install beautifulsoup4 lxml

(lxml 是解析器,比默认的快,建议一起装)

import requests
from bs4 import BeautifulSoup

resp = requests.get("https://www.nsn0704y.cn", timeout=10)
resp.raise_for_status()
resp.encoding = "utf-8"          # 中文网站指定编码,防止乱码

soup = BeautifulSoup(resp.text, "lxml")
print(soup.title)                # <title>南山小站 - 写在北方,留给以后。</title>
print(soup.title.text)           # 南山小站 - 写在北方,留给以后。
print(soup.find("h1").text)      # 第一个 h1 的文字

find 和 find_all:核心查找

# find:找第一个
first_link = soup.find("a")
print(first_link.get("href"))    # 取属性

# find_all:找所有,返回列表
all_links = soup.find_all("a")
for link in all_links[:3]:
    print(link.text, "→", link.get("href"))

# 带条件查找:class / id / 属性
soup.find_all("div", class_="post")        # class 用 class_(class 是关键字)
soup.find_all("a", class_="nav-link")
soup.find(id="main-content")
soup.find_all("img", alt="封面")           # 按属性值过滤
soup.find_all("a", href=True)             # 有 href 的链接

记住 class_ 带下划线——写 class= 会直接语法错误。

CSS 选择器:select

如果你会 CSS,select 更顺手——直接用选择器语法:

# 等价写法对比
soup.select("div.post")                 # 所有 class="post" 的 div
soup.select("#main-content")            # id
soup.select("article h2")               # article 里的所有 h2
soup.select("div.post > h2")            # 直接子元素
soup.select("a[href^='https']")         # href 以 https 开头的链接
soup.select(".post:nth-of-type(2)")     # 第二个 .post

# 实战:抓取文章列表
articles = soup.select("article h2 a")
for a in articles:
    print(a.text, "→", a.get("href"))

find/find_all 适合简单场景,select 适合复杂结构——两个都学会。

提取文本的细节

# .text:取所有文字(去掉标签)
div = soup.find("div", class_="content")
print(div.text)                 # 可能带多余空白
print(div.get_text(strip=True)) # 去掉首尾空白

# .get():取属性,带默认值
img = soup.find("img")
print(img.get("src", ""))       # 没有 src 就返回空字符串

# 多个元素拼接文字
for p in soup.select("div.content p"):
    print(p.get_text(strip=True))

实战:抓一个简单页面

完整流程:请求 → 解析 → 提取 → 存文件:

import requests
from bs4 import BeautifulSoup
import time

def fetch_article_titles(url):
    resp = requests.get(url, timeout=10, headers={"User-Agent": "MyStudyBot/1.0"})
    resp.raise_for_status()
    resp.encoding = "utf-8"
    soup = BeautifulSoup(resp.text, "lxml")
    titles = []
    for a in soup.select("article h2 a"):
        title = a.get_text(strip=True)
        href = a.get("href")
        titles.append((title, href))
    return titles

titles = fetch_article_titles("https://www.nsn0704y.cn")
for t, href in titles:
    print(f"{t} → {href}")
    time.sleep(1)      # 礼貌:每请求间隔 1 秒(这里只请求了一次,示例而已)

数据清洗:提取完还要处理

抓下来的数据往往是脏的,常见的清洗操作:

raw = ["  Python入门  ", "价格:¥99", "", "  ", "3,200 次阅读"]

cleaned = []
for item in raw:
    s = item.strip()                    # 去首尾空白
    if not s:                           # 跳过空串
        continue
    s = s.replace("¥", "").replace(",", "")   # 去掉货币符号和千分位逗号
    cleaned.append(s)
print(cleaned)   # ['Python入门', '价格:99', '3200 次阅读']

新手坑

坑 1:页面是 JS 渲染的。requests 拿到的是原始 HTML,很多现代网站的数据是 JavaScript 动态加载的,HTML 里没有。这时要么找它的数据接口(打开开发者工具看 Network),要么用 Playwright/Selenium 这类浏览器自动化。别在 soup 上死磕。

坑 2:编码乱码。中文乱码先试 resp.encoding = "utf-8";还乱就 resp.apparent_encoding 自动检测。

坑 3:爬太快被反爬。连续高频请求会被封 IP。加 sleep、限并发、别伪装成浏览器爬(容易被定性为恶意)。

小结

  • 合规三底线:只采公开数据、尊重 robots.txt、控制频率
  • BeautifulSoup(html, "lxml") 解析;find/find_all 按标签查找,注意 class_
  • select 用 CSS 选择器,适合复杂结构
  • .text/.get_text(strip=True) 取文字,.get("属性") 取属性
  • JS 渲染的页面用 requests 抓不到,找数据接口或上浏览器自动化

练习

  1. 抓取任意一个静态网站(如新闻站),提取页面里所有链接和标题
  2. 抓一个文章页,提取正文所有段落文字,存成 txt 文件
  3. 思考:为什么说”伪装成浏览器”爬虫比”老实声明身份”更危险?翻一下你常访问网站的 robots.txt,看看它允许爬什么