返回博客

BeautifulSoup 网页抓取:从静态 HTML 到动态页面

Ava Wilson
Ava Wilson

Expert in Web Scraping Technologies

14-Aug-2026

TL;DR:

  • BeautifulSoup 解析 HTML;它不执行 JavaScript。 响应体必须已经包含您想选择的记录。
  • Requests 加上 BeautifulSoup 是处理静态页面的最短路径。 在一个 Python 进程中提取、验证、解析、规范化和导出。
  • CSS 选择器对于嵌套卡片非常实用。 在每条记录内限定字段选择器的范围,以便值不会在行间漂移。
  • 动态页面需要不同的获取路径。 首先检查内部 JSON 端点,然后在需要浏览器执行时使用渲染的 HTML。
  • BeautifulSoup 在渲染后仍然有用。 通用抓取 API 可以返回 HTML,而 BeautifulSoup 保持解析和 JSON 输出的所有权。

BeautifulSoup 网络抓取在页面获取和 HTML 解析被视为两个独立任务时效果最佳。Requests 获取一个表示。BeautifulSoup 将该表示转换为可导航的树形结构。无论哪个操作都不能证明依赖于 JavaScript 的内容已经出现。

本教程构建一个静态抓取器,添加有限的分页,演示 JavaScript 页面上的空壳问题,并在管理的渲染步骤后保持相同的 BeautifulSoup 解析器。

静态 HTTP、内部 JSON 还是渲染的 HTML?

在编写选择器之前选择获取路径。

页面行为 最佳首选路径 BeautifulSoup 的角色
记录在初始 HTML 中存在 Requests 直接解析响应
页面从公共 JSON 请求加载记录 请求该稳定端点 仅在需要时解析嵌入的 HTML 字段
所需内容在 JavaScript 之后出现 管理的渲染器或浏览器 解析返回的渲染 HTML
工作流程需要点击、表单或会话操作 浏览器自动化 解析快照或最终 HTML

查看源代码或检查 Requests 的响应,而不仅仅是浏览器的元素面板。浏览器显示的是执行后 JavaScript 的 DOM;Requests 看到的是服务器的响应。

先决条件

您需要 Python、一个虚拟环境,以及访问公共目标页面的权限。可运行的示例使用 requestsbeautifulsoup4,并结合 Python 的内置 HTML 解析器。

Beautiful Soup 文档 涵盖了解析器选择和 CSS 选择器。Requests 快速入门 文档描述了响应处理和状态检查。

安装 BeautifulSoup 和 Requests

创建一个隔离环境,并安装脚本导入的确切包:

bash Copy
python3 -m venv .venv
. .venv/bin/activate
python -m pip install beautifulsoup4 requests

在添加网络代码之前确认导入已解决:

bash Copy
python -c "import bs4, requests; print(bs4.__version__, requests.__version__)"

抓取一个静态 HTML 页面

公共的抓取静态页面将每个报价卡片放置在响应的 HTML 中。解析器在每个 .quote 记录内限定文本、作者和标签。

python Copy
import json
from urllib.parse import urljoin

import requests
from bs4 import BeautifulSoup

url = "https://quotes.toscrape.com/page/1/"
response = requests.get(url, timeout=30)
response.raise_for_status()

soup = BeautifulSoup(response.text, "html.parser")
records = []

for card in soup.select(".quote"):
    records.append({
        "text": card.select_one(".text").get_text(strip=True),
        "author": card.select_one(".author").get_text(strip=True),
        "tags": [tag.get_text(strip=True) for tag in card.select(".tag")],
        "source_url": response.url,
    })

next_link = soup.select_one("li.next a")
next_url = urljoin(response.url, next_link["href"]) if next_link else None

print(json.dumps({
    "count": len(records),
    "first": records[0],
    "next_url": next_url,
}, indent=2))

解析器执行三项有用操作:首先选择完整的卡片,保持每个字段在卡片范围内,并保留源 URL。该源 URL 使后续选择器或分页问题可重现。

find_all 与 CSS 选择器

