Compare commits

...
8 Commits
Author SHA1 Message Date
André Koch-Kramer 13ebcb782e Release of version 1.2 2016-09-22 19:04:25 +02:00
André Koch-Kramer 50faad0a04 Minimal updated description 2016-09-22 19:02:26 +02:00
André Koch-Kramer 98c2847afd Implemented feature: store geotags/locations 2016-09-22 18:28:13 +02:00
Alexander Graf be8546812a Use reStructuredText as file format for README
rst seems to be cooler than md, and is the only format accepted by pypi,
and is accepted by github as well.

There is an intro to rst at:
http://docutils.sourceforge.net/docs/user/rst/quickref.html
2016-09-19 19:45:15 +02:00
Alexander Graf 1d506b5f95 Minor documentation improvements 2016-09-19 19:26:59 +02:00
Alexander Graf 1036fe45b5 README.md: Note pip3 as way to install 2016-09-18 17:44:27 +02:00
Alexander Graf bbcebb25eb setup.py: print message if python version is too old 2016-09-18 17:43:57 +02:00
Alexander Graf 8408183b79 minor, irrelevant setup.py fix 2016-09-18 17:27:10 +02:00
4 changed files with 198 additions and 126 deletions
-103
View File
@@ -1,103 +0,0 @@
# instaloader
Tool to download pictures (or videos) and captions from Instagram, from a given set
of profiles, from your feed or from all followees of a given profile.
## Usage
instaloader is written in Python, thus ensure having
[Python](https://www.python.org/) (at least version 3.3) installed.
If you intend to use this tool under Windows, it is highly recommended to first install
[win-unicode-console](https://pypi.python.org/pypi/win_unicode_console).
After having [downloaded instaloader](https://github.com/Thammus/instaloader/releases), unzip it and
invoke bundled `setup.py` (requiring [setuptools](https://pypi.python.org/pypi/setuptools)) to
install it:
```
./setup.py install [--user]
```
(pass `--user` to install it for your user only instead of globally)
instaloader requires [python3-requests](https://pypi.python.org/pypi/requests/), which will be
installed automatically by setup.py, if not already installed.
Now, to download a set of profiles, do
```
instaloader profile [profile ...]
```
where `profile` is the name of a profile you want to download. Instead of only one profile, you may
also specify a list of profiles.
To later update your local copy of that profile, you may run
```
instaloader --fast-update profile [profile ...]
```
When `--fast-update` is given, instaloder terminates when arriving at the first already-downloaded
picture.
Instaloader can also be used to **download private profiles**. To do so, invoke it with
```
instaloader --login=your_username profile [profile ...]
```
When invoked like this, it also **stores the session cookies** in a file in `/tmp`, which will be
reused later when `--login` is given. So you can download private profiles **non-interactively**
when you already have a valid session cookies file.
If you want to **download all followees of a given profile**, call
```
instaloader --login=your_username @profile
```
To **download all the pictures which you have liked**, call
```
instaloader --login=your_username :feed-liked
```
The `--quiet` option makes it also **suitable as a cron job**.
To get a list of other helpful flags, run `instaloader --help`.
## Usage as library
You may also use parts of instaloader as library to do other interesting things.
For example, to get a list of all followers of a profile as well as their follower count, do
```python
import instaloader
# login
session = instaloader.get_logged_in_session(USERNAME)
# get followees
followees = instaloader.get_followees(PROFILE, session)
for f in followees:
print("%i\t%s\t%s" % (f['follower_count'], f['username'], f['full_name']))
```
Then, you may download all pictures of all followees with
```python
for f in followees:
try:
instaloader.download(f['username'], session)
except instaloader.NonfatalException:
pass
```
You could also download your last 20 liked pics with
```python
instaloader.download_feed_pics(session, max_count=20, fast_update=True,
filter_func=lambda node: not node["likes"]["viewer_has_liked"])
```
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
```python
instaloader.get_id_by_username(PROFILE_NAME)
```
`get_followees()` also returns unique IDs for all loaded followees. To get the current username of a
profile, given this unique ID
`get_username_by_id()` can be used. For example:
```python
instaloader.get_username_by_id(session, followees[0]['id'])
```
+138
View File
@@ -0,0 +1,138 @@
Instaloader
===========
Installation
------------
instaloader is written in Python, thus ensure having
`Python <https://www.python.org/>`__ (at least version 3.3) installed.
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
::
pip3 install instaloader
Alternatively, you may `download instaloader from
github <https://github.com/Thammus/instaloader/releases>`__, unzip it
and invoke bundled ``setup.py`` (requiring
`setuptools <https://pypi.python.org/pypi/setuptools>`__) to install it:
::
python3 setup.py install [--user]
(pass ``--user`` to install it for your user only instead of globally)
instaloader requires
`requests <https://pypi.python.org/pypi/requests>`__, which
will be installed automatically by setup.py, if not already installed.
How to automatically download pictures from Instagram
-----------------------------------------------------
To download a set of profiles, do
::
instaloader profile [profile ...]
where ``profile`` is the name of a profile you want to download. Instead
of only one profile, you may also specify a list of profiles.
To later update your local copy of that profile, you may run
::
instaloader --fast-update profile [profile ...]
When ``--fast-update`` is given, instaloder terminates when arriving at
the first already-downloaded picture.
Instaloader can also be used to **download private profiles**. To do so,
invoke it with
::
instaloader --login=your_username profile [profile ...]
When invoked like this, it also **stores the session cookies** in a file
in ``/tmp``, 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.
If you want to **download all followees of a given profile**, call
::
instaloader --login=your_username @profile
To **download all the pictures from your feed which you have liked**, call
::
instaloader --login=your_username :feed-liked
The ``--quiet`` option makes it also **suitable as a cron job**.
To get a list of other helpful flags, run ``instaloader --help``.
Usage as library
----------------
You may also use parts of instaloader as library to do other interesting
things.
For example, to get a list of all followers of a profile as well as
their follower count, do
.. code:: python
import instaloader
# login
session = instaloader.get_logged_in_session(USERNAME)
# get followees
followees = instaloader.get_followees(PROFILE, session)
for f in followees:
print("%i\t%s\t%s" % (f['follower_count'], f['username'], f['full_name']))
Then, you may download all pictures of all followees with
.. code:: python
for f in followees:
try:
instaloader.download(f['username'], session)
except instaloader.NonfatalException:
pass
You could also download your last 20 liked pics with
.. code:: python
instaloader.download_feed_pics(session, max_count=20, fast_update=True,
filter_func=lambda node: not node["likes"]["viewer_has_liked"])
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
.. code:: python
instaloader.get_id_by_username(PROFILE_NAME)
``get_followees()`` also returns unique IDs for all loaded followees. To
get the current username of a profile, given this unique ID
``get_username_by_id()`` can be used. For example:
.. code:: python
instaloader.get_username_by_id(session, followees[0]['id'])
+46 -18
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""Tool to download pictures (or videos) and captions from Instagram, from a given set
of profiles, from your feed or from all followees of a given profile."""
of profiles (even if private), from your feed or from all followees of a given profile."""
import re, json, datetime, shutil, os, time, random, sys, pickle, getpass, tempfile
from argparse import ArgumentParser
@@ -60,7 +60,7 @@ def _log(*msg, sep='', end='\n', flush=False, quiet=False):
def get_json(name, session, max_id=0, sleep=True):
"""Return JSON of a profile"""
resp = session.get('http://www.instagram.com/'+name, \
resp = session.get('http://www.instagram.com/'+name,
params={'max_id': max_id})
if sleep:
time.sleep(4 * random.random() + 1)
@@ -206,7 +206,7 @@ def save_caption(name, date_epoch, caption, shorter_output=False, quiet=False):
return None
else:
def get_filename(index):
return filename if index==0 else (filename[:-4] + '_old_' + \
return filename if index==0 else (filename[:-4] + '_old_' +
(str(0) if index<10 else str()) + str(index) + filename[-4:])
i = 0
while os.path.isfile(get_filename(i)):
@@ -229,6 +229,17 @@ def save_caption(name, date_epoch, caption, shorter_output=False, quiet=False):
os.utime(filename, (datetime.datetime.now().timestamp(), date_epoch))
def save_location(name, location_json, date_epoch):
filename = name.lower() + '/' + _epoch_to_string(date_epoch) + '_location.txt'
location_string = location_json["name"]+"\n" + \
"https://maps.google.com/maps?q={0},{1}&ll={0},{1}\n" \
.format(location_json["lat"], location_json["lng"])
os.makedirs(name.lower(), exist_ok=True)
with open(filename, 'wb') as text_file:
shutil.copyfileobj(BytesIO(location_string.encode()), text_file)
os.utime(filename, (datetime.datetime.now().timestamp(), date_epoch))
def download_profilepic(name, url, quiet=False):
"""Downloads and saves profile pic with given url."""
date_object = datetime.datetime.strptime(requests.head(url).headers["Last-Modified"], \
@@ -414,8 +425,18 @@ def get_feed_json(session, end_cursor=None, sleep=True):
return json.loads(resp.text)
def get_location(node, session, sleep=True):
pic_json = get_json("p/" + node["code"], session, sleep=sleep)
if pic_json["entry_data"]["PostPage"][0]["media"]["location"] is not None:
location_json = get_json("explore/locations/" +
pic_json["entry_data"]["PostPage"][0]["media"]["location"]["id"],
session, sleep=sleep)
return location_json["entry_data"]["LocationsPage"][0]["location"]
def download_node(node, session, name,
download_videos=True, sleep=True, shorter_output=False, quiet=False):
download_videos=True, geotags=False,
sleep=True, shorter_output=False, quiet=False):
"""
Download everything associated with one instagram node, i.e. picture, caption and video.
@@ -440,12 +461,17 @@ def download_node(node, session, name,
download_pic(name,
video_data['entry_data']['PostPage'][0]['media']['video_url'],
node["date"], 'mp4', quiet=quiet)
if geotags:
location = get_location(node, session, sleep)
if location:
save_location(name, location, node["date"])
_log(quiet=quiet)
return downloaded
def download_feed_pics(session, max_count=None, fast_update=False, filter_func=None,
download_videos=True, shorter_output=False, sleep=True, quiet=False):
download_videos=True, geotags=False,
shorter_output=False, sleep=True, quiet=False):
"""
Download pictures from the user's feed.
@@ -475,8 +501,8 @@ def download_feed_pics(session, max_count=None, fast_update=False, filter_func=N
_log("[%3i] %s " % (count, name), end="", flush=True, quiet=quiet)
count += 1
downloaded = download_node(node, session, name,
download_videos=download_videos, sleep=sleep,
shorter_output=shorter_output, quiet=quiet)
download_videos=download_videos, geotags=geotags,
sleep=sleep, shorter_output=shorter_output, quiet=quiet)
if fast_update and not downloaded:
return
data = get_feed_json(session, end_cursor=data["feed"]["media"]["page_info"]["end_cursor"],
@@ -520,10 +546,10 @@ def check_id(profile, session, json_data, quiet=False):
raise ProfileNotExistsException("Profile {0} does not exist.".format(profile))
def download(name, session, profile_pic_only=False, download_videos=True,
def download(name, session, profile_pic_only=False, download_videos=True, geotags=False,
fast_update=False, shorter_output=False, sleep=True, quiet=False):
"""Download one profile"""
# pylint:disable=too-many-branches
# pylint:disable=too-many-branches,too-many-locals
# Get profile main page json
data = get_json(name, session, sleep=sleep)
# check if profile does exist or name has changed since last download
@@ -567,8 +593,8 @@ def download(name, session, profile_pic_only=False, download_videos=True,
_log("[%3i/%3i] " % (count, totalcount), end="", flush=True, quiet=quiet)
count += 1
downloaded = download_node(node, session, name,
download_videos=download_videos, sleep=sleep,
shorter_output=shorter_output, quiet=quiet)
download_videos=download_videos, geotags=geotags,
sleep=sleep, shorter_output=shorter_output, quiet=quiet)
if fast_update and not downloaded:
return
data = get_json(name, session, max_id=get_last_id(data), sleep=sleep)
@@ -589,7 +615,7 @@ def get_logged_in_session(username, password=None, quiet=False):
password = None
def download_profiles(profilelist, username=None, password=None, sessionfile=None,
profile_pic_only=False, download_videos=True, fast_update=False,
profile_pic_only=False, download_videos=True, geotags=False, fast_update=False,
sleep=True, shorter_output=False, quiet=False):
"""Download set of profiles and handle sessions"""
# pylint:disable=too-many-branches,too-many-locals
@@ -614,14 +640,14 @@ def download_profiles(profilelist, username=None, password=None, sessionfile=Non
elif pentry == ":feed-all" and username is not None:
_log("Retrieving pictures from your feed...", quiet=quiet)
download_feed_pics(session, fast_update=fast_update,
download_videos=download_videos, shorter_output=shorter_output,
sleep=sleep, quiet=quiet)
download_videos=download_videos, geotags=geotags,
shorter_output=shorter_output, sleep=sleep, quiet=quiet)
elif pentry == ":feed-liked" and username is not None:
_log("Retrieving pictures you liked from your feed...", quiet=quiet)
download_feed_pics(session, fast_update=fast_update,
filter_func=lambda node: not node["likes"]["viewer_has_liked"],
download_videos=download_videos, shorter_output=shorter_output,
sleep=sleep, quiet=quiet)
download_videos=download_videos, geotags=geotags,
shorter_output=shorter_output, sleep=sleep, quiet=quiet)
else:
targets.add(pentry)
if len(targets) == 0:
@@ -632,7 +658,7 @@ def download_profiles(profilelist, username=None, password=None, sessionfile=Non
for target in targets:
try:
download(target, session, profile_pic_only, download_videos,
fast_update, shorter_output, sleep, quiet)
geotags, fast_update, shorter_output, sleep, quiet)
except NonfatalException as err:
failedtargets.append(target)
print(err, file=sys.stderr)
@@ -670,6 +696,8 @@ def main():
help='Only download profile picture')
parser.add_argument('-V', '--skip-videos', action='store_true',
help='Do not download videos')
parser.add_argument('-G', '--geotags', action='store_true',
help='Store geotags when available')
parser.add_argument('-F', '--fast-update', action='store_true',
help='Abort at encounter of first already-downloaded picture')
parser.add_argument('-S', '--no-sleep', action='store_true',
@@ -682,7 +710,7 @@ def main():
args = parser.parse_args()
try:
download_profiles(args.profile, args.login, args.password, args.sessionfile,
args.profile_pic_only, not args.skip_videos, args.fast_update,
args.profile_pic_only, not args.skip_videos, args.geotags, args.fast_update,
not args.no_sleep, args.shorter_output, args.quiet)
except InstaloaderException as err:
raise SystemExit("Fatal error: %s" % err)
+14 -5
View File
@@ -1,17 +1,23 @@
#!/usr/bin/env python3
import sys
from setuptools import setup
if sys.version_info < (3, 3):
sys.exit('Instaloader requires Python >= 3.3.')
setup(
name='instaloader',
version='1.1',
version='1.2',
py_modules=['instaloader'],
url='https://github.com/Thammus/instaloader',
license='MIT',
author='Alexander Graf, André Koch-Kramer',
author_email='mail@agraf.me, koch-kramer@web.de',
description='Tool to download pictures (or videos) and captions from Instagram, from a given '
'set of profiles, from your feed or from all followees of a given profile.',
description='Tool to download Instagram pictures (or videos) and captions from a given set of '
'profiles (even if private), from your feed or from all followees of a given '
'profile.',
long_description=open('README.rst').read(),
install_requires=['requests>=2.4'],
python_requires='>=3.3',
entry_points={'console_scripts': ['instaloader=instaloader:main']},
@@ -20,12 +26,15 @@ setup(
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Console',
'Intended Audience :: End Users/Desktop',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6'
'Topic :: Internet'
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3 :: Only',
'Topic :: Internet',
'Topic :: Multimedia :: Graphics'
]
)