Compare commits

...
53 Commits
Author SHA1 Message Date
Alexander Graf 7862bdb173 First Release Candidate for Version 3.0 2017-08-29 12:19:27 +02:00
Alexander Graf e143096140 Minor documentation fixes 2017-08-29 12:13:38 +02:00
Alexander Graf 5b5d540310 caption_hashtags property for only-if evaluation
caption_hashtags is a list of all hashtags that are mentioned in the
Post's caption. It allows to easily filter Posts that have multiple
hashtags, and as such fixes #24.

Further, the documentation of --only-if has been completed by linking to
a description of the syntax in the Python documentation, and by linking
to a list of all defined properties with their meanings. So, this commit
also closes #42.
2017-08-29 11:03:12 +02:00
Alexander Graf d84136b2dd Minor doc-related improvements 2017-08-28 21:45:43 +02:00
Alexander Graf fbbbfdf53c Do not repeat errors that were fixed by retrying 2017-08-28 21:05:46 +02:00
Lars Lindqvist 2dbd510486 Ignore device_timestamp for stories.
I've come across several implausible values for `device_timestamp` (such as 182428140, which would be in october 1975, assuming millisecond). I guess it is due to improperly configured phones, or maybe some third party software that's mangeling the EXIF data on images before posting. Anyway. Since by the nature of stories, the `taken_at` timestamp (presumably when the instagram servers received the post) ought to be approximately when an image was actually taken. So there's no real value trying to use the timestamp provided by the photo-taking-device.
2017-08-27 22:51:30 +02:00
Alexander Graf e748b53b6a Add layout template extension
(forgot to commit it. Sorry...)
2017-08-26 13:49:54 +02:00
Alexander Graf 5aff8273b0 Minor tweaks to documentation 2017-08-26 12:42:04 +02:00
Alexander Graf 1f89dba702 Minor Sphinx theme switch
(The links sidebar was not shown on readthedocs.io)
2017-08-25 22:46:36 +02:00
Alexander Graf 90c5cc3c72 Minor documentation tunings 2017-08-25 22:38:54 +02:00
Alexander Graf d7b1e2002d Avoid unicode character in docs/conf.py
(caused problems on readthedocs)
2017-08-25 19:41:35 +02:00
Alexander Graf ec2e09edcf Create minimal documentation with Sphinx 2017-08-25 16:54:36 +02:00
André Koch-Kramer 1928db63bb Added ability to download videos within sidecars
Closes #41
2017-08-25 13:50:58 +02:00
Alexander Graf ca54088bdc Merge branch 'v3-dev' 2017-08-24 21:38:50 +02:00
André Koch-Kramer 1a26d7336c Fix TypeError on retry in get_json() 2017-08-24 21:17:49 +02:00
André Koch-Kramer bb71c40b56 Wait smarter to avoid HTTP error code 429
Additional sleeps are necessary because Instagram is rate limiting
GraphQL queries. The error does not occur if not more than 100 queries
are made in a sliding window of eleven minutes.
Ports a894c2d to version 3.
2017-08-24 18:30:46 +02:00
Alexander Graf 2e47642f74 Remove --shorter-output from --help 2017-08-24 17:51:39 +02:00
Alexander Graf 9939eab8ec Minor fixes to README.rst 2017-08-24 17:23:46 +02:00
Alexander Graf c61d6a93b2 Restructure README.rst 2017-08-24 17:10:32 +02:00
Alexander Graf bbdd3873e2 --skip-videos -> --no-videos; no --shorter-output 2017-08-24 16:03:24 +02:00
Alexander Graf 43bfd6b5ab Put all our GitHub keywords in setup.py 2017-08-24 15:54:07 +02:00
Alexander Graf dd99417e7b Require win_unicode_console on Windows Python 3.5 2017-08-24 15:40:41 +02:00
Alexander Graf 8cf1997460 Evaluate --only-if filter smarter
If --only-if='likes>1000 or viewer_has_liked' is given, it is not
neccessary to evaluate viewer_has_liked if the post has more than 1000
likes. The new implementation smartly handles this case.
2017-08-23 15:38:16 +02:00
Alexander Graf ce38f5880f Call download_stories in _error_catcher
download_stories() may trow a BadResponseException, which should not
cause abortion of download_profile(). Now, all calls of
download_stories() are within an _error_catcher context.
2017-08-22 09:21:47 +02:00
Lars Lindqvist 79f143b1f8 Minor stories-related cleanup 2017-08-20 20:32:45 +02:00
Alexander Graf ad34fc09b6 Wait longer after HTTP err 429 (Too Many Requests) 2017-08-20 11:48:19 +02:00
Alexander Graf 566ef02b94 Change sleep interval between requests
These are now adapted to how many requests have already been done. With
the current settings, Instaloader does not more than

