
    i	i#                         d Z ddlZddlZddlZddlmZ ddlmZmZm	Z	m
Z
 ddlmZmZ ddlmZmZ ddlmZmZmZmZ  ee      j,                  j,                  d	z  d
z  Z G d de      Zy)a  Reddit subreddit scraper - PRIMARY collector for trending questions.

Scrapes "explain it to me" subreddits where people ask about trending topics.
Uses direct HTTP calls to old.reddit.com JSON endpoints - NO PRAW, NO auth.
Supports incremental collection via reddit_seen table.
    N)Path)ListOptionalSetTuple)datetimetimezone   )BaseCollector	TrendItem)REDDIT_SUBREDDITSREDDIT_POSTS_PER_SUBREDDIT_REQUEST_DELAYREDDIT_USER_AGENToutputztrends_history.dbc                        e Zd ZdZddee   f fdZdee   fdZdee	eef      fdZ
dded	edee   fd
Z	 ddedededee   de	ee   ee	eef      f   f
dZddedee   fdZ xZS )RedditScraperCollectorzTScrapes Reddit subreddits for trending questions/topics with incremental collection.
subredditsc                     t         |   d       dt        i| _        g d| _        ddi d| _        |r|| _        yt        | _        y)a4  
        Initialize the Reddit scraper.

        Args:
            subreddits: Optional list of subreddit names to scrape. If None,
                       uses the default REDDIT_SUBREDDITS from config.py.
                       When a channel is specified, pass the channel's configured subreddits.
        redditz
User-Agent)   <   x      i,  r   newseenby_subredditN)super__init__r   headersbackoff_timesstatsr   r   )selfr   	__class__s     C/home/sietch6/trending-topics-pipeline/collectors/reddit_scraper.pyr    zRedditScraperCollector.__init__   sE     	"$&7842>
(2*8I    returnc                 b   	 t        j                  t              }|j                         }|j	                  d       |j                         D ch c]  }|d   	 }}|j                          |S c c}w # t        $ r2}| j                  j                  d|        t               cY d}~S d}~ww xY w)z0Load all previously seen post IDs from database.zSELECT post_id FROM reddit_seenr   zError loading seen posts: N)sqlite3connectDB_PATHcursorexecutefetchallclose	Exceptionerrorsappendset)r$   connr-   rowr   es         r&   _get_seen_post_idsz)RedditScraperCollector._get_seen_post_ids-   s    		??7+D[[]FNN<=&,oo&78sCF8D8JJLK 9  	KK!;A3?@5L	s0   AA3 A.A3 .A3 3	B.<'B)#B.)B.postsc           	         |sy	 t        j                  t              }|j                         }t	        j
                  t        j                        j                         }|j                  d|D cg c]  }|d   |d   |f c}       |j                          |j                          yc c}w # t        $ r(}| j                  j                  d|        Y d}~yd}~ww xY w)zqMark post IDs as seen in database.

        Args:
            posts: List of (post_id, subreddit) tuples
        NzVINSERT OR IGNORE INTO reddit_seen (post_id, subreddit, first_seen_at) VALUES (?, ?, ?)r   r
   zError marking posts seen: )r*   r+   r,   r-   r   nowr	   utc	isoformatexecutemanycommitr0   r1   r2   r3   )r$   r9   r5   r-   r;   pr7   s          r&   _mark_posts_seenz'RedditScraperCollector._mark_posts_seen:   s     	A??7+D[[]F,,x||,668Ch,12q!A$!c"2 KKMJJL 3  	AKK!;A3?@@	As*   A*B. /B)
'B. )B. .	C7CCurlretriesc           
      V   t        |      D ]  }	 t        j                  || j                  d      }|j                  dk(  rT| j
                  t        |t        | j
                        dz
           }t        d| d       t        j                  |       |j                          |j                         c S  y
# t        j                  j                  $ rR}| j                  j!                  d| dt#        |              ||dz
  k  rt        j                  d	       Y d
}~d
}~ww xY w)z
        Fetch JSON from Reddit with exponential backoff on 429s.

        Args:
            url: The Reddit JSON endpoint
            retries: Number of retry attempts

        Returns:
            Parsed JSON dict or None on failure
           )r!   timeouti  r
   z    Rate limited, waiting zs...zFetch error z:    N)rangerequestsgetr!   status_coder"   minlenprinttimesleepraise_for_statusjson
