@danhachuel/thunderbolt 0.6.51 → 0.6.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/integrations/instagram_public.py +53 -23
- package/package.json +1 -1
|
@@ -284,12 +284,12 @@ def _profile_users_from_document(document: str) -> list[Mapping[str, Any]]:
|
|
|
284
284
|
|
|
285
285
|
|
|
286
286
|
def _fetch_profile_with_playwright(username: str, headers: Mapping[str, str] | None = None, cookies: Mapping[str, str] | None = None) -> dict[str, Any] | None:
|
|
287
|
-
"""Fetch the
|
|
287
|
+
"""Fetch the structured profile JSON requested by the public Instagram page."""
|
|
288
288
|
headers = headers or _instagram_headers()
|
|
289
289
|
cookies = cookies or _instagram_cookies()
|
|
290
|
-
|
|
290
|
+
captured_user: dict[str, Any] | None = None
|
|
291
291
|
try:
|
|
292
|
-
from playwright.sync_api import sync_playwright
|
|
292
|
+
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError, sync_playwright
|
|
293
293
|
with sync_playwright() as playwright:
|
|
294
294
|
browser = playwright.chromium.launch(headless=True)
|
|
295
295
|
try:
|
|
@@ -299,21 +299,37 @@ def _fetch_profile_with_playwright(username: str, headers: Mapping[str, str] | N
|
|
|
299
299
|
)
|
|
300
300
|
if cookies:
|
|
301
301
|
page.context.add_cookies([{'name': name, 'value': value, 'domain': '.instagram.com', 'path': '/'} for name, value in cookies.items()])
|
|
302
|
+
|
|
303
|
+
def capture_profile_response(response: Any) -> None:
|
|
304
|
+
nonlocal captured_user
|
|
305
|
+
if '/api/v1/users/web_profile_info/' not in response.url:
|
|
306
|
+
return
|
|
307
|
+
try:
|
|
308
|
+
payload = response.json()
|
|
309
|
+
user = ((payload.get('data') or {}).get('user') if isinstance(payload, Mapping) else None)
|
|
310
|
+
if isinstance(user, Mapping):
|
|
311
|
+
captured_user = _merge_profile(captured_user, user)
|
|
312
|
+
LOGGER.debug('Instagram Playwright API profile captured user=%s', username)
|
|
313
|
+
except (ValueError, TypeError, AttributeError) as exc:
|
|
314
|
+
LOGGER.debug('Não foi possível interpretar resposta web_profile_info: %s', exc)
|
|
315
|
+
|
|
316
|
+
page.on('response', capture_profile_response)
|
|
302
317
|
url = f'https://www.instagram.com/{username}/'
|
|
303
318
|
response = page.goto(url, wait_until='domcontentloaded', timeout=30000)
|
|
304
319
|
LOGGER.debug('Instagram Playwright profile URL=%s status=%s', url, response.status if response else None)
|
|
305
320
|
if response is None or response.status >= 400:
|
|
306
321
|
return None
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
322
|
+
try:
|
|
323
|
+
page.wait_for_load_state('networkidle', timeout=10000)
|
|
324
|
+
except (PlaywrightTimeoutError, TimeoutError):
|
|
325
|
+
LOGGER.debug('Instagram Playwright networkidle excedido; usando resposta API já capturada.')
|
|
326
|
+
LOGGER.debug('Instagram Playwright API profile user=%s quality=%s', bool(captured_user), _profile_quality(captured_user or {}))
|
|
311
327
|
finally:
|
|
312
328
|
browser.close()
|
|
313
329
|
except Exception as exc:
|
|
314
330
|
LOGGER.warning('Falha no Playwright do perfil Instagram: %s', exc, exc_info=True)
|
|
315
331
|
_streamlit_message('error', f'Playwright não conseguiu consultar o perfil Instagram: {exc}', 'profile_playwright_error')
|
|
316
|
-
return
|
|
332
|
+
return captured_user
|
|
317
333
|
|
|
318
334
|
|
|
319
335
|
def _fetch_web_profile_user(username: str) -> dict[str, Any] | None:
|
|
@@ -651,12 +667,12 @@ def _post_from_node(node: dict[str, Any]) -> dict[str, Any] | None:
|
|
|
651
667
|
|
|
652
668
|
|
|
653
669
|
def _fetch_posts_with_playwright(reference: Mapping[str, str], limit: int, cookies: Mapping[str, str] | None = None) -> list[dict[str, Any]]:
|
|
654
|
-
# CORREÇÃO WINDOWS: posts são extraídos exclusivamente
|
|
670
|
+
# CORREÇÃO WINDOWS: posts são extraídos exclusivamente da resposta API capturada pelo Playwright.
|
|
655
671
|
posts: list[dict[str, Any]] = []
|
|
656
672
|
seen: set[str] = set()
|
|
657
673
|
cookies = cookies or _instagram_cookies()
|
|
658
674
|
try:
|
|
659
|
-
from playwright.sync_api import sync_playwright
|
|
675
|
+
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError, sync_playwright
|
|
660
676
|
with sync_playwright() as playwright:
|
|
661
677
|
browser = playwright.chromium.launch(headless=True)
|
|
662
678
|
try:
|
|
@@ -666,23 +682,37 @@ def _fetch_posts_with_playwright(reference: Mapping[str, str], limit: int, cooki
|
|
|
666
682
|
)
|
|
667
683
|
if cookies:
|
|
668
684
|
page.context.add_cookies([{'name': name, 'value': value, 'domain': '.instagram.com', 'path': '/'} for name, value in cookies.items()])
|
|
685
|
+
|
|
686
|
+
def capture_profile_posts(response: Any) -> None:
|
|
687
|
+
if '/api/v1/users/web_profile_info/' not in response.url:
|
|
688
|
+
return
|
|
689
|
+
try:
|
|
690
|
+
payload = response.json()
|
|
691
|
+
user = ((payload.get('data') or {}).get('user') if isinstance(payload, Mapping) else None)
|
|
692
|
+
media = user.get('edge_owner_to_timeline_media') if isinstance(user, Mapping) else None
|
|
693
|
+
edges = media.get('edges') if isinstance(media, Mapping) else []
|
|
694
|
+
for edge in edges or []:
|
|
695
|
+
node = edge.get('node') if isinstance(edge, Mapping) else None
|
|
696
|
+
if not isinstance(node, dict):
|
|
697
|
+
continue
|
|
698
|
+
post = _post_from_node(node)
|
|
699
|
+
if post and post['id'] not in seen:
|
|
700
|
+
seen.add(post['id'])
|
|
701
|
+
posts.append(post)
|
|
702
|
+
except (ValueError, TypeError, AttributeError) as exc:
|
|
703
|
+
LOGGER.debug('Não foi possível interpretar posts web_profile_info: %s', exc)
|
|
704
|
+
|
|
705
|
+
page.on('response', capture_profile_posts)
|
|
669
706
|
response = page.goto(reference['url'], wait_until='domcontentloaded', timeout=30000)
|
|
670
707
|
LOGGER.debug('Instagram Playwright posts URL=%s status=%s', reference['url'], response.status if response else None)
|
|
671
708
|
if response is None or response.status >= 400:
|
|
672
709
|
return []
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
if not post or post['id'] in seen:
|
|
680
|
-
continue
|
|
681
|
-
seen.add(post['id'])
|
|
682
|
-
posts.append(post)
|
|
683
|
-
LOGGER.debug('Instagram Playwright post extracted id=%s', post['id'])
|
|
684
|
-
if len(posts) >= max(1, int(limit)):
|
|
685
|
-
return posts
|
|
710
|
+
try:
|
|
711
|
+
page.wait_for_load_state('networkidle', timeout=10000)
|
|
712
|
+
except (PlaywrightTimeoutError, TimeoutError):
|
|
713
|
+
LOGGER.debug('Instagram Playwright networkidle excedido; usando posts API já capturados.')
|
|
714
|
+
LOGGER.debug('Instagram Playwright API posts extracted=%s limit=%s', len(posts), limit)
|
|
715
|
+
return posts[:max(1, int(limit))]
|
|
686
716
|
finally:
|
|
687
717
|
browser.close()
|
|
688
718
|
except Exception as exc:
|
package/package.json
CHANGED