使用BeautifulSoup和JSON有效抓取动态加载的网页表格数据

使用beautifulsoup和json有效抓取动态加载的网页表格数据

本教程旨在解决使用BeautifulSoup抓取网页表格时,因数据动态加载导致部分内容缺失的问题。通过分析网页背后的API请求,直接获取并解析JSON数据源,再结合BeautifulSoup提取的HTML结构信息,最终实现完整且准确的数据抓取。文章将提供详细的代码示例和实现步骤。

理解网页动态内容与数据抓取挑战

在进行网页数据抓取时,我们经常会遇到使用BeautifulSoup等工具无法获取到完整数据的情况,尤其是在表格内容中。这通常是因为目标网站采用了动态加载技术。这意味着网页的初始HTML文档可能只包含一个骨架,而实际的数据(例如表格中的价格信息)是在页面加载完成后,通过JavaScript向后端API发送异步请求(AJAX或Fetch API)获取,然后动态地插入到DOM中的。

当使用requests库获取网页内容并用BeautifulSoup解析时,我们只能得到初始的HTML。如果数据是动态加载的,那么这部分数据在初始HTML中是不存在的,因此BeautifulSoup自然无法找到。这会导致抓取到的表格中某些字段(如本例中的价格)显示为空或不正确,与通过浏览器开发者工具检查时看到的内容不符。

识别动态数据源:JSON API

解决动态加载数据问题的关键在于找到数据真正的来源。通常,这些数据会通过API以JSON或XML格式返回。我们可以利用浏览器的开发者工具来定位这些API请求:

打开目标网页。打开浏览器的开发者工具(通常按F12)。切换到“Network”(网络)标签页。刷新页面,观察网络请求列表。筛选请求类型,例如选择“XHR”或“Fetch/XHR”,以查找异步数据请求。仔细检查请求的URL和响应内容。通常,你会发现一个返回JSON数据的URL,其中包含了你正在寻找的动态内容。

在本例中,经过检查发现,Oracle云计算服务的定价数据实际上是从一个名为cloud-price-list.json的JSON文件中加载的,其URL为https://www.oracle.com/a/ocom/docs/pricing/cloud-price-list.json。识别出这个JSON源是解决问题的核心。

数据抓取实现步骤

一旦确定了JSON数据源,我们的策略就是:首先直接获取并解析JSON数据,然后结合BeautifulSoup从HTML中提取结构信息(如产品名称和唯一标识符),最后将两者关联起来,构建完整的结构化数据。

1. 导入所需库

我们将使用requests进行HTTP请求,BeautifulSoup解析HTML,re进行正则表达式匹配,以及pandas用于数据结构化和输出。

from bs4 import BeautifulSoupimport requestsimport reimport pandas as pd

2. 获取JSON定价数据

首先,直接向JSON数据源发送GET请求,并将其解析为Python字典。同时,定义我们感兴趣的货币类型。

# JSON数据源URLjson_url = 'https://www.oracle.com/a/ocom/docs/pricing/cloud-price-list.json'jsonData = requests.get(json_url).json()currency = 'USD' # 定义目标货币

3. 获取HTML表格结构

接下来,获取包含表格骨架的HTML页面,并使用BeautifulSoup进行解析。我们需要从中提取产品名称以及与JSON数据关联的唯一标识符(partNumber)。

# 目标网页URLurl = 'https://www.oracle.com/uk/cloud/compute/pricing/#compute-vm'oracle_website = requests.get(url).textsoup = BeautifulSoup(oracle_website, "html.parser")# 定位到包含虚拟机实例的表格virtual_machine_table = soup.find("div", class_="rc34w5 rw-neutral-00bg").table

4. 遍历表格行并整合数据

这是最关键的步骤。我们将遍历HTML表格的每一行,提取产品信息和partNumber,然后利用partNumber从之前获取的JSON数据中查找对应的价格。

