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

花韻仙語
发布: 2025-10-27 11:02:16
原创
760人浏览过

使用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请求:

  1. 打开目标网页。
  2. 打开浏览器的开发者工具(通常按F12)。
  3. 切换到“Network”(网络)标签页。
  4. 刷新页面,观察网络请求列表。
  5. 筛选请求类型,例如选择“XHR”或“Fetch/XHR”,以查找异步数据请求。
  6. 仔细检查请求的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用于数据结构化和输出。

Find JSON Path Online
Find JSON Path Online

Easily find JSON paths within JSON objects using our intuitive Json Path Finder

Find JSON Path Online 30
查看详情 Find JSON Path Online
from bs4 import BeautifulSoup
import requests
import re
import pandas as pd
登录后复制

2. 获取JSON定价数据

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

# JSON数据源URL
json_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)。

# 目标网页URL
url = 'https://www.oracle.com/uk/cloud/compute/pricing/#compute-vm'
oracle_website = requests.get(url).text
soup = 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 BeautifulSoup
import requests
import re
import pandas as pd

def 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 df

if __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 |
登录后复制

注意事项与总结

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

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

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

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

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