paulhemb commited on
Commit
32c065c
·
verified ·
1 Parent(s): 0ba67b4

Update reddit_client.py

Browse files
Files changed (1) hide show
  1. reddit_client.py +93 -20
reddit_client.py CHANGED
@@ -1,61 +1,134 @@
 
1
  import time
 
2
  import requests
3
  import logging
4
- from typing import List, Dict
5
- from config import REDDIT_RPS
6
 
7
  logger = logging.getLogger(__name__)
8
 
9
- HEADERS = {"User-Agent": "AltomicAi/4.0 (by /u/yourusername)"}
 
10
 
11
  class RateLimiter:
 
12
  def __init__(self, requests_per_second: float):
13
  self.interval = 1.0 / requests_per_second if requests_per_second > 0 else 0
14
  self.last_request = 0.0
 
15
  def wait(self):
16
- if self.interval <= 0: return
 
17
  now = time.time()
18
  elapsed = now - self.last_request
19
  if elapsed < self.interval:
20
  time.sleep(self.interval - elapsed)
21
  self.last_request = time.time()
22
 
 
23
  class RedditClient:
24
  BASE_URL = "https://www.reddit.com"
25
- def __init__(self):
26
- self.rate_limiter = RateLimiter(REDDIT_RPS)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  def _request(self, url: str, params: dict = None) -> dict:
 
28
  self.rate_limiter.wait()
 
29
  try:
30
- resp = requests.get(url, headers=HEADERS, params=params, timeout=15)
 
 
 
 
 
 
31
  resp.raise_for_status()
32
  return resp.json()
33
- except Exception as e:
34
- logger.error(f"Reddit request failed: {e}")
 
35
  return {}
36
- def get_top_posts(self, subreddit: str, limit: int = 30, time_filter="day") -> List[dict]:
37
- all_posts, after = [], None
38
- remaining = limit
 
 
 
 
 
39
  per_page = min(limit, 100)
 
 
40
  while remaining > 0:
41
- params = {"limit": min(remaining, per_page), "t": time_filter}
42
- if after: params["after"] = after
 
 
 
 
 
43
  url = f"{self.BASE_URL}/r/{subreddit}/top.json"
44
  data = self._request(url, params)
45
- if not data: break
 
 
46
  children = data.get("data", {}).get("children", [])
47
- if not children: break
 
 
48
  for child in children:
49
  all_posts.append(child["data"])
50
  remaining -= 1
51
- if remaining == 0: break
 
 
52
  after = data.get("data", {}).get("after")
53
- if not after: break
 
 
 
54
  return all_posts
55
- def get_post_comments(self, post_id: str, limit: int = 5) -> List[dict]:
 
 
56
  url = f"{self.BASE_URL}/comments/{post_id}.json"
57
  data = self._request(url)
58
  if not data or not isinstance(data, list) or len(data) < 2:
59
  return []
60
  comments_data = data[1].get("data", {}).get("children", [])
61
- return [c["data"] for c in comments_data if c["kind"] == "t1"][:limit]
 
 
 
 
 
 
 
 
1
+ import os
2
  import time
3
+ import random
4
  import requests
5
  import logging
6
+ from typing import List, Dict, Optional
 
7
 
8
  logger = logging.getLogger(__name__)
9
 
10
+ HEADERS = {"User-Agent": "AltomicAi/3.0 (by /u/yourusername)"}
11
+
12
 
13
  class RateLimiter:
14
+ """Simple rate limiter to enforce a given number of requests per second."""
15
  def __init__(self, requests_per_second: float):
16
  self.interval = 1.0 / requests_per_second if requests_per_second > 0 else 0
17
  self.last_request = 0.0
18
+
19
  def wait(self):
20
+ if self.interval <= 0:
21
+ return
22
  now = time.time()
23
  elapsed = now - self.last_request
24
  if elapsed < self.interval:
25
  time.sleep(self.interval - elapsed)
26
  self.last_request = time.time()
27
 
28
+
29
  class RedditClient:
30
  BASE_URL = "https://www.reddit.com"