BeautifulSoup 提供基于方法的搜索和 CSS 选择。

  • find() 通过名称或属性返回第一个匹配的标签。
  • find_all() 返回所有匹配的标签。
  • select_one() 返回第一个 CSS 选择器匹配。
  • select() 返回所有 CSS 选择器匹配。

CSS 选择器对于嵌套卡片布局来说简洁明了。基于方法的搜索在匹配一个标签和一个属性时可以更清晰。为项目选择一种风格,并在记录容器下限定子字段。

避免与表现类绑定的冗长选择器链。当页面暴露时,优先考虑耐用属性、语义元素、稳定的 URL 模式或内部 JSON 键。

添加有限的分页

分页需要一个停止条件,即使目标当前有明确的下一个链接。此脚本收集两个静态页面,并在链接消失时提前停止。

python Copy
import json
from urllib.parse import urljoin

import requests
from bs4 import BeautifulSoup

url = "https://quotes.toscrape.com/page/1/"
rows = []

for _ in range(2):
    response = requests.get(url, timeout=30)
    response.raise_for_status()
    soup = BeautifulSoup(response.text, "html.parser")

    for card in soup.select(".quote"):
        rows.append({
            "text": card.select_one(".text").get_text(strip=True),
            "author": card.select_one(".author").get_text(strip=True),
            "source_url": response.url,
        })

    next_link = soup.select_one("li.next a")
    if not next_link:
        break
    url = urljoin(response.url, next_link["href"])

print(json.dumps({"count": len(rows), "last": rows[-1]}, indent=2))

在将模式应用于更大的站点之前,使用最大页面数量、访问的 URL 集合和稳定的记录键。保持并行工作精简,并尊重站点政策。

使用 Scrapeless 开始抓取

使用 Scrapeless 提升您的网络抓取和自动化工作流程!
今天注册并获得 $5 的免费信用——无需信用卡

立即在 Scrapeless Dashboard 领取您的免费信用。
Scrapeless Dashboard 显示 $5.00 的团队积分

为什么 BeautifulSoup 返回一个空的动态页面

JavaScript版本的同一演示在直接请求时返回一个HTML外壳。报价卡在浏览器运行页面脚本后附加,因此soup.select('.quote')在初始响应中找不到记录。

该区分在短诊断中显而易见:

python Copy
import requests
from bs4 import BeautifulSoup

url = "https://quotes.toscrape.com/js/"
response = requests.get(url, timeout=30)
response.raise_for_status()
soup = BeautifulSoup(response.text, "html.parser")

print({
    "status": response.status_code,
    "title": soup.title.get_text(strip=True),
    "quote_cards": len(soup.select(".quote")),
})

状态200意味着服务器返回了成功的HTTP响应;这并不意味着表示中包含业务记录。HTTP语义标准定义了状态行为,而抓取器必须验证页面身份和所需选择器。

在选择渲染器之前,请检查浏览器开发工具中的Fetch/XHR请求。一个稳定的公共JSON响应可能比渲染的DOM小且更易于验证。不要重现依赖于私人凭证、受限端点或您未持有的授权的请求。

BeautifulSoup的止步

BeautifulSoup在解析时停止。它不会运行页面脚本、点击控件、维护浏览器执行环境或解决在HTML到达解析器之前发生的获取问题。

无抓取通用抓取API可以为公共JavaScript页面返回渲染的HTML。BeautifulSoup可以使用与本地HTML相同的选择器解析该返回字符串。

注意:以下云请求需要读者拥有的SCRAPELESS_API_KEY。文档中的请求形状已被验证;在此环境中未执行凭证受限的调用。

python Copy
import os
import requests
from bs4 import BeautifulSoup

response = requests.post(
    "https://api.scrapeless.com/api/v2/unlocker/request",
    headers={"x-api-token": os.environ["SCRAPELESS_API_KEY"]},
    json={
        "actor": "unlocker.webunlocker",
        "proxy": {"country": "ANY"},
        "input": {
            "url": "https://quotes.toscrape.com/js/",
            "jsRender": {
                "enabled": True,
                "waitUntil": "domcontentloaded",
                "response": {"type": "html"}
            }
        }
    },
    timeout=60
)
response.raise_for_status()
payload = response.json()
if payload.get("code") != 200:
    raise RuntimeError("Rendered HTML was not returned")

