Skip to content

Commit

Permalink
Add 'Story Reel' and 'Story' endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
niuware committed Jul 16, 2019
1 parent 4e8f5e6 commit d6ffbb3
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 20 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Instpector

A simple Instagram's web API library written in Python. Login with your web user and password and you are ready to go. No selenium or webdriver required.
A simple Instagram's web API library written in Python. No selenium or webdriver required.

# Installation

Expand Down Expand Up @@ -42,6 +42,8 @@ Check more in the `examples` directory.
- Following
- Timeline
- Profile
- Story Reel
- Story

More to come

Expand Down
36 changes: 36 additions & 0 deletions examples/get_story_reel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from sys import argv
from context import Instpector, endpoints

def get_story_reel(**options):

instpector = Instpector()
if not instpector.login(user=options.get("user"), password=options.get("password")):
return

profile = endpoints.factory.create("profile", instpector)
story_reel = endpoints.factory.create("story_reel", instpector)
story = endpoints.factory.create("story", instpector)

target_profile = profile.of_user(options.get("target_username"))

for story_item in story_reel.of_user(target_profile.id):
print(story_item.view_count)
for viewer in story.viewers_for(story_item.id):
print(viewer.username)

instpector.logout()

if __name__ == '__main__':
if len(argv) < 6:
print((
"Missing arguments: "
"--user {user} "
"--password {password} "
"--target_username {username}"
))
exit(1)
get_story_reel(
user=argv[2],
password=argv[4],
target_username=argv[6]
)
12 changes: 11 additions & 1 deletion instpector/apis/instagram/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,15 @@
from .parser import Parser
from .profile import Profile
from .timeline import Timeline
from .story_reel import StoryReel
from .story import Story

__all__ = ["Authenticate", "Followers", "Parser", "Following", "Profile", "Timeline"]
__all__ = ["Authenticate",
"Followers",
"Parser",
"Following",
"Profile",
"Timeline",
"StoryReel",
"Story"
]
14 changes: 10 additions & 4 deletions instpector/apis/instagram/base_graph_ql.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,22 @@ def _loop(self, query_hash, variables, **parser_callbacks):
while True:
if not page_info.has_next_page:
return
data = self._get_partial_data(query_hash, variables, page_info.end_cursor)
next_cursor_name = parser_callbacks.get("page_info_next_cursor")
data = self._get_partial_data(query_hash, variables, page_info.end_cursor,
next_cursor_name)
if data:
page_info = Parser.page_info(data, parser_callbacks.get("page_info_parser"))
page_info = Parser.page_info(data, parser_callbacks.get("page_info_parser"),
parser_callbacks.get("page_info_parser_path"))
for result in parser_callbacks.get("data_parser")(data):
yield result

def _get_partial_data(self, query_hash, variables, end_cursor):
def _get_partial_data(self, query_hash, variables, end_cursor, cursor_name):
cursor_name = cursor_name or "after"
cursor = end_cursor or ""
if cursor_name != "after" and not cursor:
cursor = "0"
cursor_param = {
"after": cursor
cursor_name: cursor
}
post_variables = json.dumps({**variables, **cursor_param})
params = {
Expand Down
4 changes: 4 additions & 0 deletions instpector/apis/instagram/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@
))

TTimelinePost = namedtuple("TTimelinePost", "id timestamp is_video like_count comment_count")

TStoryReelItem = namedtuple("TStoryReelItem", "id timestamp expire_at audience is_video view_count")

TStoryViewer = namedtuple("TStoryViewer", "id username")
48 changes: 40 additions & 8 deletions instpector/apis/instagram/parser.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .definitions import TUser, TPageInfo, TProfile, TTimelinePost
from .definitions import TUser, TPageInfo, TProfile, TTimelinePost, TStoryReelItem, TStoryViewer


class Parser:
Expand Down Expand Up @@ -27,7 +27,7 @@ def profile(data):
followers_count = user["edge_followed_by"]["count"]
following_count = user["edge_follow"]["count"]
is_private = user["is_private"]
except KeyError:
except (KeyError, TypeError):
print(f"Error parsing profile")
return TProfile(
id=user_id,
Expand All @@ -38,14 +38,15 @@ def profile(data):
is_private=is_private)

