DEV Community

Cover image for Solved: Migrate Evernote Notes to Obsidian: Converting HTML to Markdown
Darian Vance
Darian Vance

Posted on • Originally published at wp.me

Solved: Migrate Evernote Notes to Obsidian: Converting HTML to Markdown

🚀 Executive Summary

TL;DR: This guide provides a programmatic solution using Python to migrate Evernote notes, exported as HTML, into Obsidian’s Markdown format. It addresses the core problem of format incompatibility by automating the conversion process, including handling images and cleaning HTML, to ensure a smooth transition to a local-first knowledge base.

🎯 Key Takeaways

  • The migration leverages Python with beautifulsoup4 for HTML parsing and cleaning, and html2text for converting cleaned HTML into Markdown.
  • The script automates the discovery of HTML files, excludes irrelevant ones (like \_resources content), and preserves the relative directory structure for converted notes.
  • Crucially, it includes logic to identify and copy associated \_resources folders (containing images and attachments) to the Obsidian vault, maintaining valid image links.
  • Users must configure EVERNOTE\_EXPORT\_DIR and OBSIDIAN\_VAULT\_DIR paths and set up a Python virtual environment with beautifulsoup4 and html2text installed.
  • Common pitfalls include potential issues with complex image paths, inconsistent Evernote HTML structures, and the need for manual re-linking of internal notes within Obsidian.

Migrate Evernote Notes to Obsidian: Converting HTML to Markdown

As Senior DevOps Engineers and Technical Writers at TechResolve, we frequently encounter scenarios where users wish to transition from proprietary, cloud-based services to more open, self-controlled alternatives.

One such common challenge involves migrating years of accumulated knowledge from Evernote, a popular but increasingly restrictive note-taking application, to Obsidian, a powerful, local-first, Markdown-based knowledge base.

The primary hurdle in this migration is format incompatibility. Evernote typically exports notes as HTML files, while Obsidian thrives on Markdown. Manually converting hundreds or thousands of HTML notes to Markdown is not only tedious and time-consuming but also prone to errors and inconsistency.

Manual work is boring, inefficient, and often leads to suboptimal results.

This comprehensive tutorial will guide you through a programmatic, step-by-step approach using Python to automate the conversion of your Evernote HTML exports into clean, Obsidian-ready Markdown files.

We’ll provide the necessary scripts and explanations to ensure a smooth transition, empowering you to reclaim ownership of your valuable notes and unlock the full potential of Obsidian.

Prerequisites

Before we dive into the migration process, ensure you have the following:

  • Evernote Application: Installed on your machine, with access to the notes you wish to export.
  • Obsidian Application: Installed and ready to create or open a vault.
  • Python 3.x: Installed on your system. You can download it from the official Python website.
  • Command-Line Interface (CLI): Familiarity with basic command-line operations (e.g., navigating directories, running scripts).
  • Text Editor/IDE: Such as VS Code, Sublime Text, or Atom, for writing and editing the Python script.

Step-by-Step Guide

Step 1: Exporting Notes from Evernote

The first crucial step is to get your notes out of Evernote in a usable format.

Evernote allows you to export notebooks or individual notes as HTML files.

  1. Select Notes: In Evernote, select the notes or notebooks you wish to migrate. You can select multiple notes by holding Ctrl (Windows) or Cmd (macOS) and clicking, or select a range by clicking the first note, then holding Shift and clicking the last.
  2. Initiate Export: Go to File > Export Notes... from the menu bar.
  3. Choose Export Format: In the export dialog, select “Multiple web pages (.html)”. This option will create a separate HTML file for each note and an accompanying _resources folder containing images and other attachments linked to the notes. This structure is ideal for our conversion process.
  4. Choose Destination: Select an empty folder on your local machine where you want to save the exported HTML files. Create a new folder specifically for this purpose, for example, ~/evernote_export/.
  5. Confirm Export: Click Export to begin the process. Depending on the number and size of your notes, this may take some time.

Once completed, your chosen folder will contain numerous .html files and potentially several _resources directories.

Step 2: Setting Up Your Python Environment

To keep our project dependencies isolated and avoid conflicts, we’ll set up a Python virtual environment and install the necessary libraries.

  1. Create Project Directory: Navigate to a suitable location in your terminal and create a new directory for your migration script.
   mkdir evernote_to_obsidian
   cd evernote_to_obsidian
Enter fullscreen mode Exit fullscreen mode
  1. Create Virtual Environment:
   python3 -m venv venv
Enter fullscreen mode Exit fullscreen mode
  1. Activate Virtual Environment:

    • macOS/Linux:
     source venv/bin/activate
    
  • Windows (Command Prompt):

     venv\Scripts\activate.bat
    
  • Windows (PowerShell):

     .\venv\Scripts\Activate.ps1
    
  1. Install Required Libraries: We’ll use beautifulsoup4 for parsing and cleaning HTML, and html2text for converting cleaned HTML to Markdown.
   pip install beautifulsoup4 html2text
Enter fullscreen mode Exit fullscreen mode