exceptionsRequestExceptionr2   r3   str)r$   rB   rC   attemptresponsebackoffr7   s          r&   _fetch_jsonz"RedditScraperCollector._fetch_jsonO   s    W~ 	"G"#<< LL ''3."00Wc$BTBT>UXY>Y1Z[G6witDEJJw'))+}}&!	".  &&77 """\#bQ#ABWq[(JJqM"s   BB9B99D(AD##D(	subredditsortparamsseen_idsc                 2   |
t               }d| d| d}dt        i}|r|j                  |       dj                  d |j	                         D              }| d| }| j                  |      }	|	sg g fS g }
g }|	j                  d	i       j                  d
g       }t        |d      D ]i  \  }}|j                  d	i       }|j                  dd      }|j                  dd      }|r|sB||v r| j                  dxx   dz  cc<   ^|j                  d      r|j                  dd      dd nd}|
j                  t        |d|dt        j                  t        j                        j                         d|j                  dd       |||j                  dd      |j                  dd      |j                  d      |||j                  d      |j                  d      d	             |j                  ||f       | j                  dxx   dz  cc<   l |
|fS ) a  
        Scrape posts from a subreddit, filtering out already-seen posts.

        Args:
            subreddit: Subreddit name (without r/)
            sort: Sort type (hot, top, rising)
            params: Additional URL params (e.g., t=month for top)
            seen_ids: Set of post IDs to skip (already collected)

        Returns:
            Tuple of (List of TrendItem objects, List of (post_id, subreddit) for new posts)
        Nzhttps://old.reddit.com/r//z.jsonlimit&c              3   0   K   | ]  \  }}| d |   yw)=N ).0kvs      r&   	<genexpr>z;RedditScraperCollector._scrape_subreddit.<locals>.<genexpr>   s     GDAqs!A3ZGs   ?datachildrenr
   )startid titler   selftexti  r   globalzhttps://reddit.com	permalinkscorer   num_commentscreated_utcupvote_ratioauthor)	rZ   post_idrs   rt   ru   rp   r[   rv   rw   )
topic_namesourcerankregion	timestamprB   metadatar   )r4   r   updatejoinitemsrY   rJ   	enumerater#   r3   r   r   r;   r	   r<   r=   )r$   rZ   r[   r\   r]   base_url
url_params	param_strrB   rj   r   new_post_idsr9   r{   post	post_datarx   ro   rp   s                      r&   _scrape_subredditz(RedditScraperCollector._scrape_subreddits   s#    uH.yk4&F34
f%HHGJ4D4D4FGG	
!I;'$r6M$((R8#E3 $	#JD$,ImmD"-GMM'2.E ("

6"a'" ?HmmJ>Wy}}Z4Tc:]_HLL ",,x||4>>@({B)G(HI!*&&]]7A6$-MM.!$D#,==#? ( $-MM.$A'mmH5
 & ) 45JJu"I$	#L l""r'   r|   c                 n   ddi d| _         | j                         }t        dt        |       d       g }g }| j                  D ]  }t        d| dd       g }g }| j                  |d	|
      \  }}	|j                  |       |j                  |	       |j                  d |	D               t        j                  t               | j                  |dddi|
      \  }
}|j                  |
       |j                  |       |j                  d |D               t        j                  t               | j                  |dddi|
      \  }}|j                  |       |j                  |       |j                  d |D               t        j                  t               t        |      | j                   d   |<   t        t        |       d       |j                  |       |j                  |        | j                  |       t        d| j                   d    d| j                   d    d       |S )a^  
        Collect trending questions from all target subreddits (incremental).

        Fetches 'hot', 'top/month', and 'rising' for each subreddit.
        Skips posts already seen in previous runs.

        Args:
            region: Ignored for Reddit (always global)

        Returns:
            List of TrendItem objects (only NEW posts)
        r   r   z    Loaded z previously seen post IDsz    Scraping r/z... )endhot)r]   c              3   &   K   | ]	  \  }}|  y wNrd   re   rm   _s      r&   rh   z1RedditScraperCollector.collect.<locals>.<genexpr>        452qB4   toptmonthc              3   &   K   | ]	  \  }}|  y wr   rd   r   s      r&   rh   z1RedditScraperCollector.collect.<locals>.<genexpr>   r   r   risingr`   2   c              3   &   K   | ]	  \  }}|  y wr   rd   r   s      r&   rh   z1RedditScraperCollector.collect.<locals>.<genexpr>   s     752qB7r   r   z
 new postsz    Reddit collection: r   z new, r   z skipped (already seen))r#   r8   rN   rM   r   r   extendr   rO   rP   r   rA   )r$   r|   r]   	all_itemsall_new_post_idsrZ   	sub_itemssub_new_ids	hot_itemshot_ids	top_itemstop_idsrising_items
rising_idss                 r&   collectzRedditScraperCollector.collect   s!    2>
 **,CM?*CDE	 !	1IOI;c2<IK "&!7!7	5S[!7!\IwY'w'OO4G44JJ+, "&!7!7	53PW.ck!7!lIwY'w'OO4G44JJ+, (,'='=8gr]X (> ($L* \*z*OO7J77JJ+,47	NDJJ~&y1S^$J/0Y'##K0C!	1H 	./'

5(9':&FAS@TTklmr'   r   )   )NN)rq   )__name__
__module____qualname____doc__r   rU   r    r   r8   r   rA   intr   dictrY   r   r   r   __classcell__)r%   s   @r&   r   r      s    ^J49 JCH Ad5c?&; A*"s "S "$ "J TXH#H#$'H#15H#HKCH#	tIU38_ 55	6H#T?c ?i ?r'   r   )r   r*   rO   rI   pathlibr   typingr   r   r   r   r   r	   baser   r   configr   r   r   r   __file__parentr,   r   rd   r'   r&   <module>r      s^        - - ' *  x.


&
&
14G
Ga] ar'   