0

0

解决BeautifulSoup爬取网页表格中动态内容缺失问题

花韻仙語

花韻仙語

发布时间:2025-10-27 11:07:01

|

666人浏览过

|

来源于php中文网

原创

解决beautifulsoup爬取网页表格中动态内容缺失问题

本文旨在解决使用BeautifulSoup爬取网页表格时,因部分数据通过JavaScript动态加载导致内容缺失的问题。通过详细分析Oracle云定价页面的案例,教程将指导读者如何识别并获取隐藏在JSON API中的动态数据,并将其与BeautifulSoup解析的静态HTML内容有效整合,最终构建一个完整、准确的数据集。

在进行网页数据抓取时,开发者常常会遇到BeautifulSoup无法获取到页面上所有可见内容的情况。这通常是因为网页中的某些数据并非直接嵌入在初始HTML中,而是通过JavaScript在页面加载后动态请求并渲染的。本文将以Oracle云定价页面为例,详细阐述如何识别这类动态内容,并通过结合静态HTML解析与动态API数据获取的方法,实现完整、准确的数据抓取。

1. 问题识别:BeautifulSoup与浏览器开发者工具的差异

当使用BeautifulSoup解析网页时,如果发现某些表格单元格(如价格信息)在打印出的HTML中显示为空或不完整,但在浏览器开发者工具中却能看到完整内容,这便是一个典型的动态加载数据信号。

例如,在抓取Oracle云虚拟机构建定价表时,初始的BeautifulSoup代码可能得到如下结果,其中价格相关的<td>标签内容为空:

<tr data-partnumber="B93297">
<td><div>Compute – Ampere A1 – OCPU</div></td>
<td class="rw-theme-40bg"><div data-minrange="3000" data-type="vcpu"><br/>
</div></td>
<td><span data-minrange="3000"><span data-model="pay_as_you_go"></span></span></td>
<td><div>OCPU per hour</div></td>
</tr>

而通过浏览器开发者工具检查同一行,会发现第二和第三列实际包含了价格信息:

<tr data-partnumber="B93297">
<th scope="row"><div>Compute – Ampere A1 – OCPU</div></th>
<td class="rw-theme-40bg"><div data-minrange="3000" data-type="vcpu" class="">$0.01<span></span></div></td>
<td><span data-minrange="3000" class="">$0.01<span></span></span></td>
<td><div>OCPU per hour</div></td>
</tr>

这种差异表明,价格数据是在页面加载后通过异步请求获取的。

2. 定位动态数据源

解决此问题的关键在于找到动态加载数据的源头。通常,这可以通过浏览器的开发者工具(Network标签页)来完成。当页面加载时,观察网络请求,寻找返回JSON格式数据的XHR或Fetch请求。

雾象
雾象

WaytoAGI推出的AI动画生成引擎

下载

在Oracle云定价页面的案例中,经过检查可以发现,价格数据是从一个JSON文件加载的:https://www.oracle.com/a/ocom/docs/pricing/cloud-price-list.json。这个JSON文件包含了所有产品的定价信息,并通过partNumber(部件号)与HTML表格中的产品进行关联。

3. 数据抓取与整合策略

一旦确定了动态数据源,接下来的步骤是:

  1. 使用requests库获取并解析静态HTML页面内容。
  2. 使用requests库获取并解析动态JSON数据。
  3. 通过partNumber或其他唯一标识符,将HTML表格中抓取的产品信息与JSON数据中的价格信息进行匹配和整合。

3.1 核心代码实现

以下是实现这一策略的Python代码:

from bs4 import BeautifulSoup
import requests
import re
import pandas as pd

# 1. 获取动态价格数据
# ----------------------------------------------------------------------
json_data_url = 'https://www.oracle.com/a/ocom/docs/pricing/cloud-price-list.json'
try:
    json_data = requests.get(json_data_url).json()
except requests.exceptions.RequestException as e:
    print(f"Error fetching JSON data: {e}")
    json_data = {} # Fallback to empty dict
currency = 'USD' # 假设我们关注美元价格

# 2. 获取静态HTML页面内容
# ----------------------------------------------------------------------
url = 'https://www.oracle.com/uk/cloud/compute/pricing/#compute-vm'
try:
    oracle_website = requests.get(url).text
    soup = BeautifulSoup(oracle_website, "html.parser")
