Monday, October 22, 2018

Exporting Notes from iOS 12 in python

A friend, who doesn't use iCloud, asked me how to export the Notes on an iPhone. Searching around I found that the way to do this is to do a non-encrypted backup of the device and then pay a surprising amount of money for an app to read the files and export the notes. There are multiple apps and they are free for the first few notes and then $60 or more to get the rest!

Backup files, including notes, are stored as sqlite3 files (but using cryptic names).

Here's the procedure.

Connect your iOS device to your Mac and say yes to any trust questions. Click on the little icon of the device that appears and look for the "Manually Backup and Restore" panel:


Click "Back up Now".

You don't need to transfer purchases if it asks. Under normal circumstances it's best to encrypt backups but in this case you must click "Don't Encrypt"


When the backup has completed. Go to Preferences in iTunes and click on Device Preferences. Select the backup you just did.


Now, right click on the backup and choose "Show in Finder". You'll see a scrambled mess like this:


Copy the highlighted folder (by holding the Option key and dragging) to your desktop.

Below a little python program I wrote that you run with the path to the copy of the backup folder. It will create a folder on your desktop with all of your notes as separate HTML files.


I hope this helps you and saves a little money. Apple should make this easier. Note that this only works for iOS 12 and it's likely it will break in the future.

import sys
import os
import sqlite3

output_folder = "%s/Desktop/Notes_exported" % os.path.expanduser("~")


def main():
    if len(sys.argv) < 2:
        print("Usage: %s " % (sys.argv[0]))
        return
    backup_path = sys.argv[1]
    notes_db_path = "%s/ca/ca3bc056d4da0bbf88b5fb3be254f3b7147e639c" % backup_path
    conn = sqlite3.connect(notes_db_path)
    c = conn.cursor()
    c.execute("SELECT COUNT(*) from ZNOTEBODY;")
    all_rows = c.fetchall()
    total_rows = all_rows[0][0]
    print("There are %d notes found" % (total_rows))

    print("Checking for %s" % output_folder)
    create_dir(output_folder)

    c.execute("SELECT ZCONTENT FROM ZNOTEBODY;")
    all_rows = c.fetchall()
    counter = 0    for row in all_rows:
        note_text = row[0]
        if note_text:
            file_name = "%s/Note%04d.html" % (output_folder, counter)
            out_file = open(file_name, "w")
            out_file.write(note_text)
            out_file.close()
            counter += 1
    conn.commit()
    conn.close()
    print("Done!")


def create_dir(directory):
    if not os.path.exists(directory):
        print("Creating %s" % (directory))
        os.makedirs(directory)


if __name__ == "__main__":
    main()

Update: A reasonable commercial option

One of the commercial options I looked at was iMazing. I rejected it as I thought that US$70 is a lot to pay for a one time use. In a Reddit thread on this topic, Minority shared a link to a US$20 offer.

I think this is a reasonable price and I have bought the program. It's good and does what I want and lots more.

My theory with the wild price variations is that they make a large sum on people who desperately need the feature and still make sales for people with less urgency. I'd be pretty annoyed if I'd paid the full price.

No comments: