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:
Stijn Peeters 2018-12-17 21:35:31 +01:00 committed by Alexander Graf
parent 1ab9e44104
commit be5d02ef3b
6 changed files with 73 additions and 5 deletions

View File

@ -85,6 +85,11 @@ Instaloader supports the following targets:
- ``"#hashtag"``
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``
The currently-visible **stories** of your followees (requires
:option:`--login`),
@ -126,7 +131,7 @@ Filename Specification
^^^^^^^^^^^^^^^^^^^^^^
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.
:option:`--dirname-pattern` allows to configure the directory name of each

View File

@ -8,7 +8,7 @@ Instaloader is invoked with::
$ instaloader [options] target [target ...]
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
posts marked as saved).
@ -171,7 +171,7 @@ Which Posts to Download
.. option:: --count COUNT, -c
Do not attempt to download more than COUNT posts. Applies only to
``#hashtag`` and ``:feed``.
``#hashtag``, ``%location id``, and ``:feed``.
Login (Download Private Profiles)

View File

@ -43,7 +43,7 @@ See :ref:`install` for more options on how to install Instaloader.
instaloader [--comments] [--geotags] [--stories] [--highlights] [--tagged]
[--login YOUR-USERNAME] [--fast-update]
profile | "#hashtag" |
profile | "#hashtag" | %location_id |
:stories | :feed | :saved
See :ref:`download-pictures-from-instagram` for a detailed introduction on how

View File

@ -20,7 +20,7 @@ def usage_string():
return """
{0} [--comments] [--geotags] [--stories] [--highlights] [--tagged]
{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), '')
@ -130,6 +130,9 @@ def _main(instaloader: Instaloader, targetlist: List[str],
post_filter=post_filter)
elif target[0] == '-':
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":
instaloader.download_feed_posts(fast_update=fast_update, max_count=max_count,
post_filter=post_filter)
@ -208,6 +211,7 @@ def main():
help="Download all followees of profile. Requires --login. "
"Consider using :feed rather than @yourself.")
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",
help="Download pictures from your feed. Requires --login.")
g_targets.add_argument('_stories', nargs='*', metavar=":stories",

View File

@ -647,6 +647,55 @@ class Instaloader:
if fast_update and not downloaded:
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
def get_explore_posts(self) -> Iterator[Post]:
"""Get Posts which are worthy of exploring suggested by Instagram.

View File

@ -12,6 +12,7 @@ PROFILE_WITH_HIGHLIGHTS = 325732271
PUBLIC_PROFILE = "selenagomez"
PUBLIC_PROFILE_ID = 460563723
HASHTAG = "kitten"
LOCATION = "362629379"
OWN_USERNAME = "aandergr"
NORMAL_MAX_COUNT = 2
PAGING_MAX_COUNT = 15
@ -69,6 +70,15 @@ class TestInstaloaderAnonymously(unittest.TestCase):
if count == PAGING_MAX_COUNT:
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):
self.assertEqual(PUBLIC_PROFILE_ID,
instaloader.Profile.from_username(self.L.context, PUBLIC_PROFILE).userid)