mirror of
https://github.com/instaloader/instaloader.git
synced 2026-10-19 23:22:16 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ced65ea401 | ||
|
|
73ec884ea4 | ||
|
|
74d5e35eb8 | ||
|
|
8612712f44 | ||
|
|
67ac8f3397 | ||
|
|
5fe2a70374 | ||
|
|
8bd9c4449c |
Vendored
+10
-1
@@ -1,3 +1,12 @@
|
|||||||
code {
|
code {
|
||||||
color: #222;
|
color: #222;
|
||||||
}
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
dl.option dd {
|
||||||
|
padding-left: 12px;
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+14
@@ -0,0 +1,14 @@
|
|||||||
|
<li class="dropdown globaltoc-container">
|
||||||
|
<a role="button"
|
||||||
|
id="dLabelGlobalToc"
|
||||||
|
data-toggle="dropdown"
|
||||||
|
data-target="#"
|
||||||
|
href="{{ pathto(master_doc) }}">{{ theme_navbar_site_name }} <b class="caret"></b></a>
|
||||||
|
<ul class="dropdown-menu globaltoc"
|
||||||
|
role="menu"
|
||||||
|
aria-labelledby="dLabelGlobalToc">
|
||||||
|
{{ toctree(maxdepth=theme_globaltoc_depth|toint, collapse=False, includehidden=theme_globaltoc_includehidden|tobool) }}
|
||||||
|
<li role="separator" class="divider"></li>
|
||||||
|
<li class="toctree-l1"><a class="reference internal" href="genindex.html">Index</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
Vendored
+7
@@ -1,4 +1,11 @@
|
|||||||
{% extends "!layout.html" %}
|
{% extends "!layout.html" %}
|
||||||
|
{% block htmltitle %}
|
||||||
|
{% if pagename == "index" %}
|
||||||
|
<title>{{ title|striptags|e }}</title>
|
||||||
|
{% else %}
|
||||||
|
<title>{{ title|striptags|e }}{{ titlesuffix }}</title>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
{% block extrahead %}
|
{% block extrahead %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
<meta name="google-site-verification" content="FNUsDdkOJM9obm9QEdrNBVd9AtAzHzA-aaSMCUrS6C8" />
|
<meta name="google-site-verification" content="FNUsDdkOJM9obm9QEdrNBVd9AtAzHzA-aaSMCUrS6C8" />
|
||||||
|
|||||||
Vendored
+3
-3
@@ -14,7 +14,7 @@
|
|||||||
{% if theme_navbar_title -%}{{ theme_navbar_title|e }}{%- else -%}{{ project|e }}{%- endif -%}
|
{% if theme_navbar_title -%}{{ theme_navbar_title|e }}{%- else -%}{{ project|e }}{%- endif -%}
|
||||||
</a>
|
</a>
|
||||||
<span class="navbar-text navbar-version pull-left">
|
<span class="navbar-text navbar-version pull-left">
|
||||||
<a href="https://github.com/instaloader/instaloader/releases/tag/v{{current_release}}" title="Released on {{current_release_date}}.">v<b>{{current_release}}</b></a>
|
<a href="https://github.com/instaloader/instaloader/releases/tag/v{{current_release}}" title="Released on {{current_release_date}}">v<b>{{current_release}}</b></a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% block navbartoc %}
|
{% block navbartoc %}
|
||||||
{% include "globaltoc.html" %}
|
{% include "globaltoc.html" %}
|
||||||
{% if theme_navbar_pagenav %}
|
{% if theme_navbar_pagenav and pagename not in ("index", "genindex", "installation") %}
|
||||||
{% include "navbartoc.html" %}
|
{% include "navbartoc.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabelUsefulLinks">
|
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabelUsefulLinks">
|
||||||
<li><a href="https://github.com/instaloader/instaloader/issues">Issue Tracker</a></li>
|
<li><a href="https://github.com/instaloader/instaloader/issues">Issue Tracker</a></li>
|
||||||
<li><a href="https://github.com/instaloader/instaloader/releases">Version History</a></li>
|
<li><a href="https://github.com/instaloader/instaloader/releases">Version History</a></li>
|
||||||
<li><a href="https://pypi.python.org/pypi/instaloader">Instaloader on PyPI</a></li>
|
<li><a href="https://pypi.org/project/instaloader/">Instaloader on PyPI</a></li>
|
||||||
<li><a href="https://github.com/instaloader/instaloader">Instaloader on GitHub</a></li>
|
<li><a href="https://github.com/instaloader/instaloader">Instaloader on GitHub</a></li>
|
||||||
<li role="separator" class="divider"></li>
|
<li role="separator" class="divider"></li>
|
||||||
<li style="padding-left: 20px;"><a class="github-button" href="https://github.com/instaloader/instaloader" data-icon="octicon-star" data-size="large" data-show-count="true" aria-label="Star Instaloader on GitHub">Star</a></li>
|
<li style="padding-left: 20px;"><a class="github-button" href="https://github.com/instaloader/instaloader" data-icon="octicon-star" data-size="large" data-show-count="true" aria-label="Star Instaloader on GitHub">Star</a></li>
|
||||||
|
|||||||
+1
-1
@@ -212,7 +212,7 @@ html_domain_indices = False
|
|||||||
|
|
||||||
# If false, no index is generated.
|
# If false, no index is generated.
|
||||||
#
|
#
|
||||||
html_use_index = False
|
html_use_index = True
|
||||||
|
|
||||||
# If true, the index is split into individual pages for each letter.
|
# If true, the index is split into individual pages for each letter.
|
||||||
#
|
#
|
||||||
|
|||||||
+5
-3
@@ -5,6 +5,8 @@
|
|||||||
saved media, and their metadata, comments and captions.
|
saved media, and their metadata, comments and captions.
|
||||||
Written in Python.
|
Written in Python.
|
||||||
|
|
||||||
|
.. title:: Instaloader — Download Instagram Photos and Metadata
|
||||||
|
|
||||||
Instaloader
|
Instaloader
|
||||||
===========
|
===========
|
||||||
|
|
||||||
@@ -43,8 +45,8 @@ With `Python <https://www.python.org/>`__ installed, do::
|
|||||||
:stories | :feed | :saved
|
:stories | :feed | :saved
|
||||||
|
|
||||||
|
|
||||||
Table of Contents
|
Instaloader Documentation
|
||||||
-----------------
|
-------------------------
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
@@ -59,7 +61,7 @@ Useful Links
|
|||||||
------------
|
------------
|
||||||
|
|
||||||
- `Git Repository (on GitHub) <https://github.com/instaloader/instaloader>`__
|
- `Git Repository (on GitHub) <https://github.com/instaloader/instaloader>`__
|
||||||
- `PyPI Project Page <https://pypi.python.org/pypi/instaloader>`__
|
- `PyPI Project Page <https://pypi.org/project/instaloader/>`__
|
||||||
- `Issue Tracker / Bug Tracker <https://github.com/instaloader/instaloader/issues>`__
|
- `Issue Tracker / Bug Tracker <https://github.com/instaloader/instaloader/issues>`__
|
||||||
- `Version History <https://github.com/instaloader/instaloader/releases>`__
|
- `Version History <https://github.com/instaloader/instaloader/releases>`__
|
||||||
|
|
||||||
|
|||||||
+65
-32
@@ -3,6 +3,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."""
|
||||||
import ast
|
import ast
|
||||||
import getpass
|
import getpass
|
||||||
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import pickle
|
import pickle
|
||||||
@@ -30,7 +31,7 @@ import requests.utils
|
|||||||
import urllib3
|
import urllib3
|
||||||
|
|
||||||
|
|
||||||
__version__ = '3.3.4'
|
__version__ = '3.3.6'
|
||||||
|
|
||||||
# NOTE: duplicated in README.rst and docs/index.rst
|
# NOTE: duplicated in README.rst and docs/index.rst
|
||||||
USAGE_STRING = """
|
USAGE_STRING = """
|
||||||
@@ -201,6 +202,7 @@ class Post:
|
|||||||
self._profile = profile
|
self._profile = profile
|
||||||
self._profile_id = profile_id
|
self._profile_id = profile_id
|
||||||
self._full_metadata_dict = None
|
self._full_metadata_dict = None
|
||||||
|
self._rhx_gis = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_shortcode(cls, instaloader: 'Instaloader', shortcode: str):
|
def from_shortcode(cls, instaloader: 'Instaloader', shortcode: str):
|
||||||
@@ -239,11 +241,9 @@ class Post:
|
|||||||
@property
|
@property
|
||||||
def _full_metadata(self) -> Dict[str, Any]:
|
def _full_metadata(self) -> Dict[str, Any]:
|
||||||
if not self._full_metadata_dict:
|
if not self._full_metadata_dict:
|
||||||
pic_json = self._instaloader.get_json("p/{0}/".format(self.shortcode), params={'__a': 1})
|
pic_json = self._instaloader.get_json("p/{0}/".format(self.shortcode), params={})
|
||||||
if "graphql" in pic_json:
|
self._full_metadata_dict = pic_json['entry_data']['PostPage'][0]['graphql']['shortcode_media']
|
||||||
self._full_metadata_dict = pic_json["graphql"]["shortcode_media"]
|
self._rhx_gis = pic_json['rhx_gis']
|
||||||
else:
|
|
||||||
self._full_metadata_dict = pic_json["media"]
|
|
||||||
return self._full_metadata_dict
|
return self._full_metadata_dict
|
||||||
|
|
||||||
def _field(self, *keys) -> Any:
|
def _field(self, *keys) -> Any:
|
||||||
@@ -384,9 +384,11 @@ class Post:
|
|||||||
if self.comments == len(comment_edges):
|
if self.comments == len(comment_edges):
|
||||||
# If the Post's metadata already contains all comments, don't do GraphQL requests to obtain them
|
# If the Post's metadata already contains all comments, don't do GraphQL requests to obtain them
|
||||||
yield from (comment['node'] for comment in comment_edges)
|
yield from (comment['node'] for comment in comment_edges)
|
||||||
|
return
|
||||||
yield from self._instaloader.graphql_node_list(17852405266163336, {'shortcode': self.shortcode},
|
yield from self._instaloader.graphql_node_list(17852405266163336, {'shortcode': self.shortcode},
|
||||||
'https://www.instagram.com/p/' + self.shortcode + '/',
|
'https://www.instagram.com/p/' + self.shortcode + '/',
|
||||||
lambda d: d['data']['shortcode_media']['edge_media_to_comment'])
|
lambda d: d['data']['shortcode_media']['edge_media_to_comment'],
|
||||||
|
rhx_gis=self._rhx_gis)
|
||||||
|
|
||||||
def get_likes(self) -> Iterator[Dict[str, Any]]:
|
def get_likes(self) -> Iterator[Dict[str, Any]]:
|
||||||
"""Iterate over all likes of the post.
|
"""Iterate over all likes of the post.
|
||||||
@@ -401,9 +403,11 @@ class Post:
|
|||||||
if self.likes == len(likes_edges):
|
if self.likes == len(likes_edges):
|
||||||
# If the Post's metadata already contains all likes, don't do GraphQL requests to obtain them
|
# If the Post's metadata already contains all likes, don't do GraphQL requests to obtain them
|
||||||
yield from (like['node'] for like in likes_edges)
|
yield from (like['node'] for like in likes_edges)
|
||||||
|
return
|
||||||
yield from self._instaloader.graphql_node_list("1cb6ec562846122743b61e492c85999f", {'shortcode': self.shortcode},
|
yield from self._instaloader.graphql_node_list("1cb6ec562846122743b61e492c85999f", {'shortcode': self.shortcode},
|
||||||
'https://www.instagram.com/p/' + self.shortcode + '/',
|
'https://www.instagram.com/p/' + self.shortcode + '/',
|
||||||
lambda d: d['data']['shortcode_media']['edge_liked_by'])
|
lambda d: d['data']['shortcode_media']['edge_liked_by'],
|
||||||
|
rhx_gis=self._rhx_gis)
|
||||||
|
|
||||||
def get_location(self) -> Optional[Dict[str, str]]:
|
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'."""
|
||||||
@@ -600,7 +604,7 @@ class Instaloader:
|
|||||||
:raises QueryReturnedNotFoundException: When the server responds with a 404.
|
:raises QueryReturnedNotFoundException: When the server responds with a 404.
|
||||||
:raises ConnectionException: When query repeatedly failed.
|
:raises ConnectionException: When query repeatedly failed.
|
||||||
"""
|
"""
|
||||||
def graphql_query_waittime(query_id: int, untracked_queries: bool = False) -> int:
|
def graphql_query_waittime(query_id: Union[int, str], untracked_queries: bool = False) -> int:
|
||||||
sliding_window = 660
|
sliding_window = 660
|
||||||
timestamps = self.previous_queries.get(query_id)
|
timestamps = self.previous_queries.get(query_id)
|
||||||
if not timestamps:
|
if not timestamps:
|
||||||
@@ -611,9 +615,9 @@ class Instaloader:
|
|||||||
if len(timestamps) < 100 and not untracked_queries:
|
if len(timestamps) < 100 and not untracked_queries:
|
||||||
return 0
|
return 0
|
||||||
return round(min(timestamps) + sliding_window - current_time) + 6
|
return round(min(timestamps) + sliding_window - current_time) + 6
|
||||||
is_graphql_query = 'query_id' in params and 'graphql/query' in path
|
is_graphql_query = 'graphql/query' in path
|
||||||
if is_graphql_query:
|
if is_graphql_query:
|
||||||
query_id = params['query_id']
|
query_id = params['query_id'] if 'query_id' in params else params['query_hash']
|
||||||
waittime = graphql_query_waittime(query_id)
|
waittime = graphql_query_waittime(query_id)
|
||||||
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.'.format(waittime))
|
||||||
@@ -633,7 +637,13 @@ class Instaloader:
|
|||||||
raise TooManyRequests("429 - Too Many Requests")
|
raise TooManyRequests("429 - Too Many Requests")
|
||||||
if resp.status_code != 200:
|
if resp.status_code != 200:
|
||||||
raise ConnectionException("HTTP error code {}.".format(resp.status_code))
|
raise ConnectionException("HTTP error code {}.".format(resp.status_code))
|
||||||
resp_json = resp.json()
|
if not is_graphql_query and not "__a" in params and host == "www.instagram.com":
|
||||||
|
match = re.search(r'window\._sharedData = (.*);</script>', resp.text)
|
||||||
|
if match is None:
|
||||||
|
raise ConnectionException("Could not find \"window._sharedData\" in html response.")
|
||||||
|
return json.loads(match.group(1))
|
||||||
|
else:
|
||||||
|
resp_json = resp.json()
|
||||||
if 'status' in resp_json and resp_json['status'] != "ok":
|
if 'status' in resp_json and resp_json['status'] != "ok":
|
||||||
if 'message' in resp_json:
|
if 'message' in resp_json:
|
||||||
raise ConnectionException("Returned \"{}\" status, message \"{}\".".format(resp_json['status'],
|
raise ConnectionException("Returned \"{}\" status, message \"{}\".".format(resp_json['status'],
|
||||||
@@ -693,7 +703,7 @@ class Instaloader:
|
|||||||
return session
|
return session
|
||||||
|
|
||||||
def graphql_query(self, query_identifier: Union[int, str], variables: Dict[str, Any],
|
def graphql_query(self, query_identifier: Union[int, str], variables: Dict[str, Any],
|
||||||
referer: Optional[str] = None) -> Dict[str, Any]:
|
referer: Optional[str] = None, rhx_gis: Optional[str] = None) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Do a GraphQL Query.
|
Do a GraphQL Query.
|
||||||
|
|
||||||
@@ -711,9 +721,18 @@ class Instaloader:
|
|||||||
tmpsession.headers['accept'] = '*/*'
|
tmpsession.headers['accept'] = '*/*'
|
||||||
if referer is not None:
|
if referer is not None:
|
||||||
tmpsession.headers['referer'] = urllib.parse.quote(referer)
|
tmpsession.headers['referer'] = urllib.parse.quote(referer)
|
||||||
|
|
||||||
|
variables_json = json.dumps(variables, separators=(',', ':'))
|
||||||
|
|
||||||
|
if rhx_gis:
|
||||||
|
values = "{}:{}:{}:{}".format(rhx_gis, tmpsession.cookies['csrftoken'], self.user_agent, variables_json)
|
||||||
|
x_instagram_gis = hashlib.md5(values.encode()).hexdigest()
|
||||||
|
tmpsession.cookies.set('ig_pr', '2')
|
||||||
|
tmpsession.headers['x-instagram-gis'] = x_instagram_gis
|
||||||
|
|
||||||
resp_json = self.get_json('graphql/query',
|
resp_json = self.get_json('graphql/query',
|
||||||
params={'query_id' if isinstance(query_identifier, int) else 'query_hash': query_identifier,
|
params={'query_id' if isinstance(query_identifier, int) else 'query_hash': query_identifier,
|
||||||
'variables': json.dumps(variables, separators=(',', ':'))},
|
'variables': variables_json},
|
||||||
session=tmpsession)
|
session=tmpsession)
|
||||||
if 'status' not in resp_json:
|
if 'status' not in resp_json:
|
||||||
self.error("GraphQL response did not contain a \"status\" field.")
|
self.error("GraphQL response did not contain a \"status\" field.")
|
||||||
@@ -738,20 +757,21 @@ class Instaloader:
|
|||||||
def get_id_by_username(self, profile: str) -> int:
|
def get_id_by_username(self, profile: str) -> int:
|
||||||
"""Each Instagram profile has its own unique ID which stays unmodified even if a user changes
|
"""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 this function."""
|
his/her username. To get said ID, given the profile's name, you may call this function."""
|
||||||
return int(self.get_profile_metadata(profile)['user']['id'])
|
return int(self.get_profile_metadata(profile)[0]['user']['id'])
|
||||||
|
|
||||||
def graphql_node_list(self, query_identifier: Union[int, str], query_variables: Dict[str, Any],
|
def graphql_node_list(self, query_identifier: Union[int, str], query_variables: Dict[str, Any],
|
||||||
query_referer: Optional[str],
|
query_referer: Optional[str],
|
||||||
edge_extractor: Callable[[Dict[str, Any]], Dict[str, Any]]) -> Iterator[Dict[str, Any]]:
|
edge_extractor: Callable[[Dict[str, Any]], Dict[str, Any]],
|
||||||
|
rhx_gis: Optional[str] = None) -> Iterator[Dict[str, Any]]:
|
||||||
"""Retrieve a list of GraphQL nodes."""
|
"""Retrieve a list of GraphQL nodes."""
|
||||||
query_variables['first'] = Instaloader.GRAPHQL_PAGE_LENGTH
|
query_variables['first'] = Instaloader.GRAPHQL_PAGE_LENGTH
|
||||||
data = self.graphql_query(query_identifier, query_variables, query_referer)
|
data = self.graphql_query(query_identifier, query_variables, query_referer, rhx_gis)
|
||||||
while True:
|
while True:
|
||||||
edge_struct = edge_extractor(data)
|
edge_struct = edge_extractor(data)
|
||||||
yield from [edge['node'] for edge in edge_struct['edges']]
|
yield from [edge['node'] for edge in edge_struct['edges']]
|
||||||
if edge_struct['page_info']['has_next_page']:
|
if edge_struct['page_info']['has_next_page']:
|
||||||
query_variables['after'] = edge_struct['page_info']['end_cursor']
|
query_variables['after'] = edge_struct['page_info']['end_cursor']
|
||||||
data = self.graphql_query(query_identifier, query_variables, query_referer)
|
data = self.graphql_query(query_identifier, query_variables, query_referer, rhx_gis)
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -1255,7 +1275,7 @@ class Instaloader:
|
|||||||
|
|
||||||
if not self.is_logged_in:
|
if not self.is_logged_in:
|
||||||
return
|
return
|
||||||
data = self.get_profile_metadata(self.username)
|
data, full_metadata = self.get_profile_metadata(self.username)
|
||||||
user_id = data["user"]["id"]
|
user_id = data["user"]["id"]
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
@@ -1275,7 +1295,8 @@ class Instaloader:
|
|||||||
break
|
break
|
||||||
data = self.graphql_query("f883d95537fbcd400f466f63d42bd8a1",
|
data = self.graphql_query("f883d95537fbcd400f466f63d42bd8a1",
|
||||||
{'id': user_id, 'first': Instaloader.GRAPHQL_PAGE_LENGTH,
|
{'id': user_id, 'first': Instaloader.GRAPHQL_PAGE_LENGTH,
|
||||||
'after': saved_media["page_info"]["end_cursor"]})['data']
|
'after': saved_media["page_info"]["end_cursor"]},
|
||||||
|
rhx_gis=full_metadata['rhx_gis'])['data']
|
||||||
|
|
||||||
def download_saved_posts(self, max_count: int = None, fast_update: bool = False,
|
def download_saved_posts(self, max_count: int = None, fast_update: bool = False,
|
||||||
filter_func: Optional[Callable[[Post], bool]] = None) -> None:
|
filter_func: Optional[Callable[[Post], bool]] = None) -> None:
|
||||||
@@ -1302,10 +1323,19 @@ class Instaloader:
|
|||||||
|
|
||||||
def get_hashtag_posts(self, hashtag: str) -> Iterator[Post]:
|
def get_hashtag_posts(self, hashtag: str) -> Iterator[Post]:
|
||||||
"""Get Posts associated with a #hashtag."""
|
"""Get Posts associated with a #hashtag."""
|
||||||
yield from (Post(self, node) for node in
|
has_next_page = True
|
||||||
self.graphql_node_list(17875800862117404, {'tag_name': hashtag},
|
end_cursor = None
|
||||||
'https://www.instagram.com/explore/tags/{0}/'.format(hashtag),
|
while has_next_page:
|
||||||
lambda d: d['data']['hashtag']['edge_hashtag_to_media']))
|
if end_cursor:
|
||||||
|
params = {'__a': 1, 'max_id': end_cursor}
|
||||||
|
else:
|
||||||
|
params = {'__a': 1}
|
||||||
|
hashtag_data = self.get_json('explore/tags/{0}/'.format(hashtag),
|
||||||
|
params)['graphql']['hashtag']['edge_hashtag_to_media']
|
||||||
|
yield from (Post(self, edge['node']) for edge in
|
||||||
|
hashtag_data['edges'])
|
||||||
|
has_next_page = hashtag_data['page_info']['has_next_page']
|
||||||
|
end_cursor = hashtag_data['page_info']['end_cursor']
|
||||||
|
|
||||||
def download_hashtag(self, hashtag: str,
|
def download_hashtag(self, hashtag: str,
|
||||||
max_count: Optional[int] = None,
|
max_count: Optional[int] = None,
|
||||||
@@ -1364,6 +1394,9 @@ class Instaloader:
|
|||||||
self._log("Profile {0} does not match the stored unique ID {1}.".format(profile, profile_id))
|
self._log("Profile {0} does not match the stored unique ID {1}.".format(profile, profile_id))
|
||||||
else:
|
else:
|
||||||
self._log("Trying to find profile {0} using its unique ID {1}.".format(profile, profile_id))
|
self._log("Trying to find profile {0} using its unique ID {1}.".format(profile, profile_id))
|
||||||
|
if not self.is_logged_in:
|
||||||
|
self.error("Profile {} changed its name. If you use --login=USERNAME, I can find out the new name.")
|
||||||
|
raise LoginRequiredException("Login required to obtain username from userid")
|
||||||
newname = self.get_username_by_id(profile_id)
|
newname = self.get_username_by_id(profile_id)
|
||||||
self._log("Profile {0} has changed its name to {1}.".format(profile, newname))
|
self._log("Profile {0} has changed its name to {1}.".format(profile, newname))
|
||||||
if ((format_string_contains_key(self.dirname_pattern, 'profile') or
|
if ((format_string_contains_key(self.dirname_pattern, 'profile') or
|
||||||
@@ -1389,15 +1422,15 @@ class Instaloader:
|
|||||||
return profile, profile_id
|
return profile, profile_id
|
||||||
raise ProfileNotExistsException("Profile {0} does not exist.".format(profile))
|
raise ProfileNotExistsException("Profile {0} does not exist.".format(profile))
|
||||||
|
|
||||||
def get_profile_metadata(self, profile_name: str) -> Dict[str, Any]:
|
def get_profile_metadata(self, profile_name: str) -> Tuple[Dict[str, Any], Dict[str, Any]]:
|
||||||
"""Retrieves a profile's metadata, for use with e.g. :meth:`get_profile_posts` and :meth:`check_profile_id`."""
|
"""Retrieves a profile's metadata, for use with e.g. :meth:`get_profile_posts` and :meth:`check_profile_id`."""
|
||||||
try:
|
try:
|
||||||
metadata = self.get_json('{}/'.format(profile_name), params={'__a': 1})
|
metadata = self.get_json('{}/'.format(profile_name), params={})
|
||||||
return metadata['graphql'] if 'graphql' in metadata else metadata
|
return metadata['entry_data']['ProfilePage'][0]['graphql'], metadata
|
||||||
except QueryReturnedNotFoundException:
|
except QueryReturnedNotFoundException:
|
||||||
raise ProfileNotExistsException('Profile {} does not exist.'.format(profile_name))
|
raise ProfileNotExistsException('Profile {} does not exist.'.format(profile_name))
|
||||||
|
|
||||||
def get_profile_posts(self, profile_metadata: Dict[str, Any]) -> Iterator[Post]:
|
def get_profile_posts(self, profile_metadata: Dict[str, Any], rhx_gis: str) -> Iterator[Post]:
|
||||||
"""Retrieve all posts from a profile."""
|
"""Retrieve all posts from a profile."""
|
||||||
profile_name = profile_metadata['user']['username']
|
profile_name = profile_metadata['user']['username']
|
||||||
profile_id = int(profile_metadata['user']['id'])
|
profile_id = int(profile_metadata['user']['id'])
|
||||||
@@ -1418,7 +1451,7 @@ class Instaloader:
|
|||||||
data = self.graphql_query(17888483320059182, {'id': profile_metadata['user']['id'],
|
data = self.graphql_query(17888483320059182, {'id': profile_metadata['user']['id'],
|
||||||
'first': Instaloader.GRAPHQL_PAGE_LENGTH,
|
'first': Instaloader.GRAPHQL_PAGE_LENGTH,
|
||||||
'after': end_cursor},
|
'after': end_cursor},
|
||||||
'https://www.instagram.com/{0}/'.format(profile_name))
|
referer='https://www.instagram.com/{0}/'.format(profile_name), rhx_gis=rhx_gis)
|
||||||
media = data['data']['user']['edge_owner_to_timeline_media']
|
media = data['data']['user']['edge_owner_to_timeline_media']
|
||||||
yield from (Post(self, edge['node'], profile=profile_name, profile_id=profile_id)
|
yield from (Post(self, edge['node'], profile=profile_name, profile_id=profile_id)
|
||||||
for edge in media['edges'])
|
for edge in media['edges'])
|
||||||
@@ -1438,14 +1471,14 @@ class Instaloader:
|
|||||||
with suppress(ProfileNotExistsException):
|
with suppress(ProfileNotExistsException):
|
||||||
# ProfileNotExistsException is raised again later in check_profile_id() when we search the profile, so we
|
# ProfileNotExistsException is raised again later in check_profile_id() when we search the profile, so we
|
||||||
# must suppress it here.
|
# must suppress it here.
|
||||||
profile_metadata = self.get_profile_metadata(name)
|
profile_metadata, full_metadata = self.get_profile_metadata(name)
|
||||||
|
|
||||||
# check if profile does exist or name has changed since last download
|
# check if profile does exist or name has changed since last download
|
||||||
# and update name and json data if necessary
|
# and update name and json data if necessary
|
||||||
name_updated, profile_id = self.check_profile_id(name, profile_metadata)
|
name_updated, profile_id = self.check_profile_id(name, profile_metadata)
|
||||||
if name_updated != name:
|
if name_updated != name:
|
||||||
name = name_updated
|
name = name_updated
|
||||||
profile_metadata = self.get_profile_metadata(name)
|
profile_metadata, full_metadata = self.get_profile_metadata(name)
|
||||||
|
|
||||||
# Download profile picture
|
# Download profile picture
|
||||||
if profile_pic or profile_pic_only:
|
if profile_pic or profile_pic_only:
|
||||||
@@ -1480,7 +1513,7 @@ class Instaloader:
|
|||||||
else:
|
else:
|
||||||
totalcount = profile_metadata["user"]["edge_owner_to_timeline_media"]["count"]
|
totalcount = profile_metadata["user"]["edge_owner_to_timeline_media"]["count"]
|
||||||
count = 1
|
count = 1
|
||||||
for post in self.get_profile_posts(profile_metadata):
|
for post in self.get_profile_posts(profile_metadata, rhx_gis=full_metadata['rhx_gis']):
|
||||||
self._log("[%3i/%3i] " % (count, totalcount), end="", flush=True)
|
self._log("[%3i/%3i] " % (count, totalcount), end="", flush=True)
|
||||||
count += 1
|
count += 1
|
||||||
if filter_func is not None and not filter_func(post):
|
if filter_func is not None and not filter_func(post):
|
||||||
|
|||||||
Reference in New Issue
Block a user