Step 3: Crafting the Conversion Script

Now, let’s write the Python script that will automate the conversion. Create a new file named migrate_evernote.py in your evernote_to_obsidian directory and add the following code:

import os
import glob
from bs4 import BeautifulSoup
import html2text

# --- Configuration ---
# Path to your Evernote exported HTML files
EVERNOTE_EXPORT_DIR = "C:/Users/YourUser/evernote_export" # Adjust this path
# Directory where converted Markdown files will be saved
OBSIDIAN_VAULT_DIR = "C:/Users/YourUser/Documents/Obsidian_Vault/Evernote_Import" # Adjust this path

# Ensure the output directory exists
os.makedirs(OBSIDIAN_VAULT_DIR, exist_ok=True)

# Configure html2text
h = html2text.HTML2Text()
h.body_width = 0 # Disable line wrapping
h.pad_alt_text = True # Add padding around alt text
h.ignore_emphasis = False # Keep bold/italic
h.ignore_tables = False # Attempt to convert tables
h.single_spacing_lines = True # Reduce excessive blank lines
h.use_automatic_links = True # Convert URLs to Markdown links

print(f"Starting migration from '{EVERNOTE_EXPORT_DIR}' to '{OBSIDIAN_VAULT_DIR}'...")

# Find all HTML files, excluding those in _resources directories
html_files = [f for f in glob.glob(os.path.join(EVERNOTE_EXPORT_DIR, '**/*.html'), recursive=True)
              if '_resources' not in f and not f.endswith('index.html')] # Exclude index files and resource folders

print(f"Found {len(html_files)} HTML files for conversion.")

for html_file_path in html_files:
    try:
        # Determine the output filename
        relative_path = os.path.relpath(html_file_path, EVERNOTE_EXPORT_DIR)
        markdown_filename = os.path.splitext(relative_path)[0] + ".md"
        output_markdown_path = os.path.join(OBSIDIAN_VAULT_DIR, markdown_filename)

        # Ensure output subdirectory exists if the HTML was nested
        output_dir = os.path.dirname(output_markdown_path)
        os.makedirs(output_dir, exist_ok=True)

        with open(html_file_path, 'r', encoding='utf-8') as f:
            html_content = f.read()

        # Parse HTML with BeautifulSoup for cleaning
        soup = BeautifulSoup(html_content, 'html.parser')

        # --- HTML Cleaning (Optional but Recommended) ---
        # Remove Evernote's specific empty divs/spans that clutter Markdown
        for tag in soup.find_all(['div', 'span'], class_=lambda x: x and ('en-markup' in x or 'en-note' in x)):
            if not tag.get_text(strip=True) and not tag.find_all('img'): # Remove if empty and no images inside
                tag.decompose()

        # Remove script and style tags which are irrelevant for Markdown
        for script_or_style in soup(['script', 'style']):
            script_or_style.decompose()

        # Get the cleaned body content
        # If the HTML has a body, use that. Otherwise, use the whole soup.
        clean_html = str(soup.find('body') if soup.find('body') else soup)

        # Convert HTML to Markdown
        markdown_content = h.handle(clean_html)

        # Post-processing for Obsidian compatibility (optional)
        # For instance, if Evernote's internal links need adjustment
        # This part might need custom logic based on how Evernote links notes
        # For simplicity, we'll assume most internal note links are broken and left as text for manual fix.

        # Write Markdown to file
        with open(output_markdown_path, 'w', encoding='utf-8') as f:
            f.write(markdown_content)

        print(f"Converted '{relative_path}' to '{output_markdown_path}'")

        # --- Handle resources (images) ---
        # Evernote exports images to a _resources folder sibling to the HTML file.
        # We need to copy these resources to the Obsidian vault relative to the markdown file.
        resources_dir_name = os.path.splitext(os.path.basename(html_file_path))[0] + '_resources'
        source_resources_path = os.path.join(os.path.dirname(html_file_path), resources_dir_name)

        if os.path.exists(source_resources_path) and os.path.isdir(source_resources_path):
            target_resources_path = os.path.join(output_dir, resources_dir_name)
            if not os.path.exists(target_resources_path):
                import shutil
                shutil.copytree(source_resources_path, target_resources_path)
                print(f"Copied resources from '{resources_dir_name}' to '{os.path.relpath(target_resources_path, OBSIDIAN_VAULT_DIR)}'")
            else:
                print(f"Resources directory '{resources_dir_name}' already exists in target, skipping copy.")


    except Exception as e:
        print(f"Error converting '{html_file_path}': {e}")

print("\nMigration complete! Review your notes in Obsidian.")
Enter fullscreen mode Exit fullscreen mode

