Python爬虫实战案例:抓取静态网页文本与图片的代码示例

一只小鹿
一只小鹿
发布时间:2026-11-08 | 3人浏览过
原创

针对静态网页的Python爬虫实战案例:深入讲解使用requests获取HTML、BeautifulSoup解析文本与标签属性,以及配合urllib.parse处理相对路径并流式下载保存图片的代码示例与避坑指南。

抓取静态网页中的文本与图片,核心解决路径是通过 requests 库模拟浏览器向目标 URL 发起 HTTP GET 请求获取完整的 HTML 源码,再利用 BeautifulSoup 构建 DOM 语法树,根据标签层级与属性精准提取文章标题、段落文字及 <img> 标签的地址。针对提取出的图片相对路径,使用 urllib.parse.urljoin 转换为标准绝对地址,最后通过流式网络请求将二进制数据分块写入本地磁盘。本文将通过结构化的步骤拆解、完整可复现的代码示例与工程避坑点,展示静态爬虫的标准开发全流程。

Python爬虫实战案例:抓取静态网页文本与图片的代码示例
缩略概览:Python 抓取静态网页的文本抽取与二进制图片落盘双轨流程

一、抓取目标与环境准备

所谓“静态网页”,是指由 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 请求,并以二进制字节流模式保存到本地磁盘。

Python爬虫实战案例:抓取静态网页文本与图片的代码示例
图 1:静态网页请求与文本/图片两阶段抓取模型

如上图所示,整个抓取过程划分为三个清晰的阶段:

  1. 初次请求阶段:客户端配置伪装请求头(如 User-Agent)与超时阈值,发送 GET 请求。服务器返回包含页面骨架的 HTML 源码文本。
  2. DOM 解析与分流阶段:BeautifulSoup 将扁平的 HTML 字符串转换为树状节点。文本解析分支提取 h1、p 节点的文本并清洗空白字符;图片解析分支获取 img 标签的 src 属性,并通过 urljoin 修正相对地址。
  3. 落盘持久化阶段:文本数据以 utf-8 编码写入纯文本或结构化文件;图片则遍历绝对 URL 发起二进制流式下载,写入 .jpg 或 .png 文件。

三、实战开发分步操作与关键代码

为了让整个操作过程清晰直观,我们按照标准的开发步骤逐步推进。整个过程包含“页面结构探查”、“网络请求与编码校验”、“文本提取”、“图片地址清洗补全”以及“媒体流式存储”五个环节。

Python爬虫实战案例:抓取静态网页文本与图片的代码示例
图 2:抓取静态网页文本与图片的五步操作路径

步骤 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

五、避坑指南:初学写法与工程规范对比

很多初学者在编写爬虫时容易写出脆弱的代码,在小规模测试时看似正常,一旦放到真实网络环境中就会频繁遭遇字符乱码、内存泄漏、路径拼接错误或连接被直接重置。

Python爬虫实战案例:抓取静态网页文本与图片的代码示例
图 3:初学者易错模式 vs 规范稳健模式关键对比

结合上图的对比,需要重点把控以下四个核心细节:

  • 必须使用 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 线程默认将无限期挂起等待,导致爬虫任务永久假死。

六、知识全景与爬虫行为规范总结

静态网页抓取是整个网络数据采集体系的基石。在具备了文本抽取与二进制多媒体落盘的能力后,还需要建立起全局的技术认知与安全边界意识。

Python爬虫实战案例:抓取静态网页文本与图片的代码示例
图 4:静态网页爬虫核心技能全景与防御规范总结
合规与礼仪备忘:
  1. 限制并发与请求频次:在批量爬取多个页面或大量图片时,务必在循环体中加入 time.sleep(1) 等随机停顿,避免给目标服务器造成拒绝服务攻击式的流量冲击。
  2. 遵守 Robots 协议:在抓取前查看目标站点根目录下的 /robots.txt,了解对方明确禁止抓取的路径范围。
  3. 技术适用边界:如果页面源码中没有正文内容,只有类似 <div id="root"></div> 的占位骨架,说明正文由客户端 JavaScript 动态渲染生成。此时 requests + BeautifulSoup 将无法直接获取渲染后的内容,需要转用逆向分析其内部 API 接口,或使用自动化浏览器工具(如 Playwright、Selenium)。

通过本文所讲解的 requests 结合 BeautifulSoup 的架构模式,辅以 urljoin 规范化地址和分块流式写入策略,读者便可以稳定、安全地完成绝大多数静态资讯、博客、文档类站点的文本与多媒体资源的抓取工作。

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表