rows = [] # 用于存储所有抓取到的数据行# 遍历表格中的所有tbody,再遍历每个tbody中的所有tr(行)for compute_products in virtual_machine_table.find_all("tbody"):    trs = compute_products.find_all("tr")    for tr in trs:        # 尝试获取partNumber        partNumber = None        # 优先从第二个td中的div里查找data-partnumber属性        vCPUcompPrice_td = tr.find_all('td')[1]        checkForPartNum = vCPUcompPrice_td.find('div', {'data-partnumber': re.compile('.*')})        if checkForPartNum:            partNumber = checkForPartNum['data-partnumber']        else:            # 如果td中没有,则尝试从tr标签自身查找data-partnumber属性            try:                partNumber = tr['data-partnumber']            except KeyError:                partNumber = None # 如果tr也没有,则partNumber为None        # 根据partNumber从JSON数据中查找对应的价格        compPrice = '-'        unitPrice = '-'        if partNumber == 'B93297': # 特殊处理某个特定partNumber            # 示例:对于B93297,价格可能在vcpuRangeItems中            if partNumber in jsonData['vcpuRangeItems']:                compPrice = jsonData['vcpuRangeItems'][partNumber][currency][-1]['value']                unitPrice = jsonData['vcpuRangeItems'][partNumber][currency][-1]['value']        elif partNumber is None: # 处理没有partNumber的行(例如“Free”产品)            compPrice = 'Free'            unitPrice = 'Free'        else: # 其他partNumber的通用处理逻辑            # 尝试从vcpuItems中获取比较价格            if partNumber in jsonData['vcpuItems']:                compPrice = jsonData['vcpuItems'][partNumber][currency]            # 尝试从items或rangeItems中获取单位价格            if partNumber in jsonData['items']:                unitPrice = jsonData['items'][partNumber][currency]            elif partNumber in jsonData['rangeItems']:                unitPrice = jsonData['rangeItems'][partNumber][currency][-1]['value']        # 提取产品名称和单位        product_name = tr.find_all('td')[0].text.strip()        unit = tr.find_all('td')[-1].text.strip()        # 构建当前行的数据字典        row = {            'partNumber': partNumber,            'Product': product_name,            'Comparison Price (/vCPU)': compPrice,            'Unit price': unitPrice,            'Unit': unit,        }        rows.append(row) # 将数据行添加到列表中

5. 数据整理与输出

最后,将收集到的数据列表转换为Pandas DataFrame,以便于后续的数据分析、存储或展示。

df = pd.DataFrame(rows)print(df.to_markdown()) # 以Markdown表格形式打印DataFrame

完整代码示例

