Shashank2k3 commited on
Commit
f702bfb
·
verified ·
1 Parent(s): 44a3d99

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +229 -34
src/streamlit_app.py CHANGED
@@ -1,40 +1,235 @@
 
 
 
 
 
1
  import numpy as np
2
  import pandas as pd
3
- import streamlit as st
4
  import joblib
5
  from apify_client import ApifyClient
6
- model = joblib.load("classifier.pkl")
7
- client = ApifyClient("apify_api_nscRkHOyMh3mytIWftXpHpZlIzBhgF4mZyPV")
8
- st.title("Fake Instagram Profile Detection")
9
- st.write("Please provide instagram account details you would like to predict")
10
- n = st.text_input("Enter username ")
11
- run_input = { "usernames": [n] }
12
- run = client.actor("dSCLg0C3YEZ83HzYX").call(run_input=run_input)
13
- m = client.dataset(run["defaultDatasetId"])
14
- for item in m.iterate_items():
15
- postsCount= item.get('postsCount')
16
- followersCount = item.get('followersCount')
17
- followsCount = item.get('followsCount')
18
- private=item.get('private')
19
- verified=item.get('verified')
20
-
21
- def predictor(postsCount,followersCount,followsCount,private,verified):
22
- prediction = model.predict([[postsCount,followersCount,followsCount,private,verified]])
23
- print(prediction)
24
- return prediction
25
-
26
-
27
- if st.button("Predict"):
28
- result = predictor(postsCount,followersCount,followsCount,private,verified)
29
- st.write("The number of posts : " , postsCount)
30
- st.write("The number of followers : " ,followersCount)
31
- st.write("The number of following : " ,followsCount)
32
- st.write("Private : " ,private)
33
- st.write("Verified : " ,verified)
34
- if postsCount == None:
35
- st.error("The User Doesn't exist")
36
- elif result == 0 and postsCount != None:
37
- st.error("The Account is Likely to be Fake ")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  else:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
- st.success("The Account is Likely to be Real")
 
1
+ from __future__ import annotations
2
+ import os
3
+ from pathlib import Path
4
+ from typing import Optional, Dict, Any, Tuple
5
+
6
  import numpy as np
7
  import pandas as pd
8
+ import streamlit as st
9
  import joblib
10
  from apify_client import ApifyClient