12 request in the first ten seconds,
28 requests in the first minute,
40 requests in the first two minutes,
63 requests in the first five minutes,
90 requests in the first ten minutes,
and after that 50 requests per ten minutes.

This should make it less likely that Instaloader is rate-limited by
Instagram, while still being fast if downloading only a few posts.

Further, option --no-sleep is hidden in --help output and README.rst.
2017-08-20 11:31:46 +02:00
Alexander Graf 6300c217b3 Fixes to instaloader.Post metadata retrieval
Fixes a KeyError which occurred when fetching some information note
available in Post._node. Makes all properties not throw exceptions,
rather they return None if the information cannot be obtained.
sidecar_edges is now a method get_sidecar_edges(). get_comments() does
not do any additional requests if the post does not have any comment.

Fixes #39.
2017-08-20 10:37:58 +02:00
Alexander Graf d967400cb4 Fix downloading hashtags with unicode characters
Non-latin characters in the referer string used in the HTTP headers are
now properly quoted.
2017-08-19 22:44:08 +02:00
Alexander Graf 9ee98a2925 Use lowercase hashtags and profile names
Since both hashtags and profile names are case insensitive, this might
be a useful normalization and could workaround some user-induced bugs.
2017-08-19 18:26:42 +02:00
Alexander Graf ee9993d7c2 Filter posts with --only-if=FILTER
where FILTER is a boolean expression in python syntax where all names
are evaluated to instaloader.Post properties.

Examples:

instaloader --login=USER --only-if='viewer_has_liked' :feed

instaloader --only-if='likes>1000 and comments>5' profile
2017-08-19 17:54:23 +02:00
Lars Lindqvist 09d2592635 Use anonymous session for HEAD request.
The default Instaloader headers aren't passed with a simple `requests.head()`, so leakage of user agents such as `"python-requests/2.18.1"` will occur.
2017-08-19 15:17:43 +02:00
Alexander Graf 0f64768dd8 Post class representing an Instagram Post
This simplifies accessing properties of a Post. Method download_post()
remains to class Instaloader rather than Post, as it fits there better.

Also, since it is now easily possible, all download_*() functions now
have a filter_func parameter. Its meaning has been reverted to be
consistent of how a filter is commonly understood: A post is downloaded
iff filter_func is None or evaluates to True.

