从零到接单 09:BeautifulSoup 静态网页爬取——从 HTML 中提取一切
pythontutorial爬虫自动化接单BeautifulSoupHTML
系列目录:本文是「从零到接单:Python 自动化与爬虫实战」系列的第 9 篇。上一篇我们用 requests 拿到了网页的 HTML 源码——但那是一大坨看不懂的字符串。这篇我们学 BeautifulSoup,把 HTML 解析成结构化的数据,精确提取你想要的信息。
requests 负责"把网页搬回来",BeautifulSoup 负责"拆开网页、挑出有用的部分"。两者配合就是一套完整的静态爬虫。
一、安装并快速上手
pip install beautifulsoup4 lxml
beautifulsoup4:HTML 解析库lxml:高性能解析器(比内置的 html.parser 更快更好)
import requests
from bs4 import BeautifulSoup
# 获取网页
resp = requests.get("https://httpbin.org/html")
resp.encoding = "utf-8"
# 解析 HTML
soup = BeautifulSoup(resp.text, "lxml")
# 美化输出(看看结构)
# print(soup.prettify())
# 获取标题
print(soup.title) # <title>...</title>
print(soup.title.string) # 标题文本内容
print(soup.title.name) # 标签名:title
二、使用本地 HTML 练习
为了避免频繁请求网站,先用一段本地 HTML 练习:
html_doc = """
<html>
<head><title>测试页面</title></head>
<body>
<div class="article-list">
<div class="article">
<h2><a href="/post/1">Python 爬虫入门</a></h2>
<p class="summary">从零开始学习 Python 爬虫技术...</p>
<span class="date">2026-07-01</span>
<span class="author">张三</span>
<span class="views">1280 阅读</span>
</div>
<div class="article">
<h2><a href="/post/2">自动化脚本实战</a></h2>
<p class="summary">用 Python 自动处理 Excel 报表...</p>
<span class="date">2026-07-05</span>
<span class="author">李四</span>
<span class="views">2560 阅读</span>
</div>
<div class="article">
<h2><a href="/post/3">RPA 入门指南</a></h2>
<p class="summary">RPA 是什么?如何用 Python 实现...</p>
<span class="date">2026-07-10</span>
<span class="author">张三</span>
<span class="views">960 阅读</span>
</div>
</div>
</body>
</html>
"""
soup = BeautifulSoup(html_doc, "lxml")
三、核心查找方法
find():找第一个匹配
# 按标签名找
article = soup.find("div", class_="article")
print(article.h2.a.string) # Python 爬虫入门
# 按属性找
link = soup.find("a", href="/post/2")
print(link.string) # 自动化脚本实战
# 多条件
el = soup.find("span", class_="date", string="2026-07-05")
print(el) # <span class="date">2026-07-05</span>
⚠️ 注意是
class_不是class(因为class是 Python 关键字)。
find_all():找所有匹配(最常用!)
# 找所有文章
articles = soup.find_all("div", class_="article")
print(f"找到 {len(articles)} 篇文章")
for article in articles:
title = article.h2.a.string
author = article.find("span", class_="author").string
views = article.find("span", class_="views").string
print(f"{title} | {author} | {views}")
# 使用 limit 限制数量
first_two = soup.find_all("div", class_="article", limit=2)
select():CSS 选择器(推荐!)
# 标签选择器
titles = soup.select("h2")
print([t.a.string for t in titles])
# 类选择器
articles = soup.select(".article")
summaries = soup.select(".summary")
# 属性选择器
link = soup.select('a[href="/post/1"]')
# 层级选择器
links_in_article = soup.select(".article h2 a")
# 伪类:第 n 个
first_article = soup.select(".article:nth-of-type(1)")
# 找所有 class 包含 views 的 span
all_views = soup.select("span.views")
total_views = sum(int(v.string.replace(" 阅读", "")) for v in all_views)
print(f"总阅读量:{total_views}") # 总阅读量:4800
四、提取数据
提取文本
tag = soup.find("h2")
# .string:直接子文本
print(tag.string) # None(因为 h2 里还有 a 标签)
# .get_text():所有文本(推荐)
print(tag.get_text()) # Python 爬虫入门
print(tag.get_text(strip=True)) # 去掉首尾空白
# .text:属性方式
print(tag.text) # Python 爬虫入门
提取属性
link = soup.find("a")
print(link["href"]) # /post/1
print(link.get("href")) # /post/1(推荐,不存在返回 None)
print(link.attrs) # {'href': '/post/1'}
导航查找
article = soup.find("div", class_="article")
# 向下找
article.h2 # 第一个 h2 子标签
article.h2.a # h2 里的 a 标签
# 找父节点
article.parent # 父标签
# 找兄弟节点
h2 = article.h2
h2.find_next_sibling() # 下一个兄弟
h2.find_previous_sibling() # 上一个兄弟
h2.find_next_siblings() # 后面所有兄弟
五、实际案例:爬取一个博客列表
假设我们要爬取一个博客网站的文章列表:
import requests
from bs4 import BeautifulSoup
import csv
import time
class BlogCrawler:
def __init__(self):
self.session = requests.Session()
self.session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36"
})
def crawl_list_page(self, url):
"""爬取列表页"""
try:
resp = self.session.get(url, timeout=10)
resp.raise_for_status()
resp.encoding = resp.apparent_encoding
soup = BeautifulSoup(resp.text, "lxml")
return soup
except Exception as e:
print(f"请求失败:{e}")
return None
def parse_articles(self, soup):
"""从 soup 中解析文章列表"""
articles = []
# 找到所有文章卡片(根据实际网站结构调整选择器)
for card in soup.select(".article"):
title = card.select_one("h2 a")
summary = card.select_one(".summary")
date = card.select_one(".date")
author = card.select_one(".author")
articles.append({
"title": title.get_text(strip=True) if title else "",
"link": title["href"] if title else "",
"summary": summary.get_text(strip=True) if summary else "",
"date": date.get_text(strip=True) if date else "",
"author": author.get_text(strip=True) if author else "",
})
return articles
def save_to_csv(self, articles, filename="articles.csv"):
"""保存到 CSV"""
if not articles:
print("没有数据可保存")
return
with open(filename, "w", newline="", encoding="utf-8-sig") as f:
writer = csv.DictWriter(f, fieldnames=articles[0].keys())
writer.writeheader()
writer.writerows(articles)
print(f"已保存 {len(articles)} 篇文章到 {filename}")
# 使用
if __name__ == "__main__":
crawler = BlogCrawler()
# 这里用 httpbin.org 演示结构,实际使用时替换为真实网站
# soup = crawler.crawl_list_page("https://example-blog.com")
# 用本地数据演示
soup = BeautifulSoup(html_doc, "lxml") # html_doc 是上面定义的测试 HTML
articles = crawler.parse_articles(soup)
for a in articles:
print(f"• {a['title']} ({a['date']}) - {a['author']}")
crawler.save_to_csv(articles)
六、常见坑与解决
1. 元素找不到返回 None
title = soup.select_one(".nonexistent")
# title 是 None!直接调用 .string 会报错!
# ✅ 安全写法
if title:
print(title.string)
else:
print("标题未找到")
# 或者用 get
print(title.get_text() if title else "未找到")
2. 编码问题(中文乱码)
resp = requests.get(url)
resp.encoding = resp.apparent_encoding # 自动检测编码
# 或者手动指定
resp.encoding = "utf-8"
3. 动态加载的内容拿不到
# BeautifulSoup 只能解析 HTML 中的静态内容
# 如果网站用 JavaScript 加载数据,BS4 看不到!
# → 需要下一篇讲的 Selenium/Playwright
4. 请求频率太高被 Ban
import time
import random
for page in range(1, 11):
url = f"https://example.com/list?page={page}"
# ... 爬取 ...
time.sleep(random.uniform(1, 3)) # 随机延时 1-3 秒
七、快速参考卡
from bs4 import BeautifulSoup
soup = BeautifulSoup(html, "lxml")
# ── 查找 ──
soup.find("div") # 找第一个 div
soup.find("div", class_="box") # 带 class
soup.find_all("a") # 找所有 a
soup.select(".article") # CSS 选择器
soup.select_one("#main h1") # 选一个
# ── 提取 ──
tag.get_text(strip=True) # 文本
tag["href"] # 属性
tag.get("class", []) # 属性(带默认值)
# ── 导航 ──
tag.parent # 父节点
tag.children # 所有子节点
tag.next_sibling # 下一个兄弟
tag.previous_sibling # 上一个兄弟
总结
BeautifulSoup 让 HTML 解析变得简单优雅。核心三点:
soup.find()/soup.select_one():找一个元素soup.find_all()/soup.select():找所有元素.get_text()/["attr"]:提取文本或属性
requests + BeautifulSoup 的组合能处理 80% 的静态网页爬取需求。但有些网页的数据是 JavaScript 动态加载的——下一篇我们学习 Selenium/Playwright,搞定动态网页。
练习:找一个你感兴趣的博客或新闻网站,用 requests + BeautifulSoup 爬取它的文章列表页,提取每篇文章的标题、发布日期和链接,保存为 CSV 文件。记住设置合理的请求间隔!
Comments
Sign in to leave a comment.