Profile: Cache iphone data structure if queried

Profile.get_profile_pic_url() and Profile.has_highlight_reels did the same
query without remembering the response, resulting in that query being made
twice when downloading stories and profile pic of a profile.
This commit is contained in:
Alexander Graf 2018-05-15 17:18:08 +02:00
parent b9c7713d6a
commit a3eb93c21a
2 changed files with 26 additions and 13 deletions

View File

@ -266,7 +266,7 @@ class Instaloader:
def _epoch_to_string(epoch: datetime) -> str: def _epoch_to_string(epoch: datetime) -> str:
return epoch.strftime('%Y-%m-%d_%H-%M-%S') return epoch.strftime('%Y-%m-%d_%H-%M-%S')
profile_pic_url = profile.get_profile_pic_url() profile_pic_url = profile.profile_pic_url
with self.context.get_anonymous_session() as anonymous_session: with self.context.get_anonymous_session() as anonymous_session:
date_object = datetime.strptime(anonymous_session.head(profile_pic_url).headers["Last-Modified"], date_object = datetime.strptime(anonymous_session.head(profile_pic_url).headers["Last-Modified"],
'%a, %d %b %Y %H:%M:%S GMT') '%a, %d %b %Y %H:%M:%S GMT')

View File

@ -374,10 +374,13 @@ class Profile:
def __init__(self, context: InstaloaderContext, node: Dict[str, Any]): def __init__(self, context: InstaloaderContext, node: Dict[str, Any]):
assert 'username' in node assert 'username' in node
self._context = context self._context = context
self._has_highlight_reels = None
self._has_public_story = None self._has_public_story = None
self._node = node self._node = node
self._rhx_gis = None self._rhx_gis = None
self._iphone_struct_ = None
if 'iphone_struct' in node:
# if loaded from JSON with load_structure_from_file()
self._iphone_struct_ = node['iphone_struct']
@classmethod @classmethod
def from_username(cls, context: InstaloaderContext, username: str): def from_username(cls, context: InstaloaderContext, username: str):
@ -424,6 +427,8 @@ class Profile:
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)
if self._iphone_struct_:
json_node['iphone_struct'] = self._iphone_struct_
return json_node return json_node
def _obtain_metadata(self): def _obtain_metadata(self):
@ -448,6 +453,15 @@ class Profile:
d = d[key] d = d[key]
return d return d
@property
def _iphone_struct(self) -> Dict[str, Any]:
if not self._iphone_struct_:
with self._context.anonymous_copy() as anonymous_context:
data = anonymous_context.get_json(path='api/v1/users/{}/info/'.format(self.userid),
params={}, host='i.instagram.com')
self._iphone_struct_ = data['user']
return self._iphone_struct_
@property @property
def userid(self) -> int: def userid(self) -> int:
"""User ID""" """User ID"""
@ -519,12 +533,7 @@ class Profile:
This becomes `True` if the :class:`Profile` has any stories currently available, This becomes `True` if the :class:`Profile` has any stories currently available,
even if not viewable by the viewer. even if not viewable by the viewer.
""" """
if not self._has_highlight_reels: return self._iphone_struct['has_highlight_reels']
with self._context.anonymous_copy() as anonymous_context:
data = anonymous_context.get_json(path='api/v1/users/{}/info/'.format(self.userid),
params={}, host='i.instagram.com')
self._has_highlight_reels = data['user']['has_highlight_reels']
return self._has_highlight_reels
@property @property
def has_public_story(self) -> bool: def has_public_story(self) -> bool:
@ -563,17 +572,21 @@ class Profile:
def requested_by_viewer(self) -> bool: def requested_by_viewer(self) -> bool:
return self._metadata('requested_by_viewer') return self._metadata('requested_by_viewer')
def get_profile_pic_url(self) -> str: @property
def profile_pic_url(self) -> str:
"""Return URL of profile picture""" """Return URL of profile picture"""
try: try:
with self._context.get_anonymous_session() as anonymous_session: return self._iphone_struct['hd_profile_pic_url_info']['url']
data = self._context.get_json(path='api/v1/users/{0}/info/'.format(self.userid), params={},
host='i.instagram.com', session=anonymous_session)
return data["user"]["hd_profile_pic_url_info"]["url"]
except (InstaloaderException, KeyError) as err: except (InstaloaderException, KeyError) as err:
self._context.error('{} Unable to fetch high quality profile pic.'.format(err)) self._context.error('{} Unable to fetch high quality profile pic.'.format(err))
return self._metadata("profile_pic_url_hd") return self._metadata("profile_pic_url_hd")
def get_profile_pic_url(self) -> str:
""".. deprecated:: 4.0.3
Use :attr:`profile_pic_url`."""
return self.profile_pic_url
def get_posts(self) -> Iterator[Post]: def get_posts(self) -> Iterator[Post]:
"""Retrieve all posts from a profile.""" """Retrieve all posts from a profile."""
self._obtain_metadata() self._obtain_metadata()