Python爬虫实战案例:抓取静态网页文本与图片的代码示例
针对静态网页的Python爬虫实战案例:深入讲解使用requests获取HTML、BeautifulSoup解析文本与标签属性,以及配合urllib.parse处理相对路径并流式下载保存图片的代码示例与避坑指南。
抓取静态网页中的文本与图片,核心解决路径是通过 requests 库模拟浏览器向目标 URL 发起 HTTP GET 请求获取完整的 HTML 源码,再利用 BeautifulSoup 构建 DOM 语法树,根据标签层级与属性精准提取文章标题、段落文字及 <img> 标签的地址。针对提取出的图片相对路径,使用 urllib.parse.urljoin 转换为标准绝对地址,最后通过流式网络请求将二进制数据分块写入本地磁盘。本文将通过结构化的步骤拆解、完整可复现的代码示例与工程避坑点,展示静态爬虫的标准开发全流程。
一、抓取目标与环境准备
所谓“静态网页”,是指由 Web 服务器直接返回包含完整内容的 HTML 源码,客户端无需运行 JavaScript 异步请求(如 AJAX 或 Fetch)即可直接在网页源代码中看到所有文字与图片链接。如果通过浏览器的“查看网页源代码”能够直接搜索到目标文字和 <img src="...">,则属于标准的静态页面场景。
完成本次实战需要安装两个基础第三方库:负责网络通信的 requests,以及负责 HTML 解析的 beautifulsoup4。在终端或命令行中执行如下安装命令:
pip install requests beautifulsoup4
安装完成后,建议在同一 Python 脚本中统一导入所需要的模块:
import os
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup
二、静态网页抓取的执行模型与原理分析
静态网页的数据提取之所以高效,是因为文本与媒体元信息在第一次网络响应时就已经全部到达内存。然而,文本与图片在物理存储和传输机制上存在根本差异:文本直接包含在 HTML 字符串中,解码即可呈现;而图片在 HTML 中仅仅记录为一个地址标记(如 <img src="/upload/banner.png">),要获取图片文件本身,爬虫必须根据地址向服务器发起第二次独立的 HTTP 请求,并以二进制字节流模式保存到本地磁盘。
如上图所示,整个抓取过程划分为三个清晰的阶段:
- 初次请求阶段:客户端配置伪装请求头(如 User-Agent)与超时阈值,发送 GET 请求。服务器返回包含页面骨架的 HTML 源码文本。
- DOM 解析与分流阶段:BeautifulSoup 将扁平的 HTML 字符串转换为树状节点。文本解析分支提取
h1、p节点的文本并清洗空白字符;图片解析分支获取img标签的src属性,并通过urljoin修正相对地址。 - 落盘持久化阶段:文本数据以
utf-8编码写入纯文本或结构化文件;图片则遍历绝对 URL 发起二进制流式下载,写入.jpg或.png文件。
三、实战开发分步操作与关键代码
为了让整个操作过程清晰直观,我们按照标准的开发步骤逐步推进。整个过程包含“页面结构探查”、“网络请求与编码校验”、“文本提取”、“图片地址清洗补全”以及“媒体流式存储”五个环节。
步骤 1:分析目标网页 DOM 结构
在编写爬虫代码前,先使用 Chrome 或 Edge 浏览器打开目标页面,按下键盘 F12 打开开发者工具,切换到 Elements(元素)面板。检查网页正文所在的容器类名或标签结构。假设我们抓取一个典型的文章页,其 HTML 片段结构如下:
<!-- 典型目标静态网页结构示例 -->
<article class="post-article">
<h1 class="entry-title">Python数据分析入门精要</h1>
<div class="entry-content">
<p>数据采集是数据分析的第一步,掌握轻量级的爬虫技术尤为关键。</p>
<img src="/static/images/flowchart.png" alt="数据分析流程图">
<p>在解析静态网页时,需要理清HTML标签层级。</p>
<img src="https://example.com/assets/banner.jpg" alt="架构示范">
</div>
</article>
从结构可见:标题位于 h1.entry-title 标签中,段落文字分布在 div.entry-content 下的所有 p 标签中,图片则由 img 标签的 src 属性声明。
步骤 2:发起安全可靠的 HTTP 请求
许多网站会检测客户端请求头中的 User-Agent。如果直接使用 requests.get(url),默认的标识头为 python-requests/x.x.x,容易被服务器安全策略拦截并返回 403 Forbidden 状态码。因此必须设置标准浏览器 User-Agent,并显式指定 timeout 防止因网络挂起导致程序死锁。
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"
}
# 发起 GET 请求并设置 10 秒超时
response = requests.get(target_url, headers=headers, timeout=10)
# 确保状态码为 200,若返回 4xx/5xx 则直接抛出异常中断执行
response.raise_for_status()
# 矫正网页字符编码,避免中文乱码
response.encoding = response.apparent_encoding
html_text = response.text
步骤 3:BeautifulSoup 解析与正文抽取
获取到 html_text 后,通过 BeautifulSoup(html_text, 'html.parser') 初始化解析器。使用 get_text(strip=True) 能够自动去除多余的前后空格及换行符。
soup = BeautifulSoup(html_text, "html.parser")
# 1. 提取文章标题(若未找到则返回默认占位字符串)
title_tag = soup.find("h1", class_="entry-title") or soup.find("h1")
title = title_tag.get_text(strip=True) if title_tag else "未命名文章"
# 2. 提取所有正文段落
paragraphs = []
content_div = soup.find("div", class_="entry-content")
if content_div:
p_tags = content_div.find_all("p")
else:
p_tags = soup.find_all("p")
for p in p_tags:
text = p.get_text(strip=True)
if text: # 过滤空段落
paragraphs.append(text)
article_body = "\n\n".join(paragraphs)
步骤 4:提取图片链接并规范化 URL
HTML 中的 img 标签往往包含相对路径(如 /static/images/flowchart.png),如果直接请求该地址,Python 无法识别网络主机名。必须使用 Python 自带的标准库 urllib.parse.urljoin(target_url, src) 将相对路径与当前页面地址进行合并,自动转换为规范的绝对 URL。
image_urls = []
img_tags = soup.find_all("img")
for img in img_tags:
# 提取 src 属性,注意可能存在空属性或 data-src 懒加载情况
src = img.get("src") or img.get("data-src")
if not src:
continue
# 过滤掉 base64 嵌入内联数据
if src.startswith("data:image"):
continue
# 使用 urljoin 自动处理:/a/b.jpg、../b.jpg、//cdn.com/b.jpg 等各种相对路径
absolute_url = urljoin(target_url, src)
image_urls.append(absolute_url)
步骤 5:二进制流式下载与磁盘存储
保存文本可直接使用内置的 open(filename, 'w', encoding='utf-8')。但对于图片,应当开启 stream=True 并使用 iter_content(chunk_size=8192) 分块边读边写,防止单张大图片占满内存。
def save_image(img_url, save_dir, headers, index):
try:
# 获取文件名或按序号生成文件名
url_path = img_url.split("?")[0] # 剔除 url 参数
ext = os.path.splitext(url_path)[1].lower()
if ext not in [".jpg", ".jpeg", ".png", ".gif", ".webp"]:
ext = ".jpg" # 缺少明确扩展名时缺省使用 jpg
file_name = f"image_{index:03d}{ext}"
save_path = os.path.join(save_dir, file_name)
# 开启 stream=True 进行流式下载
with requests.get(img_url, headers=headers, stream=True, timeout=15) as r:
r.raise_for_status()
with open(save_path, "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
if chunk:
f.write(chunk)
return save_path
except Exception as err:
print(f"下载图片失败 [{img_url}]: {err}")
return None
四、完整可运行实战代码示例
下面给出一段模块化、可直接复现的完整 Python 脚本。该脚本封装为一个简洁的爬虫函数,执行时会自动在当前工作目录下创建数据保存目录,抓取文本保存为 article.txt,抓取图片按顺序保存到 images/ 子文件夹中。
# -*- coding: utf-8 -*-
# Python 静态网页文本与图片抓取实战完整示例
# 环境依赖: requests, beautifulsoup4
import os
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup
def scrape_static_article(url, output_base_dir="crawler_output"):
"""
抓取指定静态网页中的文本正文与内联图片
:param url: 目标网页绝对地址
:param output_base_dir: 输出根目录
"""
headers = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/124.0.0.0 Safari/537.36"
),
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8"
}
print(f"[1/4] 正在请求目标页面: {url}")
try:
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
# 自动识别页面真实字符集编码,避免中文乱码
response.encoding = response.apparent_encoding
html_content = response.text
except requests.RequestException as e:
print(f"[错误] 网页请求失败: {e}")
return
# 初始化 BeautifulSoup 解析树
soup = BeautifulSoup(html_content, "html.parser")
# 1. 解析标题
title_node = soup.find("h1")
page_title = title_node.get_text(strip=True) if title_node else "无标题网页"
print(f"[2/4] 成功解析网页标题: {page_title}")
# 2. 解析文本正文
# 优先在 article 或 main 容器中查找段落,未找到则在全局查找
main_container = soup.find("article") or soup.find("main") or soup.body
paragraphs = []
if main_container:
for p in main_container.find_all("p"):
text = p.get_text(strip=True)
if text:
paragraphs.append(text)
# 3. 准备本地输出文件夹
article_dir = os.path.join(output_base_dir, "article_data")
images_dir = os.path.join(article_dir, "images")
os.makedirs(images_dir, exist_ok=True)
# 4. 写入文本文件
text_file_path = os.path.join(article_dir, "article.txt")
with open(text_file_path, "w", encoding="utf-8") as f:
f.write(f"【标题】{page_title}\n")
f.write(f"【原文地址】{url}\n")
f.write("-" * 50 + "\n\n")
for i, para in enumerate(paragraphs, start=1):
f.write(f"第 {i} 段:\n{para}\n\n")
print(f"[3/4] 正文提取完成 (共 {len(paragraphs)} 个有效段落),已写入: {text_file_path}")
# 5. 解析并下载图片
img_nodes = soup.find_all("img")
print(f"[4/4] 扫描到 {len(img_nodes)} 个 <img> 标签,开始逐一抓取...")
saved_images_count = 0
for idx, img in enumerate(img_nodes, start=1):
raw_src = img.get("src") or img.get("data-src")
if not raw_src or raw_src.startswith("data:"):
continue
# 补全相对路径为完整 HTTP 地址
full_img_url = urljoin(url, raw_src)
# 提取文件扩展名并清洗参数
clean_path = full_img_url.split("?")[0]
ext = os.path.splitext(clean_path)[1].lower()
if ext not in [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg"]:
ext = ".jpg"
local_img_name = f"image_{idx:02d}{ext}"
target_img_path = os.path.join(images_dir, local_img_name)
try:
# 下载图片流
with requests.get(full_img_url, headers=headers, stream=True, timeout=15) as img_resp:
if img_resp.status_code == 200:
with open(target_img_path, "wb") as img_file:
for chunk in img_resp.iter_content(chunk_size=8192):
if chunk:
img_file.write(chunk)
saved_images_count += 1
print(f" - 下载成功: {local_img_name} <= {full_img_url}")
else:
print(f" - [跳过] 状态码异常 {img_resp.status_code}: {full_img_url}")
except requests.RequestException as err:
print(f" - [失败] 无法下载 {full_img_url}: {err}")
print("-" * 50)
print(f"任务执行完毕!成功归档文本 1 份,保存图片 {saved_images_count} 张。")
print(f"输出目录: {os.path.abspath(article_dir)}")
if __name__ == "__main__":
# 示例运行:可替换为任意合法的静态网页地址进行抓取测试
test_url = "https://quotes.toscrape.com/"
scrape_static_article(test_url)
示例运行输出
在本地终端执行上述脚本时,终端将呈现如下清晰的执行进度反馈(此为示例输出):
$ python static_scraper.py
[1/4] 正在请求目标页面: https://quotes.toscrape.com/
[2/4] 成功解析网页标题: Quotes to Scrape
[3/4] 正文提取完成 (共 10 个有效段落),已写入: /workspace/crawler_output/article_data/article.txt
[4/4] 扫描到 0 个 <img> 标签,开始逐一抓取...
--------------------------------------------------
任务执行完毕!成功归档文本 1 份,保存图片 0 张。
输出目录: /workspace/crawler_output/article_data
五、避坑指南:初学写法与工程规范对比
很多初学者在编写爬虫时容易写出脆弱的代码,在小规模测试时看似正常,一旦放到真实网络环境中就会频繁遭遇字符乱码、内存泄漏、路径拼接错误或连接被直接重置。
结合上图的对比,需要重点把控以下四个核心细节:
- 必须使用
urljoin解析图片 URL:网页源码里的图片地址五花八门,有以/开头的根路径、以./或../开头的相对路径,甚至以//开头的协议自适应地址。手动使用字符串+拼接几乎必然产生非法 URL。urllib.parse.urljoin能够根据目标网页的 Base URL 严格遵守 RFC 3986 规范完成解析。 - 坚持流式分块写入图片(
stream=True):直接使用open().write(response.content)会把所有二进制数据一次性读进 Python 进程内存。若目标页面存在多张高清图或无损大图,会引起瞬间内存峰值暴涨,引发系统 OOM(内存溢出)崩溃。 - 使用
apparent_encoding防止中文乱码:HTTP 响应头中的Content-Type若未指定charset,requests会默认推导为ISO-8859-1,直接提取会导致中文字符变成乱码符号。通过response.apparent_encoding能够从二进制报文内容与 HTML 中的<meta charset>智能推导真实编码(如 UTF-8 或 GBK)。 - 设定超时防死锁(
timeout):如果不设定超时,当目标站点防火墙丢弃 SYN 报文或网络阻断时,Python 线程默认将无限期挂起等待,导致爬虫任务永久假死。
六、知识全景与爬虫行为规范总结
静态网页抓取是整个网络数据采集体系的基石。在具备了文本抽取与二进制多媒体落盘的能力后,还需要建立起全局的技术认知与安全边界意识。
- 限制并发与请求频次:在批量爬取多个页面或大量图片时,务必在循环体中加入
time.sleep(1)等随机停顿,避免给目标服务器造成拒绝服务攻击式的流量冲击。 - 遵守 Robots 协议:在抓取前查看目标站点根目录下的
/robots.txt,了解对方明确禁止抓取的路径范围。 - 技术适用边界:如果页面源码中没有正文内容,只有类似
<div id="root"></div>的占位骨架,说明正文由客户端 JavaScript 动态渲染生成。此时requests+BeautifulSoup将无法直接获取渲染后的内容,需要转用逆向分析其内部 API 接口,或使用自动化浏览器工具(如 Playwright、Selenium)。
通过本文所讲解的 requests 结合 BeautifulSoup 的架构模式,辅以 urljoin 规范化地址和分块流式写入策略,读者便可以稳定、安全地完成绝大多数静态资讯、博客、文档类站点的文本与多媒体资源的抓取工作。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