except requests.exceptions.RequestException as e:
    print(f"Error fetching HTML page: {e}")
    exit() # Exit if we can't get the page

# 3. 遍历表格并整合数据
# ----------------------------------------------------------------------
rows_data = []
# 定位包含虚拟机定价表的div
virtual_machine_table_div = soup.find("div", class_="rc34w5 rw-neutral-00bg")

if virtual_machine_table_div and virtual_machine_table_div.table:
    virtual_machine_table = virtual_machine_table_div.table
    for compute_products_tbody in virtual_machine_table.find_all("tbody"):
        trs = compute_products_tbody.find_all("tr")

        for tr in trs:
            part_number = None
            # 尝试从tr标签的data-partnumber属性获取
            if 'data-partnumber' in tr.attrs:
                part_number = tr['data-partnumber']
            else:
                # 尝试从td内部的div获取data-partnumber
                # 注意:原始问题中的第二个td是价格占位符,这里我们找第一个可能包含partNumber的td
                # 更稳健的做法是遍历所有td或直接从tr获取
                first_td_div = tr.find('td')
                if first_td_div:
                    div_with_part_num = first_td_div.find('div', {'data-partnumber': re.compile('.*')})
                    if div_with_part_num:
                        part_number = div_with_part_num['data-partnumber']

            # 初始化价格
            comp_price = '-'
            unit_price = '-'

            # 根据part_number从JSON数据中查找价格
            if part_number:
                if part_number == 'B93297': # 特殊处理 'Compute – Ampere A1 – OCPU'
                    if 'vcpuRangeItems' in json_data and part_number in json_data['vcpuRangeItems']:
                        price_info = json_data['vcpuRangeItems'][part_number].get(currency)
                        if price_info:
                            comp_price = price_info[-1]['value']
                            unit_price = price_info[-1]['value']
                elif part_number not in json_data['vcpuItems'] and part_number not in json_data['items'] and part_number not in json_data['rangeItems']:
                    # 如果part_number不在任何已知价格类型中,则价格未知
                    comp_price = '-'
                    unit_price = '-'
                else:
                    # 从vcpuItems获取Comparison Price
                    if 'vcpuItems' in json_data and part_number in json_data['vcpuItems']:
                        comp_price = json_data['vcpuItems'][part_number].get(currency, '-')

                    # 从items或rangeItems获取Unit Price
                    if 'items' in json_data and part_number in json_data['items']:
                        unit_price = json_data['items'][part_number].get(currency, '-')
                    elif 'rangeItems' in json_data and part_number in json_data['rangeItems']:
                        price_info = json_data['rangeItems'][part_number].get(currency)
                        if price_info:
                            unit_price = price_info[-1]['value'] # 取最后一个(可能是最高范围或默认值)

            elif part_number is None: # 处理没有part_number的产品,如Free tier
                product_name_td = tr.find('td')
                if product_name_td and "Free" in product_name_td.text:
                    comp_price = 'Free'
                    unit_price = 'Free'

            # 提取产品名称和单位
            tds = tr.find_all('td')
            product_name = tds[0].text.strip() if len(tds) > 0 else ''
            unit = tds[-1].text.strip() if len(tds) > 0 else '' # 最后一个td是单位

            row = {
                'partNumber': part_number,
                'Product': product_name,
                'Comparison Price (/vCPU)': comp_price,
                'Unit price': unit_price,
                'Unit': unit,
            }
            rows_data.append(row)
else:
    print("Could not find the virtual machine table.")

# 4. 使用pandas生成结构化输出
# ----------------------------------------------------------------------
df = pd.DataFrame(rows_data)
print(df.to_markdown(index=False))

3.2 代码解析与注意事项

  1. 导入必要的库: BeautifulSoup用于HTML解析,requests用于HTTP请求,re用于正则表达式匹配(在某些情况下定位data-partnumber),pandas用于数据整理和输出。
  2. 获取JSON数据: 使用requests.get(json_data_url).json()直接获取并解析JSON格式的定价数据。这是解决问题的核心步骤。
  3. 获取HTML数据: 同样使用requests.get(url).text获取网页内容,并用BeautifulSoup进行解析。
  4. 定位表格: 通过soup.find("div", class_="rc34w5 rw-neutral-00bg").table准确找到目标定价表格。
  5. 遍历行与提取partNumber:
    • 遍历每个<tr>标签。
    • partNumber是连接HTML产品信息和JSON价格数据的关键。它可能存在于<tr>标签的data-partnumber属性中,也可能嵌套在<td>标签内的<div>元素中。代码中尝试了两种获取方式。
  6. 价格查找逻辑:
    • JSON数据结构可能复杂,包含多种价格类型(如vcpuRangeItems, vcpuItems, items, rangeItems)。需要根据partNumber的特点和JSON结构,编写相应的条件逻辑来查找正确的Comparison Price和Unit Price。
    • 例如,B93297在vcpuRangeItems中,而其他产品可能在vcpuItems或items中。
    • 对于没有partNumber但产品名称中包含“Free”的行,需要特殊处理,将其价格标记为“Free”。
    • 使用json_data.get(key, {})或dict.get(key, default_value)可以避免在键不存在时引发KeyError,提高代码的健壮性。
  7. 数据整合: 将提取的产品名称、partNumber、计算出的价格和单位信息组织成字典,并添加到列表中。
  8. Pandas输出: 最后,使用pandas.DataFrame将收集到的数据转换为表格形式,并打印为Markdown格式,便于查看和进一步处理。

4. 总结

当BeautifulSoup无法抓取到网页上可见的全部内容时,很可能是因为这些内容是通过JavaScript动态加载的。解决这类问题的关键在于:

  • 利用浏览器开发者工具(尤其是Network标签页)识别动态数据请求及其API地址。
  • 直接请求动态数据API(通常返回JSON或XML格式)。
  • 将动态数据与静态HTML解析结果进行整合,通常通过一个唯一的标识符(如data-partnumber)进行匹配。

这种方法比使用Selenium等全功能浏览器自动化工具更高效,因为它避免了渲染整个页面和执行JavaScript的开销,直接获取了原始数据。掌握这种“静态+动态”结合的抓取策略,能有效应对现代网页的复杂性,实现更精准和高效的数据抓取。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

457

2023.08.07

json是什么
json是什么

JSON是一种轻量级的数据交换格式,具有简洁、易读、跨平台和语言的特点,JSON数据是通过键值对的方式进行组织,其中键是字符串,值可以是字符串、数值、布尔值、数组、对象或者null,在Web开发、数据交换和配置文件等方面得到广泛应用。本专题为大家提供json相关的文章、下载、课程内容,供大家免费下载体验。

549

2023.08.23

jquery怎么操作json
jquery怎么操作json

操作的方法有:1、“$.parseJSON(jsonString)”2、“$.getJSON(url, data, success)”;3、“$.each(obj, callback)”;4、“$.ajax()”。更多jquery怎么操作json的详细内容,可以访问本专题下面的文章。

337

2023.10.13

go语言处理json数据方法
go语言处理json数据方法

本专题整合了go语言中处理json数据方法,阅读专题下面的文章了解更多详细内容。

82

2025.09.10

js正则表达式
js正则表达式

php中文网为大家提供各种js正则表达式语法大全以及各种js正则表达式使用的方法,还有更多js正则表达式的相关文章、相关下载、相关课程,供大家免费下载体验。

531

2023.06.20

正则表达式不包含
正则表达式不包含

正则表达式,又称规则表达式,,是一种文本模式,包括普通字符和特殊字符,是计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串,通常被用来检索、替换那些符合某个模式的文本。php中文网给大家带来了有关正则表达式的相关教程以及文章,希望对大家能有所帮助。

258

2023.07.05

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

766

2023.07.05

java正则表达式匹配字符串
java正则表达式匹配字符串

在Java中,我们可以使用正则表达式来匹配字符串。本专题为大家带来java正则表达式匹配字符串的相关内容,帮助大家解决问题。

219

2023.08.11

Python异步编程与Asyncio高并发应用实践
Python异步编程与Asyncio高并发应用实践

本专题围绕 Python 异步编程模型展开,深入讲解 Asyncio 框架的核心原理与应用实践。内容包括事件循环机制、协程任务调度、异步 IO 处理以及并发任务管理策略。通过构建高并发网络请求与异步数据处理案例,帮助开发者掌握 Python 在高并发场景中的高效开发方法,并提升系统资源利用率与整体运行性能。

37

2026.03.12

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
SQL 教程
SQL 教程

共61课时 | 4.3万人学习

Java 教程
Java 教程

共578课时 | 81.5万人学习

oracle知识库
oracle知识库

共0课时 | 0.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号