Explanation of the Script Logic:

  • Configuration: Define EVERNOTE_EXPORT_DIR (where your exported HTML files are) and OBSIDIAN_VAULT_DIR (where you want the Markdown files to go inside your Obsidian vault). Remember to adjust these paths!
  • html2text Configuration: We initialize html2text.HTML2Text() with several options to improve Markdown output quality, such as disabling line wrapping, preserving emphasis, and attempting table conversion.
  • File Discovery: The script uses glob.glob to find all .html files recursively within your export directory, explicitly excluding files within _resources folders and common index.html files that don’t represent actual notes.
  • HTML Parsing and Cleaning:
    • For each HTML file, BeautifulSoup is used to parse the content.
    • We include optional cleaning steps to remove Evernote-specific empty <div> and <span> tags, as well as <script> and <style> tags, which can clutter the Markdown output.
    • The script attempts to extract the <body> content, as this typically contains the actual note data.
  • Markdown Conversion: The cleaned HTML content is then passed to h.handle() (from html2text) to perform the conversion to Markdown.
  • Output and Resource Handling:
    • The converted Markdown content is saved to a new .md file in the specified Obsidian directory, preserving the relative directory structure from the export if applicable.
    • Crucially, the script also identifies any sibling _resources directories (which contain images and attachments for a specific note) and copies them to the corresponding location within your Obsidian import folder. This ensures that image links in the converted Markdown files remain valid, assuming relative paths are maintained.
  • Error Handling: A basic try-except block is included to catch and report errors for individual file conversions, allowing the script to continue processing other notes.

Step 4: Running the Migration Script and Importing to Obsidian

With the script ready, let’s execute it and bring your notes into Obsidian.

  1. Update Configuration: Double-check and update the EVERNOTE_EXPORT_DIR and OBSIDIAN_VAULT_DIR variables in migrate_evernote.py to match your system paths.
  2. Run the Script: Ensure your virtual environment is activated, then execute the Python script from your terminal:
   python migrate_evernote.py
Enter fullscreen mode Exit fullscreen mode

You will see output indicating the progress of each file conversion and resource copying.

  1. Open Obsidian: Once the script completes, open your Obsidian application.
  2. Open or Create Vault:
    • If you specified an existing Obsidian vault directory in OBSIDIAN_VAULT_DIR, Obsidian should automatically detect and index the new Markdown files upon opening the vault.
    • If you pointed to a new, empty directory (which Obsidian doesn’t know about yet), you can open it as a new vault by clicking Open another vault or Open folder as vault and selecting your OBSIDIAN_VAULT_DIR.
  3. Verify: Navigate through your new Obsidian vault. You should find your Evernote notes, now in Markdown format, complete with images (if handled correctly by the resource copying step).

Common Pitfalls

While this script automates much of the process, here are a few things that might go wrong or require manual intervention:

  • Image Paths and Attachments: While the script attempts to copy _resources folders, complex image linking or specific attachment types might not translate perfectly. Always check a few notes with images to ensure they render correctly. If not, manual adjustments to image paths within the Markdown files might be needed, or further customization of the script to handle unique Evernote linking conventions.
  • Inconsistent HTML Structure: Evernote’s HTML output can vary, especially for older notes or those created with different versions of the app. This can lead to less-than-perfect Markdown conversion for certain notes (e.g., malformed tables, extra blank lines, or incorrect headings). Spot-checking your most important notes after conversion is highly recommended.
  • Code Blocks and Syntax Highlighting: Evernote does not inherently support Markdown-style code blocks with syntax highlighting. While html2text will convert preformatted text, you might need to manually wrap them in triple backticks ( `

`) and add language identifiers in Obsidian for proper rendering.
* **Internal Note Links:** Evernote’s internal links (links from one note to another) are proprietary HTML links. `html2text` will likely convert them to standard Markdown links, but they won’t automatically point to other notes within Obsidian unless their titles exactly match the converted filenames. You may need to manually re-link these within Obsidian using its internal linking syntax (`[[Note Title]]`).
* **Special Characters and Encoding:** Although we specify UTF-8 encoding, very rare or unusual special characters might occasionally cause issues. If you see mojibake, verify your file encodings.

## Conclusion

Migrating from a closed ecosystem like Evernote to an open, extensible platform like Obsidian offers unparalleled control and future-proofing for your knowledge base.  
By leveraging Python and the power of libraries like `BeautifulSoup` and `html2text`, we’ve transformed a potentially daunting task into an automated, manageable process.

You now have a robust foundation of your notes in Markdown, ready to be explored, linked, and expanded within Obsidian. This script serves as a starting point; feel free to customize it further to meet your specific needs, such as handling unique Evernote tags, more advanced image processing, or refining Markdown output for specific formatting preferences.

As a next step, delve into Obsidian’s powerful features: explore its rich plugin ecosystem, set up synchronization for your vault, and begin to build a truly interconnected personal knowledge graph.  
**Welcome to the world of local-first knowledge management!**



---

![Darian Vance](https://techresolve.blog/wp-content/uploads/2025/12/TechResolve-bio-box.png)

👉 [Read the original article on TechResolve.blog](https://wp.me/pbK4oa-8A)



---

☕ **Support my work**  

If this article helped you, you can buy me a coffee:  

👉 <https://buymeacoffee.com/darianvance>
Enter fullscreen mode Exit fullscreen mode

Top comments (0)