Compare commits

...
8 Commits
Author SHA1 Message Date
Alexander Graf dcea0e9672 Release of version 4.0.7 2018-08-05 09:37:18 +02:00
Alexander Graf 809b23a996 doc: Mark Profile.has_higlight_reels as deprecated 2018-08-05 09:13:49 +02:00
Alexander Graf c634f21dcc Fix anon-retry for profiles that blocked viewer
This was broken with 50a5330fec.
2018-08-05 09:01:22 +02:00
Alexander Graf a070ed42ae get_stories(): Fetch in chunks of 100 users
It seems there is a limit of how many user stories can be requested at once.
Now, Instaloader.get_stories() chunks the stories request.

Fixes #157 and fixes #134.
2018-08-05 08:06:53 +02:00
Alexander Graf a34d750152 rate handling: Show %H:%M when request is retried 2018-08-05 08:02:35 +02:00
Lars Lindqvist bfa5d8ed41 Fix :stories
50a5330fec breaks `:stories` if userids is None (non-iterable).
2018-08-05 03:29:24 +02:00
Alexander Graf 7f81985911 _ArbitraryItemFormatter: Allow chaining attributes
Override get_value() rather than get_field(), to allow chaining of item
attributes, such as --post-metadata-txt={owner_profile.profile_pic_url}.

