Location search (#212)
* Add %location search option Search for posts for a given location ID using %[location id] as the query * Document %location search * Make pylint happy * Use correct paths for location results * Fix —help output Add description of location argument and fix output error for short help. * Add unit tests for location download * Add extra unit test for locations
This commit is contained in:
parent
1ab9e44104
commit
be5d02ef3b
@ -85,6 +85,11 @@ Instaloader supports the following targets:
|
|||||||
- ``"#hashtag"``
|
- ``"#hashtag"``
|
||||||
Posts with a certain **hashtag** (the quotes are usually necessary),
|
Posts with a certain **hashtag** (the quotes are usually necessary),
|
||||||
|
|
||||||
|
- ``%location id``
|
||||||
|
Posts tagged with a given location; the location ID is the numerical ID
|
||||||
|
Instagram labels a location with (e.g.
|
||||||
|
\https://www.instagram.com/explore/locations/**362629379**/plymouth-naval-memorial/).
|
||||||
|
|
||||||
- ``:stories``
|
- ``:stories``
|
||||||
The currently-visible **stories** of your followees (requires
|
The currently-visible **stories** of your followees (requires
|
||||||
:option:`--login`),
|
:option:`--login`),
|
||||||
@ -126,7 +131,7 @@ Filename Specification
|
|||||||
^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
For each target, Instaloader creates a directory named after the target,
|
For each target, Instaloader creates a directory named after the target,
|
||||||
i.e. ``profile``, ``#hashtag``, ``:feed``, etc. and therein saves the
|
i.e. ``profile``, ``#hashtag``, ``%location id``, ``:feed``, etc. and therein saves the
|
||||||
posts in files named after the post's timestamp.
|
posts in files named after the post's timestamp.
|
||||||
|
|
||||||
:option:`--dirname-pattern` allows to configure the directory name of each
|
:option:`--dirname-pattern` allows to configure the directory name of each
|
||||||
|
@ -8,7 +8,7 @@ Instaloader is invoked with::
|
|||||||
$ instaloader [options] target [target ...]
|
$ instaloader [options] target [target ...]
|
||||||
|
|
||||||
where ``target`` is a ``profile``, a ``"#hashtag"``, ``@profile`` (all profiles
|
where ``target`` is a ``profile``, a ``"#hashtag"``, ``@profile`` (all profiles
|
||||||
that *profile* is following), or if logged in ``:feed`` (pictures from your
|
that *profile* is following), ``%location ID``, or if logged in ``:feed`` (pictures from your
|
||||||
feed), ``:stories`` (stories of your followees) or ``:saved`` (collection of
|
feed), ``:stories`` (stories of your followees) or ``:saved`` (collection of
|
||||||
posts marked as saved).
|
posts marked as saved).
|
||||||
|
|
||||||
@ -171,7 +171,7 @@ Which Posts to Download
|
|||||||
.. option:: --count COUNT, -c
|
.. option:: --count COUNT, -c
|
||||||
|
|
||||||
Do not attempt to download more than COUNT posts. Applies only to
|
Do not attempt to download more than COUNT posts. Applies only to
|
||||||
``#hashtag`` and ``:feed``.
|
``#hashtag``, ``%location id``, and ``:feed``.
|
||||||
|
|
||||||
|
|
||||||
Login (Download Private Profiles)
|
Login (Download Private Profiles)
|
||||||
|
@ -43,7 +43,7 @@ See :ref:`install` for more options on how to install Instaloader.
|
|||||||
|
|
||||||
instaloader [--comments] [--geotags] [--stories] [--highlights] [--tagged]
|
instaloader [--comments] [--geotags] [--stories] [--highlights] [--tagged]
|
||||||
[--login YOUR-USERNAME] [--fast-update]
|
[--login YOUR-USERNAME] [--fast-update]
|
||||||
profile | "#hashtag" |
|
profile | "#hashtag" | %location_id |
|
||||||
:stories | :feed | :saved
|
:stories | :feed | :saved
|
||||||
|
|
||||||
See :ref:`download-pictures-from-instagram` for a detailed introduction on how
|
See :ref:`download-pictures-from-instagram` for a detailed introduction on how
|
||||||
|
@ -20,7 +20,7 @@ def usage_string():
|
|||||||
return """
|
return """
|
||||||
{0} [--comments] [--geotags] [--stories] [--highlights] [--tagged]
|
{0} [--comments] [--geotags] [--stories] [--highlights] [--tagged]
|
||||||
{2:{1}} [--login YOUR-USERNAME] [--fast-update]
|
{2:{1}} [--login YOUR-USERNAME] [--fast-update]
|
||||||
{2:{1}} profile | "#hashtag" | :stories | :feed | :saved
|
{2:{1}} profile | "#hashtag" | %%location_id | :stories | :feed | :saved
|
||||||
{0} --help""".format(argv0, len(argv0), '')
|
{0} --help""".format(argv0, len(argv0), '')
|
||||||
|
|
||||||
|
|
||||||
@ -130,6 +130,9 @@ def _main(instaloader: Instaloader, targetlist: List[str],
|
|||||||
post_filter=post_filter)
|
post_filter=post_filter)
|
||||||
elif target[0] == '-':
|
elif target[0] == '-':
|
||||||
instaloader.download_post(Post.from_shortcode(instaloader.context, target[1:]), target)
|
instaloader.download_post(Post.from_shortcode(instaloader.context, target[1:]), target)
|
||||||
|
elif target[0] == "%":
|
||||||
|
instaloader.download_location(location=target[1:], max_count=max_count, fast_update=fast_update,
|
||||||
|
post_filter=post_filter)
|
||||||
elif target == ":feed":
|
elif target == ":feed":
|
||||||
instaloader.download_feed_posts(fast_update=fast_update, max_count=max_count,
|
instaloader.download_feed_posts(fast_update=fast_update, max_count=max_count,
|
||||||
post_filter=post_filter)
|
post_filter=post_filter)
|
||||||
@ -208,6 +211,7 @@ def main():
|
|||||||
help="Download all followees of profile. Requires --login. "
|
help="Download all followees of profile. Requires --login. "
|
||||||
"Consider using :feed rather than @yourself.")
|
"Consider using :feed rather than @yourself.")
|
||||||
g_targets.add_argument('_hashtag', nargs='*', metavar='"#hashtag"', help="Download #hashtag.")
|
g_targets.add_argument('_hashtag', nargs='*', metavar='"#hashtag"', help="Download #hashtag.")
|
||||||
|
g_targets.add_argument('_location', nargs='*', metavar='%location_id', help="Download %%location_id.")
|
||||||
g_targets.add_argument('_feed', nargs='*', metavar=":feed",
|
g_targets.add_argument('_feed', nargs='*', metavar=":feed",
|
||||||
help="Download pictures from your feed. Requires --login.")
|
help="Download pictures from your feed. Requires --login.")
|
||||||
g_targets.add_argument('_stories', nargs='*', metavar=":stories",
|
g_targets.add_argument('_stories', nargs='*', metavar=":stories",
|
||||||
|
@ -647,6 +647,55 @@ class Instaloader:
|
|||||||
if fast_update and not downloaded:
|
if fast_update and not downloaded:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
def get_location_posts(self, location: str) -> Iterator[Post]:
|
||||||
|
"""Get Posts which are listed by Instagram for a given Location.
|
||||||
|
|
||||||
|
:return: Iterator over Posts of a location's posts
|
||||||
|
"""
|
||||||
|
has_next_page = True
|
||||||
|
end_cursor = None
|
||||||
|
while has_next_page:
|
||||||
|
if end_cursor:
|
||||||
|
params = {'__a': 1, 'max_id': end_cursor}
|
||||||
|
else:
|
||||||
|
params = {'__a': 1}
|
||||||
|
location_data = self.context.get_json('explore/locations/{0}/'.format(location),
|
||||||
|
params)['graphql']['location']['edge_location_to_media']
|
||||||
|
yield from (Post(self.context, edge['node']) for edge in location_data['edges'])
|
||||||
|
has_next_page = location_data['page_info']['has_next_page']
|
||||||
|
end_cursor = location_data['page_info']['end_cursor']
|
||||||
|
|
||||||
|
def download_location(self, location: str,
|
||||||
|
max_count: Optional[int] = None,
|
||||||
|
post_filter: Optional[Callable[[Post], bool]] = None,
|
||||||
|
fast_update: bool = False) -> None:
|
||||||
|
"""Download pictures of one location.
|
||||||
|
|
||||||
|
To download the last 30 pictures with location 362629379, do::
|
||||||
|
|
||||||
|
loader = Instaloader()
|
||||||
|
loader.download_location(362629379, max_count=30)
|
||||||
|
|
||||||
|
:param location: Location to download, as Instagram numerical ID
|
||||||
|
:param max_count: Maximum count of pictures to download
|
||||||
|
:param post_filter: function(post), which returns True if given picture should be downloaded
|
||||||
|
:param fast_update: If true, abort when first already-downloaded picture is encountered
|
||||||
|
"""
|
||||||
|
self.context.log("Retrieving pictures for location {}...".format(location))
|
||||||
|
count = 1
|
||||||
|
for post in self.get_location_posts(location):
|
||||||
|
if max_count is not None and count > max_count:
|
||||||
|
break
|
||||||
|
self.context.log('[{0:3d}] %{1} '.format(count, location), end='', flush=True)
|
||||||
|
if post_filter is not None and not post_filter(post):
|
||||||
|
self.context.log('<skipped>')
|
||||||
|
continue
|
||||||
|
count += 1
|
||||||
|
with self.context.error_catcher('Download location {}'.format(location)):
|
||||||
|
downloaded = self.download_post(post, target='%' + location)
|
||||||
|
if fast_update and not downloaded:
|
||||||
|
break
|
||||||
|
|
||||||
@_requires_login
|
@_requires_login
|
||||||
def get_explore_posts(self) -> Iterator[Post]:
|
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.
|
||||||
|
@ -12,6 +12,7 @@ PROFILE_WITH_HIGHLIGHTS = 325732271
|
|||||||
PUBLIC_PROFILE = "selenagomez"
|
PUBLIC_PROFILE = "selenagomez"
|
||||||
PUBLIC_PROFILE_ID = 460563723
|
PUBLIC_PROFILE_ID = 460563723
|
||||||
HASHTAG = "kitten"
|
HASHTAG = "kitten"
|
||||||
|
LOCATION = "362629379"
|
||||||
OWN_USERNAME = "aandergr"
|
OWN_USERNAME = "aandergr"
|
||||||
NORMAL_MAX_COUNT = 2
|
NORMAL_MAX_COUNT = 2
|
||||||
PAGING_MAX_COUNT = 15
|
PAGING_MAX_COUNT = 15
|
||||||
@ -69,6 +70,15 @@ class TestInstaloaderAnonymously(unittest.TestCase):
|
|||||||
if count == PAGING_MAX_COUNT:
|
if count == PAGING_MAX_COUNT:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
def test_location_download(self):
|
||||||
|
self.L.download_location(LOCATION, NORMAL_MAX_COUNT)
|
||||||
|
|
||||||
|
def test_location_paging(self):
|
||||||
|
for count, post in enumerate(self.L.get_location_posts(LOCATION)):
|
||||||
|
print(post)
|
||||||
|
if count == PAGING_MAX_COUNT:
|
||||||
|
break
|
||||||
|
|
||||||
def test_get_id_by_username(self):
|
def test_get_id_by_username(self):
|
||||||
self.assertEqual(PUBLIC_PROFILE_ID,
|
self.assertEqual(PUBLIC_PROFILE_ID,
|
||||||
instaloader.Profile.from_username(self.L.context, PUBLIC_PROFILE).userid)
|
instaloader.Profile.from_username(self.L.context, PUBLIC_PROFILE).userid)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user