reddipy 1.0.0__tar.gz

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.
Files changed (36) hide show
  1. reddipy-1.0.0/LICENSE +21 -0
  2. reddipy-1.0.0/PKG-INFO +645 -0
  3. reddipy-1.0.0/README.md +613 -0
  4. reddipy-1.0.0/pyproject.toml +49 -0
  5. reddipy-1.0.0/reddipy/__init__.py +4 -0
  6. reddipy-1.0.0/reddipy/__main__.py +3 -0
  7. reddipy-1.0.0/reddipy/cli.py +238 -0
  8. reddipy-1.0.0/reddipy/client.py +254 -0
  9. reddipy-1.0.0/reddipy/config.py +44 -0
  10. reddipy-1.0.0/reddipy/exceptions.py +29 -0
  11. reddipy-1.0.0/reddipy/exporters/__init__.py +5 -0
  12. reddipy-1.0.0/reddipy/exporters/base.py +9 -0
  13. reddipy-1.0.0/reddipy/exporters/console.py +96 -0
  14. reddipy-1.0.0/reddipy/exporters/csv_exporter.py +59 -0
  15. reddipy-1.0.0/reddipy/exporters/json_exporter.py +24 -0
  16. reddipy-1.0.0/reddipy/models.py +151 -0
  17. reddipy-1.0.0/reddipy/scrapers/__init__.py +6 -0
  18. reddipy-1.0.0/reddipy/scrapers/base.py +16 -0
  19. reddipy-1.0.0/reddipy/scrapers/post.py +30 -0
  20. reddipy-1.0.0/reddipy/scrapers/search.py +36 -0
  21. reddipy-1.0.0/reddipy/scrapers/subreddit.py +29 -0
  22. reddipy-1.0.0/reddipy/scrapers/user.py +55 -0
  23. reddipy-1.0.0/reddipy/sdk.py +146 -0
  24. reddipy-1.0.0/reddipy/utils.py +70 -0
  25. reddipy-1.0.0/reddipy.egg-info/PKG-INFO +645 -0
  26. reddipy-1.0.0/reddipy.egg-info/SOURCES.txt +34 -0
  27. reddipy-1.0.0/reddipy.egg-info/dependency_links.txt +1 -0
  28. reddipy-1.0.0/reddipy.egg-info/entry_points.txt +2 -0
  29. reddipy-1.0.0/reddipy.egg-info/requires.txt +8 -0
  30. reddipy-1.0.0/reddipy.egg-info/top_level.txt +1 -0
  31. reddipy-1.0.0/setup.cfg +4 -0
  32. reddipy-1.0.0/tests/test_cli.py +184 -0
  33. reddipy-1.0.0/tests/test_client.py +367 -0
  34. reddipy-1.0.0/tests/test_exceptions.py +82 -0
  35. reddipy-1.0.0/tests/test_models.py +351 -0
  36. reddipy-1.0.0/tests/test_utils.py +89 -0