from bs4 import BeautifulSoupimport requestsimport reimport pandas as pddef scrape_oracle_pricing():    """    抓取Oracle云计算虚拟机定价数据,包括动态加载的价格信息。    """    # 1. 获取JSON定价数据    json_url = 'https://www.oracle.com/a/ocom/docs/pricing/cloud-price-list.json'    try:        jsonData = requests.get(json_url).json()    except requests.exceptions.RequestException as e:        print(f"Error fetching JSON data: {e}")        return pd.DataFrame() # 返回空DataFrame    currency = 'USD' # 定义目标货币    # 2. 获取HTML表格结构    url = 'https://www.oracle.com/uk/cloud/compute/pricing/#compute-vm'    try:        oracle_website = requests.get(url).text    except requests.exceptions.RequestException as e:        print(f"Error fetching HTML page: {e}")        return pd.DataFrame() # 返回空DataFrame    soup = BeautifulSoup(oracle_website, "html.parser")    rows = [] # 用于存储所有抓取到的数据行    # 定位到包含虚拟机实例的表格    virtual_machine_table = soup.find("div", class_="rc34w5 rw-neutral-00bg")    if not virtual_machine_table:        print("Could not find the main pricing table div.")        return pd.DataFrame()    table_element = virtual_machine_table.find("table")    if not table_element:        print("Could not find the table element inside the pricing div.")        return pd.DataFrame()    # 遍历表格中的所有tbody,再遍历每个tbody中的所有tr(行)    for compute_products_tbody in table_element.find_all("tbody"):        trs = compute_products_tbody.find_all("tr")        for tr in trs:            # 尝试获取partNumber            partNumber = None            # 优先从第二个td中的div里查找data-partnumber属性            td_elements = tr.find_all('td')            if len(td_elements) > 1: # 确保有足够的td元素                vCPUcompPrice_td = td_elements[1]                checkForPartNum = vCPUcompPrice_td.find('div', {'data-partnumber': re.compile('.*')})                if checkForPartNum:                    partNumber = checkForPartNum['data-partnumber']            if not partNumber: # 如果td中没有找到,则尝试从tr标签自身查找data-partnumber属性                try:                    partNumber = tr['data-partnumber']                except KeyError:                    partNumber = None # 如果tr也没有,则partNumber为None            # 根据partNumber从JSON数据中查找对应的价格            compPrice = '-'            unitPrice = '-'            if partNumber == 'B93297': # 特殊处理某个特定partNumber                if partNumber in jsonData.get('vcpuRangeItems', {}):                    item_data = jsonData['vcpuRangeItems'][partNumber].get(currency)                    if item_data and len(item_data) > 0:                        compPrice = item_data[-1].get('value', '-')                        unitPrice = item_data[-1].get('value', '-')            elif partNumber is None: # 处理没有partNumber的行(例如“Free”产品)                compPrice = 'Free'                unitPrice = 'Free'            else: # 其他partNumber的通用处理逻辑                # 尝试从vcpuItems中获取比较价格                if partNumber in jsonData.get('vcpuItems', {}):                    compPrice = jsonData['vcpuItems'][partNumber].get(currency, '-')                # 尝试从items或rangeItems中获取单位价格                if partNumber in jsonData.get('items', {}):                    unitPrice = jsonData['items'][partNumber].get(currency, '-')                elif partNumber in jsonData.get('rangeItems', {}):                    item_data = jsonData['rangeItems'][partNumber].get(currency)                    if item_data and len(item_data) > 0:                        unitPrice = item_data[-1].get('value', '-')            # 提取产品名称和单位            product_name = td_elements[0].text.strip() if len(td_elements) > 0 else ''            unit = td_elements[-1].text.strip() if len(td_elements) > 0 else ''            # 构建当前行的数据字典            row = {                'partNumber': partNumber,                'Product': product_name,                'Comparison Price (/vCPU)': compPrice,                'Unit price': unitPrice,                'Unit': unit,            }            rows.append(row) # 将数据行添加到列表中    # 5. 数据整理与输出    df = pd.DataFrame(rows)    return dfif __name__ == "__main__":    df_result = scrape_oracle_pricing()    if not df_result.empty:        print("抓取到的定价数据:")        print(df_result.to_markdown(index=False))

输出示例:

