Bulk-download your Google Photos library in original quality via undetected browser automation (Python/SeleniumBase).
Project description
gphotos-export
Bulk-download your entire Google Photos library in original quality via undetected browser automation.
Why this exists
Getting your own photos back out of Google Photos in original quality is weirdly hard in 2026:
- Google Takeout is extremely flaky and unreliable — it's slow, manual, non-incremental, and for large libraries it routinely hands you broken, incomplete, or impossible-to-reassemble archives.
- The Photos API can't help — Google removed the read scopes that backup tools relied on in March 2025 (which also broke
rclone-based backups). - I couldn't find a tool that actually fit — specifically one that is (a) still maintained in 2026, (b) written in Python, and (c) does the downloading for you using safe, reliable scraping — i.e. undetected automation that behaves like a real logged-in user, with retries and resume instead of brittle hacks. The closest prior art (see Credits) is an unmaintained Node/Playwright project.
So this exists to fill that gap: a maintained, Python, hardened downloader.
Worth knowing: Google has been known to disable entire accounts with little recourse (see theywillbanyou.com) — which is exactly why keeping your own local copy matters. Run this on the account you're backing up, and understand that automating any account carries its own ban risk.
How it works
Uses SeleniumBase in undetected-Chrome (uc) mode to drive the Google Photos web UI the way a real logged-in person would:
- Opens each photo in the viewer
- Presses Shift+D (Google Photos' native download shortcut)
- Reads EXIF metadata (falls back to page HTML) to sort files into
year/month/folders - Auto-extracts any zip bundles Google returns
- Saves progress to a checkpoint file so you can stop and resume anytime
Install
pip install gphotos-export
Optional but recommended: install exiftool for the most accurate photo dates (it falls back to parsing the page when exiftool isn't present):
- macOS:
brew install exiftool - Debian/Ubuntu:
sudo apt-get install libimage-exiftool-perl
The tool operates in the current directory — it creates
session/,downloads-2/, andlogs/wherever you run it. Pick a working folder and run it from there.
Usage
# 1. Log in once (opens a browser; sign into Google Photos, then close the window)
gphotos-export-login
# 2. Seed the starting point with your OLDEST photo's URL
echo "https://photos.google.com/photo/YOUR_OLDEST_PHOTO_ID" > .lastdone
# 3. Download (a visible browser is recommended for the first run)
gphotos-export --headed
# Headless
gphotos-export
# Dry run (navigate without downloading)
gphotos-export --dry-run --headed
~2x speed: run both directions at once
gphotos-export-login --backward # one-time: set up a second browser profile
gphotos-export --headed & # forward: oldest -> newest
gphotos-export --headed --backward & # backward: newest -> oldest
Features
- Original-quality downloads via the Shift+D shortcut
- Undetected automation (
ucmode) that behaves like a real logged-in user - EXIF date extraction (falls back to HTML parsing)
- Organized output:
downloads-2/year/month/filename - Resume support via
.lastdone/backward.lastdonecheckpoints - Two-worker mode (
--backward) for ~2x throughput - Auto-extracts zip bundles from Google into the right folder
- Skip list: add URLs to
skiplist.txtto skip specific items - 3-attempt retry with exponential backoff + error resilience
- Human-realistic random delays
- Ctrl+C safe (saves progress on interrupt)
Credits / Acknowledgements
This project began as a Python port of vikas5914/google-photos-backup by Vikas Kapadiya (MIT-licensed, JavaScript/Playwright). The original's core approach — driving the Photos web UI, Shift+D downloads, year/month sorting, and .lastdone resume — is his work, and this wouldn't exist without it.
This version is a substantial rework: rewritten in Python on SeleniumBase undetected-Chrome, with retry/backoff, multi-strategy navigation, a backward worker, zip auto-extraction, a skiplist, file logging, and a test suite.
Huge thanks to Vikas. Both copyrights are preserved under MIT — see LICENSE.
License
MIT — see LICENSE. Original work © Vikas Kapadiya; Python port and rework © 2026 John Pratt.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gphotos_export-0.1.0.tar.gz.
File metadata
- Download URL: gphotos_export-0.1.0.tar.gz
- Upload date:
- Size: 14.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cfcd08448af16733c9a5180799ffe6e25c51f4273586effec8dfb56742e12d2e
|
|
| MD5 |
d180dabb5628207e25114c7159752a80
|
|
| BLAKE2b-256 |
ad021bfae5244c9c8203423ccce87486a23f67089039bd9f4ef5740141bbb650
|
Provenance
The following attestation bundles were made for gphotos_export-0.1.0.tar.gz:
Publisher:
publish.yml on jpratt9/gphotos-export
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gphotos_export-0.1.0.tar.gz -
Subject digest:
cfcd08448af16733c9a5180799ffe6e25c51f4273586effec8dfb56742e12d2e - Sigstore transparency entry: 1614665987
- Sigstore integration time:
-
Permalink:
jpratt9/gphotos-export@88d186ab1c709c15c0147c5bc33295ee8f2ee368 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/jpratt9
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@88d186ab1c709c15c0147c5bc33295ee8f2ee368 -
Trigger Event:
release
-
Statement type:
File details
Details for the file gphotos_export-0.1.0-py3-none-any.whl.
File metadata
- Download URL: gphotos_export-0.1.0-py3-none-any.whl
- Upload date:
- Size: 12.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a20958456df37b0c63ccd8b122f8ddd501435d6ec1779388fb7f6f30f84d4243
|
|
| MD5 |
14371bb26d0564b866f0d1e6833ca0c6
|
|
| BLAKE2b-256 |
f94f6a8a8cc9b5822ee3f1cb3287f08a69ccc67139882bbe20e2247ed4ec1436
|
Provenance
The following attestation bundles were made for gphotos_export-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on jpratt9/gphotos-export
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gphotos_export-0.1.0-py3-none-any.whl -
Subject digest:
a20958456df37b0c63ccd8b122f8ddd501435d6ec1779388fb7f6f30f84d4243 - Sigstore transparency entry: 1614665990
- Sigstore integration time:
-
Permalink:
jpratt9/gphotos-export@88d186ab1c709c15c0147c5bc33295ee8f2ee368 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/jpratt9
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@88d186ab1c709c15c0147c5bc33295ee8f2ee368 -
Trigger Event:
release
-
Statement type: