如何自动将Jupyter Notebook含Widgets的单元格内容保存为PNG/PDF/JPG/GIF
Hey Josef, I’ve been in your exact situation before—trying to document widget configurations programmatically can be tricky when the usual nbconvert or HTML exports don’t capture the dynamic state. Let’s break down some practical solutions that should work for you:
Why your previous attempts didn’t work
First, quick context: Directly converting notebooks to HTML with nbconvert doesn’t render interactive widgets because they rely on Jupyter’s frontend runtime to load their state. html2canvas can work, but it needs to run in the same browser context where the widgets are active, which isn’t handled automatically in a standard notebook setup.
Solution 1: Use Selenium for reliable, automated screenshots
This is my go-to for batch capturing widget states because it uses a real browser to fully render the widgets, ensuring all dynamic elements are captured correctly.
Steps:
Install dependencies:
pip install selenium ipywidgets pillowYou’ll also need a browser driver (e.g., ChromeDriver for Chrome) matching your browser version.
Example code to loop through widget configurations and save screenshots:
from ipywidgets import VBox, IntSlider, Dropdown from selenium import webdriver from selenium.webdriver.chrome.options import Options import time from PIL import Image import io # Create your base widget setup slider = IntSlider(min=0, max=100) dropdown = Dropdown(options=["Option A", "Option B", "Option C"]) vbox = VBox([slider, dropdown]) # Configure Selenium to run headless (no visible browser window) chrome_options = Options() chrome_options.add_argument("--headless=new") driver = webdriver.Chrome(options=chrome_options) # Loop through your widget configurations for slider_val in [25, 50, 75]: for dropdown_val in dropdown.options: # Update widget state slider.value = slider_val dropdown.value = dropdown_val time.sleep(0.2) # Wait for widget to re-render # Export widget to HTML string widget_html = vbox._repr_html_() # Save to a temporary HTML file with open("temp_widget.html", "w") as f: f.write(f""" <html> <head> <!-- Load Jupyter widget CSS/JS to ensure rendering --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jupyter-widgets/html-manager@0.20.0/dist/embed-amd.min.css"> <script src="https://cdn.jsdelivr.net/npm/@jupyter-widgets/html-manager@0.20.0/dist/embed-amd.min.js"></script> </head> <body>{widget_html}</body> </html> """) # Load the temp file in Selenium driver.get("file:///path/to/your/temp_widget.html") time.sleep(1) # Wait for widgets to fully load # Capture screenshot and save screenshot = driver.get_screenshot_as_png() img = Image.open(io.BytesIO(screenshot)) img.save(f"widget_state_slider-{slider_val}_dropdown-{dropdown_val}.png") # Cleanup driver.quit()
Solution 2: Use html2canvas directly in the notebook (no external browser)
If you prefer a lighter setup without Selenium, you can run html2canvas directly in the notebook’s frontend via JavaScript, then pass the image data back to Python to save.
Steps:
First, load html2canvas into your notebook:
from IPython.display import Javascript, display display(Javascript(""" // Load html2canvas from CDN var script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js'; document.head.appendChild(script); """))Define helper functions to capture and save the widget:
import base64 from PIL import Image import io def save_widget_screenshot(widget, filename): # Get the widget's DOM ID (fallback to model ID if no layout ID) widget_id = widget.layout.id if widget.layout.id else widget._model_id # Run html2canvas to capture the widget js_code = f""" html2canvas(document.getElementById('{widget_id}')).then(canvas => {{ var dataURL = canvas.toDataURL('image/png'); // Send the base64 data to Python IPython.notebook.kernel.execute(`_save_screenshot_from_base64('${{dataURL}}', '{filename}')`); }}); """ display(Javascript(js_code)) def _save_screenshot_from_base64(data_url, filename): # Parse base64 data and save image_data = base64.b64decode(data_url.split(',')[1]) with open(filename, 'wb') as f: f.write(image_data) # Convert to JPG if needed if filename.endswith('.jpg'): img = Image.open(io.BytesIO(image_data)) img.convert('RGB').save(filename)Use it in your configuration loop:
# Update widget state slider.value = 50 dropdown.value = "Option B" # Save screenshot save_widget_screenshot(vbox, "widget_state_example.png")
Solution 3: Convert to PDF with wkhtmltopdf (for documentation)
If you need PDF outputs instead of images, wkhtmltopdf can convert the widget’s HTML (with proper resources loaded) into a high-quality PDF.
Install
wkhtmltopdf(follow instructions for your OS) and the Python wrapper:pip install pdfkitExample code:
import pdfkit # Export widget HTML with required resources widget_html = f""" <html> <head> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@jupyter-widgets/html-manager@0.20.0/dist/embed-amd.min.css"> <script src="https://cdn.jsdelivr.net/npm/@jupyter-widgets/html-manager@0.20.0/dist/embed-amd.min.js"></script> </head> <body>{vbox._repr_html_()}</body> </html> """ # Convert to PDF pdfkit.from_string(widget_html, "widget_state.pdf")
Key tips for batch processing
- Always add a small delay (
time.sleep()in Python, orsetTimeout()in JS) after updating widget state to ensure the UI finishes rendering before capturing. - For GIFs of multiple states, use
imageioto combine saved PNGs into an animated GIF:import imageio image_paths = ["state1.png", "state2.png", "state3.png"] images = [imageio.imread(path) for path in image_paths] imageio.mimsave("widget_states.gif", images, duration=0.5)
内容的提问来源于stack exchange,提问作者user1777928