Post.get_comments() foreports commit 86fb80d ("Avoid GraphQL queries if
all comments in metadata").
2017-08-19 13:02:49 +02:00
Lars Lindqvist ccdac0305f Simplify profpic regex.
The original method of substituting 2048x2048 for whatever resolution was given seemed somewhat convoluted. This accomplishes the same thing, except raising an exception if the given url is not on the right domain.
2017-08-13 23:37:53 +02:00
André Koch-Kramer d28bd3ab52 Release of version 2.2.3 2017-08-13 14:39:35 +02:00
Alexander Graf 4ce6826f82 Don't retry downloads with 404 status
Instead of retrying a download attempt answered with a 404, the download
is aborted after the first attempt. Thanks to the _error_catcher(), a
message is printed and Instaloader goes on with the next files to
download.

Further, this commit removes the unneeded NodeUnavailableException and
adjusts docstrings accordingly.
2017-08-13 12:57:31 +02:00
Alexander Graf adeb588471 Fix KeyError when generating NodeUnavailable error
Fixes #32.
2017-08-13 12:51:39 +02:00
Alexander Graf 57329482f3 Evaluate status field in JSON response
In GraphQL queries, this field should be set to "ok". Checking this
addresses #31 (and maybe other issues as well) and simplifies error
handling.
2017-08-13 11:02:47 +02:00
André Koch-Kramer a894c2d206 Wait to avoid error code 429 (Too Many Requests)
Additional sleeps are necessary because Instagram is rate limiting
GraphQL queries. The error does not occur if not more than 100 queries
are made in a sliding window of eleven minutes.
Fixes #29
2017-08-12 19:57:54 +02:00
André Koch-Kramer 86fb80d7e4 Avoid GraphQL queries if all comments in metadata
If get_node_metadata() is able to provide all comments of a node, no
additional query is needed. Especially GraphQL queries are time
expensive because no more than 100 can be queried in ten minutes. Since
get_node_metadata() does not use GraphQL queries, this is a usefull
tradeoff.
+ Additional error handling
2017-08-12 19:04:22 +02:00
Alexander Graf dcc48e37df Minor fix regarding setup.py
Now, setup.py does not assume to be called from the path where the
source tree resides. This fixes getting the long_description and the
version if setup.py is called from outside.
2017-08-11 20:34:47 +02:00
Alexander Graf 117124d1dd Options --no-captions and --no-geotags
These options instruct instaloader to not save captions or geotags
respectively, even if the regarding information can be obtained without
any additional queries to Instagram.

This feature was proposed in #25, and thus this commit should close #25.
2017-08-11 19:54:10 +02:00
Alexander Graf 0d9af81ae7 Minor enhancements
Get rid of NonfatalException (an exception is nonfatal iff it is
catched somewhere)

Foreport fixes for #26 and #30.

The current __sersion__ string is now kept in instaloader.py rather than
setup.py. This lets instaloader.__version__ always deliver the version
of the actually-loaded Instaloader module.

Minor changes to README.rst, error handling and which class methods are
public.

With these and the changes of the previous commit, we saved 31 lines of
code, indicating that it might be easier to understand and to maintain.
2017-08-11 18:20:58 +02:00
Alexander Graf 9eddc03cf1 Don't rely on fromtimestamp() raising ValueError
Instead of relying on that datetime.fromtimestamp() raises ValueError if
the timestamp of a story is in milliseconds rather than seconds, we now
compare the timestamp value with a timestamp of the year 2286 to decide
whether to divide it by 1000 or not.

This is motivated by #30, where an OSError is raised in
datetime.fromtimestamp() under Windows.
2017-08-09 18:27:05 +02:00
André Koch-Kramer 42864997b3 get_node_metadata() now also catches TypeError
Concerns #26
2017-08-08 00:43:52 +02:00
Alexander Graf 58882f508e Major code cleanup
Remove many code duplications, merely by using more pythonic idioms.

Use GraphQL more often.

Better cope with errors: All requests can be retried; failed requests do
not cause program termination; all error strings are repeated to the
user at the end of execution.

download_post() (formerly download_node()) does not repeat node metadata
request (before this commit, this request was executed up to three
times).
2017-08-06 19:27:46 +02:00
Alexander Graf 5d83a4ccf6 Release of version 2.2.2 2017-08-06 10:49:36 +02:00
Alexander Graf 56c8ac729f Cleanup .gitignore 2017-08-05 13:43:26 +02:00
André Koch-Kramer 987d95c048 Forgot "file=" in some print statements 2017-08-05 00:22:43 +02:00
André Koch-Kramer 9b5d4e34fc Raise and catch NodeUnavailableException
In case a node can not be downloaded or its metadata is needed and can
not be retrieved, a NodeUnavailableException is raised and the according
node will be skipped.
Concerns #26
2017-08-04 19:19:56 +02:00
Alexander Graf 838ea645a8 Let Travis automatically deploy releases to PyPi 2017-08-04 11:18:20 +02:00
Alexander Graf 01fc150e78 Release of version 2.2.1 2017-08-03 07:33:38 +02:00
Alexander Graf 46ac119a10 Fix AttributeError when not logging in
Mentioned in #26. Bug was introduced in 0ad50c1.
2017-08-03 07:31:58 +02:00
17 changed files with 1666 additions and 563 deletions
-3
View File
@@ -1,4 +1 @@
__pycache__/
build/
dist/
instaloader.egg-info/
+8
View File
@@ -10,3 +10,11 @@ install:
- pip install pylint requests
script:
- python3 -m pylint -r n -d bad-whitespace,missing-docstring,too-many-arguments,locally-disabled,line-too-long,too-many-public-methods,too-many-lines,too-many-instance-attributes,too-many-locals,too-many-branches,too-many-statements instaloader
deploy:
provider: pypi
user: aandergr
on:
tags: true
python: 3.6
password:
secure: KBsATtXNnEzqr0X2ALdyx2zaYj2EUfK8YjP4jEOilBELpJ4evHYXVgRkn9CKXGsD7LKbWmR3gz2rr8NZ6U42pHw0fpQHizz91MpJSKbNY7RZZh4UVWMDh81izeglchWAF+m3Wdpxpo/GBDbrZIbRQFhPgB+kjSzJo3uiCS+yVXtGG+6csJHzd0WkJjsVHz+XSdxyOiLy/qFF3nmY6ZrVrgw6WbX2YLbx9je6DxK3YRrnBqgMgH7Ui2xWWbgJCk3KKpQTMiMKnTa9mdeMJAd26YMCz1gG/X7ck6VNly/6bs5+Zq7/bU0dSolhPBhNthyccYdS0IeGPdPF3YqatblJ4fLGTfHn8oJs3/T11yIW9pvOT3UftEynPOUX+en2Qnb/3WfgwUfVts8i8DAYgEFLGEzbF5XMGy4gwAD/bZ0JW0pmgAG2PZsdOokAt+ydlNj/uxq1GFW2doZbul6k4t03p1mik6itYscvuhKoEm8Yw6wr1P9aDvMR6FFpSu4gDt2XrKmRmWkSXxpNeCfZujTjyAnISxLVQGSY7BG5K0Z2HbDtqqNz9nj4WgNGzZmngiRofCevWR+vPIDUDW7NCX03csfcFS9RRllBO/BrL7NVUV+Qp9dfTQYp+8SqYLWgHtDoXdgyMREjJaAmbx2u/TYTY8bADZ5F3vGUpv2j5hP3NTU=
+135 -65
View File
@@ -1,21 +1,14 @@
Instaloader
===========
Download pictures (or videos) along with their captions and other metadata
from Instagram.
Installation
------------
Instaloader is written in Python, thus ensure having
`Python <https://www.python.org/>`__ (at least version 3.5) installed.
.. installation-start
If you intend to use this tool under Windows, it is recommended
to install
`win-unicode-console <https://pypi.python.org/pypi/win_unicode_console>`__.
If you have `pip <https://pypi.python.org/pypi/pip>`__ installed, you
may install Instaloader using
Instaloader requires `Python <https://www.python.org/>`__, at least
version 3.5. If you have `pip <https://pypi.python.org/pypi/pip>`__
installed, you may install Instaloader using
::
@@ -31,12 +24,16 @@ Alternatively, to get the most current version of Instaloader from our
(pass ``--upgrade`` to upgrade if Instaloader is already installed)
Instaloader requires
`requests <https://pypi.python.org/pypi/requests>`__, which
`requests <http://python-requests.org/>`__, which
will be installed automatically, if it is not already installed.
How to automatically download pictures from Instagram
.. installation-end
How to Automatically Download Pictures from Instagram
-----------------------------------------------------
.. basic-usage-start
To **download all pictures and videos of a profile**, as well as the
**profile picture**, do
@@ -53,8 +50,10 @@ To later **update your local copy** of that profiles, you may run
instaloader --fast-update profile [profile ...]
When ``--fast-update`` is given, Instaloader stops when arriving at
the first already-downloaded picture.
If ``--fast-update`` is given, Instaloader stops when arriving at the
first already-downloaded picture. When updating profiles, Instaloader
automatically **detects profile name changes** and renames the target
directory accordingly.
Instaloader can also be used to **download private profiles**. To do so,
invoke it with
@@ -63,61 +62,113 @@ invoke it with
instaloader --login=your_username profile [profile ...]
When invoked like this, it also **stores the session cookies** in a file
in your temporary directory, which will be reused later when ``--login`` is given. So
you can download private profiles **non-interactively** when you already
have a valid session cookie file.
When logging in, Instaloader **stores the session cookies** in a file in
your temporary directory, which will be reused later the next time
``--login`` is given. So you can download private profiles
**non-interactively** when you already have a valid session cookie file.
Besides downloading private profiles, being logged in allows to
**download stories**:
What to Download
^^^^^^^^^^^^^^^^
::
Instaloader does not only download media by-profile. More generally, you
may specify the following targets:
instaloader --login=your_username --stories profile [profile ...]
- ``profile``: Public profile, or private profile with ``--login``,
You may also download
**the most recent pictures by hashtag**:
- ``"#hashtag"``: Posts with a certain **hashtag** (the quotes are
usually neccessary),
::
- ``:stories``: The currently-visible **stories** of your followees
(requires ``--login``),
instaloader "#hashtag"
- ``:feed``: Your **feed** (requires ``--login``),
This downloads the requested posts into a directory named according to the specified
hashtag and the filenames correspond to the timestamp of the posts.
As with all download tasks, this behavior can easily be customized, for example
encode the poster's profile name in the filenames:
- ``@profile``: All profiles which are followed by ``profile``, i.e. the
*followees* of ``profile`` (requires ``--login``).
Instaloader goes through all media matching the specified targets and
downloads the pictures and videos and their captions. You can specify
``--comments`` to also **download comments** of each post and
``--geotags`` to **download geotags** of each post and save them as
Google Maps link. For each profile you download, ``--stories``
instructs Instaloader to **download the user's stories**.
Filename Specification
^^^^^^^^^^^^^^^^^^^^^^
For each target, Instaloader creates a directory named after the target,
i.e. ``profile``, ``#hashtag``, ``:feed``, etc. and therein saves the
posts in files named after the post's timestamp.
``--dirname-pattern`` allows to configure the directory name of each
target. The default is ``--dirname-pattern={target}``. In the dirname
pattern, the token ``{target}`` is replaced by the target name, and
``{profile}`` is replaced by the owner of the post which is downloaded.
``--filename-pattern`` configures the path of the post's files relative
to the target directory. The default is ``--filename-pattern={date}``.
The tokens ``{target}`` and ``{profile}`` are replaced like in the
dirname pattern. Further, the tokens ``{date}`` and ``{shortcode}`` are
defined.
For example, encode the poster's profile name in the filenames with:
::
instaloader --filename-pattern={date}_{profile} "#hashtag"
If you want to **download all followees of a given profile**, call
The pattern string is formatted with Python's string formatter. This
gives additional flexibilty for pattern specification. For example,
`strftime-style formatting options <https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior>`__
are supported for the post's
timestamp. The default for ``{date}`` is ``{date:%Y-%m-%d_%H-%M-%S}``.
Filter Posts
^^^^^^^^^^^^
The ``--only-if`` option allows to specify criterias that posts have to
meet to be downloaded. If not given, all posts are downloaded. It must
be a boolean Python expression where the variables ``likes``,
``comments``, ``viewer_has_liked``, ``is_video``, and many
more are defined.
A few examples:
To **download the pictures from your feed that you have liked**:
::
instaloader --login=your_username @profile
instaloader --login=your_username --only-if=viewer_has_liked :feed
To **download all the pictures from your feed which you have liked**, call
Or you might only want to download **posts that either you liked or were
liked by many others**:
::
instaloader --login=your_username :feed-liked
instaloader --login=your_username --only-if="likes>100 or viewer_has_liked" profile
or to **download all pictures from your feed**:
Or you may **skip videos**:
::
instaloader --login=your_username :feed-all
instaloader --only-if="not is_video" target
**Download all stories** from the profiles you follow:
Or you may filter by hashtags that occur in the Post's caption. For
example, to download posts of kittens that are cute: ::
::
instaloader --only-if="'cute' in caption_hashtags" "#kitten"
.. basic-usage-end
(For a more complete description of the ``-only-if`` option, refer to
the `Instaloader Documentation <https://instaloader.readthedocs.io/basic-usage.html#filter-posts>`__)
instaloader --login=your_username --filename-pattern={date}_{profile} :stories
Advanced Options
----------------
.. cli-options-start
The following flags can be given to Instaloader to specify how profiles should
be downloaded.
@@ -134,25 +185,34 @@ has been renamed, Instaloader automatically **finds it by its unique ID** and
renames the folder likewise.
Instead of a *profile* or a *#hashtag*, the special targets
``:feed-all`` (pictures from your feed),
``:feed-liked`` (pictures from your feed which you liked), and
``:feed`` (pictures from your feed) and
``:stories`` (stories of your followees) can be specified.
--profile-pic-only Only download profile picture.
--skip-videos Do not download videos.
--no-videos Do not download videos.
--geotags **Download geotags** when available. Geotags are stored as
a text file with the location's name and a Google Maps
link. This requires an additional request to the
Instagram server for each picture, which is why it is
disabled by default.
--no-geotags Do not store geotags, even if they can be obtained
without any additional request.
--comments Download and update comments for each post. This
requires an additional request to the Instagram server
for each post, which is why it is disabled by default.
--no-captions Do not store media captions, although no additional
request is needed to obtain them.
--stories Also **download stories** of each profile that is
downloaded. Requires ``--login``.
--stories-only Rather than downloading regular posts of each
specified profile, only download stories.
Requires ``--login``.
--only-if filter Expression that, if given, must evaluate to True for each post to
be downloaded. Must be a syntactically valid python
expression. Variables are evaluated to
``instaloader.Post`` attributes.
Example: ``--only-if=viewer_has_liked``.
When to Stop Downloading
^^^^^^^^^^^^^^^^^^^^^^^^
@@ -204,21 +264,22 @@ How to Download
Defaults to ``{date:%Y-%m-%d_%H-%M-%S}``.
--user-agent USER_AGENT User Agent to use for HTTP requests. Per default,
Instaloader pretends being Chrome/51.
--no-sleep Do not sleep between requests to Instagram's servers.
This makes downloading faster, but may be suspicious.
Miscellaneous Options
^^^^^^^^^^^^^^^^^^^^^
--shorter-output Do not display captions while downloading.
--quiet Disable user interaction, i.e. do not print messages
(except errors) and fail if login credentials are
needed but not given. This makes Instaloader
**suitable as a cron job**.
.. cli-options-end
Usage as Python module
----------------------
.. as-module-intro-start
You may also use parts of Instaloader as library to do other interesting
things.
@@ -234,35 +295,29 @@ For example, to get a list of all followees and a list of all followers of a pro
# Login
loader.interactive_login(USERNAME)
# Retrieve followees
followees = loader.get_followees(PROFILE)
# Print followees
print(PROFILE + " follows these profiles:")
for f in followees:
for f in loader.get_followees(PROFILE):
print("\t%s\t%s" % (f['username'], f['full_name']))
# Retrieve followers
followers = loader.get_followers(PROFILE)
# Print followers
print("Followers of " + PROFILE + ":")
for f in followers:
for f in loader.get_followers(PROFILE):
print("\t%s\t%s" % (f['username'], f['full_name']))
Then, you may download all pictures of all followees with
.. code:: python
for f in followees:
try:
loader.download(f['username'])
except instaloader.NonfatalException:
pass
for f in loader.get_followees(PROFILE):
loader.download_profile(f['username'])
You could also download your last 20 liked pics with
.. code:: python
loader.download_feed_pics(max_count=20, fast_update=True,
filter_func=lambda node:
not node["likes"]["viewer_has_liked"] if "likes" in node else not node["viewer_has_liked"])
loader.download_feed_posts(max_count=20, fast_update=True,
filter_func=lambda post: post.viewer_has_liked)
To download the last 20 pictures with hashtag #cat, do
@@ -270,11 +325,14 @@ To download the last 20 pictures with hashtag #cat, do
loader.download_hashtag('cat', max_count=20)
If logged in, Instaloader is also able to download user stories:
Generally, Instaloader provides methods to iterate over the Posts from
a certain source.
.. code:: python
loader.download_stories()
for post in loader.get_hashtag_posts('cat'):
# post is an instance of instaloader.Post
self.download_post(post, target='#cat')
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
@@ -290,10 +348,22 @@ get the current username of a profile, given this unique ID
.. code:: python
loader.get_username_by_id(followees[0]['id'])
loader.get_username_by_id(loader.get_followees()[0]['id'])
.. as-module-intro-end
Refer to the
`Instaloader Documentation <https://instaloader.readthedocs.io/as-module.html>`__ for
more information.
Disclaimer
----------
This code is in no way affiliated with, authorized, maintained or endorsed by Instagram or any of its affiliates or
subsidiaries. This is an independent and unofficial project. Use at your own risk.
.. disclaimer-start
Instaloader is in no way affiliated with, authorized, maintained or endorsed by Instagram or any of its affiliates or
subsidiaries. This is an independent and unofficial project. Use at your own risk.
.. disclaimer-end
Instaloader is licensed under an MIT license. Refer to ``LICENSE`` file for more information.
+1
View File
@@ -0,0 +1 @@
_build/
+225
View File
@@ -0,0 +1,225 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " applehelp to make an Apple Help Book"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " epub3 to make an epub3"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " coverage to run coverage check of the documentation (if enabled)"
@echo " dummy to check syntax errors of document sources"
.PHONY: clean
clean:
rm -rf $(BUILDDIR)/*
.PHONY: html
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
.PHONY: dirhtml
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
.PHONY: singlehtml
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
.PHONY: pickle
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
.PHONY: json
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
.PHONY: htmlhelp
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
.PHONY: qthelp
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Instaloader.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Instaloader.qhc"
.PHONY: applehelp
applehelp:
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
@echo
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
@echo "N.B. You won't be able to view it unless you put it in" \
"~/Library/Documentation/Help or install it in your application" \
"bundle."
.PHONY: devhelp
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/Instaloader"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Instaloader"
@echo "# devhelp"
.PHONY: epub
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
.PHONY: epub3
epub3:
$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
@echo
@echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
.PHONY: latex
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
.PHONY: latexpdf
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
.PHONY: latexpdfja
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
.PHONY: text
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
.PHONY: man
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
.PHONY: texinfo
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
.PHONY: info
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
.PHONY: gettext
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
.PHONY: changes
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
.PHONY: linkcheck
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
.PHONY: doctest
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
.PHONY: coverage
coverage:
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
@echo "Testing of coverage in the sources finished, look at the " \
"results in $(BUILDDIR)/coverage/python.txt."
.PHONY: xml
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
.PHONY: pseudoxml
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
.PHONY: dummy
dummy:
$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
@echo
@echo "Build finished. Dummy builder generates no files."
+1
View File
@@ -0,0 +1 @@
<h2>Instaloader</h2>
+4
View File
@@ -0,0 +1,4 @@
{% extends "!layout.html" %}
{% block extrahead %}
<script async defer src="https://buttons.github.io/buttons.js"></script>
{% endblock %}
+18
View File
@@ -0,0 +1,18 @@
{% if next %}
<h3>Next</h3>
<ul>
<li class="toctree-l1"><a class="reference internal" href="{{ next.link|e }}">{{ next.title }}</a></li>
</ul>
{% endif %}
<h3>Links</h3>
<ul>
<li><a href="https://github.com/Thammus/instaloader/issues">Issue Tracker</a></li>
<li><a href="https://github.com/Thammus/instaloader/releases">Version History</a></li>
<li><a href="https://pypi.python.org/pypi/instaloader">Instaloader on PyPI</a></li>
<li><a href="https://github.com/Thammus/instaloader">Instaloader on GitHub</a></li>
</ul>
<ul>
<li>
<a class="github-button" href="https://github.com/Thammus/Instaloader" data-icon="octicon-star" data-size="large" data-show-count="true" aria-label="Star Thammus/Instaloader on GitHub">Star</a>
</li>
</ul>
+26
View File
@@ -0,0 +1,26 @@
Usage as Python Module
----------------------
Introduction
^^^^^^^^^^^^
.. include:: ../README.rst
:start-after: as-module-intro-start
:end-before: as-module-intro-end
``Instaloader`` (Main Class)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. autoclass:: instaloader.Instaloader
:members:
:undoc-members:
.. _post-class:
``Post`` Class
^^^^^^^^^^^^^^
.. autoclass:: instaloader.Post
:members:
:undoc-members:
+15
View File
@@ -0,0 +1,15 @@
Download Pictures from Instagram
---------------------------------
Basic Usage
^^^^^^^^^^^
.. include:: ../README.rst
:start-after: basic-usage-start
:end-before: basic-usage-end
.. (continuation of --only-if explanation)
The given string is evaluated as a
`Python boolean expression <https://docs.python.org/3/reference/expressions.html#boolean-operations>`__,
where all occuring variables are properties of the :ref:`post-class`.
+6
View File
@@ -0,0 +1,6 @@
Command Line Options
====================
.. include:: ../README.rst
:start-after: cli-options-start
:end-before: cli-options-end
+346
View File
@@ -0,0 +1,346 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Instaloader documentation build configuration file, created by
# sphinx-quickstart on Fri Aug 25 15:38:26 2017.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
import sys
sys.path.insert(0, os.path.abspath('..'))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx_autodoc_typehints',
'sphinx.ext.githubpages',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The encoding of source files.
#
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'Instaloader'
copyright = '2017, Alexander Graf and Andre Koch-Kramer'
author = 'Alexander Graf and Andre Koch-Kramer'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
#version = '3.0'
# The full version, including alpha/beta/rc tags.
#release = '3.0rc0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#
# today = ''
#
# Else, today_fmt is used as the format for a strftime call.
#
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The reST default role (used for this markup: `text`) to use for all
# documents.
#
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#
add_module_names = False
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
# keep_warnings = False
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
html_theme_options = {
'show_powered_by': False,
'sidebar_width': '290px',
'page_width': '935px' }
# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []
# The name for this set of Sphinx documents.
# "<project> v<release> documentation" by default.
#
# html_title = 'Instaloader v3.0rc0'
# A shorter title for the navigation bar. Default is the same as html_title.
#
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#
# html_logo = None
# The name of an image file (relative to this directory) to use as a favicon of
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#
# html_extra_path = []
# If not None, a 'Last updated on:' timestamp is inserted at every page
# bottom, using the given strftime format.
# The empty string is equivalent to '%b %d, %Y'.
#
# html_last_updated_fmt = None
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#
html_sidebars = {'**': ["caption.html", "globaltoc.html", "relations.html", "links.html"] }
# Additional templates that should be rendered to pages, maps page names to
# template names.
#
# html_additional_pages = {}
# If false, no module index is generated.
#
html_domain_indices = False
# If false, no index is generated.
#
html_use_index = False
# If true, the index is split into individual pages for each letter.
#
# html_split_index = False
# If true, links to the reST sources are added to the pages.
#
html_show_sourcelink = False
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#
html_show_sphinx = False
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#
html_show_copyright = False
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None
# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja'
# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh'
#
# html_search_language = 'en'
# A dictionary with options for the search language support, empty by default.
# 'ja' uses this config value.
# 'zh' user can custom change `jieba` dictionary path.
#
# html_search_options = {'type': 'default'}
# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#
# html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
htmlhelp_basename = 'Instaloaderdoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'Instaloader.tex', 'Instaloader Documentation',
'Alexander Graf, André Koch-Kramer', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#
# latex_use_parts = False
# If true, show page references after internal links.
#
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
#
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
#
# latex_appendices = []
# It false, will not define \strong, \code, itleref, \crossref ... but only
# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added
# packages.
#
# latex_keep_old_macro_names = True
# If false, no module index is generated.
#
# latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'instaloader', 'Instaloader Documentation',
[author], 1)
]
# If true, show URL addresses after external links.
#
# man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'Instaloader', 'Instaloader Documentation',
author, 'Instaloader', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#
# texinfo_appendices = []
# If false, no module index is generated.
#
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#
# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#
# texinfo_no_detailmenu = False
+60
View File
@@ -0,0 +1,60 @@
Instaloader
===========
**Instaloader** is a tool to download pictures (or videos) along with
their captions and other metadata from Instagram.
With `Python <https://www.python.org/>`__ installed, do:
::
$ pip3 install instaloader
$ instaloader profile [profile ...]
**Instaloader**
- downloads **public and private profiles, hashtags, user stories and
feeds**,
- downloads **comments, geotags and captions** of each post,
- automatically **detects profile name changes** and renames the target
directory accordingly,
- allows **fine-grained customization** of filters and where to store
downloaded media,
- is free `Open Source <https://github.com/Thammus/instaloader>`__
software written in Python.
Table of Contents
-----------------
.. toctree::
:maxdepth: 2
installation
basic-usage
cli-options
as-module
Useful Links
------------
- `Git Repository (on GitHub) <https://github.com/Thammus/instaloader>`__
- `PyPI Project Page <https://pypi.python.org/pypi/instaloader>`__
- `Issue Tracker / Bug Tracker <https://github.com/Thammus/instaloader/issues>`__
- `Version History <https://github.com/Thammus/instaloader/releases>`__
.. include:: ../README.rst
:start-after: disclaimer-start
:end-before: disclaimer-end
..
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
+11
View File
@@ -0,0 +1,11 @@
Installation
============
.. include:: ../README.rst
:start-after: installation-start
:end-before: installation-end
If you do not want to use pip, even though it is highly recommended,
and prefer installing Instaloader manually,
`Download the Source <https://github.com/Thammus/instaloader/releases>`__,
extract the Zip or Tarball and execute ``instaloader.py`` from there.
+1
View File
@@ -0,0 +1 @@
sphinx-autodoc-typehints
+776 -490
View File
File diff suppressed because it is too large Load Diff
+33 -5
View File
@@ -1,27 +1,55 @@
#!/usr/bin/env python3
import re
import sys
import os
import platform
from setuptools import setup
SRC = os.path.abspath(os.path.dirname(__file__))
def get_version():
with open(os.path.join(SRC, 'instaloader.py')) as f:
for line in f:
m = re.match("__version__ = '(.*)'", line)
if m:
return m.group(1)
raise SystemExit("Could not find version string.")
if sys.version_info < (3, 5):
sys.exit('Instaloader requires Python >= 3.5.')
requirements = ['requests>=2.4']
if platform.system() == 'Windows' and sys.version_info < (3, 6):
requirements.append('win_unicode_console')
keywords = (['instagram', 'instagram-scraper', 'instagram-client', 'instagram-feed', 'downloader', 'videos', 'photos',
'pictures', 'instagram-user-photos', 'instagram-photos', 'instagram-metadata', 'instagram-downloader',
'instagram-stories'])
# NOTE that many of the values defined in this file are duplicated on other places, such as the
# documentation.
setup(
name='instaloader',
version='2.2',
version=get_version(),
py_modules=['instaloader'],
url='https://github.com/Thammus/instaloader',
url='https://instaloader.readthedocs.io/',
license='MIT',
author='Alexander Graf, André Koch-Kramer',
author_email='mail@agraf.me, koch-kramer@web.de',
description='Download pictures (or videos) along with their captions and other metadata '
'from Instagram.',
long_description=open('README.rst').read(),
install_requires=['requests>=2.4'],
long_description=open(os.path.join(SRC, 'README.rst')).read(),
install_requires=requirements,
python_requires='>=3.5',
entry_points={'console_scripts': ['instaloader=instaloader:main']},
zip_safe=True,
keywords='instagram downloader',
keywords=keywords,
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Console',