Client for Instagram and Threads public GraphQL APIs (Python library + CLI)
Project description
outgram
Python client library and command-line interface to access Threads and Instagram public GraphQL APIs. Useful to get your own data out of Instagram and Threads for backup/archiving purposes. The concepts like profile, post, picture, video are represented as dataclasses and all information is converted to high-level Python objects, making it easy to use.
Note: this project is developed for research purposes. It's your responsability the way you use it.
License
outgram is licensed under the GNU Lesser General Public License version 3 (LGPL
v3). In short:
✅ What you can do:
- Use the library in proprietary or free/open source software projects
- Modify the library's source code
- Distribute the original or modified library along with another program, provided that:
- You notify your users that the library is used in your program and is licensed under LGPL v3
- You include a copy of the LGPL v3 with the distribution of your program
🚫 What you cannot do:
- Restrict your user's freedom to modify the library
- Distribute the library (original or modified) without providing the source code
- Incorporate significant parts of the library into your code without informing and providing the license
Installation
Install outgram from Python Package Index:
pip install outgram
Usage as a Library
Threads Profile
from pprint import pprint
from outgram import Threads
client = Threads()
username = "wsj"
simple_profile = client.simple_profile(username) # Used to get user ID
user_id = simple_profile.id
profile = client.profile_from_user_id(user_id)
# or: profile = client.profile_from_username(username)
pprint(profile)
# Result:
# ThreadsProfile(id=787132,
# username='natgeo',
# full_name='National Geographic',
# biography='Inspiring the explorer in everyone 🌎',
# is_verified=True,
# followers=15594746,
# pictures=[Picture(width=150, height=150, content_type=None),
# Picture(width=320, height=320, content_type=None)],
# is_private=False,
# bio_links=[Link(url='https://on.natgeo.com/instagram',
# title=None,
# is_verified=False,
# id=17953782509778540,
# display_text=None)],
# is_threads_only_user=False)
Threads Profile Posts
# NOTES:
# 1- Run the last example before running the code below
# 2- Since the client is not logged-in, only the last 25 posts are returned
for index, post in enumerate(client.profile_posts(user_id), start=1):
print(f"Got post {index:02d}:")
pprint(post)
# Result:
# Got post 01:
# ThreadsPost(id='3574333699320466889_787132',
# user_id=787132,
# username='natgeo',
# is_verified=True,
# text="Three-toed sloths can hang onto trees even when they're "
# "asleep 🦥 What's your favorite tree-climbing animal?",
# links=[],
# published_at=datetime.datetime(2025, 2, 23, 9, 30, 20, tzinfo=datetime.timezone.utc),
# likes=761,
# replies=17,
# reposts=24,
# quotes=3,
# is_private=False,
# media=[Picture(width=1439, height=1873, content_type=None)],
# reply_control='everyone',
# media_type=1,
# accessibility_caption='Photo by National Geographic on February '
# '23, 2025. May be an image of slow loris, '
# "sloth and text that says 'ロ BRUCE BRUCEDALE "
# "DALE'.",
# is_paid_partnership=None,
# like_and_view_counts_disabled=False,
# has_audio=None,
# original_width=1439,
# original_height=1873,
# code='DGalY_YtE3J',
# reshares=6)
# [...]
Instagram Profile
from pprint import pprint
from outgram import Instagram
client = Instagram()
username = "crio.cafe"
user_id = client.user_id(username)
profile = client.profile_from_user_id(user_id)
# or: profile = client.profile_from_username(username)
pprint(profile)
# Result:
# InstagramProfile(id=21057648761,
# username='crio.cafe',
# full_name='CRIO',
# picture=Picture(width=None, height=None, content_type=None),
# is_verified=False,
# followers=20392,
# followees=3168,
# posts=145,
# is_private=False,
# biography='Melhor forma de começar o dia.\n'
# 'Entregamos qualidade para todo o Brasil!\n'
# 'Conheça nossa loja em SP\n'
# 'R Cubatão 641 (2ª-Sáb) e Alameda Santos 1470 '
# '(2ª-6ª)',
# bio_links=[Link(url='http://delivery.crio.cafe',
# title='Enviamos para todo Brasil',
# is_verified=None,
# id=None,
# display_text=None)])
Instagram Profile Posts
# NOTE: run the last example before running the code below
posts = []
for index, post in enumerate(client.profile_posts(username), start=1):
snippet = post.text[:35].replace("\n", " ")
print(f"Got post {index:02} from @{post.author.username} ({post.published_at}): {snippet}...")
posts.append(post)
if index == 36: # 3 pages, each with 12 posts
break
# Result:
# Got post 01 from @crio.cafe (2024-01-19 18:54:50+00:00): Você ama queijo e pão de queijo com...
# Got post 02 from @crio.cafe (2023-12-11 15:00:25+00:00): Delicado, Equilibrado, Intenso, Aze...
# [...]
# Got post 35 from @crio.cafe (2023-12-22 14:02:57+00:00): DELICADO: café suave, para beber de...
# Got post 36 from @crio.cafe (2023-12-21 15:55:01+00:00): DOCE DE LEITE: sabor de fundo de pa...
Note that not always
post.author.usernamewill be the same as the username passed to.profile_posts, since the user could be sharing a post from somebody else.
Instagram Post
post_codes = ("DEhf2uTJUs0", "DF-rojvO4g-", "C2SuqhGv3U0")
for index, post_code in enumerate(post_codes, start=1):
post = client.post(post_code)
snippet = post.text[:35].replace("\n", " ")
print(f"Got specific post {index:02} from @{post.author.username} ({post.published_at}): {snippet}...")
# Result:
# Got specific post 01 from @zuck (2025-01-07 11:57:11+00:00): It's time to get back to our roots...
# Got specific post 02 from @oficialfernandatorres (2025-02-12 16:25:51+00:00): @goodmorningamerica at @abc #fern...
# Got specific post 03 from @crio.cafe (2024-01-19 18:54:50+00:00): Você ama queijo e pão de queijo com...
Download Media
Instead of looking at .url attributes on all Picture and Video objects, you can use the client object to
automatically download all media from posts in parallel, reusing the HTTPS session. The download method will work the
same way for both Instagram and Threads clients and will download all available media for any object of the types
Picture, Video, InstagramProfile, InstagramPost, ThreadsProfile and ThreadsPost:
# NOTES:
# 1- Run the last example before running the code below
# 2- Since the media will be downloaded in parallel (using threads), the filenames ordering (like `xxx_001.jpg`,
# `xxx_002.jpg` etc.) won't be the original order for each post (it's the finish order).
from pathlib import Path
for index, post in enumerate(posts, start=1):
print(f"Downloading media for post {index:02d}")
for media_index, downloaded in enumerate(client.download(post, parallel=4), start=1):
extension = downloaded.content_type.split("/")[-1].lower()
if extension == "jpeg":
extension = "jpg"
filename = Path("data") / f"{post.id}_{media_index:03d}.{extension}"
downloaded.save(filename)
print(f" Saved in: {filename}")
# Result:
# Downloading media for post 01
# Saved in: data/3283892310210737460_21057648761_001.jpg
# Saved in: data/3283892310210737460_21057648761_002.jpg
# Saved in: data/3283892310210737460_21057648761_003.jpg
# Saved in: data/3283892310210737460_21057648761_004.jpg
# Saved in: data/3283892310210737460_21057648761_005.jpg
# Saved in: data/3283892310210737460_21057648761_006.jpg
# Saved in: data/3283892310210737460_21057648761_007.jpg
# Saved in: data/3283892310210737460_21057648761_008.jpg
# Downloading media for post 02
# Saved in: data/3255507578954636322_21057648761_001.mp4
# [...]
Note: if you need to download media to many different objects (profiles, posts etc.), better use
download_manymethod.
Usage via Command-Line Interface
The command-line interface (CLI) comprises many sub-commands, one for each action. They receive parameters and save
files (like CSV) with the results. The examples shown here will only cover some features - run outgram --help or
outgram <subcommand> --help for more details/options.
Threads Profile
Get general information regarding one or more Threads user profiles. Profiles can be passed as usernames or user IDs. User IDs are preferred since finding them based on username requires one more request. Example:
outgram threads profile \
ayubionet diogocortiz wsj 42799100757 6828796459 \
data/threads-profile.csv
The data/threads-profile.csv CSV file will be created with the following columns:
idusernamefull_namebiographyis_verifiedfollowersis_privatebio_linksis_threads_only_userpicture_urlpicture_widthpicture_height
Threads Profile Posts
Get list of posts from one or more Threads user profiles. Profiles can be passed as usernames or user IDs. Different from Threads Profile, in this command usernames are preferred since finding them based on user ID requires one more request. Example:
outgram threads profile-posts \
--max-posts-per-user=10 \
--max-posts=50 \
ayubionet diogocortiz wsj 42799100757 6828796459 \
data/threads-profile-posts.csv
The data/threads-profile-posts.csv CSV file will be created with the following columns:
iduser_idusernameis_verifiedtextlinkspublished_atlikesrepliesrepostsquotesis_privatemediareply_controlmedia_typeaccessibility_captionis_paid_partnershiplike_and_view_counts_disabledhas_audiooriginal_widthoriginal_heightcodereshares
Threads Archive
Get all posts and media metadata for a list of Thredas profiles, export it to CSV, download all the media and save everything into a ZIP file. Profiles can be passed as usernames or user IDs. Example:
outgram threads archive \
wsj ayubionet \
threads-archive.zip
Instagram Profile
Get general information regarding one or more Instagram user profiles. Profiles can be passed as usernames or user IDs. User IDs are preferred since finding them based on username requires one more request. Example:
outgram instagram profile \
crio.cafe pythonbrasil 23456103 \
data/instagram-profile.csv
The data/instagram-profile.csv CSV file will be created with the following columns:
idusernamefull_nameis_verifiedfollowersfolloweespostsis_privatebiographybio_linkspicture_urlpicture_widthpicture_heightpicture_content_type
Instagram Profile Posts
Get list of posts from one or more Instagram user profiles. Profiles can be passed as usernames or user IDs. Different from Instagram Profile, in this command usernames are preferred since finding them based on user ID requires one more request. Example:
outgram instagram profile-posts \
--max-posts-per-user=20 \
--max-posts=50 \
crio.cafe pythonbrasil 23456103 \
data/instagram-profile-posts.csv
The data/instagram-profile-posts.csv CSV file will be created with the following columns:
idpkcodetypepublished_atmediapinnedcommentslikesviewsplaystextaccessibility_captionauthor_idauthor_bio_linksauthor_biographyauthor_followeesauthor_followersauthor_full_nameauthor_is_privateauthor_is_verifiedauthor_picture_urlauthor_postsauthor_usernamethumbnail_urlthumbnail_widththumbnail_heightthumbnail_content_typelocation_idlocation_namelocation_sluglocation_latlocation_lnglocation_has_public_pagelocation_addresslocation_zip_codelocation_citylocation_country_code
Note: not all information will be filled. Check "Data Completion" section for more details.
Instagram Post
Get list of posts from one or more post codes. You can get the post code from the URL: the code for
https://www.instagram.com/p/C2SuqhGv3U0/ is C2SuqhGv3U0. Example:
outgram instagram post \
--max-posts=3 \
DEhf2uTJUs0 DF-rojvO4g- C_gVusByAQH DGLQHNhOoke COWY0ydHUrI DFWFT4LyfSJ \
data/instagram-post.csv
The data/instagram-post.csv CSV file will be created with the columns as listed in the section "Instagram Profile
Posts".
Note: not all information will be filled (and this is different from "Instagram Profile Posts"). Check "Data Completion" section for more details.
Instagram Archive
Get all posts and media metadata for a list of Instagram profiles, export it to CSV, download all the media and save everything into a ZIP file. Profiles can be passed as usernames or user IDs. Example:
outgram instagram archive \
crio.cafe pythonbrasil \
instagram-archive.zip
Data Completion
It's important to note that the API may send you incomplete information depending on how you get that information, for example:
- The
InstagramPostobjects yielded byInstagram.profile_postswon't have full profile information from the author (like follower count), but the objects returned byInstagram.postwill have it. - Some resolutions (of thumbnails, for example) can be different depending on how you got your
*Postobject
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 outgram-0.1.1.tar.gz.
File metadata
- Download URL: outgram-0.1.1.tar.gz
- Upload date:
- Size: 27.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f2f6fc80da0d17156db076d8eeea2309b2011fc15c95687641543092d9fcf6d9
|
|
| MD5 |
ddb6d66f5d00f102a7f53217a0b4d2ab
|
|
| BLAKE2b-256 |
956b4a06ef5f732c07befaa190df054dfe0ceabf34efbc8ca94f8132a52060e7
|
File details
Details for the file outgram-0.1.1-py3-none-any.whl.
File metadata
- Download URL: outgram-0.1.1-py3-none-any.whl
- Upload date:
- Size: 24.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7f4d65a79689f50aed12f80703b94298495a6b26c1a44cbc950255fdbaa0ab87
|
|
| MD5 |
316d8b5da12a59405e51e19bbf32ce61
|
|
| BLAKE2b-256 |
b3e92e9b8bd909ed3ff92b655ba16059902dfade49b0ed1ebb045d9ac90f6174
|