Python数据抓取入门:用requests和BeautifulSoup提取静态网页内容
本文系统讲解使用Python的requests与BeautifulSoup库抓取静态网页内容的完整方法。涵盖HTTP请求构建、HTML文档树解析、find与CSS选择器定位策略、常见异常处理与代码实操,帮助读者扎实掌握静态网页数据提取的核心技术。
requests 库向目标服务器发送HTTP请求,把服务器直接返回的原始HTML文本下载到本地内存;再使用 BeautifulSoup 将无序的HTML字符串解析为有层级结构的文档对象模型(DOM树),通过标签名、CSS类名或层级选择器精确定位目标节点,提取其中的纯文本与标签属性。这种方案不需要启动沉重的浏览器内核,执行速度极快、系统资源开销小,是处理所有服务器端直接渲染页面的标准入门手段。
一、静态网页的特征与双库分工机制
在编写数据提取代码之前,需要先明确抓取对象是否为“静态网页”。所谓静态网页,指的是服务器响应的原始HTML正文中已经直接包含了页面呈现的全部文字和链接信息(即服务端渲染SSR)。验证方法很简单:在浏览器中打开目标页面,右键点击“查看网页源代码”(或按下快捷键 Ctrl + U / Cmd + Option + U),在弹出的源码中使用 Ctrl + F 搜索你想要的数据。如果在源码中能直接搜到对应文字,该页面就属于静态内容,完全适用本文的方案;如果在源码中找不到、只在F12“检查元素”的Elements面板中能看到,则说明内容是通过JavaScript在前端异步加载的,需要通过抓包分析接口或借助自动化工具处理。
明确了目标类型后,许多初学者容易产生一个疑问:为什么抓取网页需要同时使用 requests 和 BeautifulSoup 两个独立的库?
这是因为数据抓取过程包含两个完全不同的技术维度:网络通信与文档解析。
- requests 负责“拿回数据”:它是一个底层的HTTP客户端库,只管向指定的网络URL发送GET或POST请求,处理TCP握手、伪装请求头(Headers)、携带Cookie、获取状态码,并将服务器返回的字节流转化为Python字符串(
response.text)。但 requests 并不理解HTML语法,在它眼中,网页源码只是一段毫无结构的长字符串,无法直接按“第2个标题”或“类名为price的标签”进行筛选。 - BeautifulSoup 负责“解析提取”:它是一个纯粹的HTML/XML文档解析工具,接收上一阶段拿到的HTML字符串,在内存中构建出树状的DOM模型,并提供类似
find()、find_all()和 CSS 选择器等直观API,帮助开发者轻松遍历树分支、定位特定标签并剔除无关样式代码。但 BeautifulSoup 本身不具备任何网络收发功能,无法脱离字符串输入独立运行。
二、准备运行环境与依赖安装
在开始编写代码前,需要在Python运行环境中安装这两个第三方库。打开操作系统的终端(Terminal或CMD),执行以下命令:
pip install requests beautifulsoup4
安装完成后,可以通过运行一行简单的Python测试命令来验证环境是否就绪:
python -c "import requests; from bs4 import BeautifulSoup; print('环境安装成功')"
解析器说明:BeautifulSoup 在初始化时需要指定底层解析引擎。Python标准库自带了 html.parser,它无需安装额外的C语言编译依赖,跨平台兼容性好且足够应付大多数常规页面。如果后续需要处理超大规模或极其残破的网页文档,也可以按需安装运行速度更快的 lxml 解析器(pip install lxml),并在代码中将解析器参数切换为 'lxml'。
三、第一步:使用 requests 发起请求并获取网页源码
获取网页内容的第一步,是通过 requests.get() 向目标URL发送HTTP请求。为了写出稳健的抓取代码,有三个关键细节必须显式处理:
- 伪装 User-Agent 请求头:许多网站为了防止简陋脚本消耗带宽,会直接拦截带有默认Python标识的请求。如果不传任何自定义请求头,requests 会发送形如
python-requests/x.xx.x的标头,极易直接触发403 Forbidden错误。因此,应主动传入包含主流浏览器标识的字典。 - 显式设置网络超时(timeout):网络请求可能因目标服务器无响应或路由波动而陷入长时间挂起。如果不指定
timeout参数,程序可能会永远停在发起请求的行上,阻塞整个批处理任务。通常推荐设置合理的秒数(例如timeout=10)。 - 检查响应状态码:通过
response.status_code == 200或调用response.raise_for_status()来断言请求是否真正成功。当服务器返回 404 或 500 等错误时,raise_for_status()会立即抛出requests.exceptions.HTTPError异常,便于及时在异常分支中记录。
import requests
url = "https://httpbin.org/html"
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
}
try:
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
# 乱码预防:若默认推断为 ISO-8859-1,使用 apparent_encoding 重新矫正
if response.encoding.lower() == "iso-8859-1":
response.encoding = response.apparent_encoding
html_content = response.text
print("成功获取网页源码,字符长度:", len(html_content))
except requests.exceptions.RequestException as e:
print(f"请求失败: {e}")
四、第二步:使用 BeautifulSoup 解析 HTML 结构
拿到 response.text 后,我们需要将它交给 BeautifulSoup 处理。创建 BeautifulSoup 对象的语法非常简洁:
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_content, "html.parser")
这一步执行完成后,变量 soup 便成为了一个包含了整张网页全部DOM树节点的对象。此时,HTML中那些可能缺失闭合标签(如缺少 </p> 或 </li>)的不规范语法已经被解析器自动修复,整个文档被整齐地组织成了父节点、子节点、兄弟节点互相连接的多叉树结构。
五、第三步:精确定位目标标签与提取数据
定位HTML中的目标元素通常有两种主流方式:原生查找方法与CSS选择器。掌握这两种方式的适用场景,可以灵活应对不同复杂度的网页排版。
1. 原生方法查找:find() 与 find_all()
这是 BeautifulSoup 最直观的API体系:
find(name, attrs, ...):在树结构中查找第一个符合条件的标签。匹配成功则返回一个bs4.element.Tag对象;若没找到则返回None。find_all(name, attrs, limit=None, ...):查找所有符合条件的标签,返回包含多个Tag对象的ResultSet列表。若没找到则返回空列表[]。
注意 class 属性关键字冲突:因为在 Python 中 class 是用于定义类的保留关键字,所以不能直接写 find('div', class='item')。BeautifulSoup 特别将参数名命名为 class_(带下划线),例如 soup.find('div', class_='article-title');或者使用通用字典参数 attrs={'class': 'article-title'}。
2. CSS选择器定位:select_one() 与 select()
如果你有前端开发经验,或者习惯直接使用浏览器F12开发者工具中的“Copy Selector(复制选择器)”,使用CSS选择器会更加高效快捷:
select_one("css_selector"):返回匹配该CSS选择器的首个Tag对象,找不到时返回None。select("css_selector"):返回匹配该CSS选择器的所有Tag对象列表。
常见的CSS选择器语法包括:.intro(类名选择器)、#main-content(ID选择器)、div.card > h3(直接子元素选择器)、ul.list li a(后代层级选择器)以及 a[href^="https"](属性筛选)。
3. 提取纯文本与标签属性
在通过上述方法锁定目标 Tag 对象后,便可以从节点中取出最终所需的数据:
- 提取内部纯文本:使用
tag.get_text(strip=True)。该方法会自动剥离当前标签及其内部所有子标签的HTML代码,仅提取可读文字;传入strip=True可以自动去除文本首尾多余的换行符与空格。 - 提取标签属性:
Tag对象支持像 Python 字典一样读取属性值。例如提取超链接地址:tag['href']或tag.get('href')。在实际编码中,更推荐使用tag.get('href'),当标签偶尔遗漏该属性时它会返回None,而不会直接抛出KeyError中断程序。
六、完整实战示例:抓取静态列表并输出结构化数据
为了直观演示全套逻辑的配合方式,下面提供一段包含“模拟静态HTML结构”、“文档树解析”、“多节点遍历”、“健壮性判空提取”以及“输出清洗后列表字典”的完整可运行代码。
import requests
from bs4 import BeautifulSoup
# 此处使用一段典型的静态文章列表HTML字符串作为演示目标(实际开发中替换为 requests.get(url).text)
html_sample = """
<div class="container">
<div class="post-card">
<h2 class="post-title"><a href="/posts/101.html">Python数据分析初探</a></h2>
<span class="publish-date">2026-10-15</span>
<p class="summary">介绍Pandas和NumPy的核心操作与数据过滤方法。</p>
</div>
<div class="post-card">
<h2 class="post-title"><a href="/posts/102.html">Linux常用基础命令备忘</a></h2>
<span class="publish-date">2026-10-20</span>
<p class="summary">系统维护、文件权限管理及常用网络诊断命令汇编。</p>
</div>
<div class="post-card">
<h2 class="post-title"><a href="/posts/103.html">Git分支管理与协同工作流</a></h2>
<span class="publish-date">2026-10-28</span>
<!-- 模拟部分标签缺少summary摘要的情况 -->
</div>
</div>
"""
def extract_posts_data(html_text):
soup = BeautifulSoup(html_text, "html.parser")
# 步骤1:定位所有的文章卡片父容器
cards = soup.select("div.container > div.post-card")
results = []
# 步骤2:遍历每一张卡片,逐一提取内部字段
for card in cards:
# 定位标题链接元素
title_tag = card.select_one("h2.post-title > a")
date_tag = card.find("span", class_="publish-date")
summary_tag = card.find("p", class_="summary")
# 步骤3:安全提取属性与文本(包含空值保护)
title = title_tag.get_text(strip=True) if title_tag else "无标题"
link = title_tag.get("href", "") if title_tag else ""
date = date_tag.get_text(strip=True) if date_tag else "未知日期"
summary = summary_tag.get_text(strip=True) if summary_tag else "暂无摘要"
results.append({
"title": title,
"link": link,
"date": date,
"summary": summary
})
return results
if __name__ == "__main__":
extracted_data = extract_posts_data(html_sample)
print(f"成功解析出 {len(extracted_data)} 篇文章数据:")
for item in extracted_data:
print(item)
运行上述代码,控制台将输出清晰规整的结构化字典列表(示例输出):
成功解析出 3 篇文章数据:
{'title': 'Python数据分析初探', 'link': '/posts/101.html', 'date': '2026-10-15', 'summary': '介绍Pandas和NumPy的核心操作与数据过滤方法。'}
{'title': 'Linux常用基础命令备忘', 'link': '/posts/102.html', 'date': '2026-10-20', 'summary': '系统维护、文件权限管理及常用网络诊断命令汇编。'}
{'title': 'Git分支管理与协同工作流', 'link': '/posts/103.html', 'date': '2026-10-28', 'summary': '暂无摘要'}
七、抓取过程中的常见问题与防护处理
在真实的抓取场景中,网络环境的不确定性与网页结构的多变性往往会导致脚本报错。在编写生产或批处理任务时,需重点防范以下四类常见隐患:
1. AttributeError: 'NoneType' object has no attribute 'get_text'
这是初学者遇到频率最高的问题。产生原因是调用了 find() 或 select_one() 但没有匹配到任何元素,返回了 None,紧接着直接调用了 .get_text()。解决方法是永远不要假设页面中的每个标签都必然存在,在提取文本或属性前进行判空,例如使用条件表达式:
target = soup.find('h1')
content = target.get_text(strip=True) if target is not None else ""
2. 中文页面乱码问题
当抓取部分国内网站时,可能会出现文字全变成 å容 或类似符号的情况。这是因为 HTTP 标头中未标明 charset 或误标为 ISO-8859-1。解决方式是在读取 response.text 前加入编码重置:
response.encoding = response.apparent_encoding
apparent_encoding 会根据网页内容的实际字节特征自动推算编码(通常为 UTF-8 或 GB18030/GBK),能够有效解决绝大多数中文乱码。
3. 网络异常与超时保护
必须使用 try...except requests.exceptions.RequestException 包裹网络请求段落,捕获包括连接超时(Timeout)、主机名无法解析(ConnectionError)在内的各类网络错误,避免一个页面的瞬时网络波动直接导致整个爬虫进程崩溃退出。
4. 遵守抓取合规与礼貌原则
在批量抓取静态页面时,每次请求之间应当加入合理的延时(如 time.sleep(1)),避免因并发过高对目标服务器造成负荷冲击;同时应优先查阅目标站点的 /robots.txt 协议文件,明确允许爬取的路径范围,只抓取公开且合法授权的数据。
八、全流程总结与方案适用边界
回顾静态网页内容提取的完整过程,本质是一套严密的模块化数据管道:由 requests 负责安全可靠地建立网络连接并取回原始HTML,再由 BeautifulSoup 负责将文本映射为DOM树并执行节点提取与规整。
需要牢记的是该方案的适用边界:它只适用于服务器端直接渲染出完整HTML的静态网页。当遇到需要用户交互后才加载的数据、由前端框架(如Vue、React)通过异步接口动态渲染的内容,或者需要滑动滚轮触发懒加载的列表时,requests.get() 拿到的源码中将不会包含这些动态数据。面对动态场景,应当优先打开浏览器的 Network 面板寻找真正传输数据的 Fetch/XHR 接口,或者转而使用 Playwright、Selenium 等能够驱动完整浏览器渲染环境的自动化工具。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













