mirror of
https://github.com/instaloader/instaloader.git
synced 2026-10-19 23:22:16 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e268f9e625 | ||
|
|
793b7e82ba | ||
|
|
7329ee10d8 | ||
|
|
57d2ce9538 | ||
|
|
c56657855d | ||
|
|
8c7561c5d7 | ||
|
|
9c2e7c70ac | ||
|
|
ffbf5b517c | ||
|
|
0843e10187 | ||
|
|
78a93c945e | ||
|
|
bb3e309195 | ||
|
|
6cc2ed54ea | ||
|
|
41cf260fdd | ||
|
|
f910705c51 |
+3
-6
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
.. badges-start
|
.. badges-start
|
||||||
|
|
||||||
|travis| |pypi| |pyversion| |license| |aur| |contributors| |downloads| |saythanks|
|
|travis| |pypi| |pyversion| |license| |aur| |contributors| |downloads|
|
||||||
|
|
||||||
.. |travis| image:: https://img.shields.io/travis/instaloader/instaloader/master.svg
|
.. |travis| image:: https://img.shields.io/travis/instaloader/instaloader/master.svg
|
||||||
:alt: Travis-CI Build Status
|
:alt: Travis-CI Build Status
|
||||||
@@ -23,10 +23,6 @@
|
|||||||
:alt: Contributor Count
|
:alt: Contributor Count
|
||||||
:target: https://github.com/instaloader/instaloader/graphs/contributors
|
:target: https://github.com/instaloader/instaloader/graphs/contributors
|
||||||
|
|
||||||
.. |saythanks| image:: https://img.shields.io/badge/Say%20Thanks-!-ff7efd.svg
|
|
||||||
:alt: Say Thanks!
|
|
||||||
:target: https://saythanks.io/to/aandergr
|
|
||||||
|
|
||||||
.. |aur| image:: https://img.shields.io/aur/version/instaloader.svg
|
.. |aur| image:: https://img.shields.io/aur/version/instaloader.svg
|
||||||
:alt: Arch User Repository Package
|
:alt: Arch User Repository Package
|
||||||
:target: https://aur.archlinux.org/packages/instaloader/
|
:target: https://aur.archlinux.org/packages/instaloader/
|
||||||
@@ -57,7 +53,8 @@
|
|||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
instaloader [--comments] [--geotags] [--stories] [--highlights] [--tagged]
|
instaloader [--comments] [--geotags]
|
||||||
|
[--stories] [--highlights] [--tagged] [--igtv]
|
||||||
[--login YOUR-USERNAME] [--fast-update]
|
[--login YOUR-USERNAME] [--fast-update]
|
||||||
profile | "#hashtag" | :stories | :feed | :saved
|
profile | "#hashtag" | :stories | :feed | :saved
|
||||||
|
|
||||||
|
|||||||
Vendored
-3
@@ -51,9 +51,6 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a target="_blank" class="nav-link" href="https://github.com/instaloader/instaloader">GitHub</a>
|
<a target="_blank" class="nav-link" href="https://github.com/instaloader/instaloader">GitHub</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item d-sm-none d-lg-block">
|
|
||||||
<a target="_blank" class="nav-link" href="https://saythanks.io/to/aandergr">Say Thanks!</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
<form class="form-inline" action="search.html" method="get">
|
<form class="form-inline" action="search.html" method="get">
|
||||||
<div class="input-group my-2 my-sm-0 ml-2">
|
<div class="input-group my-2 my-sm-0 ml-2">
|
||||||
|
|||||||
@@ -77,10 +77,13 @@ Instaloader supports the following targets:
|
|||||||
instructs Instaloader to also **download the user's stories**,
|
instructs Instaloader to also **download the user's stories**,
|
||||||
|
|
||||||
- :option:`--highlights`
|
- :option:`--highlights`
|
||||||
to **download highlights of each profile that is downloaded**, and
|
to **download highlights of each profile that is downloaded**,
|
||||||
|
|
||||||
- :option:`--tagged`
|
- :option:`--tagged`
|
||||||
to **download posts where the user is tagged**.
|
to **download posts where the user is tagged**, and
|
||||||
|
|
||||||
|
- :option:`--igtv`
|
||||||
|
to **download IGTV videos**.
|
||||||
|
|
||||||
- ``"#hashtag"``
|
- ``"#hashtag"``
|
||||||
Posts with a certain **hashtag** (the quotes are usually necessary),
|
Posts with a certain **hashtag** (the quotes are usually necessary),
|
||||||
|
|||||||
@@ -129,6 +129,12 @@ What to Download of each Profile
|
|||||||
|
|
||||||
.. versionadded:: 4.1
|
.. versionadded:: 4.1
|
||||||
|
|
||||||
|
.. option:: --igtv
|
||||||
|
|
||||||
|
Also download IGTV videos.
|
||||||
|
|
||||||
|
.. versionadded:: 4.3
|
||||||
|
|
||||||
.. option:: --stories-only
|
.. option:: --stories-only
|
||||||
|
|
||||||
.. deprecated:: 4.1
|
.. deprecated:: 4.1
|
||||||
|
|||||||
+2
-1
@@ -45,7 +45,8 @@ See :ref:`install` for more options on how to install Instaloader.
|
|||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
instaloader [--comments] [--geotags] [--stories] [--highlights] [--tagged]
|
instaloader [--comments] [--geotags]
|
||||||
|
[--stories] [--highlights] [--tagged] [--igtv]
|
||||||
[--login YOUR-USERNAME] [--fast-update]
|
[--login YOUR-USERNAME] [--fast-update]
|
||||||
profile | "#hashtag" | %location_id |
|
profile | "#hashtag" | %location_id |
|
||||||
:stories | :feed | :saved
|
:stories | :feed | :saved
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Download pictures (or videos) along with their captions and other metadata from Instagram."""
|
"""Download pictures (or videos) along with their captions and other metadata from Instagram."""
|
||||||
|
|
||||||
|
|
||||||
__version__ = '4.3a2'
|
__version__ = '4.3.2'
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
+16
-7
@@ -19,7 +19,8 @@ def usage_string():
|
|||||||
argv0 = os.path.basename(sys.argv[0])
|
argv0 = os.path.basename(sys.argv[0])
|
||||||
argv0 = "instaloader" if argv0 == "__main__.py" else argv0
|
argv0 = "instaloader" if argv0 == "__main__.py" else argv0
|
||||||
return """
|
return """
|
||||||
{0} [--comments] [--geotags] [--stories] [--highlights] [--tagged]
|
{0} [--comments] [--geotags]
|
||||||
|
{2:{1}} [--stories] [--highlights] [--tagged] [--igtv]
|
||||||
{2:{1}} [--login YOUR-USERNAME] [--fast-update]
|
{2:{1}} [--login YOUR-USERNAME] [--fast-update]
|
||||||
{2:{1}} profile | "#hashtag" | %%location_id | :stories | :feed | :saved
|
{2:{1}} profile | "#hashtag" | %%location_id | :stories | :feed | :saved
|
||||||
{0} --help""".format(argv0, len(argv0), '')
|
{0} --help""".format(argv0, len(argv0), '')
|
||||||
@@ -61,7 +62,10 @@ def _main(instaloader: Instaloader, targetlist: List[str],
|
|||||||
username: Optional[str] = None, password: Optional[str] = None,
|
username: Optional[str] = None, password: Optional[str] = None,
|
||||||
sessionfile: Optional[str] = None,
|
sessionfile: Optional[str] = None,
|
||||||
download_profile_pic: bool = True, download_posts=True,
|
download_profile_pic: bool = True, download_posts=True,
|
||||||
download_stories: bool = False, download_highlights: bool = False, download_tagged: bool = False,
|
download_stories: bool = False,
|
||||||
|
download_highlights: bool = False,
|
||||||
|
download_tagged: bool = False,
|
||||||
|
download_igtv: bool = False,
|
||||||
fast_update: bool = False,
|
fast_update: bool = False,
|
||||||
max_count: Optional[int] = None, post_filter_str: Optional[str] = None,
|
max_count: Optional[int] = None, post_filter_str: Optional[str] = None,
|
||||||
storyitem_filter_str: Optional[str] = None) -> None:
|
storyitem_filter_str: Optional[str] = None) -> None:
|
||||||
@@ -158,7 +162,8 @@ def _main(instaloader: Instaloader, targetlist: List[str],
|
|||||||
try:
|
try:
|
||||||
profile = instaloader.check_profile_id(target)
|
profile = instaloader.check_profile_id(target)
|
||||||
if instaloader.context.is_logged_in and profile.has_blocked_viewer:
|
if instaloader.context.is_logged_in and profile.has_blocked_viewer:
|
||||||
if download_profile_pic or ((download_posts or download_tagged) and not profile.is_private):
|
if download_profile_pic or ((download_posts or download_tagged or download_igtv)
|
||||||
|
and not profile.is_private):
|
||||||
raise ProfileNotExistsException("{} blocked you; But we download her anonymously."
|
raise ProfileNotExistsException("{} blocked you; But we download her anonymously."
|
||||||
.format(target))
|
.format(target))
|
||||||
else:
|
else:
|
||||||
@@ -169,7 +174,7 @@ def _main(instaloader: Instaloader, targetlist: List[str],
|
|||||||
# Not only our profile.has_blocked_viewer condition raises ProfileNotExistsException,
|
# Not only our profile.has_blocked_viewer condition raises ProfileNotExistsException,
|
||||||
# check_profile_id() also does, since access to blocked profile may be responded with 404.
|
# check_profile_id() also does, since access to blocked profile may be responded with 404.
|
||||||
if instaloader.context.is_logged_in and (download_profile_pic or download_posts or
|
if instaloader.context.is_logged_in and (download_profile_pic or download_posts or
|
||||||
download_tagged):
|
download_tagged or download_igtv):
|
||||||
instaloader.context.log(err)
|
instaloader.context.log(err)
|
||||||
instaloader.context.log("Trying again anonymously, helps in case you are just blocked.")
|
instaloader.context.log("Trying again anonymously, helps in case you are just blocked.")
|
||||||
with instaloader.anonymous_copy() as anonymous_loader:
|
with instaloader.anonymous_copy() as anonymous_loader:
|
||||||
@@ -185,14 +190,15 @@ def _main(instaloader: Instaloader, targetlist: List[str],
|
|||||||
if profiles and download_profile_pic and not instaloader.context.is_logged_in:
|
if profiles and download_profile_pic and not instaloader.context.is_logged_in:
|
||||||
instaloader.context.error("Warning: Use --login to download HD version of profile pictures.")
|
instaloader.context.error("Warning: Use --login to download HD version of profile pictures.")
|
||||||
instaloader.download_profiles(profiles,
|
instaloader.download_profiles(profiles,
|
||||||
download_profile_pic, download_posts, download_tagged, download_highlights,
|
download_profile_pic, download_posts, download_tagged, download_igtv,
|
||||||
download_stories, fast_update, post_filter, storyitem_filter)
|
download_highlights, download_stories,
|
||||||
|
fast_update, post_filter, storyitem_filter)
|
||||||
if anonymous_retry_profiles:
|
if anonymous_retry_profiles:
|
||||||
instaloader.context.log("Downloading anonymously: {}"
|
instaloader.context.log("Downloading anonymously: {}"
|
||||||
.format(' '.join([p.username for p in anonymous_retry_profiles])))
|
.format(' '.join([p.username for p in anonymous_retry_profiles])))
|
||||||
with instaloader.anonymous_copy() as anonymous_loader:
|
with instaloader.anonymous_copy() as anonymous_loader:
|
||||||
anonymous_loader.download_profiles(anonymous_retry_profiles,
|
anonymous_loader.download_profiles(anonymous_retry_profiles,
|
||||||
download_profile_pic, download_posts, download_tagged,
|
download_profile_pic, download_posts, download_tagged, download_igtv,
|
||||||
fast_update=fast_update, post_filter=post_filter)
|
fast_update=fast_update, post_filter=post_filter)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\nInterrupted by user.", file=sys.stderr)
|
print("\nInterrupted by user.", file=sys.stderr)
|
||||||
@@ -287,6 +293,8 @@ def main():
|
|||||||
help='Also download highlights of each profile that is downloaded. Requires --login.')
|
help='Also download highlights of each profile that is downloaded. Requires --login.')
|
||||||
g_prof.add_argument('--tagged', action='store_true',
|
g_prof.add_argument('--tagged', action='store_true',
|
||||||
help='Also download posts where each profile is tagged.')
|
help='Also download posts where each profile is tagged.')
|
||||||
|
g_prof.add_argument('--igtv', action='store_true',
|
||||||
|
help='Also download IGTV videos.')
|
||||||
|
|
||||||
g_cond = parser.add_argument_group("Which Posts to Download")
|
g_cond = parser.add_argument_group("Which Posts to Download")
|
||||||
|
|
||||||
@@ -411,6 +419,7 @@ def main():
|
|||||||
download_stories=download_stories,
|
download_stories=download_stories,
|
||||||
download_highlights=args.highlights,
|
download_highlights=args.highlights,
|
||||||
download_tagged=args.tagged,
|
download_tagged=args.tagged,
|
||||||
|
download_igtv=args.igtv,
|
||||||
fast_update=args.fast_update,
|
fast_update=args.fast_update,
|
||||||
max_count=int(args.count) if args.count is not None else None,
|
max_count=int(args.count) if args.count is not None else None,
|
||||||
post_filter_str=args.post_filter,
|
post_filter_str=args.post_filter,
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ class Instaloader:
|
|||||||
:param storyitem_metadata_txt_pattern: :option:`--storyitem-metadata-txt`, default is empty (=none)
|
:param storyitem_metadata_txt_pattern: :option:`--storyitem-metadata-txt`, default is empty (=none)
|
||||||
:param max_connection_attempts: :option:`--max-connection-attempts`
|
:param max_connection_attempts: :option:`--max-connection-attempts`
|
||||||
:param commit_mode: :option:`--commit-mode`
|
:param commit_mode: :option:`--commit-mode`
|
||||||
:param request_timeout: "option:`--request-timeout`, set per-request timeout (seconds)
|
:param request_timeout: :option:`--request-timeout`, set per-request timeout (seconds)
|
||||||
|
|
||||||
.. attribute:: context
|
.. attribute:: context
|
||||||
|
|
||||||
@@ -703,8 +703,10 @@ class Instaloader:
|
|||||||
|
|
||||||
while True:
|
while True:
|
||||||
feed = data["user"]["edge_web_feed_timeline"]
|
feed = data["user"]["edge_web_feed_timeline"]
|
||||||
yield from (Post(self.context, edge["node"]) for edge in feed["edges"]
|
for edge in feed["edges"]:
|
||||||
if not edge["node"]["__typename"] == "GraphSuggestedUserFeedUnit")
|
node = edge["node"]
|
||||||
|
if node.get("__typename") in Post.supported_graphql_types() and node.get("shortcode") is not None:
|
||||||
|
yield Post(self.context, node)
|
||||||
if not feed["page_info"]["has_next_page"]:
|
if not feed["page_info"]["has_next_page"]:
|
||||||
break
|
break
|
||||||
data = self.context.graphql_query("d6f4427fbe92d846298cf93df0b937d3",
|
data = self.context.graphql_query("d6f4427fbe92d846298cf93df0b937d3",
|
||||||
@@ -911,11 +913,29 @@ class Instaloader:
|
|||||||
count += 1
|
count += 1
|
||||||
if post_filter is not None and not post_filter(post):
|
if post_filter is not None and not post_filter(post):
|
||||||
self.context.log('<{} skipped>'.format(post))
|
self.context.log('<{} skipped>'.format(post))
|
||||||
|
continue
|
||||||
with self.context.error_catcher('Download tagged {}'.format(profile.username)):
|
with self.context.error_catcher('Download tagged {}'.format(profile.username)):
|
||||||
downloaded = self.download_post(post, target if target else Path(profile.username) / Path(':tagged'))
|
downloaded = self.download_post(post, target if target else Path(profile.username) / Path(':tagged'))
|
||||||
if fast_update and not downloaded:
|
if fast_update and not downloaded:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
def download_igtv(self, profile: Profile, fast_update: bool = False,
|
||||||
|
post_filter: Optional[Callable[[Post], bool]] = None) -> None:
|
||||||
|
"""Download IGTV videos of a profile.
|
||||||
|
|
||||||
|
.. versionadded:: 4.3"""
|
||||||
|
self.context.log("Retrieving IGTV videos for profile {}.".format(profile.username))
|
||||||
|
for number, post in enumerate(profile.get_igtv_posts()):
|
||||||
|
self.context.log("[{0:{w}d}/{1:{w}d}] ".format(number+1, profile.igtvcount, w=len(str(profile.igtvcount))),
|
||||||
|
end="", flush=True)
|
||||||
|
if post_filter is not None and not post_filter(post):
|
||||||
|
self.context.log('<{} skipped>'.format(post))
|
||||||
|
continue
|
||||||
|
with self.context.error_catcher('Download IGTV {}'.format(post.shortcode)):
|
||||||
|
downloaded = self.download_post(post, target=profile.username)
|
||||||
|
if fast_update and not downloaded:
|
||||||
|
break
|
||||||
|
|
||||||
def _get_id_filename(self, profile_name: str) -> str:
|
def _get_id_filename(self, profile_name: str) -> str:
|
||||||
if ((format_string_contains_key(self.dirname_pattern, 'profile') or
|
if ((format_string_contains_key(self.dirname_pattern, 'profile') or
|
||||||
format_string_contains_key(self.dirname_pattern, 'target'))):
|
format_string_contains_key(self.dirname_pattern, 'target'))):
|
||||||
@@ -988,7 +1008,10 @@ class Instaloader:
|
|||||||
|
|
||||||
def download_profiles(self, profiles: Set[Profile],
|
def download_profiles(self, profiles: Set[Profile],
|
||||||
profile_pic: bool = True, posts: bool = True,
|
profile_pic: bool = True, posts: bool = True,
|
||||||
tagged: bool = False, highlights: bool = False, stories: bool = False,
|
tagged: bool = False,
|
||||||
|
igtv: bool = False,
|
||||||
|
highlights: bool = False,
|
||||||
|
stories: bool = False,
|
||||||
fast_update: bool = False,
|
fast_update: bool = False,
|
||||||
post_filter: Optional[Callable[[Post], bool]] = None,
|
post_filter: Optional[Callable[[Post], bool]] = None,
|
||||||
storyitem_filter: Optional[Callable[[Post], bool]] = None,
|
storyitem_filter: Optional[Callable[[Post], bool]] = None,
|
||||||
@@ -999,6 +1022,7 @@ class Instaloader:
|
|||||||
:param profile_pic: not :option:`--no-profile-pic`.
|
:param profile_pic: not :option:`--no-profile-pic`.
|
||||||
:param posts: not :option:`--no-posts`.
|
:param posts: not :option:`--no-posts`.
|
||||||
:param tagged: :option:`--tagged`.
|
:param tagged: :option:`--tagged`.
|
||||||
|
:param igtv: :option:`--igtv`.
|
||||||
:param highlights: :option:`--highlights`.
|
:param highlights: :option:`--highlights`.
|
||||||
:param stories: :option:`--stories`.
|
:param stories: :option:`--stories`.
|
||||||
:param fast_update: :option:`--fast-update`.
|
:param fast_update: :option:`--fast-update`.
|
||||||
@@ -1008,7 +1032,11 @@ class Instaloader:
|
|||||||
Whether :exc:`LoginRequiredException` and :exc:`PrivateProfileNotFollowedException` should be raised or
|
Whether :exc:`LoginRequiredException` and :exc:`PrivateProfileNotFollowedException` should be raised or
|
||||||
catched and printed with :meth:`InstaloaderContext.error_catcher`.
|
catched and printed with :meth:`InstaloaderContext.error_catcher`.
|
||||||
|
|
||||||
.. versionadded:: 4.1"""
|
.. versionadded:: 4.1
|
||||||
|
|
||||||
|
.. versionchanged:: 4.3
|
||||||
|
Add `igtv` parameter.
|
||||||
|
"""
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def _error_raiser(_str):
|
def _error_raiser(_str):
|
||||||
@@ -1034,7 +1062,7 @@ class Instaloader:
|
|||||||
self.save_metadata_json(json_filename, profile)
|
self.save_metadata_json(json_filename, profile)
|
||||||
|
|
||||||
# Catch some errors
|
# Catch some errors
|
||||||
if profile.is_private and (tagged or highlights or posts):
|
if profile.is_private and (tagged or igtv or highlights or posts):
|
||||||
if not self.context.is_logged_in:
|
if not self.context.is_logged_in:
|
||||||
raise LoginRequiredException("--login=USERNAME required.")
|
raise LoginRequiredException("--login=USERNAME required.")
|
||||||
if not profile.followed_by_viewer and self.context.username != profile.username:
|
if not profile.followed_by_viewer and self.context.username != profile.username:
|
||||||
@@ -1045,6 +1073,11 @@ class Instaloader:
|
|||||||
with self.context.error_catcher('Download tagged of {}'.format(profile_name)):
|
with self.context.error_catcher('Download tagged of {}'.format(profile_name)):
|
||||||
self.download_tagged(profile, fast_update=fast_update, post_filter=post_filter)
|
self.download_tagged(profile, fast_update=fast_update, post_filter=post_filter)
|
||||||
|
|
||||||
|
# Download IGTV, if requested
|
||||||
|
if igtv:
|
||||||
|
with self.context.error_catcher('Download IGTV of {}'.format(profile_name)):
|
||||||
|
self.download_igtv(profile, fast_update=fast_update, post_filter=post_filter)
|
||||||
|
|
||||||
# Download highlights, if requested
|
# Download highlights, if requested
|
||||||
if highlights:
|
if highlights:
|
||||||
with self.context.error_catcher('Download highlights of {}'.format(profile_name)):
|
with self.context.error_catcher('Download highlights of {}'.format(profile_name)):
|
||||||
|
|||||||
@@ -239,6 +239,12 @@ class InstaloaderContext:
|
|||||||
resp_json['message']))
|
resp_json['message']))
|
||||||
else:
|
else:
|
||||||
raise ConnectionException("Login error: \"{}\" status.".format(resp_json['status']))
|
raise ConnectionException("Login error: \"{}\" status.".format(resp_json['status']))
|
||||||
|
if 'authenticated' not in resp_json:
|
||||||
|
# Issue #472
|
||||||
|
if 'message' in resp_json:
|
||||||
|
raise ConnectionException("Login error: Unexpected response, \"{}\".".format(resp_json['message']))
|
||||||
|
else:
|
||||||
|
raise ConnectionException("Login error: Unexpected response, this might indicate a blocked IP.")
|
||||||
if not resp_json['authenticated']:
|
if not resp_json['authenticated']:
|
||||||
if resp_json['user']:
|
if resp_json['user']:
|
||||||
# '{"authenticated": false, "user": true, "status": "ok"}'
|
# '{"authenticated": false, "user": true, "status": "ok"}'
|
||||||
|
|||||||
@@ -98,6 +98,11 @@ class Post:
|
|||||||
raise InvalidArgumentException("Wrong mediaid {0}, unable to convert to shortcode".format(str(mediaid)))
|
raise InvalidArgumentException("Wrong mediaid {0}, unable to convert to shortcode".format(str(mediaid)))
|
||||||
return b64encode(mediaid.to_bytes(9, 'big'), b'-_').decode().replace('A', ' ').lstrip().replace(' ', 'A')
|
return b64encode(mediaid.to_bytes(9, 'big'), b'-_').decode().replace('A', ' ').lstrip().replace(' ', 'A')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def supported_graphql_types() -> List[str]:
|
||||||
|
"""The values of __typename fields that the :class:`Post` class can handle."""
|
||||||
|
return ["GraphImage", "GraphVideo", "GraphSidecar"]
|
||||||
|
|
||||||
def _asdict(self):
|
def _asdict(self):
|
||||||
node = self._node
|
node = self._node
|
||||||
if self._full_metadata_dict:
|
if self._full_metadata_dict:
|
||||||
@@ -134,6 +139,12 @@ class Post:
|
|||||||
pic_json = self._context.get_json("p/{0}/".format(self.shortcode), params={})
|
pic_json = self._context.get_json("p/{0}/".format(self.shortcode), params={})
|
||||||
self._full_metadata_dict = pic_json['entry_data']['PostPage'][0]['graphql']['shortcode_media']
|
self._full_metadata_dict = pic_json['entry_data']['PostPage'][0]['graphql']['shortcode_media']
|
||||||
self._rhx_gis_str = pic_json.get('rhx_gis')
|
self._rhx_gis_str = pic_json.get('rhx_gis')
|
||||||
|
if self._full_metadata_dict is None:
|
||||||
|
# issue #449
|
||||||
|
self._context.error("Fetching Post metadata failed (issue #449). "
|
||||||
|
"The following data has been returned:\n"
|
||||||
|
+ json.dumps(pic_json['entry_data'], indent=2))
|
||||||
|
raise BadResponseException("Fetching Post metadata failed.")
|
||||||
if self.shortcode != self._full_metadata_dict['shortcode']:
|
if self.shortcode != self._full_metadata_dict['shortcode']:
|
||||||
self._node.update(self._full_metadata_dict)
|
self._node.update(self._full_metadata_dict)
|
||||||
raise PostChangedException
|
raise PostChangedException
|
||||||
@@ -224,7 +235,11 @@ class Post:
|
|||||||
def get_sidecar_nodes(self) -> Iterator[PostSidecarNode]:
|
def get_sidecar_nodes(self) -> Iterator[PostSidecarNode]:
|
||||||
"""Sidecar nodes of a Post with typename==GraphSidecar."""
|
"""Sidecar nodes of a Post with typename==GraphSidecar."""
|
||||||
if self.typename == 'GraphSidecar':
|
if self.typename == 'GraphSidecar':
|
||||||
for edge in self._field('edge_sidecar_to_children', 'edges'):
|
edges = self._field('edge_sidecar_to_children', 'edges')
|
||||||
|
if any(edge['node']['is_video'] for edge in edges):
|
||||||
|
# video_url is only present in full metadata, issue #558.
|
||||||
|
edges = self._full_metadata['edge_sidecar_to_children']['edges']
|
||||||
|
for edge in edges:
|
||||||
node = edge['node']
|
node = edge['node']
|
||||||
is_video = node['is_video']
|
is_video = node['is_video']
|
||||||
yield PostSidecarNode(is_video=is_video, display_url=node['display_url'],
|
yield PostSidecarNode(is_video=is_video, display_url=node['display_url'],
|
||||||
@@ -518,10 +533,11 @@ class Profile:
|
|||||||
|
|
||||||
def _asdict(self):
|
def _asdict(self):
|
||||||
json_node = self._node.copy()
|
json_node = self._node.copy()
|
||||||
# remove posts
|
# remove posts to avoid "Circular reference detected" exception
|
||||||
json_node.pop('edge_media_collections', None)
|
json_node.pop('edge_media_collections', None)
|
||||||
json_node.pop('edge_owner_to_timeline_media', None)
|
json_node.pop('edge_owner_to_timeline_media', None)
|
||||||
json_node.pop('edge_saved_media', None)
|
json_node.pop('edge_saved_media', None)
|
||||||
|
json_node.pop('edge_felix_video_timeline', None)
|
||||||
if self._iphone_struct_:
|
if self._iphone_struct_:
|
||||||
json_node['iphone_struct'] = self._iphone_struct_
|
json_node['iphone_struct'] = self._iphone_struct_
|
||||||
return json_node
|
return json_node
|
||||||
@@ -598,6 +614,10 @@ class Profile:
|
|||||||
def mediacount(self) -> int:
|
def mediacount(self) -> int:
|
||||||
return self._metadata('edge_owner_to_timeline_media', 'count')
|
return self._metadata('edge_owner_to_timeline_media', 'count')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def igtvcount(self) -> int:
|
||||||
|
return self._metadata('edge_felix_video_timeline', 'count')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def followers(self) -> int:
|
def followers(self) -> int:
|
||||||
return self._metadata('edge_followed_by', 'count')
|
return self._metadata('edge_followed_by', 'count')
|
||||||
@@ -741,6 +761,19 @@ class Profile:
|
|||||||
lambda d: d['data']['user']['edge_user_to_photos_of_you'],
|
lambda d: d['data']['user']['edge_user_to_photos_of_you'],
|
||||||
self._rhx_gis))
|
self._rhx_gis))
|
||||||
|
|
||||||
|
def get_igtv_posts(self) -> Iterator[Post]:
|
||||||
|
"""Retrieve all IGTV posts.
|
||||||
|
|
||||||
|
.. versionadded:: 4.3"""
|
||||||
|
self._obtain_metadata()
|
||||||
|
yield from (Post(self._context, node, self) for node in
|
||||||
|
self._context.graphql_node_list('bc78b344a68ed16dd5d7f264681c4c76',
|
||||||
|
{'id': self.userid},
|
||||||
|
'https://www.instagram.com/{0}/channel/'.format(self.username),
|
||||||
|
lambda d: d['data']['user']['edge_felix_video_timeline'],
|
||||||
|
self._rhx_gis,
|
||||||
|
self._metadata('edge_felix_video_timeline')))
|
||||||
|
|
||||||
def get_followers(self) -> Iterator['Profile']:
|
def get_followers(self) -> Iterator['Profile']:
|
||||||
"""
|
"""
|
||||||
Retrieve list of followers of given profile.
|
Retrieve list of followers of given profile.
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import instaloader
|
|||||||
PROFILE_WITH_HIGHLIGHTS = 325732271
|
PROFILE_WITH_HIGHLIGHTS = 325732271
|
||||||
PUBLIC_PROFILE = "selenagomez"
|
PUBLIC_PROFILE = "selenagomez"
|
||||||
PUBLIC_PROFILE_ID = 460563723
|
PUBLIC_PROFILE_ID = 460563723
|
||||||
|
PUBLIC_PROFILE_WITH_IGTV = "natgeo"
|
||||||
HASHTAG = "kitten"
|
HASHTAG = "kitten"
|
||||||
LOCATION = "362629379"
|
LOCATION = "362629379"
|
||||||
OWN_USERNAME = "aandergr"
|
OWN_USERNAME = "aandergr"
|
||||||
@@ -104,6 +105,11 @@ class TestInstaloaderAnonymously(unittest.TestCase):
|
|||||||
PAGING_MAX_COUNT):
|
PAGING_MAX_COUNT):
|
||||||
print(post)
|
print(post)
|
||||||
|
|
||||||
|
def test_public_profile_igtv(self):
|
||||||
|
for post in islice(instaloader.Profile.from_username(self.L.context, PUBLIC_PROFILE_WITH_IGTV).get_igtv_posts(),
|
||||||
|
PAGING_MAX_COUNT):
|
||||||
|
print(post)
|
||||||
|
|
||||||
|
|
||||||
class TestInstaloaderLoggedIn(TestInstaloaderAnonymously):
|
class TestInstaloaderLoggedIn(TestInstaloaderAnonymously):
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user