
本文探讨了如何高效地从网页图表中提取通常通过鼠标悬停显示的数据。针对使用Selenium进行鼠标悬停操作的局限性,文章提出了一种更优的方案:通过Python的Requests库直接获取网页源代码,并利用正则表达式解析内嵌的JavaScript数据。最后,结合Pandas库对提取的数据进行结构化处理,实现快速、稳定的数据采集,避免了不必要的浏览器自动化开销,适用于数据已直接存在于HTML源中的场景。
在进行网页数据抓取时,我们经常会遇到数据以动态方式呈现的情况,例如图表中的详细信息通常在鼠标悬停时才显示。对于这类场景,许多开发者会首先考虑使用selenium等浏览器自动化工具来模拟用户行为。然而,并非所有动态内容都需要完整的浏览器模拟,有时通过更直接的方式可以显著提高效率和稳定性。
问题分析:Selenium鼠标悬停的局限性
原始问题中尝试使用Selenium来模拟鼠标悬停以获取图表数据。其核心思路是定位图表元素,然后对每个潜在的数据点执行鼠标悬停操作,期望通过ActionChains触发悬停信息并抓取。
from selenium import webdriverfrom selenium.webdriver import ActionChainsfrom selenium.webdriver.chrome.service import Servicefrom selenium.webdriver.chrome.options import Optionsfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support import expected_conditions as ECoptions = Options()options.add_argument("start-maximized")webdriver_service = Service()driver = webdriver.Chrome(options=options, service=webdriver_service)driver.get('https://www.cpubenchmark.net/cpu.php?cpu=Intel+Core+i9-11900K+%40+3.50GHz&id=3904')# 尝试定位图表元素element = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, "//*[@id='placeholder']/div/canvas[2]")))# 错误点:element是一个WebElement对象,不可迭代for el in element: ActionChains(driver).move_to_element(el).perform() mouseover = WebDriverWait(driver, 30).until(EC.visibility_of_element_located((By.SELECTOR, ".placeholder > div > div.canvasjs-chart-tooltip > div > span"))) print(mouseover.text)
上述代码存在两个主要问题:
WebElement对象不可迭代: EC.presence_of_element_located返回的是一个WebElement对象,而不是一个列表。因此,直接对其进行for el in element循环会导致’WebElement’ object is not iterable错误。即使改为find_elements,也需要精确知道每个数据点的子元素才能有效模拟悬停。效率与稳定性: 依赖Selenium模拟复杂的UI交互(如精确地在图表的每个数据点上悬停并等待提示出现)效率低下,且容易受到页面加载速度、元素定位不准或DOM结构微小变化的影响,导致抓取不稳定。
在许多情况下,图表所展示的数据并非完全通过前端AJAX请求动态获取,而是直接嵌入在页面的JavaScript代码中。这意味着,我们无需模拟浏览器行为,可以直接从网页源代码中提取这些数据。
更优方案:Requests + 正则表达式 + Pandas
对于CPU价格历史图表这类数据,通过检查网页源代码可以发现,其数据通常以JavaScript变量的形式直接存在于HTML中。例如,常见的图表库(如CanvasJS、Highcharts等)在初始化时会直接在标签中定义数据数组。
立即学习“Python免费学习笔记(深入)”;
我们可以利用Python的requests库获取完整的HTML内容,然后使用正则表达式(re模块)从JavaScript代码中匹配并提取所需的数据。最后,结合pandas库对提取的结构化数据进行进一步处理,如类型转换、时间格式化等。
1. 获取网页内容
使用requests.get()方法获取指定URL的HTML内容。
import requestsimport reimport pandas as pdurl = "https://www.cpubenchmark.net/cpu.php?cpu=Intel+Core+i9-11900K+%40+3.50GHz&id=3904"html_text = requests.get(url).text
2. 正则表达式提取数据
通过分析目标网页的源代码,我们可以找到图表数据通常以dataArray.push({x: …, y: …})的形式存在于某个JavaScript块中。我们可以构建一个正则表达式来匹配这些模式。
例如,目标数据格式为{x: 时间戳, y: 价格}。正则表达式r”dataArray.push({x: (d+), y: ([d.]+)}”可以精确匹配:
dataArray.push(:匹配字面字符串”dataArray.push(“。x: (d+):匹配”x: “后跟着一个或多个数字(时间戳),并将其捕获为第一个分组。, y: ([d.]+)}:匹配”, y: “后跟着一个或多个数字或点(价格),并将其捕获为第二个分组。
# 使用re.findall找到所有匹配的数据对# re.findall会返回一个元组列表,每个元组包含正则表达式捕获的组data_tuples = re.findall(r"dataArray.push({x: (d+), y: ([d.]+)}", html_text)
3. 使用Pandas处理数据
将提取到的数据转换为Pandas DataFrame,便于后续的数据清洗、分析和存储。时间戳通常是Unix时间戳(秒或毫秒),需要转换为可读的日期时间格式。
# 创建DataFrame,指定列名df = pd.DataFrame(data_tuples, columns=["time", "price"])# 将'time'列从字符串转换为整数,再除以1000(如果原始是毫秒),然后转换为datetime对象# 示例中原始时间戳是秒,所以直接除以1000得到秒,然后unit='s'df["time"] = pd.to_datetime(df["time"].astype(int) // 1000, unit="s")# 将'price'列转换为浮点数df["price"] = df["price"].astype(float)print(df.tail())
完整代码示例
import requestsimport reimport pandas as pddef scrape_cpu_pricing_history(cpu_url: str) -> pd.DataFrame: """ 从CPU Benchmark网站抓取指定CPU的价格历史数据。 数据通过解析网页内嵌的JavaScript变量获取。 Args: cpu_url (str): CPU详细页面的URL。 Returns: pd.DataFrame: 包含时间戳和价格的DataFrame。 如果抓取失败或未找到数据,返回空的DataFrame。 """ try: html_text = requests.get(cpu_url, timeout=10).text except requests.exceptions.RequestException as e: print(f"请求URL失败: {e}") return pd.DataFrame() # 正则表达式匹配JavaScript中的dataArray.push({x: time, y: price})模式 # x是时间戳(秒),y是价格 pattern = r"dataArray.push({x: (d+), y: ([d.]+)}" data_matches = re.findall(pattern, html_text) if not data_matches: print("未在页面中找到价格历史数据。") return pd.DataFrame() # 将匹配结果转换为DataFrame df = pd.DataFrame(data_matches, columns=["time", "price"]) # 数据类型转换和格式化 try: # 原始时间戳通常是毫秒级,转换为秒后再转datetime # 根据观察,此网站的时间戳是秒级,所以直接//1000是错误的,应根据实际情况调整 # 此处根据答案中的 // 1000 判断,原始数据可能以毫秒存储,但to_datetime的unit='s'表示输入是秒 # 如果原始数据是秒,则无需 // 1000 df["time"] = pd.to_datetime(df["time"].astype(int), unit="s") # 假设原始是秒 df["price"] = df["price"].astype(float) except ValueError as e: print(f"数据类型转换失败: {e}") return pd.DataFrame() return df# 示例调用cpu_detail_url = "https://www.cpubenchmark.net/cpu.php?cpu=Intel+Core+i9-11900K+%40+3.50GHz&id=3904"pricing_df = scrape_cpu_pricing_history(cpu_detail_url)if not pricing_df.empty: print("成功获取CPU价格历史数据:") print(pricing_df.tail())else: print("未能获取CPU价格历史数据。")
输出示例:
time price236 2023-05-28 06:00:00 317.86237 2023-05-29 06:00:00 319.43238 2023-05-30 06:00:00 429.99239 2023-05-31 06:00:00 314.64240 2023-06-01 06:00:00 318.9
注意事项与总结
优先级判断: 在尝试抓取动态内容之前,务必优先检查网页源代码(通过浏览器开发者工具或直接requests.get().text)。很多时候,你认为的“动态”数据其实已经直接嵌入在HTML或JavaScript变量中。效率与资源: requests库轻量且高效,仅请求HTML内容。相比之下,selenium需要启动一个完整的浏览器实例,消耗更多系统资源和时间,适用于需要模拟用户登录、点击、滚动或执行复杂JavaScript交互的场景。正则表达式的鲁棒性: 正则表达式依赖于特定的字符串模式。如果目标网站的HTML或JavaScript结构发生变化,正则表达式可能需要调整。因此,在部署前和定期维护时,应检查其有效性。数据清洗: 抓取到的原始数据可能需要进一步的清洗和类型转换,pandas库在此方面提供了强大的支持。错误处理: 在实际项目中,应加入更完善的错误处理机制,例如网络请求超时、解析失败等。
通过上述方法,我们能够以更高效、更稳定的方式从网页中提取图表数据,避免了不必要的浏览器自动化开销,使爬虫程序更加健壮。在遇到类似问题时,优先分析数据源,选择最直接的抓取策略是提升爬虫性能的关键。
以上就是Python网络爬虫:高效提取网页图表悬停数据的详细内容,更多请关注创想鸟其它相关文章!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1365713.html
微信扫一扫
支付宝扫一扫