| partNumber | Product                                                         | Comparison Price (/vCPU) | Unit price | Unit              ||:-----------|:----------------------------------------------------------------|:-------------------------|:-----------|:------------------|| B93297     | Compute – Ampere A1 – OCPU                                      | 0.01                     | 0.01       | OCPU per hour     || B93298     | Compute – Ampere A1 – Memory                                    | -                        | 0.0015     | Gigabyte per hour || B93311     | Compute - Virtual Machine Optimized - X9                        | 0.027                    | 0.054      | OCPU per hour     || B93312     | Compute - Virtual Machine Optimized - X9 - Memory               | -                        | 0.0015     | Gigabyte per hour || B88514     | Compute - Virtual Machine Standard - X7                         | 0.0319                   | 0.0638     | OCPU per hour     || B88516     | Compute - Virtual Machine Dense I/O - X7                        | 0.06375                  | 0.1275     | OCPU per hour     || B93113     | Compute - Standard - E4 - OCPU                                  | 0.0125                   | 0.025      | OCPU per hour     || B93114     | Compute - Standard - E4 - Memory                                | -                        | 0.0015     | Gigabyte per hour || B92306     | Compute - Standard - E3 - OCPU                                  | 0.0125                   | 0.025      | OCPU per hour     || B92307     | Compute - Standard - E3 - Memory                                | -                        | 0.0015     | Gigabyte per hour ||            | Compute - Virtual Machine Standard - E2 Micro - Free            | Free                     | Free       | OCPU per hour     || B91372     | Database - Marketplace Compute Image - Microsoft SQL Enterprise | 0.735                    | 1.47       | OCPU per hour     || B91373     | Database - Marketplace Compute Image - Microsoft SQL Standard   | 0.185                    | 0.37       | OCPU per hour     || B94176     | Oracle Cloud Infrastructure - Compute - Standard - X9           | 0.02                     | 0.04       | OCPU per hour     || B94177     | Oracle Cloud Infrastructure - Compute - Standard - X9 - Memory  | -                        | 0.0015     | Gigabyte per hour |

注意事项与总结

网站结构变化: 网页的HTML结构和JSON API的格式都可能随时改变。因此,依赖于特定HTML类名或JSON键的爬虫代码可能需要定期维护和更新。爬虫伦理与法律: 在进行网页抓取时,请务必遵守网站的robots.txt文件规定,控制请求频率,避免对网站服务器造成过大压力。未经授权的大规模抓取可能违反网站的服务条款,甚至涉及法律问题。错误处理: 生产级别的爬虫代码应该包含更完善的错误处理机制,例如try-except块来处理网络请求失败、JSON解析错误、HTML元素未找到等情况,提高代码的健壮性。数据复杂性: 示例中的JSON数据结构相对简单。在实际应用中,JSON数据可能更复杂,需要更精细的解析逻辑来提取所需信息。替代方案: 对于大量动态加载的网页,如果直接解析JSON或API非常复杂,可以考虑使用Selenium等自动化测试工具,它们能够模拟浏览器行为,等待页面完全加载并执行JavaScript,从而获取渲染后的完整HTML内容。

通过本教程,我们学习了如何识别和处理网页动态加载数据的问题,并通过结合BeautifulSoup解析HTML结构和requests获取JSON数据的方法,成功抓取了完整的表格信息。这种方法在面对现代网页的复杂性时,是提高数据抓取效率和准确性的重要策略。

以上就是使用BeautifulSoup和JSON有效抓取动态加载的网页表格数据的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1589245.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月23日 04:56:47
下一篇 2025年12月23日 04:57:07

