mirror of
https://github.com/instaloader/instaloader.git
synced 2026-10-16 21:52:17 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5204990db9 | ||
|
|
94df221da2 | ||
|
|
e7decba5ee | ||
|
|
a3a09e1952 | ||
|
|
b1e55a41a4 | ||
|
|
b5ad92e236 | ||
|
|
084cfb102c | ||
|
|
2722da6ae4 | ||
|
|
1469064176 | ||
|
|
440c59ecb9 | ||
|
|
c9198d232f | ||
|
|
e689dadf00 | ||
|
|
d90c05e0a4 | ||
|
|
eecaca7c7b | ||
|
|
729aa31d86 | ||
|
|
94df60292e | ||
|
|
09f2eb3601 | ||
|
|
3e904e8a14 | ||
|
|
f16b99bec0 | ||
|
|
d316ddf02a | ||
|
|
efa0b93fda | ||
|
|
ced65ea401 | ||
|
|
73ec884ea4 | ||
|
|
74d5e35eb8 | ||
|
|
8612712f44 | ||
|
|
67ac8f3397 | ||
|
|
5fe2a70374 | ||
|
|
8bd9c4449c |
+6
-1
@@ -3,6 +3,8 @@
|
||||
Documentation of Instaloader module, a powerful and intuitive
|
||||
Python library to download Instagram media and metadata.
|
||||
|
||||
.. _python-module-instaloader:
|
||||
|
||||
Python Module :mod:`instaloader`
|
||||
--------------------------------
|
||||
|
||||
@@ -72,7 +74,10 @@ metadata of a Profile. :class:`Profile` instances can be created with:
|
||||
profile = Profile.from_username(L.context, USERNAME)
|
||||
|
||||
- :meth:`Profile_from_userid`
|
||||
given its User ID (currently requires to be logged in).
|
||||
given its User ID. This allows to easily lookup a Profile's username given
|
||||
its ID::
|
||||
|
||||
Profile.from_id(L.context, USERID).username
|
||||
|
||||
- :meth:`Profile.get_followees`
|
||||
Profiles that are followed by given user.
|
||||
|
||||
+37
-1
@@ -5,6 +5,8 @@
|
||||
and private profiles, hashtags, stories, feeds, saved media, and
|
||||
their metadata, comments and captions.
|
||||
|
||||
.. _download-pictures-from-instagram:
|
||||
|
||||
Download Pictures from Instagram
|
||||
---------------------------------
|
||||
|
||||
@@ -35,7 +37,9 @@ To later **update your local copy** of that profiles, you may run
|
||||
instaloader --fast-update profile [profile ...]
|
||||
|
||||
If :option:`--fast-update` is given, Instaloader stops when arriving at the
|
||||
first already-downloaded picture. When updating profiles, Instaloader
|
||||
first already-downloaded picture.
|
||||
|
||||
When updating profiles, Instaloader
|
||||
automatically **detects profile name changes** and renames the target directory
|
||||
accordingly.
|
||||
|
||||
@@ -90,6 +94,9 @@ downloads the pictures and videos and their captions. You can specify
|
||||
**download geotags** of each post and save them as
|
||||
Google Maps link,
|
||||
|
||||
For a reference of all supported command line options, see
|
||||
:ref:`command-line-options`.
|
||||
|
||||
.. _filename-specification:
|
||||
|
||||
Filename Specification
|
||||
@@ -237,3 +244,32 @@ files. Say, you now also want to export the number of comments the Posts had
|
||||
when they were downloaded::
|
||||
|
||||
instaloader --post-metadata-txt="{likes} likes, {comments} comments." <target>/*.json.xz
|
||||
|
||||
.. _instaloader-as-cronjob:
|
||||
|
||||
Instaloader as Cronjob
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Instaloader is suitable for running as a cronjob to periodically update your
|
||||
personal Instagram archive. The :option:`--quiet` option disables user
|
||||
interactions and logging of non-error messages. To non-interactively use
|
||||
Instaloader logged-in, create a session file, e.g. in your home directory::
|
||||
|
||||
instaloader --login=your_username --sessionfile=~/.instaloader-session
|
||||
|
||||
Then use the same parameters in your cronjob to load the session and download
|
||||
the given targets::
|
||||
|
||||
instaloader --login=your_username --sessionfile=~/.instaloader-session --quiet <target> [...]
|
||||
|
||||
Without :option:`--sessionfile` option, Instaloader saves the session file in
|
||||
a path within your temporary directory.
|
||||
|
||||
Programming Instaloader
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If your task cannot be done with the command line interface of Instaloader,
|
||||
consider taking a look at the :ref:`python-module-instaloader`.
|
||||
Instaloader exposes its internally used methods and structures, making it a
|
||||
powerful and intuitive Python API for Instagram, allowing to further customize
|
||||
obtaining media and metadata.
|
||||
|
||||
+11
-3
@@ -1,3 +1,5 @@
|
||||
.. _command-line-options:
|
||||
|
||||
Command Line Options
|
||||
====================
|
||||
|
||||
@@ -12,7 +14,9 @@ posts marked as saved).
|
||||
|
||||
Here we explain the additional options that can be given to Instaloader to
|
||||
customize its behavior. To get a list of all flags, their abbreviations and
|
||||
their descriptions, you may also run ``instaloader --help``.
|
||||
their descriptions, you may also run ``instaloader --help``. For an
|
||||
introduction on how to use Instaloader, see
|
||||
:ref:`download-pictures-from-instagram`.
|
||||
|
||||
What to Download
|
||||
^^^^^^^^^^^^^^^^
|
||||
@@ -142,6 +146,10 @@ Instaloader to login.
|
||||
Password for your Instagram account. Without this option, you'll be prompted
|
||||
for your password interactively if there is not yet a valid session file.
|
||||
|
||||
.. warning:: Using :option:`--password` option is discouraged for security
|
||||
reasons. Enter your password interactively when asked, or use the
|
||||
sessionfile feature (:option:`--sessionfile` to customize path).
|
||||
|
||||
How to Download
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -177,5 +185,5 @@ Miscellaneous Options
|
||||
.. option:: --quiet, -q
|
||||
|
||||
Disable user interaction, i.e. do not print messages (except errors) and fail
|
||||
if login credentials are needed but not given. This makes Instaloader
|
||||
**suitable as a cron job**.
|
||||
if login credentials are needed but not given.
|
||||
This is handy for running :ref:`instaloader-as-cronjob`.
|
||||
|
||||
@@ -21,6 +21,8 @@ With `Python <https://www.python.org/>`__ installed, do::
|
||||
|
||||
$ instaloader profile [profile ...]
|
||||
|
||||
See :ref:`install` for more options on how to install Instaloader.
|
||||
|
||||
**Instaloader**
|
||||
|
||||
- downloads **public and private profiles, hashtags, user stories,
|
||||
@@ -44,6 +46,8 @@ With `Python <https://www.python.org/>`__ installed, do::
|
||||
profile | "#hashtag" |
|
||||
:stories | :feed | :saved
|
||||
|
||||
See :ref:`download-pictures-from-instagram` for a detailed introduction on how
|
||||
to use Instaloader to download pictures from Instagram.
|
||||
|
||||
Instaloader Documentation
|
||||
-------------------------
|
||||
|
||||
@@ -33,7 +33,7 @@ Install Instaloader
|
||||
- On **Windows 10**, you may download the standalone executable from the
|
||||
`current release page <https://github.com/instaloader/instaloader/releases/latest>`__.
|
||||
|
||||
To get the most current version of Instaloader from our
|
||||
`Git repository <https://github.com/instaloader/instaloader>`__::
|
||||
- To get the most current version of Instaloader from our
|
||||
`Git repository <https://github.com/instaloader/instaloader>`__::
|
||||
|
||||
pip3 install --upgrade git+https://github.com/instaloader/instaloader
|
||||
pip3 install --upgrade git+https://github.com/instaloader/instaloader
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Download pictures (or videos) along with their captions and other metadata from Instagram."""
|
||||
|
||||
|
||||
__version__ = '4.0rc1'
|
||||
__version__ = '4.0'
|
||||
|
||||
|
||||
try:
|
||||
|
||||
@@ -137,7 +137,7 @@ def _main(instaloader: Instaloader, targetlist: List[str],
|
||||
else:
|
||||
profiles.add(target)
|
||||
if len(profiles) > 1:
|
||||
instaloader.context.log("Downloading {} profiles: {}".format(len(profiles), ','.join(profiles)))
|
||||
instaloader.context.log("Downloading {} profiles: {}".format(len(profiles), ' '.join(profiles)))
|
||||
# Iterate through profiles list and download them
|
||||
for target in profiles:
|
||||
with instaloader.context.error_catcher(target):
|
||||
|
||||
@@ -529,11 +529,10 @@ class Instaloader:
|
||||
for post in Profile.from_username(self.context, self.context.username).get_saved_posts():
|
||||
if max_count is not None and count > max_count:
|
||||
break
|
||||
name = post.owner_username
|
||||
if post_filter is not None and not post_filter(post):
|
||||
self.context.log("<pic by {} skipped".format(name), flush=True)
|
||||
self.context.log("<{} skipped>".format(post), flush=True)
|
||||
continue
|
||||
self.context.log("[{:>3}] {} ".format(count, name), end=str(), flush=True)
|
||||
self.context.log("[{:>3}] ".format(count), end=str(), flush=True)
|
||||
count += 1
|
||||
with self.context.error_catcher('Download saved posts'):
|
||||
downloaded = self.download_post(post, target=':saved')
|
||||
@@ -629,10 +628,6 @@ class Instaloader:
|
||||
else:
|
||||
self.context.log("Trying to find profile {0} using its unique ID {1}.".format(profile_name,
|
||||
profile_id))
|
||||
if not self.context.is_logged_in:
|
||||
self.context.error("Profile {} changed its name. "
|
||||
"If you use --login=USERNAME, I can find out the new name.")
|
||||
raise LoginRequiredException("--login=USERNAME required to obtain profile name from its ID number")
|
||||
profile_from_id = Profile.from_id(self.context, profile_id)
|
||||
newname = profile_from_id.username
|
||||
self.context.log("Profile {0} has changed its name to {1}.".format(profile_name, newname))
|
||||
@@ -647,7 +642,7 @@ class Instaloader:
|
||||
'{0}/{1}_id'.format(self.dirname_pattern.format(), newname.lower()))
|
||||
return profile_from_id
|
||||
return profile
|
||||
except FileNotFoundError:
|
||||
except (FileNotFoundError, ValueError):
|
||||
pass
|
||||
if profile_exists:
|
||||
os.makedirs(self.dirname_pattern.format(profile=profile_name.lower(),
|
||||
|
||||
@@ -56,6 +56,7 @@ class InstaloaderContext:
|
||||
self.quiet = quiet
|
||||
self.max_connection_attempts = max_connection_attempts
|
||||
self._graphql_page_length = 50
|
||||
self._root_rhx_gis = None
|
||||
|
||||
# error log, filled with error() and printed at the end of Instaloader.main()
|
||||
self.error_log = []
|
||||
@@ -376,3 +377,14 @@ class InstaloaderContext:
|
||||
except KeyboardInterrupt:
|
||||
self.error("[skipped by user]", repeat_at_end=False)
|
||||
raise ConnectionException(error_string) from err
|
||||
|
||||
@property
|
||||
def root_rhx_gis(self) -> Optional[str]:
|
||||
"""rhx_gis string returned in the / query."""
|
||||
if self.is_logged_in:
|
||||
# At the moment, rhx_gis seems to be required for anonymous requests only. By returning None when logged
|
||||
# in, we can save the root_rhx_gis lookup query.
|
||||
return None
|
||||
if not self._root_rhx_gis:
|
||||
self._root_rhx_gis = self.get_json('', {})['rhx_gis']
|
||||
return self._root_rhx_gis
|
||||
|
||||
+10
-15
@@ -139,20 +139,17 @@ class Post:
|
||||
|
||||
@property
|
||||
def owner_profile(self) -> 'Profile':
|
||||
""":class:`Profile` instance of the Post's owner."""
|
||||
if not self._owner_profile:
|
||||
if 'username' in self._node['owner']:
|
||||
owner_struct = self._node['owner']
|
||||
else:
|
||||
# Sometimes, the 'owner' structure does not contain the username, only the user's ID. In that case,
|
||||
# this call triggers downloading of the complete Post metadata struct, where the owner username
|
||||
# is contained. This is better than to get the username by user ID, since it is possible anonymously
|
||||
# and gives us other information that is more likely to be usable.
|
||||
# is contained.
|
||||
# Note that we cannot use Profile.from_id() here since that would lead us into a recursion.
|
||||
owner_struct = self._full_metadata['owner']
|
||||
if 'username' in owner_struct:
|
||||
self._owner_profile = Profile(self._context, owner_struct)
|
||||
else:
|
||||
# Fallback, if we still did not get the owner username
|
||||
self._owner_profile = Profile.from_id(self._context, owner_struct['id'])
|
||||
self._owner_profile = Profile(self._context, owner_struct)
|
||||
return self._owner_profile
|
||||
|
||||
@property
|
||||
@@ -379,17 +376,16 @@ class Profile:
|
||||
|
||||
@classmethod
|
||||
def from_id(cls, context: InstaloaderContext, profile_id: int):
|
||||
"""If logged in, create a Profile instance from a given userid. If possible, use :meth:`Profile.from_username`
|
||||
or constructor directly rather than this method, since does many requests.
|
||||
"""Create a Profile instance from a given userid. If possible, use :meth:`Profile.from_username`
|
||||
or constructor directly rather than this method, since it does many requests.
|
||||
|
||||
:param context: :attr:`Instaloader.context`
|
||||
:param profile_id: userid
|
||||
:raises: :class:`ProfileNotExistsException`, :class:`LoginRequiredException`, :class:`ProfileHasNoPicsException`
|
||||
:raises: :class:`ProfileNotExistsException`, :class:`ProfileHasNoPicsException`
|
||||
"""
|
||||
if not context.is_logged_in:
|
||||
raise LoginRequiredException("--login=USERNAME required to obtain profile metadata from its ID number.")
|
||||
data = context.graphql_query("472f257a40c653c64c666ce877d59d2b",
|
||||
{'id': str(profile_id), 'first': 1})['data']['user']
|
||||
{'id': str(profile_id), 'first': 1},
|
||||
rhx_gis=context.root_rhx_gis)['data']['user']
|
||||
if data:
|
||||
data = data["edge_owner_to_timeline_media"]
|
||||
else:
|
||||
@@ -400,8 +396,7 @@ class Profile:
|
||||
raise ProfileHasNoPicsException("Profile with ID {0}: no pics found.".format(str(profile_id)))
|
||||
else:
|
||||
raise LoginRequiredException("Login required to determine username (ID: " + str(profile_id) + ").")
|
||||
username = Post.from_mediaid(context, int(data['edges'][0]["node"]["id"])).owner_username
|
||||
return cls(context, {'username': username.lower(), 'id': profile_id})
|
||||
return Post(context, data['edges'][0]['node']).owner_profile
|
||||
|
||||
def _asdict(self):
|
||||
json_node = self._node.copy()
|
||||
|
||||
@@ -54,6 +54,10 @@ class TestInstaloaderAnonymously(unittest.TestCase):
|
||||
self.assertEqual(PUBLIC_PROFILE_ID,
|
||||
instaloader.Profile.from_username(self.L.context, PUBLIC_PROFILE).userid)
|
||||
|
||||
def test_get_username_by_id(self):
|
||||
self.assertEqual(PUBLIC_PROFILE.lower(),
|
||||
instaloader.Profile.from_id(self.L.context, PUBLIC_PROFILE_ID).username)
|
||||
|
||||
def test_post_from_mediaid(self):
|
||||
for post in instaloader.Profile.from_username(self.L.context, PUBLIC_PROFILE).get_posts():
|
||||
post2 = instaloader.Post.from_mediaid(self.L.context, post.mediaid)
|
||||
|
||||
Reference in New Issue
Block a user