11
+
12
+ # ---------- Page setup ----------
13
+ st.set_page_config(
14
+ page_title="Fake Instagram Profile Detector",
15
+ page_icon="🕵️‍♂️",
16
+ layout="centered",
17
+ initial_sidebar_state="expanded",
18
+ )
19
+
20
+ # ---------- Minimal styling ----------
21
+ st.markdown("""
22
+ <style>
23
+ /* Make it feel app-like */
24
+ .reportview-container .main .block-container {padding-top: 2rem; padding-bottom: 2rem;}
25
+ .small-muted {font-size: 0.9rem; color: rgba(0,0,0,0.55);}
26
+ .kpi {padding: 0.75rem 1rem; border-radius: 0.75rem; border: 1px solid rgba(0,0,0,0.08);}
27
+ .status-pill {display:inline-block; padding: .25rem .6rem; border-radius: 999px; font-weight:600;}
28
+ .status-ok {background:#E7F6EC; color:#137333;}
29
+ .status-bad {background:#FCE8E6; color:#B3261E;}
30
+ .status-warn {background:#FFF4E5; color:#8A4D00;}
31
+ </style>
32
+ """, unsafe_allow_html=True)
33
+
34
+ # ---------- Config & Secrets ----------
35
+ def get_apify_token() -> Optional[str]:
36
+ # Prefer Streamlit secrets; fallback to env var; last resort None
37
+ token = st.secrets.get("APIFY_TOKEN", None) if hasattr(st, "secrets") else None
38
+ return token or os.getenv("APIFY_TOKEN") # don't hardcode into source code
39
+
40
+ APIFY_ACTOR_ID = "dSCLg0C3YEZ83HzYX" # your actor id
41
+ # If your actor expects a different input shape, adjust below.
42
+ DEFAULT_RUN_INPUT_KEY = "usernames"
43
+
44
+ # ---------- Model loading ----------
45
+ @st.cache_resource(show_spinner=False)
46
+ def load_model() -> Any:
47
+ # Load relative to this file to avoid CWD issues
48
+ here = Path(__file__).resolve().parent
49
+ model_path = here / "classifier.pkl" # place classifier.pkl inside src/
50
+ if not model_path.exists():
51
+ raise FileNotFoundError(f"Model not found at: {model_path}")
52
+ return joblib.load(model_path)
53
+
54
+ model = None
55
+ model_load_error = None
56
+ try:
57
+ with st.spinner("Loading model..."):
58
+ model = load_model()
59
+ except Exception as e:
60
+ model_load_error = str(e)
61
+
62
+ # ---------- Apify helpers ----------
63
+ @st.cache_data(show_spinner=False, ttl=60) # cache for a minute
64
+ def fetch_instagram_profile(username: str, token: str) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
65
+ try:
66
+ client = ApifyClient(token)
67
+ run_input = {DEFAULT_RUN_INPUT_KEY: [username]}
68
+ run = client.actor(APIFY_ACTOR_ID).call(run_input=run_input)
69
+ dataset = client.dataset(run["defaultDatasetId"])
70
+
71
+ # We'll take the first item that matches
72
+ for item in dataset.iterate_items():
73
+ # normalize keys we care about
74
+ out = {
75
+ "postsCount": item.get("postsCount"),
76
+ "followersCount": item.get("followersCount"),
77
+ "followsCount": item.get("followsCount"),
78
+ "private": item.get("private"),
79
+ "verified": item.get("verified"),
80
+ }
81
+ return out, None
82
+
83
+ return None, "No data returned for this username."
84
+ except Exception as e:
85
+ return None, f"Apify error: {e}"
86
+
87
+ def to_numeric_features(raw: Dict[str, Any]) -> Optional[np.ndarray]:
88
+ try:
89
+ posts = int(raw.get("postsCount")) if raw.get("postsCount") is not None else None
90
+ followers = int(raw.get("followersCount")) if raw.get("followersCount") is not None else None
91
+ follows = int(raw.get("followsCount")) if raw.get("followsCount") is not None else None
92
+ private = 1 if bool(raw.get("private")) else 0
93
+ verified = 1 if bool(raw.get("verified")) else 0
94
+ if None in (posts, followers, follows):
95
+ return None
96
+ return np.array([[posts, followers, follows, private, verified]], dtype=np.float64)
97
+ except Exception:
98
+ return None
99
+
100
+ def predict_with_model(X: np.ndarray) -> Dict[str, Any]:
101
+ # Try to get probabilities if available; else binary prediction
102
+ result: Dict[str, Any] = {}
103
+ if hasattr(model, "predict_proba"):
104
+ proba = model.predict_proba(X)
105
+ # Assume class 1 = Real, class 0 = Fake (adjust if reversed in your model)
106
+ # Try to find mapping if model has classes_
107
+ label_index = getattr(model, "classes_", np.array([0, 1]))
108
+ # map probabilities to classes
109
+ probs = dict(zip(label_index.tolist(), proba[0].tolist()))
110
+ result["proba_real"] = probs.get(1, None)
111
+ result["proba_fake"] = probs.get(0, None)
112
+ result["pred"] = int(model.predict(X)[0])
113
  else:
114
+ y = int(model.predict(X)[0])
115
+ result["pred"] = y
116
+ result["proba_real"] = None
117
+ result["proba_fake"] = None
118
+ return result
119
+
120
+ # ---------- Sidebar ----------
121
+ with st.sidebar:
122
+ st.header("⚙️ Settings")
123
+ st.caption("Configure how the app connects and behaves.")
124
+ token = get_apify_token()
125
+ if not token:
126
+ token = st.text_input("Apify API token (not saved)", type="password", placeholder="APIFY_...")
127
+
128
+ st.divider()
129
+ st.markdown("**About**")
130
+ st.write(
131
+ "This app checks basic Instagram profile signals "
132
+ "and runs a classifier to estimate whether an account looks fake or real."
133
+ )
134
+ st.markdown(
135
+ '<span class="small-muted">For demo/educational purposes only. '
136
+ 'Always verify results with additional signals.</span>',
137
+ unsafe_allow_html=True
138
+ )
139
+
140
+ # ---------- Header ----------
141
+ st.title("🕵️‍♂️ Fake Instagram Profile Detector")
142
+ st.write("Enter a username and we’ll fetch basic public signals, then run a trained model to estimate risk.")
143
+
144
+ if model_load_error:
145
+ st.error(f"Model failed to load: {model_load_error}")
146
+ st.stop()
147
+
148
+ # ---------- Main Form ----------
149
+ with st.form("username_form", clear_on_submit=False):
150
+ username = st.text_input("Instagram Username", placeholder="e.g., nasa")
151
+ submitted = st.form_submit_button("Analyze")
152
+
153
+ if not submitted:
154
+ st.info("Enter a username and click **Analyze** to get started.")
155
+ st.stop()
156
+
157
+ # ---------- Validation ----------
158
+ if not username.strip():
159
+ st.warning("Please provide a username.")
160
+ st.stop()
161
+
162
+ if not token:
163
+ st.error("Missing Apify token. Add it to `.streamlit/secrets.toml` as `APIFY_TOKEN` or set the env var.")
164
+ st.stop()
165
+
166
+ # ---------- Fetch & Predict ----------
167
+ with st.spinner("Fetching profile data..."):
168
+ raw_data, fetch_err = fetch_instagram_profile(username.strip(), token)
169
+
170
+ if fetch_err:
171
+ st.error(fetch_err)
172
+ st.stop()
173
+ if not raw_data:
174
+ st.warning("No data found. Double-check the username.")
175
+ st.stop()
176
+
177
+ # KPIs
178
+ st.subheader(f"Profile Signals — @{username}")
179
+ c1, c2, c3 = st.columns(3)
180
+ c4, c5 = st.columns(2)
181
+
182
+ with c1:
183
+ st.markdown('<div class="kpi"><div class="small-muted">Posts</div>'
184
+ f'<h3>{raw_data["postsCount"] if raw_data["postsCount"] is not None else "—"}</h3></div>', unsafe_allow_html=True)
185
+ with c2:
186
+ st.markdown('<div class="kpi"><div class="small-muted">Followers</div>'
187
+ f'<h3>{raw_data["followersCount"] if raw_data["followersCount"] is not None else "—"}</h3></div>', unsafe_allow_html=True)
188
+ with c3:
189
+ st.markdown('<div class="kpi"><div class="small-muted">Following</div>'
190
+ f'<h3>{raw_data["followsCount"] if raw_data["followsCount"] is not None else "—"}</h3></div>', unsafe_allow_html=True)
191
+
192
+ with c4:
193
+ private_pill = '<span class="status-pill status-warn">Private</span>' if raw_data.get("private") else '<span class="status-pill status-ok">Public</span>'
194
+ st.markdown(f'<div class="kpi"><div class="small-muted">Privacy</div><div>{private_pill}</div></div>', unsafe_allow_html=True)
195
+
196
+ with c5:
197
+ verified_pill = '<span class="status-pill status-ok">Verified</span>' if raw_data.get("verified") else '<span class="status-pill status-bad">Not Verified</span>'
198
+ st.markdown(f'<div class="kpi"><div class="small-muted">Verification</div><div>{verified_pill}</div></div>', unsafe_allow_html=True)
199
+
200
+ # Prepare features
201
+ X = to_numeric_features(raw_data)
202
+ if X is None:
203
+ st.error("Insufficient numeric data to run the classifier (missing posts/followers/following).")
204
+ st.stop()
205
+
206
+ with st.spinner("Running prediction..."):
207
+ out = predict_with_model(X)
208
+
209
+ pred = out["pred"]
210
+ proba_real = out.get("proba_real")
211
+ proba_fake = out.get("proba_fake")
212
+
213
+ # ---------- Verdict ----------
214
+ st.subheader("Verdict")
215
+ if raw_data.get("postsCount") is None:
216
+ st.error("The user may not exist or data could not be fetched.")
217
+ elif pred == 0:
218
+ st.error("The account is **likely to be Fake**.")
219
+ else:
220
+ st.success("The account is **likely to be Real**.")
221
+
222
+ # ---------- Confidence ----------
223
+ if (proba_real is not None) or (proba_fake is not None):
224
+ st.write("**Confidence**")
225
+ cc1, cc2 = st.columns(2)
226
+ with cc1:
227
+ st.metric("Probability: Real", f"{(proba_real or 0)*100:0.1f}%")
228
+ with cc2:
229
+ st.metric("Probability: Fake", f"{(proba_fake or 0)*100:0.1f}%")
230
+
231
+ # ---------- Raw data (expandable) ----------
232
+ with st.expander("See fetched features"):
233
+ st.json(raw_data)
234
 
235
+ st.caption("⚠️ This tool provides an indicative score. Use responsibly and verify via additional checks.")