reddipy-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Muhammad Zaid
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
reddipy-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,645 @@
1
+ Metadata-Version: 2.4
2
+ Name: reddipy
3
+ Version: 1.0.0
4
+ Summary: Scrape Reddit posts, comments, and user activity — no API key needed. Python SDK + CLI tool.
5
+ Author: Muhammad Zaid
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/zaidkx37/reddipy
8
+ Project-URL: Repository, https://github.com/zaidkx37/reddipy
9
+ Project-URL: Issues, https://github.com/zaidkx37/reddipy/issues
10
+ Keywords: reddit,scraper,reddit-scraper,reddit-api,posts,comments,subreddit,user-activity,data-extraction,web-scraping,cli,sdk,no-api-key,json,csv,export
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Internet :: WWW/HTTP
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Environment :: Console
21
+ Classifier: Operating System :: OS Independent
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: requests>=2.28.0
26
+ Requires-Dist: rich>=13.0
27
+ Provides-Extra: socks
28
+ Requires-Dist: requests[socks]; extra == "socks"
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=7.0; extra == "dev"
31
+ Dynamic: license-file
32
+
33
+ <h1 align="center">Reddipy</h1>
34
+
35
+ <p align="center">
36
+ <strong>Scrape Reddit posts, comments, and user activity. Python SDK + CLI.</strong>
37
+ </p>
38
+
39
+ <p align="center">
40
+ <a href="https://pypi.org/project/reddipy/"><img src="https://img.shields.io/pypi/v/reddipy.svg?style=flat-square&color=blue" alt="PyPI version"></a>
41
+ <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.10%20|%203.11%20|%203.12%20|%203.13-blue.svg?style=flat-square" alt="Python versions"></a>
42
+ <a href="https://github.com/zaidkx37/reddipy/actions"><img src="https://img.shields.io/github/actions/workflow/status/zaidkx37/reddipy/ci.yml?style=flat-square&label=CI" alt="CI"></a>
43
+ <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-green.svg?style=flat-square" alt="License: MIT"></a>
44
+ <a href="https://pypi.org/project/reddipy/"><img src="https://img.shields.io/pypi/dm/reddipy?style=flat-square&color=orange&label=downloads" alt="Downloads"></a>
45
+ </p>
46
+
47
+ <p align="center">
48
+ Built on Reddit's public JSON endpoints. Two interfaces: <b>Python SDK</b> and <b>CLI</b>.
49
+ </p>
50
+
51
+ ---
52
+
53
+ ## Features
54
+
55
+ - **Search** Reddit by keyword with sort, time, and subreddit filters
56
+ - **Scrape posts** with full comment trees, scores, and metadata
57
+ - **Subreddit browsing** with hot, new, top, rising, and controversial sorting
58
+ - **User profiles** with post/comment activity filtering
59
+ - **Export** to JSON, CSV, or rich console tables
60
+ - **Auto-pagination** to collect beyond the 100-per-request limit
61
+ - **Proxy rotation** with single proxy, proxy file, and dead proxy cooldown
62
+ - **Rate limiting** with configurable delay and exponential backoff
63
+ - **Realistic headers** matching a real Chrome browser fingerprint
64
+ - **Lightweight** with only `requests` and `rich` as dependencies
65
+
66
+ ---
67
+
68
+ ## Installation
69
+
70
+ Recommended to **[install using pip](https://pypi.org/project/reddipy/)**
71
+
72
+ ```bash
73
+ pip install reddipy
74
+ ```
75
+
76
+ For SOCKS5 proxy support:
77
+
78
+ ```bash
79
+ pip install "reddipy[socks]"
80
+ ```
81
+
82
+ **Requirements:** Python 3.10+
83
+
84
+ ## Authentication
85
+
86
+ Reddipy requires a `reddit_session` cookie from your browser. This is the only cookie needed.
87
+
88
+ ### Step 1: Get the Cookie
89
+
90
+ 1. Open [reddit.com](https://www.reddit.com) in your browser and log in
91
+ 2. Open DevTools (`F12`) > **Application** tab > **Cookies** > `https://www.reddit.com`
92
+ 3. Find the cookie named `reddit_session`
93
+ 4. Copy its **Value** (a long JWT string starting with `eyJ...`)
94
+
95
+ ### Step 2: Configure (One-Time)
96
+
97
+ **SDK:**
98
+
99
+ ```python
100
+ from reddipy import Reddit
101
+
102
+ Reddit.configure(cookie="eyJhbGciOiJS...")
103
+ ```
104
+
105
+ **CLI:**
106
+
107
+ ```bash
108
+ reddipy configure "eyJhbGciOiJS..."
109
+ ```
110
+
111
+ The cookie is saved to `~/.reddipy/config.json`. You only need to do this once. All future SDK and CLI calls will use it automatically.
112
+
113
+ ### Auth Management
114
+
115
+ ```python
116
+ Reddit.configure(cookie="...") # save cookie
117
+ Reddit.is_configured() # check if cookie exists (returns True/False)
118
+ Reddit.logout() # remove stored cookie
119
+ ```
120
+
121
+ ```bash
122
+ reddipy configure "cookie" # save
123
+ reddipy logout # remove
124
+ ```
125
+
126
+ ### Override Stored Cookie
127
+
128
+ If you need to use a different cookie for a single session:
129
+
130
+ ```python
131
+ reddit = Reddit(cookie="different_cookie_here")
132
+ ```
133
+
134
+ ```bash
135
+ reddipy search "python" --cookie "different_cookie_here"
136
+ ```
137
+
138
+ ---
139
+
140
+ ## Python SDK
141
+
142
+ ### Initialize
143
+
144
+ ```python
145
+ from reddipy import Reddit
146
+
147
+ reddit = Reddit()
148
+ ```
149
+
150
+ All methods below assume you have already configured authentication.
151
+
152
+ ### Search
153
+
154
+ Search Reddit for posts matching a keyword.
155
+
156
+ ```python
157
+ # Basic search
158
+ posts = reddit.search("machine learning", limit=10)
159
+ for post in posts:
160
+ print(f"[{post.score}] {post.title}")
161
+ print(f" r/{post.subreddit} by {post.author}")
162
+ ```
163
+
164
+ **Search within a specific subreddit:**
165
+
166
+ ```python
167
+ posts = reddit.search("async", subreddit="python", limit=10)
168
+ ```
169
+
170
+ **All parameters:**
171
+
172
+ | Parameter | Type | Default | Description |
173
+ |-----------|------|---------|-------------|
174
+ | `query` | str | required | Search keywords |
175
+ | `limit` | int | 25 | Max results (up to ~1000) |
176
+ | `sort` | str | "relevance" | relevance, hot, new, top, comments |
177
+ | `time_filter` | str | "all" | day, week, month, year, all |
178
+ | `subreddit` | str | None | Scope search to a subreddit |
179
+
180
+ **Returns:** `list[Post]`
181
+
182
+ ### Scrape a Post
183
+
184
+ Extract a single post with its full comment tree.
185
+
186
+ ```python
187
+ post, comments = reddit.post("https://www.reddit.com/r/python/comments/abc123/title/")
188
+
189
+ # Post data
190
+ print(post.title)
191
+ print(post.author)
192
+ print(post.score)
193
+ print(post.selftext) # post body
194
+ print(post.flair) # post flair (or None)
195
+ print(post.num_comments)
196
+ print(post.upvote_ratio)
197
+
198
+ # Comments
199
+ print(f"{len(comments)} top-level comments loaded")
200
+
201
+ for comment in comments[:5]:
202
+ print(f"[{comment.score}] {comment.author}: {comment.body[:80]}")
203
+ print(f" depth: {comment.depth}, is_submitter: {comment.is_submitter}")
204
+ ```
205
+
206
+ Accepts any Reddit post URL format:
207
+
208
+ ```python
209
+ # All of these work
210
+ reddit.post("https://www.reddit.com/r/python/comments/abc123/title/")
211
+ reddit.post("https://old.reddit.com/r/python/comments/abc123/")
212
+ reddit.post("https://reddit.com/r/python/comments/abc123/title")
213
+ reddit.post("/r/python/comments/abc123/title/") # bare permalink
214
+ ```
215
+
216
+ **Returns:** `tuple[Post, list[Comment]]`
217
+
218
+ ### Subreddit Posts
219
+
220
+ Fetch posts from a subreddit with sort and time filters.
221
+
222
+ ```python
223
+ # Hot posts (default)
224
+ posts = reddit.subreddit("python")
225
+
226
+ # Top posts this week
227
+ posts = reddit.subreddit("python", sort="top", time_filter="week", limit=50)
228
+
229
+ # New posts
230
+ posts = reddit.subreddit("datascience", sort="new", limit=20)
231
+
232
+ # Rising posts
233
+ posts = reddit.subreddit("technology", sort="rising", limit=10)
234
+
235
+ for post in posts:
236
+ print(f"[{post.score:>5}] {post.title}")
237
+ print(f" {post.num_comments} comments | r/{post.subreddit}")
238
+ ```
239
+
240
+ **All parameters:**
241
+
242
+ | Parameter | Type | Default | Description |
243
+ |-----------|------|---------|-------------|
244
+ | `name` | str | required | Subreddit name (without `r/`) |
245
+ | `sort` | str | "hot" | hot, new, top, rising, controversial |
246
+ | `time_filter` | str | "all" | day, week, month, year, all (for top/controversial) |
247
+ | `limit` | int | 25 | Max posts |
248
+
249
+ **Returns:** `list[Post]`
250
+
251
+ ### User Activity
252
+
253
+ Fetch a Reddit user's recent posts and comments.
254
+
255
+ ```python
256
+ # All activity
257
+ activity = reddit.user("spez", limit=10)
258
+
259
+ for item in activity:
260
+ print(f"[{item.type}] r/{item.subreddit} ({item.score} pts)")
261
+ if item.type == "post":
262
+ print(f" {item.title}")
263
+ else:
264
+ print(f" {item.body[:80]}")
265
+ ```
266
+
267
+ **Filter by activity type:**
268
+
269
+ ```python
270
+ # Posts only
271
+ posts = reddit.user("spez", limit=10, activity_type="posts")
272
+
273
+ # Comments only
274
+ comments = reddit.user("spez", limit=10, activity_type="comments")
275
+
276
+ # Both (default)
277
+ all_activity = reddit.user("spez", limit=10, activity_type="all")
278
+ ```
279
+
280
+ **All parameters:**
281
+
282
+ | Parameter | Type | Default | Description |
283
+ |-----------|------|---------|-------------|
284
+ | `username` | str | required | Reddit username (without `u/`) |
285
+ | `limit` | int | 25 | Max items |
286
+ | `sort` | str | "new" | new, hot, top |
287
+ | `activity_type` | str | "all" | posts, comments, all |
288
+
289
+ **Returns:** `list[UserActivity]`
290
+
291
+ ### Export Data
292
+
293
+ Save scraped data to JSON, CSV, or display as a formatted table.
294
+
295
+ **JSON:**
296
+
297
+ ```python
298
+ from reddipy.exporters import JSONExporter
299
+
300
+ posts = reddit.search("python", limit=20)
301
+ JSONExporter().export(posts, "results.json")
302
+
303
+ # Or print to stdout (no file path)
304
+ JSONExporter().export(posts)
305
+ ```
306
+
307
+ **CSV:**
308
+
309
+ ```python
310
+ from reddipy.exporters import CSVExporter
311
+
312
+ posts = reddit.subreddit("python", sort="top", limit=50)
313
+ CSVExporter().export(posts, "posts.csv")
314
+ ```
315
+
316
+ **Console table (rich formatted):**
317
+
318
+ ```python
319
+ from reddipy.exporters import ConsoleExporter
320
+
321
+ posts = reddit.search("python", limit=10)
322
+ ConsoleExporter().export(posts)
323
+ ```
324
+
325
+ **Exporting comments:**
326
+
327
+ ```python
328
+ post, comments = reddit.post("https://reddit.com/r/python/comments/abc123/title/")
329
+
330
+ # Comments to JSON (includes post and comment data)
331
+ import json
332
+ combined = {
333
+ "post": post.to_dict(),
334
+ "comments": [c.to_dict() for c in comments]
335
+ }
336
+ with open("post_data.json", "w") as f:
337
+ json.dump(combined, f, indent=2)
338
+
339
+ # Comments to CSV (flattened, with depth/parent_id columns)
340
+ CSVExporter().export(comments, "comments.csv")
341
+ ```
342
+
343
+ ### Proxy Support (SDK)
344
+
345
+ Route requests through proxies to distribute load or bypass IP restrictions.
346
+
347
+ **Single proxy:**
348
+
349
+ ```python
350
+ reddit = Reddit(proxy="http://1.2.3.4:8080")
351
+ ```
352
+
353
+ **Multiple proxies with random rotation:**
354
+
355
+ ```python
356
+ reddit = Reddit(proxy_file="proxies.txt")
357
+ ```
358
+
359
+ Each request picks a random proxy from the file. Failed proxies are automatically skipped for 60 seconds.
360
+
361
+ **proxies.txt format:**
362
+
363
+ ```
364
+ http://1.2.3.4:8080
365
+ http://5.6.7.8:3128
366
+ socks5://9.10.11.12:1080
367
+ ```
368
+
369
+ SOCKS5 requires: `pip install "reddipy[socks]"`
370
+
371
+ ### Data Models
372
+
373
+ Every scraped item is a Python dataclass with typed fields. All models have a `.to_dict()` method for serialization.
374
+
375
+ **Post**
376
+
377
+ | Field | Type | Description |
378
+ |-------|------|-------------|
379
+ | `id` | str | Reddit post ID |
380
+ | `title` | str | Post title |
381
+ | `author` | str | Author username |
382
+ | `subreddit` | str | Subreddit name |
383
+ | `score` | int | Net upvotes |
384
+ | `upvote_ratio` | float | Upvote percentage (0.0 - 1.0) |
385
+ | `num_comments` | int | Total comment count |
386
+ | `created_utc` | datetime | Post creation time (UTC) |
387
+ | `selftext` | str | Post body text (empty for link posts) |
388
+ | `url` | str | Post URL or linked URL |
389
+ | `permalink` | str | Reddit permalink |
390
+ | `flair` | str or None | Post flair text |
391
+
392
+ **Comment**
393
+
394
+ | Field | Type | Description |
395
+ |-------|------|-------------|
396
+ | `id` | str | Comment ID |
397
+ | `author` | str | Author username |
398
+ | `body` | str | Comment text |
399
+ | `score` | int | Net upvotes |
400
+ | `created_utc` | datetime | Comment creation time (UTC) |
401
+ | `parent_id` | str | Parent post/comment ID |
402
+ | `depth` | int | Nesting depth (0 = top-level) |
403
+ | `is_submitter` | bool | True if comment author is the post author |
404
+ | `replies` | list[Comment] | Nested reply comments |
405
+
406
+ **UserActivity**
407
+
408
+ | Field | Type | Description |
409
+ |-------|------|-------------|
410
+ | `type` | str | "post" or "comment" |
411
+ | `id` | str | Item ID |
412
+ | `author` | str | Username |
413
+ | `subreddit` | str | Subreddit name |
414
+ | `score` | int | Net upvotes |
415
+ | `created_utc` | datetime | Creation time (UTC) |
416
+ | `title` | str or None | Post title (None for comments) |
417
+ | `body` | str or None | Comment body (None for posts) |
418
+ | `permalink` | str | Reddit permalink |
419
+
420
+ **Serialization:**
421
+
422
+ ```python
423
+ post_dict = post.to_dict()
424
+ # {"id": "abc123", "title": "...", "created_utc": "2026-07-03T12:00:00+00:00", ...}
425
+
426
+ # datetime fields become ISO 8601 strings in to_dict()
427
+ # Nested comment replies are recursively serialized
428
+ ```
429
+
430
+ ### Error Handling
431
+
432
+ Reddipy raises specific exceptions you can catch:
433
+
434
+ ```python
435
+ from reddipy.exceptions import (
436
+ ReddipyError, # base exception (catch-all)
437
+ SubredditNotFoundError, # subreddit is private, banned, or doesn't exist
438
+ PostNotFoundError, # post URL is invalid or deleted
439
+ UserNotFoundError, # user is suspended or doesn't exist
440
+ RateLimitError, # rate limited after 3 retries with backoff
441
+ NetworkError, # connection failure or timeout
442
+ ProxyError, # all proxies are dead
443
+ )
444
+
445
+ try:
446
+ posts = reddit.subreddit("nonexistent_sub_12345")
447
+ except SubredditNotFoundError:
448
+ print("Subreddit doesn't exist")
449
+ except RateLimitError:
450
+ print("Rate limited, try again later or reduce --delay")
451
+ except NetworkError:
452
+ print("Connection failed")
453
+ ```
454
+
455
+ ### Advanced Configuration
456
+
457
+ ```python
458
+ reddit = Reddit(
459
+ cookie="override_cookie", # override stored cookie
460
+ delay=1.0, # seconds between requests (default: 2.0)
461
+ proxy="http://1.2.3.4:8080", # single proxy
462
+ proxy_file="proxies.txt", # proxy list with random rotation
463
+ user_agent="Custom Agent/1.0", # custom User-Agent string
464
+ verbose=True, # print progress to stderr
465
+ )
466
+ ```
467
+
468
+ ---
469
+
470
+ ## CLI Usage
471
+
472
+ All CLI commands use stored authentication by default. Run `reddipy configure` first.
473
+
474
+ ### Search (CLI)
475
+
476
+ ```bash
477
+ # Basic search
478
+ reddipy search "python"
479
+
480
+ # With filters
481
+ reddipy search "machine learning" --limit 20 --sort top --time week
482
+
483
+ # Search within a subreddit
484
+ reddipy search "flask" --subreddit python --limit 10
485
+ reddipy search "hooks" -s react --sort new
486
+ ```
487
+
488
+ ### Post (CLI)
489
+
490
+ ```bash
491
+ # Scrape a post with all comments
492
+ reddipy post "https://www.reddit.com/r/python/comments/abc123/title/"
493
+
494
+ # Export to JSON
495
+ reddipy post "https://reddit.com/r/python/comments/abc123/title/" --export json -o post.json
496
+ ```
497
+
498
+ ### Subreddit (CLI)
499
+
500
+ ```bash
501
+ # Hot posts (default)
502
+ reddipy subreddit python
503
+
504
+ # Top posts this month
505
+ reddipy subreddit datascience --sort top --time month --limit 50
506
+
507
+ # New posts
508
+ reddipy subreddit technology --sort new --limit 20
509
+
510
+ # Rising
511
+ reddipy subreddit programming --sort rising
512
+ ```
513
+
514
+ ### User (CLI)
515
+
516
+ ```bash
517
+ # All activity
518
+ reddipy user spez --limit 10
519
+
520
+ # Posts only
521
+ reddipy user spez --type posts --limit 20
522
+
523
+ # Comments only, sorted by top
524
+ reddipy user spez --type comments --sort top
525
+ ```
526
+
527
+ ### Export (CLI)
528
+
529
+ ```bash
530
+ # JSON file
531
+ reddipy search "python" --export json -o results.json
532
+
533
+ # CSV file
534
+ reddipy subreddit python --export csv -o posts.csv
535
+
536
+ # Console table (default, no flag needed)
537
+ reddipy search "python" --limit 5
538
+
539
+ # Auto-generated filename (omit -o)
540
+ reddipy search "python" --export json
541
+ # Creates: reddipy_search_20260703_143000.json
542
+ ```
543
+
544
+ ### Proxy Support (CLI)
545
+
546
+ ```bash
547
+ # Single proxy
548
+ reddipy search "python" --proxy http://1.2.3.4:8080
549
+
550
+ # Multiple proxies with random rotation
551
+ reddipy search "python" --proxy-file proxies.txt
552
+ ```
553
+
554
+ **proxies.txt** (one proxy per line):
555
+
556
+ ```
557
+ http://1.2.3.4:8080
558
+ http://5.6.7.8:3128
559
+ socks5://9.10.11.12:1080
560
+ ```
561
+
562
+ SOCKS5 requires: `pip install "reddipy[socks]"`
563
+
564
+ ### All CLI Flags
565
+
566
+ **Common flags (available on all commands):**
567
+
568
+ | Flag | Description | Default |
569
+ |------|-------------|---------|
570
+ | `--limit` | Max number of results | 25 |
571
+ | `--sort` | Sort order | varies by command |
572
+ | `--time` / `-t` | Time filter (day/week/month/year/all) | all |
573
+ | `--export` | Output format (json/csv/console) | console |
574
+ | `--output` / `-o` | Output file path | auto-generated |
575
+ | `--cookie` | Override stored reddit_session cookie | stored config |
576
+ | `--cookie-file` | Path to JSON file with cookies | - |
577
+ | `--proxy` | Single proxy URL | - |
578
+ | `--proxy-file` | Path to proxy list file | - |
579
+ | `--delay` | Seconds between requests | 2.0 |
580
+ | `--user-agent` | Custom User-Agent header | Chrome 149 |
581
+ | `--verbose` / `-v` | Show progress and retry info | false |
582
+
583
+ **Command-specific flags:**
584
+
585
+ | Command | Flag | Description | Default |
586
+ |---------|------|-------------|---------|
587
+ | `search` | `--subreddit` / `-s` | Scope search to a subreddit | all of Reddit |
588
+ | `user` | `--type` | Filter activity (posts/comments/all) | all |
589
+
590
+ **Exit codes:**
591
+
592
+ | Code | Meaning |
593
+ |------|---------|
594
+ | 0 | Success |
595
+ | 1 | General error (invalid args, unexpected failure) |
596
+ | 2 | Network error (connection failed, rate limited, proxies dead) |
597
+ | 3 | Not found (subreddit/post/user doesn't exist) |
598
+
599
+ ---
600
+
601
+ ## Rate Limiting
602
+
603
+ Reddipy has built-in protections to avoid getting blocked:
604
+
605
+ - **Request delay:** 2-second gap between requests (configurable with `--delay` or `delay=` parameter)
606
+ - **Cooldown on 429:** Automatic exponential backoff (2s, 4s, 8s) when rate-limited by Reddit
607
+ - **Max retries:** 3 attempts with increasing wait times before raising an error
608
+ - **Dead proxy cooldown:** Failed proxies are skipped for 60 seconds before being retried
609
+ - **Realistic headers:** All requests include 14 browser-standard headers (Accept, Sec-CH-UA, Sec-Fetch-*, etc.) matching a real Chrome browser fingerprint
610
+
611
+ **Tips for staying safe:**
612
+
613
+ - Keep `delay` at 2.0 or higher for normal use
614
+ - Use `--verbose` to see when retries and cooldowns happen
615
+ - For large scrapes (500+ items), use proxies to distribute load
616
+ - Don't run multiple instances on the same account simultaneously
617
+
618
+ ## Project Structure
619
+
620
+ ```
621
+ reddipy/
622
+ __init__.py # Package exports: Reddit, Post, Comment, UserActivity
623
+ sdk.py # Python SDK (Reddit class)
624
+ cli.py # CLI entry point (argparse subcommands)
625
+ client.py # HTTP client (headers, rate limiting, pagination, proxies)
626
+ config.py # Persistent auth (~/.reddipy/config.json)
627
+ models.py # Post, Comment, UserActivity dataclasses
628
+ exceptions.py # ReddipyError, SubredditNotFoundError, etc.
629
+ utils.py # URL parsing, timestamp conversion, filename generation
630
+ scrapers/
631
+ base.py # BaseScraper abstract class
632
+ search.py # SearchScraper
633
+ post.py # PostScraper
634
+ subreddit.py # SubredditScraper
635
+ user.py # UserScraper
636
+ exporters/
637
+ base.py # BaseExporter abstract class
638
+ json_exporter.py
639
+ csv_exporter.py
640
+ console.py # Rich formatted tables
641
+ ```
642
+
643
+ ## License
644
+
645
+ MIT