mirror of
https://github.com/instaloader/instaloader.git
synced 2024-11-23 02:32:46 +01:00
Update as-module.rst and docstrings
This commit is contained in:
parent
b9ac40899d
commit
2c50972e08
@ -10,6 +10,9 @@ Python Module :mod:`instaloader`
|
||||
|
||||
.. highlight:: python
|
||||
|
||||
.. contents::
|
||||
:backlinks: none
|
||||
|
||||
Instaloader exposes its internally used methods as a Python module, making it a
|
||||
**powerful and easy-to-use Python API for Instagram**, allowing to further
|
||||
customize obtaining media and metadata.
|
||||
@ -42,34 +45,8 @@ Besides :func:`Instaloader.get_hashtag_posts`, there is
|
||||
Also, :class:`Post` instances can be created with :func:`Post.from_shortcode`
|
||||
and :func:`Post.from_mediaid`.
|
||||
|
||||
Further, information about profiles can be easily obtained. For example, you may
|
||||
print a list of all followees and followers of a profile with::
|
||||
|
||||
# Print followees
|
||||
print(PROFILE + " follows these profiles:")
|
||||
for f in L.get_followees(PROFILE):
|
||||
print("\t%s\t%s" % (f['username'], f['full_name']))
|
||||
|
||||
# Print followers
|
||||
print("Followers of " + PROFILE + ":")
|
||||
for f in L.get_followers(PROFILE):
|
||||
print("\t%s\t%s" % (f['username'], f['full_name']))
|
||||
|
||||
Then, you may download all pictures of all followees with::
|
||||
|
||||
for f in L.get_followees(PROFILE):
|
||||
L.download_profile(f['username'])
|
||||
|
||||
Each Instagram profile has its own unique ID which stays unmodified even if a
|
||||
user changes his/her username. To get said ID, given the profile's name, you may
|
||||
call::
|
||||
|
||||
L.get_id_by_username(PROFILE_NAME)
|
||||
|
||||
A reference of the many methods provided by the :mod:`instaloader` module is
|
||||
provided in the remainder of this document. Feel free to direct any issue or
|
||||
contribution to
|
||||
`Instaloader on Github <https://github.com/instaloader/instaloader>`__.
|
||||
provided in the remainder of this document.
|
||||
|
||||
``Instaloader`` (Main Class)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@ -77,24 +54,34 @@ contribution to
|
||||
.. autoclass:: Instaloader
|
||||
:no-show-inheritance:
|
||||
|
||||
``Profile`` Class
|
||||
^^^^^^^^^^^^^^^^^
|
||||
Instagram Structures
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. autoclass:: Profile
|
||||
:no-show-inheritance:
|
||||
.. autofunction:: load_structure_from_file
|
||||
|
||||
``Post`` Class
|
||||
^^^^^^^^^^^^^^
|
||||
.. autofunction:: save_structure_to_file
|
||||
|
||||
``Post``
|
||||
""""""""
|
||||
|
||||
.. autofunction:: mediaid_to_shortcode
|
||||
|
||||
.. autofunction:: shortcode_to_mediaid
|
||||
|
||||
.. autoclass:: Post
|
||||
:no-show-inheritance:
|
||||
|
||||
Miscellaneous Functions
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
``StoryItem``
|
||||
"""""""""""""
|
||||
|
||||
.. autofunction:: shortcode_to_mediaid
|
||||
.. autoclass:: StoryItem
|
||||
:no-show-inheritance:
|
||||
|
||||
.. autofunction:: mediaid_to_shortcode
|
||||
``Profile``
|
||||
"""""""""""
|
||||
|
||||
.. autoclass:: Profile
|
||||
:no-show-inheritance:
|
||||
|
||||
Exceptions
|
||||
^^^^^^^^^^
|
||||
@ -102,6 +89,8 @@ Exceptions
|
||||
.. autoexception:: InstaloaderException
|
||||
:no-show-inheritance:
|
||||
|
||||
.. autoexception:: QueryReturnedBadRequestException
|
||||
|
||||
.. autoexception:: QueryReturnedNotFoundException
|
||||
|
||||
.. autoexception:: QueryReturnedForbiddenException
|
||||
@ -123,3 +112,9 @@ Exceptions
|
||||
.. autoexception:: ConnectionException
|
||||
|
||||
.. autoexception:: TooManyRequestsException
|
||||
|
||||
``InstaloaderContext`` (Low-level functions)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. autoclass:: InstaloaderContext
|
||||
:no-show-inheritance:
|
||||
|
@ -369,13 +369,5 @@ current_release_date = subprocess.check_output(["git", "log", "-1", "--tags", "-
|
||||
html_context = {'current_release': current_release, 'current_release_date': current_release_date}
|
||||
|
||||
|
||||
def skip(app, what, name, obj, skip, options):
|
||||
# Ensure constructors are documented
|
||||
if name == "__init__":
|
||||
return False
|
||||
return skip
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.connect('autodoc-skip-member', skip)
|
||||
app.add_stylesheet("style.css")
|
||||
|
@ -14,5 +14,6 @@ else:
|
||||
|
||||
from .exceptions import *
|
||||
from .instaloader import Instaloader
|
||||
from .instaloadercontext import InstaloaderContext
|
||||
from .structures import (Post, Profile, Story, StoryItem, load_structure_from_file, mediaid_to_shortcode,
|
||||
save_structure_to_file, shortcode_to_mediaid)
|
||||
|
@ -71,6 +71,36 @@ class _PostPathFormatter(_ArbitraryItemFormatter):
|
||||
|
||||
|
||||
class Instaloader:
|
||||
"""Instaloader Class.
|
||||
|
||||
::
|
||||
|
||||
L = Instaloader()
|
||||
|
||||
# Optionally, login or load session
|
||||
L.login(USER, PASSWORD) # (login)
|
||||
L.interactive_login(USER) # (ask password on terminal)
|
||||
L.load_session_from_file(USER) # (load session created w/
|
||||
# `instaloader -l USERNAME`)
|
||||
|
||||
:mod:`instaloader` provides the :class:`Post` structure, which represents a
|
||||
picture, video or sidecar (set of multiple pictures/videos) posted in a user's
|
||||
profile. :class:`Instaloader` provides methods to iterate over Posts from a
|
||||
certain source::
|
||||
|
||||
for post in L.get_hashtag_posts('cat'):
|
||||
# post is an instance of Post
|
||||
L.download_post(post, target='#cat')
|
||||
|
||||
Besides :func:`Instaloader.get_hashtag_posts`, there is
|
||||
:func:`Instaloader.get_feed_posts`, :func:`Profile.get_posts` and
|
||||
:func:`Profile.get_saved_posts`.
|
||||
Also, :class:`Post` instances can be created with :func:`Post.from_shortcode`
|
||||
and :func:`Post.from_mediaid`.
|
||||
|
||||
Also, this class provides methods :meth:`Instaloader.download_profile`,
|
||||
:meth:`Instaloader.download_hashtag` and many more to download targets.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
sleep: bool = True, quiet: bool = False,
|
||||
@ -119,6 +149,7 @@ class Instaloader:
|
||||
new_loader.close()
|
||||
|
||||
def close(self):
|
||||
"""Close associated session objects and repeat error log."""
|
||||
self.context.close()
|
||||
|
||||
def __enter__(self):
|
||||
@ -176,7 +207,7 @@ class Instaloader:
|
||||
self.context.log('comments', end=' ', flush=True)
|
||||
|
||||
def save_caption(self, filename: str, mtime: datetime, caption: str) -> None:
|
||||
"""Updates picture caption"""
|
||||
"""Updates picture caption / Post metadata info"""
|
||||
filename += '.txt'
|
||||
caption += '\n'
|
||||
pcaption = caption.replace('\n', ' ').strip()
|
||||
@ -214,6 +245,7 @@ class Instaloader:
|
||||
os.utime(filename, (datetime.now().timestamp(), mtime.timestamp()))
|
||||
|
||||
def save_location(self, filename: str, location_json: Dict[str, str], mtime: datetime) -> None:
|
||||
"""Save post location name and Google Maps link."""
|
||||
filename += '_location.txt'
|
||||
location_string = (location_json["name"] + "\n" +
|
||||
"https://maps.google.com/maps?q={0},{1}&ll={0},{1}\n".format(location_json["lat"],
|
||||
@ -250,7 +282,10 @@ class Instaloader:
|
||||
|
||||
@_requires_login
|
||||
def save_session_to_file(self, filename: Optional[str] = None) -> None:
|
||||
"""Saves internally stored :class:`requests.Session` object."""
|
||||
"""Saves internally stored :class:`requests.Session` object.
|
||||
|
||||
:param filename: Filename, or None to use default filename.
|
||||
"""
|
||||
if filename is None:
|
||||
filename = get_default_session_filename(self.context.username)
|
||||
dirname = os.path.dirname(filename)
|
||||
@ -437,7 +472,10 @@ class Instaloader:
|
||||
|
||||
@_requires_login
|
||||
def get_feed_posts(self) -> Iterator[Post]:
|
||||
"""Get Posts of the user's feed."""
|
||||
"""Get Posts of the user's feed.
|
||||
|
||||
:return: Iterator over Posts of the user's feed.
|
||||
"""
|
||||
|
||||
data = self.context.graphql_query("d6f4427fbe92d846298cf93df0b937d3", {})["data"]
|
||||
|
||||
@ -514,7 +552,10 @@ class Instaloader:
|
||||
|
||||
@_requires_login
|
||||
def get_explore_posts(self) -> Iterator[Post]:
|
||||
"""Get Posts which are worthy of exploring suggested by Instagram."""
|
||||
"""Get Posts which are worthy of exploring suggested by Instagram.
|
||||
|
||||
:return: Iterator over Posts of the user's suggested posts.
|
||||
"""
|
||||
data = self.context.get_json('explore/', {})
|
||||
yield from (Post(self.context, node)
|
||||
for node in self.context.graphql_node_list("df0dcc250c2b18d9fd27c5581ef33c7c",
|
||||
|
@ -139,9 +139,11 @@ class InstaloaderContext:
|
||||
return session
|
||||
|
||||
def save_session_to_file(self, sessionfile):
|
||||
"""Not meant to be used directly, use :meth:`Instaloader.save_session_to_file`."""
|
||||
pickle.dump(requests.utils.dict_from_cookiejar(self._session.cookies), sessionfile)
|
||||
|
||||
def load_session_from_file(self, username, sessionfile):
|
||||
"""Not meant to be used directly, use :meth:`Instaloader.load_session_to_file`."""
|
||||
session = requests.Session()
|
||||
session.cookies = requests.utils.cookiejar_from_dict(pickle.load(sessionfile))
|
||||
session.headers.update(self._default_http_header())
|
||||
@ -150,10 +152,12 @@ class InstaloaderContext:
|
||||
self.username = username
|
||||
|
||||
def test_login(self) -> Optional[str]:
|
||||
"""Not meant to be used directly, use :meth:`Instaloader.test_login`."""
|
||||
data = self.graphql_query("d6f4427fbe92d846298cf93df0b937d3", {})
|
||||
return data["data"]["user"]["username"] if data["data"]["user"] is not None else None
|
||||
|
||||
def login(self, user, passwd):
|
||||
"""Not meant to be used directly, use :meth:`Instaloader.login`."""
|
||||
session = requests.Session()
|
||||
session.cookies.update({'sessionid': '', 'mid': '', 'ig_pr': '1',
|
||||
'ig_vw': '1920', 'csrftoken': '',
|
||||
|
@ -28,23 +28,26 @@ class Post:
|
||||
Structure containing information about an Instagram post.
|
||||
|
||||
Created by methods :meth:`Profile.get_posts`, :meth:`Instaloader.get_hashtag_posts`,
|
||||
:meth:`Instaloader.get_feed_posts` and :meth:`Profile.get_saved_posts`.
|
||||
:meth:`Instaloader.get_feed_posts` and :meth:`Profile.get_saved_posts`, which return iterators of Posts::
|
||||
|
||||
L = Instaloader()
|
||||
for post in L.get_hashtag_posts(HASHTAG):
|
||||
L.download_post(post, target='#'+HASHTAG)
|
||||
|
||||
Might also be created with::
|
||||
|
||||
post = Post.from_shortcode(L.context, SHORTCODE)
|
||||
|
||||
This class unifies access to the properties associated with a post. It implements == and is
|
||||
hashable.
|
||||
|
||||
The properties defined here are accessible by the filter expressions specified with the :option:`--only-if`
|
||||
parameter and exported into JSON files with :option:`--metadata-json`.
|
||||
:param context: :attr:`Instaloader.context` used for additional queries if neccessary..
|
||||
:param node: Node structure, as returned by Instagram.
|
||||
:param owner_profile: The Profile of the owner, if already known at creation.
|
||||
"""
|
||||
|
||||
def __init__(self, context: InstaloaderContext, node: Dict[str, Any],
|
||||
owner_profile: Optional['Profile'] = None):
|
||||
"""Create a Post instance from a node structure as returned by Instagram.
|
||||
|
||||
:param context: :class:`InstaloaderContext` instance used for additional queries if neccessary.
|
||||
:param node: Node structure, as returned by Instagram.
|
||||
:param owner_profile: The Profile of the owner, if already known at creation.
|
||||
"""
|
||||
|
||||
assert 'shortcode' in node or 'code' in node
|
||||
|
||||
self._context = context
|
||||
@ -296,7 +299,7 @@ class Post:
|
||||
self._rhx_gis)
|
||||
|
||||
def get_location(self) -> Optional[Dict[str, str]]:
|
||||
"""If the Post has a location, returns a dictionary with fields 'lat' and 'lng'."""
|
||||
"""If the Post has a location, returns a dictionary with fields 'lat' and 'lng' and 'name'."""
|
||||
loc_dict = self._field("location")
|
||||
if loc_dict is not None:
|
||||
location_json = self._context.get_json("explore/locations/{0}/".format(loc_dict["id"]),
|
||||
@ -311,7 +314,25 @@ class Profile:
|
||||
Provides methods for accessing profile properties, as well as :meth:`Profile.get_posts` and for own profile
|
||||
:meth:`Profile.get_saved_posts`.
|
||||
|
||||
This class implements == and is hashable.
|
||||
Get instances with :meth:`Post.owner_profile`, :meth:`StoryItem.owner_profile`, :meth:`Profile.get_followees`,
|
||||
:meth:`Profile.get_followers` or::
|
||||
|
||||
L = Instaloader()
|
||||
profile = Profile.from_username(L.context, USERNAME)
|
||||
|
||||
Provides :meth:`Profile.get_posts` and for own profile :meth:`Profile.get_saved_posts` to iterate over associated
|
||||
:class:`Post` objects::
|
||||
|
||||
for post in profile.get_posts():
|
||||
L.download_post(post, target=profile.username)
|
||||
|
||||
:meth:`Profile.get_followees` and :meth:`Profile.get_followers`::
|
||||
|
||||
print("{} follows these profiles:".format(profile.username))
|
||||
for followee in profile.get_followees():
|
||||
print(followee.username)
|
||||
|
||||
Also, this class implements == and is hashable.
|
||||
"""
|
||||
def __init__(self, context: InstaloaderContext, node: Dict[str, Any]):
|
||||
assert 'username' in node
|
||||
@ -321,6 +342,12 @@ class Profile:
|
||||
|
||||
@classmethod
|
||||
def from_username(cls, context: InstaloaderContext, username: str):
|
||||
"""Create a Profile instance from a given username, raise exception if it does not exist.
|
||||
|
||||
:param context: :attr:`Instaloader.context`
|
||||
:param username: Username
|
||||
:raises: :class:`ProfileNotExistsException`
|
||||
"""
|
||||
# pylint:disable=protected-access
|
||||
profile = cls(context, {'username': username.lower()})
|
||||
profile._obtain_metadata() # to raise ProfileNotExistException now in case username is invalid
|
||||
@ -328,6 +355,13 @@ 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.
|
||||
|
||||
:param context: :attr:`Instaloader.context`
|
||||
:param profile_id: userid
|
||||
:raises: :class:`ProfileNotExistsException`, :class:`LoginRequiredException`, :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",
|
||||
@ -377,10 +411,12 @@ class Profile:
|
||||
|
||||
@property
|
||||
def userid(self) -> int:
|
||||
"""User ID"""
|
||||
return int(self._metadata('id'))
|
||||
|
||||
@property
|
||||
def username(self) -> str:
|
||||
"""Profile Name"""
|
||||
return self._metadata('username').lower()
|
||||
|
||||
def __repr__(self):
|
||||
@ -478,8 +514,7 @@ class Profile:
|
||||
def get_followers(self) -> Iterator['Profile']:
|
||||
"""
|
||||
Retrieve list of followers of given profile.
|
||||
To use this, one needs to be logged in and private profiles has to be followed,
|
||||
otherwise this returns an empty list.
|
||||
To use this, one needs to be logged in and private profiles has to be followed.
|
||||
"""
|
||||
if not self._context.is_logged_in:
|
||||
raise LoginRequiredException("--login required to get a profile's followers.")
|
||||
@ -494,8 +529,7 @@ class Profile:
|
||||
def get_followees(self) -> Iterator['Profile']:
|
||||
"""
|
||||
Retrieve list of followees (followings) of given profile.
|
||||
To use this, one needs to be logged in and private profiles has to be followed,
|
||||
otherwise this returns an empty list.
|
||||
To use this, one needs to be logged in and private profiles has to be followed.
|
||||
"""
|
||||
if not self._context.is_logged_in:
|
||||
raise LoginRequiredException("--login required to get a profile's followees.")
|
||||
@ -712,7 +746,16 @@ class Story:
|
||||
JsonExportable = Union[Post, Profile, StoryItem]
|
||||
|
||||
|
||||
def save_structure_to_file(structure: JsonExportable, filename: str):
|
||||
def save_structure_to_file(structure: JsonExportable, filename: str) -> None:
|
||||
"""Saves a :class:`Post`, :class:`Profile` or :class:`StoryItem` to a '.json' or '.json.xz' file such that it can
|
||||
later be loaded by :func:`load_structure_from_file`.
|
||||
|
||||
If the specified filename ends in '.xz', the file will be LZMA compressed. Otherwise, a pretty-printed JSON file
|
||||
will be created.
|
||||
|
||||
:param structure: :class:`Post`, :class:`Profile` or :class:`StoryItem`
|
||||
:param filename: Filename, ends in '.json' or '.json.xz'
|
||||
"""
|
||||
json_structure = {'node': structure.get_node(),
|
||||
'instaloader': {'version': __version__, 'node_type': structure.__class__.__name__}}
|
||||
compress = filename.endswith('.xz')
|
||||
@ -725,6 +768,12 @@ def save_structure_to_file(structure: JsonExportable, filename: str):
|
||||
|
||||
|
||||
def load_structure_from_file(context: InstaloaderContext, filename: str) -> JsonExportable:
|
||||
"""Loads a :class:`Post`, :class:`Profile` or :class:`StoryItem` from a '.json' or '.json.xz' file that
|
||||
has been saved by :func:`save_structure_from_file`.
|
||||
|
||||
:param context: :attr:`Instaloader.context` linked to the new object, used for additional queries if neccessary.
|
||||
:param filename: Filename, ends in '.json' or '.json.xz'
|
||||
"""
|
||||
compressed = filename.endswith('.xz')
|
||||
if compressed:
|
||||
fp = lzma.open(filename, 'rt')
|
||||
|
Loading…
Reference in New Issue
Block a user