
本文旨在解决使用BeautifulSoup爬取网页表格时,因部分数据通过JavaScript动态加载导致内容缺失的问题。通过详细分析Oracle云定价页面的案例,教程将指导读者如何识别并获取隐藏在JSON API中的动态数据,并将其与BeautifulSoup解析的静态HTML内容有效整合,最终构建一个完整、准确的数据集。
在进行网页数据抓取时,开发者常常会遇到BeautifulSoup无法获取到页面上所有可见内容的情况。这通常是因为网页中的某些数据并非直接嵌入在初始HTML中,而是通过JavaScript在页面加载后动态请求并渲染的。本文将以Oracle云定价页面为例,详细阐述如何识别这类动态内容,并通过结合静态HTML解析与动态API数据获取的方法,实现完整、准确的数据抓取。
1. 问题识别:BeautifulSoup与浏览器开发者工具的差异
当使用BeautifulSoup解析网页时,如果发现某些表格单元格(如价格信息)在打印出的HTML中显示为空或不完整,但在浏览器开发者工具中却能看到完整内容,这便是一个典型的动态加载数据信号。
例如,在抓取Oracle云虚拟机构建定价表时,初始的BeautifulSoup代码可能得到如下结果,其中价格相关的
标签内容为空:
Compute – Ampere A1 – OCPU OCPU per hour
而通过浏览器开发者工具检查同一行,会发现第二和第三列实际包含了价格信息:
Compute – Ampere A1 – OCPU $0.01$0.01 OCPU per hour
这种差异表明,价格数据是在页面加载后通过异步请求获取的。
2. 定位动态数据源
解决此问题的关键在于找到动态加载数据的源头。通常,这可以通过浏览器的开发者工具(Network标签页)来完成。当页面加载时,观察网络请求,寻找返回JSON格式数据的XHR或Fetch请求。
在Oracle云定价页面的案例中,经过检查可以发现,价格数据是从一个JSON文件加载的:https://www.oracle.com/a/ocom/docs/pricing/cloud-price-list.json。这个JSON文件包含了所有产品的定价信息,并通过partNumber(部件号)与HTML表格中的产品进行关联。
3. 数据抓取与整合策略
一旦确定了动态数据源,接下来的步骤是:
使用requests库获取并解析静态HTML页面内容。使用requests库获取并解析动态JSON数据。通过partNumber或其他唯一标识符,将HTML表格中抓取的产品信息与JSON数据中的价格信息进行匹配和整合。
3.1 核心代码实现
以下是实现这一策略的Python代码:
from bs4 import BeautifulSoupimport requestsimport reimport 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 dictcurrency = '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 = []# 定位包含虚拟机定价表的divvirtual_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 代码解析与注意事项
导入必要的库: BeautifulSoup用于HTML解析,requests用于HTTP请求,re用于正则表达式匹配(在某些情况下定位data-partnumber),pandas用于数据整理和输出。获取JSON数据: 使用requests.get(json_data_url).json()直接获取并解析JSON格式的定价数据。这是解决问题的核心步骤。获取HTML数据: 同样使用requests.get(url).text获取网页内容,并用BeautifulSoup进行解析。定位表格: 通过soup.find(“div”, class_=”rc34w5 rw-neutral-00bg”).table准确找到目标定价表格。遍历行与提取partNumber:遍历每个标签。partNumber是连接HTML产品信息和JSON价格数据的关键。它可能存在于标签的data-partnumber属性中,也可能嵌套在标签内的
4. 总结
当BeautifulSoup无法抓取到网页上可见的全部内容时,很可能是因为这些内容是通过JavaScript动态加载的。解决这类问题的关键在于:
利用浏览器开发者工具(尤其是Network标签页)识别动态数据请求及其API地址。直接请求动态数据API(通常返回JSON或XML格式)。将动态数据与静态HTML解析结果进行整合,通常通过一个唯一的标识符(如data-partnumber)进行匹配。
这种方法比使用Selenium等全功能浏览器自动化工具更高效,因为它避免了渲染整个页面和执行JavaScript的开销,直接获取了原始数据。掌握这种“静态+动态”结合的抓取策略,能有效应对现代网页的复杂性,实现更精准和高效的数据抓取。
以上就是解决BeautifulSoup爬取网页表格中动态内容缺失问题的详细内容,更多请关注创想鸟其它相关文章!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1589236.html
微信扫一扫
支付宝扫一扫