Motivated by #151.
2018-08-04 11:41:19 +02:00
Lars Lindqvist b5aa268344 Fix owner_profile for Profile.get_tagged_posts() 2018-08-04 09:41:23 +02:00
5 changed files with 58 additions and 28 deletions
+1 -1
View File
@@ -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.0.6' __version__ = '4.0.7'
try: try:
+33 -14
View File
@@ -90,6 +90,7 @@ def _main(instaloader: Instaloader, targetlist: List[str],
instaloader.context.log("Logged in as %s." % username) instaloader.context.log("Logged in as %s." % username)
# Try block for KeyboardInterrupt (save session on ^C) # Try block for KeyboardInterrupt (save session on ^C)
profiles = set() profiles = set()
anonymous_retry_profiles = set()
try: try:
# Generate set of profiles, already downloading non-profile targets # Generate set of profiles, already downloading non-profile targets
for target in targetlist: for target in targetlist:
@@ -136,7 +137,28 @@ def _main(instaloader: Instaloader, targetlist: List[str],
instaloader.download_saved_posts(fast_update=fast_update, max_count=max_count, instaloader.download_saved_posts(fast_update=fast_update, max_count=max_count,
post_filter=post_filter) post_filter=post_filter)
else: else:
profiles.add(instaloader.check_profile_id(target)) try:
profile = instaloader.check_profile_id(target)
if instaloader.context.is_logged_in and profile.has_blocked_viewer:
if not stories_only and not profile.is_private:
raise ProfileNotExistsException("{} blocked you; But she's public, "
"so we just download her anonymously.".format(target))
else:
instaloader.context.error("{} blocked you.".format(target))
else:
profiles.add(profile)
except ProfileNotExistsException as err:
# 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.
if instaloader.context.is_logged_in and not stories_only:
instaloader.context.log(err)
instaloader.context.log("Trying again anonymously, helps in case you are just blocked.")
with instaloader.anonymous_copy() as anonymous_loader:
with instaloader.context.error_catcher():
anonymous_retry_profiles.add(anonymous_loader.check_profile_id(target))
instaloader.context.log("Looks good.")
else:
raise
if len(profiles) > 1: if len(profiles) > 1:
instaloader.context.log("Downloading {} profiles: {}".format(len(profiles), instaloader.context.log("Downloading {} profiles: {}".format(len(profiles),
' '.join([p.username for p in profiles]))) ' '.join([p.username for p in profiles])))
@@ -144,19 +166,16 @@ def _main(instaloader: Instaloader, targetlist: List[str],
# Iterate through profiles list and download them # Iterate through profiles list and download them
for target in profiles: for target in profiles:
with instaloader.context.error_catcher(target): with instaloader.context.error_catcher(target):
try: instaloader.download_profile(target, profile_pic, profile_pic_only,
instaloader.download_profile(target, profile_pic, profile_pic_only, fast_update, post_filter=post_filter)
fast_update, post_filter=post_filter) if anonymous_retry_profiles:
except ProfileNotExistsException as err: instaloader.context.log("Downloading anonymously: {}"
if instaloader.context.is_logged_in and not stories_only: .format(' '.join([p.username for p in anonymous_retry_profiles])))
instaloader.context.log(err) with instaloader.anonymous_copy() as anonymous_loader:
instaloader.context.log("Trying again anonymously, helps in case you are just blocked.") for target in anonymous_retry_profiles:
with instaloader.anonymous_copy() as anonymous_loader: with instaloader.context.error_catcher(target):
with instaloader.context.error_catcher(): anonymous_loader.download_profile(target, profile_pic, profile_pic_only,
anonymous_loader.download_profile(target, profile_pic, profile_pic_only, fast_update, post_filter=post_filter)
fast_update, post_filter=post_filter)
else:
raise
if stories or stories_only: if stories or stories_only:
with instaloader.context.error_catcher("Download stories"): with instaloader.context.error_catcher("Download stories"):
instaloader.context.log("Downloading stories") instaloader.context.log("Downloading stories")
+16 -9
View File
@@ -49,11 +49,11 @@ class _ArbitraryItemFormatter(string.Formatter):
def __init__(self, item: Any): def __init__(self, item: Any):
self._item = item self._item = item
def get_field(self, field_name, args, kwargs): def get_value(self, key, args, kwargs):
"""Override to substitute {ATTRIBUTE} by attributes of our _item.""" """Override to substitute {ATTRIBUTE} by attributes of our _item."""
if hasattr(self._item, field_name): if hasattr(self._item, key):
return self._item.__getattribute__(field_name), None return getattr(self._item, key)
return super().get_field(field_name, args, kwargs) return super().get_value(key, args, kwargs)
def format_field(self, value, format_spec): def format_field(self, value, format_spec):
"""Override :meth:`string.Formatter.format_field` to have our """Override :meth:`string.Formatter.format_field` to have our
@@ -396,17 +396,22 @@ class Instaloader:
:param userids: List of user IDs to be processed in terms of downloading their stories, or None. :param userids: List of user IDs to be processed in terms of downloading their stories, or None.
""" """
if userids is None: if not userids:
data = self.context.graphql_query("d15efd8c0c5b23f0ef71f18bf363c704", data = self.context.graphql_query("d15efd8c0c5b23f0ef71f18bf363c704",
{"only_stories": True})["data"]["user"] {"only_stories": True})["data"]["user"]
if data is None: if data is None:
raise BadResponseException('Bad stories reel JSON.') raise BadResponseException('Bad stories reel JSON.')
userids = list(edge["node"]["id"] for edge in data["feed_reels_tray"]["edge_reels_tray_to_reel"]["edges"]) userids = list(edge["node"]["id"] for edge in data["feed_reels_tray"]["edge_reels_tray_to_reel"]["edges"])
stories = self.context.graphql_query("bf41e22b1c4ba4c9f31b844ebb7d9056", def _userid_chunks():
{"reel_ids": userids, "precomposed_overlay": False})["data"] userids_per_query = 100
for i in range(0, len(userids), userids_per_query):
yield userids[i:i + userids_per_query]
yield from (Story(self.context, media) for media in stories['reels_media']) for userid_chunk in _userid_chunks():
stories = self.context.graphql_query("bf41e22b1c4ba4c9f31b844ebb7d9056",
{"reel_ids": userid_chunk, "precomposed_overlay": False})["data"]
yield from (Story(self.context, media) for media in stories['reels_media'])
@_requires_login @_requires_login
def download_stories(self, def download_stories(self,
@@ -428,8 +433,10 @@ class Instaloader:
if not userids: if not userids:
self.context.log("Retrieving all visible stories...") self.context.log("Retrieving all visible stories...")
else:
userids = [p if isinstance(p, int) else p.userid for p in userids]
for user_story in self.get_stories([p if isinstance(p, int) else p.userid for p in userids]): for user_story in self.get_stories(userids):
name = user_story.owner_username name = user_story.owner_username
self.context.log("Retrieving stories from profile {}.".format(name)) self.context.log("Retrieving stories from profile {}.".format(name))
totalcount = user_story.itemcount totalcount = user_story.itemcount
+5 -2
View File
@@ -9,6 +9,7 @@ import textwrap
import time import time
import urllib.parse import urllib.parse
from contextlib import contextmanager from contextlib import contextmanager
from datetime import datetime, timedelta
from typing import Any, Callable, Dict, Iterator, Optional from typing import Any, Callable, Dict, Iterator, Optional
import requests import requests
@@ -246,7 +247,8 @@ class InstaloaderContext:
if is_graphql_query and not query_not_limited: if is_graphql_query and not query_not_limited:
waittime = graphql_query_waittime() waittime = graphql_query_waittime()
if waittime > 0: if waittime > 0:
self.log('\nToo many queries in the last time. Need to wait {} seconds.'.format(waittime)) self.log('\nToo many queries in the last time. Need to wait {} seconds, until {:%H:%M}.'
.format(waittime, datetime.now() + timedelta(seconds=waittime)))
time.sleep(waittime) time.sleep(waittime)
if self.query_timestamps is not None: if self.query_timestamps is not None:
self.query_timestamps.append(time.monotonic()) self.query_timestamps.append(time.monotonic())
@@ -301,7 +303,8 @@ class InstaloaderContext:
if is_graphql_query: if is_graphql_query:
waittime = graphql_query_waittime(untracked_queries=True) waittime = graphql_query_waittime(untracked_queries=True)
if waittime > 0: if waittime > 0:
self.log('The request will be retried in {} seconds.'.format(waittime)) self.log('The request will be retried in {} seconds, at {:%H:%M}.'
.format(waittime, datetime.now() + timedelta(seconds=waittime)))
time.sleep(waittime) time.sleep(waittime)
self._sleep() self._sleep()
return self.get_json(path=path, params=params, host=host, session=sess, _attempt=_attempt + 1) return self.get_json(path=path, params=params, host=host, session=sess, _attempt=_attempt + 1)
+3 -2
View File
@@ -526,7 +526,8 @@ class Profile:
@property @property
def has_highlight_reels(self) -> bool: def has_highlight_reels(self) -> bool:
""" """
Always returns `True` since :issue:`153`. .. deprecated:: 4.0.6
Always returns `True` since :issue:`153`.
Before broken, this indicated whether the :class:`Profile` had available stories. Before broken, this indicated whether the :class:`Profile` had available stories.
""" """
@@ -613,7 +614,7 @@ class Profile:
def get_tagged_posts(self) -> Iterator[Post]: def get_tagged_posts(self) -> Iterator[Post]:
"""Retrieve all posts where a profile is tagged.""" """Retrieve all posts where a profile is tagged."""
self._obtain_metadata() self._obtain_metadata()
yield from (Post(self._context, node, self) for node in yield from (Post(self._context, node, self if int(node['owner']['id']) == self.userid else None) for node in
self._context.graphql_node_list("e31a871f7301132ceaab56507a66bbb7", self._context.graphql_node_list("e31a871f7301132ceaab56507a66bbb7",
{'id': self.userid}, {'id': self.userid},
'https://www.instagram.com/{0}/'.format(self.username), 'https://www.instagram.com/{0}/'.format(self.username),