31
+
32
+ def __init__(self, requests_per_second: float = 0.3):
33
+ self.rate_limiter = RateLimiter(requests_per_second)
34
+ # Load proxies from environment variable (Hugging Face secret)
35
+ proxy_list_str = os.environ.get("PROXY_LIST")
36
+ self.proxy_list = self._parse_proxies(proxy_list_str)
37
+ if self.proxy_list:
38
+ logger.info(f"Loaded {len(self.proxy_list)} proxies. Will rotate randomly.")
39
+ else:
40
+ logger.warning("No proxies loaded. Requests will be made directly (may be blocked).")
41
+
42
+ def _parse_proxies(self, content: Optional[str]) -> List[Dict[str, str]]:
43
+ """Convert proxy lines (http://user:pass@ip:port) to requests proxy dicts."""
44
+ proxies = []
45
+ if not content:
46
+ return proxies
47
+ for line in content.strip().splitlines():
48
+ line = line.strip()
49
+ if line and not line.startswith('#'):
50
+ # Ensure the line starts with http:// or https://
51
+ if not line.startswith('http'):
52
+ line = f"http://{line}"
53
+ proxies.append({"http": line, "https": line})
54
+ return proxies
55
+
56
+ def _get_random_proxy(self) -> Optional[Dict[str, str]]:
57
+ if not self.proxy_list:
58
+ return None
59
+ return random.choice(self.proxy_list)
60
+
61
  def _request(self, url: str, params: dict = None) -> dict:
62
+ """Make a request with rate limiting and proxy rotation."""
63
  self.rate_limiter.wait()
64
+ proxy = self._get_random_proxy()
65
  try:
66
+ resp = requests.get(
67
+ url,
68
+ headers=HEADERS,
69
+ params=params,
70
+ proxies=proxy,
71
+ timeout=15
72
+ )
73
  resp.raise_for_status()
74
  return resp.json()
75
+ except requests.exceptions.RequestException as e:
76
+ logger.error(f"Reddit request failed (proxy used: {proxy}): {e}")
77
+ # Optionally remove failed proxy from list? Not implemented for simplicity.
78
  return {}
79
+
80
+ def get_top_posts(self, subreddit: str, limit: int = 30, time_filter: str = "day") -> List[dict]:
81
+ """
82
+ Fetch top posts from a subreddit, handling pagination up to `limit` posts.
83
+ Returns a list of post dicts.
84
+ """
85
+ all_posts = []
86
+ after = None
87
  per_page = min(limit, 100)
88
+ remaining = limit
89
+
90
  while remaining > 0:
91
+ params = {
92
+ "limit": min(remaining, per_page),
93
+ "t": time_filter,
94
+ }
95
+ if after:
96
+ params["after"] = after
97
+
98
  url = f"{self.BASE_URL}/r/{subreddit}/top.json"
99
  data = self._request(url, params)
100
+ if not data:
101
+ break
102
+
103
  children = data.get("data", {}).get("children", [])
104
+ if not children:
105
+ break
106
+
107
  for child in children:
108
  all_posts.append(child["data"])
109
  remaining -= 1
110
+ if remaining == 0:
111
+ break
112
+
113
  after = data.get("data", {}).get("after")
114
+ if not after:
115
+ break
116
+
117
+ logger.info(f"Fetched {len(all_posts)} posts from r/{subreddit}")
118
  return all_posts
119
+
120
+ def get_post_comments(self, post_id: str, limit: int = 20) -> List[dict]:
121
+ """Fetch top-level comments for a post."""
122
  url = f"{self.BASE_URL}/comments/{post_id}.json"
123
  data = self._request(url)
124
  if not data or not isinstance(data, list) or len(data) < 2:
125
  return []
126
  comments_data = data[1].get("data", {}).get("children", [])
127
+ comments = [c["data"] for c in comments_data if c["kind"] == "t1"]
128
+ return comments[:limit]
129
+
130
+ def get_subreddit_info(self, subreddit: str) -> dict:
131
+ """Fetch subreddit metadata."""
132
+ url = f"{self.BASE_URL}/r/{subreddit}/about.json"
133
+ data = self._request(url)
134
+ return data.get("data", {}) if data else {}