soup = BeautifulSoup(payload["data"], "html.parser")
records = [
    {
        "text": card.select_one(".text").get_text(strip=True),
        "author": card.select_one(".author").get_text(strip=True),
    }
    for card in soup.select(".quote")
]
print({"count": len(records), "first": records[0] if records else None})

通用抓取API文档定义了JavaScript渲染和响应选项。将渲染器配置保留在获取层中,以便解析器保持可测试,使用保存的HTML固定装置。

在保存JSON之前验证

解析器应该拒绝错误页面,而不是将空文件导出为成功。验证:

  • 最终URL与预期的主机和路线匹配;
  • 标题或H1标识所需页面;
  • 所需的卡片选择器存在;
  • 每条接受的记录包含其业务密钥;
  • 可为空字段保持null而不是偏移相邻值;
  • 来源URL与每一行一起存储。

机器人排除协议指定自动客户端请求遵循的爬虫指令。这并不取代授权或适用法律。

故障排除BeautifulSoup抓取器

症状 可能原因 检查
状态200时零记录 JavaScript渲染的内容或错误的选择器 检查响应HTML和所需元素
混乱的文本 错误的响应编码 比较头部、文档元数据和解码的主体
字段与错误卡片配对 全局字段选择器 在每个记录容器下作用域字段查询
重复的行 分页循环重新访问URL 跟踪已访问的URLs和稳定的记录键
解析器行为不同 不同的解析器后端 明确固定所选解析器

结论

当响应已经包含数据时,BeautifulSoup网络抓取是可靠的。请求处理静态获取;BeautifulSoup处理解析;有限分页和验证将结果转换为结构化输出。

对于动态页面,首先检查公共内部JSON源。当所需的页面状态需要JavaScript时,通过通用抓取API返回渲染的HTML,并将BeautifulSoup作为解析器。


保持Python解析,将渲染移动到云端

比较Scrapeless定价,研究 Puppeteer下载指南中的浏览器边界,并创建一个Scrapeless账户。加入DiscordTelegram进行实现讨论。


常见问题

问:BeautifulSoup适合进行网络抓取吗?

当另一个组件已经获取了正确的文档时,BeautifulSoup是一个实用的HTML和XML解析器。

问:BeautifulSoup可以抓取动态网站吗?

BeautifulSoup不能执行JavaScript,但可以解析由内部API、受管理的渲染器或浏览器自动化步骤返回的HTML。
问:BeautifulSoup网络抓取合法吗?

只抓取您被授权访问的公共数据,审查网站条款和机器人指令,最小化收集并寻求相关司法管辖区的法律建议。

问:BeautifulSoup抓取器需要代理吗?

小型允许的静态请求可能不需要代理;地理或生产工作负载可能需要适当的代理和明确的流量控制。

问:当DOM发生变化时应该发生什么?

重新检查当前响应,识别持久来源,如属性或JSON键,收紧选择器,并在保存数据之前验证所需字段。

问:BeautifulSoup抓取器应该使用多少并发?

每个主机最多从三个工作线程开始,观察网站政策和响应行为,并在目标或用例需要较少流量时减少并行性。

问:这些示例可以在没有AI代理的情况下运行吗?

可以。这些Python示例直接运行;代理是在相同采集和解析步骤周围的可选协调。

在Scrapeless,我们仅访问公开可用的数据,并严格遵循适用的法律、法规和网站隐私政策。本博客中的内容仅供演示之用,不涉及任何非法或侵权活动。我们对使用本博客或第三方链接中的信息不做任何保证,并免除所有责任。在进行任何抓取活动之前,请咨询您的法律顾问,并审查目标网站的服务条款或获取必要的许可。

最受欢迎的文章

目录