MechanicalSoup无法获取已存在的cmeTradeDate标签内容问题求助
get_settldays()函数无法获取日期内容的问题 嘿,我见过不少类似的情况,你的问题大概率是目标页面的渲染逻辑在2018年3月31日后发生了变化:之前select#cmeTradeDate的选项是直接嵌入在初始HTML里的,后来改成了通过JavaScript动态加载(比如页面加载后异步请求数据再渲染选项)。而MechanicalSoup本质是基于Requests和BeautifulSoup,它只会抓取初始的HTML源码,不会执行页面中的JavaScript,所以自然拿不到动态生成的内容,导致settlDays.contents为空。
下面给你几个可行的解决方案:
方案1:用支持JS渲染的工具模拟浏览器
你可以换成Selenium或者Playwright这类能模拟真实浏览器行为的工具,它们会完整执行页面的JS代码,能获取到动态渲染后的完整内容。
举个Selenium的实现例子:
import sys, re, datetime from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def get_settldays(): # 初始化Chrome浏览器(需要提前下载对应版本的ChromeDriver) driver = webdriver.Chrome() driver.get("http://www.cmegroup.com/t...") # 等待select标签加载完成,避免因网络延迟抓不到内容 try: settl_select = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.ID, "cmeTradeDate")) ) # 获取所有选项元素 options = settl_select.find_elements(By.TAG_NAME, "option") # 提取符合格式的日期 settl_days = [] for opt in options: date_match = re.search(r'\d\d/\d\d/\d\d\d\d', opt.text) if date_match: settl_days.append(date_match.group()) # 转换为datetime对象返回 return [datetime.datetime.strptime(adat, '%m/%d/%Y') for adat in settl_days] finally: # 无论成功失败都关闭浏览器 driver.quit()
方案2:抓包直接调用数据源接口
这是更高效稳定的方式——在浏览器里打开开发者工具(按F12),切换到Network标签,刷新页面,观察哪些XHR/Fetch请求是用来获取交易日期数据的。找到对应的API接口后,直接用requests调用接口拿数据,不需要解析HTML。
比如假设找到的接口是http://www.cmegroup.com/api/trade-dates,代码可以简化成这样:
import requests, datetime, re def get_settldays(): # 直接请求数据源接口 response = requests.get("http://www.cmegroup.com/api/trade-dates") response.raise_for_status() # 抛出请求异常 date_data = response.json() # 假设接口返回JSON格式 # 提取符合格式的日期(根据实际返回的JSON结构调整) settl_days = [item['tradeDate'] for item in date_data if re.match(r'\d\d/\d\d/\d\d\d\d', item['tradeDate'])] return [datetime.datetime.strptime(adat, '%m/%d/%Y') for adat in settl_days]
方案3:优化原代码(仅适用于静态内容场景)
如果页面只是有小改动,还保留了静态的option标签,你可以优化原代码的提取逻辑,直接遍历option标签而不是转成字符串匹配:
import sys, mechanicalsoup, re, datetime def get_settldays(soup): settl_select = soup.find('select', id="cmeTradeDate") settl_days = [] # 直接遍历select下的option标签 for option in settl_select.find_all('option'): date_match = re.search(r'\d\d/\d\d/\d\d\d\d', option.get_text()) if date_match: settl_days.append(date_match.group()) return [datetime.datetime.strptime(adat, '%m/%d/%Y') for adat in settl_days] url = "http://www.cmegroup.com/t..." browser = mechanicalsoup.Browser() page = browser.get(url) print(get_settldays(page.soup))
总结
优先推荐方案2,因为它不需要模拟浏览器,效率更高也更稳定;如果找不到数据源接口,再用方案1模拟浏览器;方案3只适合页面还保留静态内容的情况,对你的场景可能不太适用,但可以试试排除问题。
内容的提问来源于stack exchange,提问作者alexcape