@staticmethod
def page_info(data, endpoint):
def page_info(data, endpoint, d_path=None):
end_cursor = ""
has_next_page = False
data_path = d_path or "user"
try:
root = data["data"]["user"][endpoint]
root = data["data"][data_path][endpoint]
end_cursor = root["page_info"]["end_cursor"]
has_next_page = root["page_info"]["has_next_page"]
except KeyError:
except (KeyError, TypeError):
print(f"Error parsing follow_page_info for {endpoint}")
return TPageInfo(
end_cursor=end_cursor,
Expand Down Expand Up @@ -77,11 +78,42 @@ def timeline(data):
yield post

@staticmethod
def _get_edges(data, endpoint):
def _get_edges(data, endpoint, d_path=None):
edges = []
data_path = d_path or "user"
try:
root = data["data"]["user"][endpoint]
root = data["data"][data_path][endpoint]
edges = root["edges"]
except KeyError:
except (KeyError, TypeError):
print(f"Error parsing follow_edge for {endpoint}")
return edges

@staticmethod
def story_reel(data):
try:
root = data["data"]["reels_media"]
if root:
items = root[0]["items"]
for item in items:
post = TStoryReelItem(
id=item["id"],
timestamp=item["taken_at_timestamp"],
expire_at=item["expiring_at_timestamp"],
is_video=item["is_video"],
view_count=item["edge_story_media_viewers"]["count"],
audience=item["audience"]
)
yield post
except (KeyError, TypeError):
print(f"Error parsing story_reel")

@staticmethod
def story(data):
edges = Parser._get_edges(data, "edge_story_media_viewers", "media")
for edge in edges:
node = edge["node"]
viewer = TStoryViewer(
id=node["id"],
username=node["username"]
)
yield viewer
7 changes: 3 additions & 4 deletions instpector/apis/instagram/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ def of_user(self, username):
params = {
"__a": 1
}
headers = {
"DNT": "1"
}
try:
data = super().get(f"/{username}/", params=params, headers=headers)
data = super().get(f"/{username}/", params=params, headers={
"DNT": "1"
})
if data:
return Parser.profile(data)
except ParseDataException:
Expand Down
18 changes: 18 additions & 0 deletions instpector/apis/instagram/story.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from .base_graph_ql import BaseGraphQL
from .parser import Parser


class Story(BaseGraphQL):

def viewers_for(self, item_id):
variables = {
"item_id": item_id,
"story_viewer_fetch_count": 50,
"story_viewer_cursor": "0"
}
return self._loop("42c6ec100f5e57a1fe09be16cd3a7021",
variables=variables,
page_info_parser="edge_story_media_viewers",
page_info_parser_path="media",
page_info_next_cursor="story_viewer_cursor",
data_parser=Parser.story)
35 changes: 35 additions & 0 deletions instpector/apis/instagram/story_reel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import json
from ..exceptions import ParseDataException
from .base_api import BaseApi
from .parser import Parser


class StoryReel(BaseApi):

def __init__(self, browser_session):
super().__init__("https://www.instagram.com", browser_session)

def of_user(self, user_id):
params = {
"query_hash": "cda12de4f7fd3719c0569ce03589f4c4",
"variables": json.dumps({
"reel_ids": [user_id],
"tag_names": [],
"location_ids": [],
"highlight_reel_ids": [],
"precomposed_overlay": False,
"show_story_viewer_list": True,
"story_viewer_fetch_count": 50,
"story_viewer_cursor": "",
"stories_video_dash_manifest": False
})
}
try:
data = super().get(f"/graphql/query/", params=params, headers={
"DNT": "1"
})
if data:
return Parser.story_reel(data)
except ParseDataException:
print(f"Invalid story reel data for user id {user_id}")
return None
2 changes: 2 additions & 0 deletions instpector/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@
factory.register_endpoint('following', instagram.Following)
factory.register_endpoint('profile', instagram.Profile)
factory.register_endpoint('timeline', instagram.Timeline)
factory.register_endpoint('story_reel', instagram.StoryReel)
factory.register_endpoint('story', instagram.Story)
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@

setup(
name="instpector",
version="0.1.5",
version="0.1.6",
description="A simple Instagram's web API library",
author="Erik Lopez",
long_description=README,
long_description_content_type="text/markdown",
keywords="instagram web-api instagram-client",
url="https://github.com/niuware/instpector",
download_url="https://github.com/niuware/instpector/archive/0.1.5.tar.gz",
download_url="https://github.com/niuware/instpector/archive/0.1.6.tar.gz",
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
Expand Down
18 changes: 18 additions & 0 deletions tests/parser_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,21 @@ def test_timeline(timeline_file=None):
data = json.loads(open(json_file, "r").read())
for post in Parser.timeline(data):
print(post)

def test_story_reel(story_reel_file=None):
json_file = "tests/story_reel.json"
if story_reel_file:
json_file = story_reel_file
if path.isfile(json_file):
data = json.loads(open(json_file, "r").read())
for item in Parser.story_reel(data):
print(item)

def test_story(story_file=None):
json_file = "tests/story.json"
if story_file:
json_file = story_file
if path.isfile(json_file):
data = json.loads(open(json_file, "r").read())
for item in Parser.story(data):
print(item)

0 comments on commit d6ffbb3

Please sign in to comment.