系列文章
Python 从入门到精通
第 26 / 36 篇
从环境安装和基础语法出发,逐步学习工程实践、自动化、数据处理与 Web 开发。
-
01
Python 怎么装、怎么用 -
02
变量、数字和字符串 -
03
列表:把一组数据放在一起 -
04
元组、集合和字典 -
05
条件判断:让程序做选择 -
06
循环:重复的事交给程序 -
07
函数:把代码整理成可复用的块 -
08
模块与包:拆分你的程序 -
09
输入、输出与字符串格式化 -
10
文件读写:保存程序的数据 -
11
异常处理:程序出错时怎么办 -
12
基础阶段练习:命令行记账本 -
13
类和对象:面向对象入门 -
14
继承、组合与特殊方法 -
15
迭代器与生成器 -
16
列表推导式与生成器表达式 -
17
装饰器:给函数增加能力 -
18
上下文管理器与 with -
19
类型标注与 dataclass -
20
正则表达式:从文本中找规律 -
21
日期、时间与时区 -
22
日志与调试 -
23
虚拟环境与依赖管理 -
24
测试:让修改不再提心吊胆 -
25
网络请求:用 Python 调用 API -
26
网页解析与合规采集正在阅读 -
27
操作 Excel、CSV 与批量文件 -
28
SQLite:给程序加一个数据库 -
29
数据分析入门:NumPy 与 Pandas -
30
画图:把数据变得直观 -
31
Flask 入门:做一个小网站 -
32
异步编程:同时处理多项任务 -
33
线程、进程与并发选择 -
34
性能分析与优化 -
35
项目结构、配置与发布 -
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 抓不到,找数据接口或上浏览器自动化
练习
- 抓取任意一个静态网站(如新闻站),提取页面里所有链接和标题
- 抓一个文章页,提取正文所有段落文字,存成 txt 文件
- 思考:为什么说”伪装成浏览器”爬虫比”老实声明身份”更危险?翻一下你常访问网站的 robots.txt,看看它允许爬什么