Skip to content

// 062025

Canvas Downloader

Archiving course files that only exist as links inside page HTML

Role
Sole developer
Stack
Python
Year
2025
Status
Works
Licence
MIT

// Why it exists

I had my own lecture notes written up, but I wanted the source material as well — to feed the knowledge base behind Hal, which files anything dropped into its inbox into a cross-linked, indexed Obsidian vault. Course material is exactly the kind of thing worth being able to search four years later, and exactly the kind of thing Canvas quietly deletes when a course is unpublished.

So this walks the Canvas REST API with a personal access token and pulls everything down in one pass — lecture slides, PDFs, page text and linked handouts — into tidy folders sorted by term and course, with a Course structure.md per course so you can tell what it covered a year later.

// The one part that isn't obvious

Pulling the Files tab is a paginated API call, and that part is dull. The problem is that a lot of material never appears in the Files tab at all. Lecturers link files inline in page text, assignments and announcements — a bare <a href="/courses/123/files/8834514">slides</a> in the page body — and that file may not be listed under Files, may live in a folder you can’t browse, and is the most likely thing to disappear.

So every page, assignment and announcement is fetched, /files/<id> references are extracted from the HTML, and each one is resolved separately through the API. In practice they’re a meaningful share of what a course actually contains.

Files resolved out of page HTML, sorted into course folders
Linked materials resolved out of page HTML — course names are placeholders

// Current state

Works — it does the job it was written for, and has been run against a full multi-year account.

Honest about scale: a single-file script written in an afternoon, with no tests, no packaging and no flags beyond the two constants at the top. That’s proportionate to what it is — the interesting part isn’t the code, it’s that it completes the Hal ecosystem.

Known rough edges: Canvas deployments vary a lot. Institutions disable endpoints, rename things and set different permissions, so an instance that behaves oddly is likely and worth an issue.

// Related

Read the code and the full README on GitHub