相关推荐

  • CSS技巧:让圆形高度与父容器动态同步

    本文详细介绍了如何利用css的height: 100%和aspect-ratio: 1 / 1属性,在高度可变的父容器中动态创建一个始终与容器高度保持一致的完美圆形。该方法摆脱了固定像素尺寸的限制,确保了圆形元素在不同布局下的响应性和视觉一致性,为前端开发提供了一种优雅的解决方案。 在网页布局中,我…

    好文分享 2025年12月23日
    000
  • 同一表单中多位置单选按钮组的联动同步实现教程

    本教程旨在解决同一HTML表单中,两个或多个视觉上独立的单选按钮组之间的数据同步问题。通过利用JavaScript的事件委托机制,我们能够实现当一个组中的单选按钮被选中时,另一个组中对应值的单选按钮也自动更新选中状态,确保用户选择的一致性,提升表单交互体验。 引言:多位置单选按钮组的同步需求 在复杂…

    2025年12月23日 好文分享
    000
  • 解决移动端全屏视频背景横向溢出问题

    本教程旨在解决网页在移动设备上出现全屏视频背景横向溢出的问题,即视频背景无法完全适应屏幕宽度导致出现不必要的横向滚动条。我们将探讨常见的css配置,并提供一个简洁有效的解决方案:通过在body元素上应用overflow-x: hidden;来消除这一视觉缺陷,确保内容完美适配视口。 在现代网页设计中…

    2025年12月23日
    000
  • 解决模态框内容溢出滚动问题:深入理解CSS transform 对布局的影响

    本文旨在解决模态框内容溢出时滚动功能受限或失效的常见问题。通过分析css中`transform: translate(-50%, -50%)`属性对元素定位的影响,揭示了其干扰滚动机制的深层原因。文章提供了具体的代码修正方案,并深入探讨了css布局与`transform`属性之间的相互作用,旨在帮助…

    2025年12月23日
    000
  • 解决BeautifulSoup爬取网页表格中动态内容缺失问题

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

    2025年12月23日
    000
  • 使用CSS实现屏幕中央方形画布的自适应定位

    本教程详细介绍了如何仅使用css,将一个方形画布元素精确地定位在屏幕中央,并确保其在不同屏幕尺寸下保持方形比例且不溢出。核心方法结合了视口单位(vw/vh)、绝对定位、css `transform`属性以及媒体查询中的`min-aspect-ratio`,以实现高度响应式的居中效果。 在网页设计和游…

    2025年12月23日
    000
  • 使用 HTML Canvas 创建动态模拟时钟

    本文将指导你如何使用 HTML Canvas 和 JavaScript 创建一个动态的模拟时钟。核心思路是利用 Canvas 绘制时钟的表盘和指针,并通过 JavaScript 的 `setInterval` 函数定时更新指针的位置,实现时钟的动态效果。文章将重点介绍如何清除上一秒的指针轨迹,避免画…

    2025年12月23日
    000
  • html5怎么设置导航边框_HTML5导航栏边框样式定制技巧

    使用CSS的border属性为HTML5的标签设置边框,可实现导航栏样式定制。通过border、border-bottom、border-left等属性可分别设置整体或单一边框,结合padding、display:flex等布局属性优化外观。示例中为导航链接添加虚线边框,并用:first-child…

    2025年12月23日
    000
  • Next.js 组件中 Image 组件缺失 “src” 属性问题的解决

    本文旨在解决 Next.js 开发中,使用 `next/image` 组件时,通过 props 传递图片路径,却出现 “Image is missing required “src” property” 错误的问题。我们将深入分析问题原因,并提供清晰的…

    2025年12月23日
    000
  • 如何在点击锚点链接后关闭下拉菜单并切换汉堡包图标(不刷新页面)

    本文旨在解决网页中点击内部锚点链接时,下拉菜单未能自动关闭且汉堡包图标状态未复位的问题。通过详细的javascript代码示例和解释,我们将展示如何监听锚点链接的点击事件,并在事件触发时隐藏下拉菜单并切换汉堡包图标的视觉状态,从而优化用户体验,确保导航的连贯性。 在现代网页设计中,下拉菜单(通常由“…

    2025年12月23日
    000
  • Bootstrap 5 中 page-header 类的替代方案及实用技巧

    本文将探讨在 bootstrap 5 中 page-header 类不再生效的原因,并提供使用 bootstrap 实用工具类(如 pb-2 mt-4 mb-2 border-bottom)来替代其功能的详细教程。通过学习如何灵活组合这些实用工具类,开发者可以轻松实现自定义的页面标题样式,从而更好地…

    2025年12月23日 好文分享
    000
  • JavaScript获取HTML 元素选中值教程

    本文详细阐述了如何使用javascript获取html “ 元素中用户选中的值。通过为 “ 元素设置唯一的 `id` 标识,并为其添加 `change` 事件监听器,开发者可以实时捕获用户选择的选项值。文章将指导读者如何利用 `event.target.value` 属性获取…

    2025年12月23日
    000
  • 精准布局:避免内容与复杂背景图重叠的响应式设计策略

    本文探讨在响应式网页设计中,如何有效避免文本内容与复杂背景图像(如带有特定形状的背景)发生重叠。文章提出一种通过重构html结构,将背景图像视为独立内容元素并利用css网格系统进行布局的策略,从而实现内容与图像的精确分离与定位,确保在不同屏幕尺寸下的视觉一致性。 在现代网页设计中,响应式布局是不可或…

    2025年12月23日
    000
  • JavaScript 实现基于域名检查的安全回退功能

    本文介绍如何使用javascript实现一个智能的“返回”按钮。该功能在用户点击时,会首先检查前一页的来源域名是否与当前页面一致。只有当来源页面属于同一域名时,才执行浏览器回退操作,从而避免意外导航到外部网站,提升用户体验和安全性。 在网页开发中,我们经常需要提供一个“返回”按钮,让用户能够方便地回…

    2025年12月23日
    000
  • HTML页面内部锚点链接的正确使用指南

    本文详细介绍了如何在html页面中通过url的片段标识符(`#`)实现精准的内部导航。文章阐明了现代html5标准下,推荐使用`id`属性来定义锚点,并解释了`name`属性在“标签上的废弃情况及其与`id`属性的兼容性处理,提供了清晰的代码示例和最佳实践建议,帮助开发者构建可靠的页面内…

    2025年12月23日 好文分享
    000
  • HTML5 视频画廊封面动态管理教程

    本教程详细介绍了如何为html5视频画廊实现封面图的动态显示与隐藏功能。通过javascript监听视频播放和暂停事件,实现点击封面播放视频、暂停时重新显示封面的交互效果。文章重点解决多视频场景下id重复导致的问题,采用类选择器和循环遍历为每个视频独立绑定事件,确保所有视频都能正确响应。 实现多视频…

    2025年12月23日 好文分享
    000
  • 优化React中SVG动画性能:深入理解与应用 will-change

    在React应用中,复杂的SVG动画可能遭遇性能瓶颈,导致动画卡顿或行为异常,即使在独立环境中运行流畅。本文将深入探讨这种现象背后的原因,并详细介绍如何通过CSS属性 `will-change: contents` 来优化浏览器渲染流程,显著提升SVG动画的流畅度,同时提供具体的代码示例和使用注意事…

    2025年12月23日
    000
  • 使用 jQuery 高亮 HTML 表格单元格及其相邻单元格

    本教程旨在指导开发者如何使用 jQuery 实现一个交互式表格,当用户在输入框中输入内容时,表格中匹配的单元格以及紧随其后的单元格会被高亮显示。我们将详细讲解代码实现,并提供可直接运行的示例,帮助你快速掌握该技巧。 1. HTML 结构 首先,我们需要一个包含输入框和表格的 HTML 结构。输入框用…

    2025年12月23日
    000
  • 如何正确在Web页面中显示图片:理解文件路径

    本教程旨在解决Web开发中常见的图片显示问题,特别是由于文件路径引用不当导致图片无法加载的情况。文章将深入探讨本地文件系统路径与Web可访问URL路径之间的关键区别,并详细演示如何利用相对路径和绝对Web路径确保图片在浏览器中正确显示,尤其是在PHP驱动的应用程序环境中。 理解Web环境中的图片路径…

    2025年12月23日 好文分享
    000
  • 前端表单验证实践:实现提交前校验与错误信息内联显示

    本文将指导读者如何利用javascript和jquery实现前端表单验证,避免提交后页面跳转以显示错误信息。通过拦截表单的默认提交行为,我们可以在客户端实时校验用户输入,并直接在表单字段旁显示具体的错误提示,从而显著提升用户体验和交互效率。 问题背景与挑战 在传统的Web表单提交流程中,如果用户输入…

    2025年12月23日
    000

发表回复

登录后才能评论
关注微信