proxy-scraper-cli 1.7.1__py3-none-any.whl

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 (57) hide show
  1. proxy_scraper_cli-1.7.1.dist-info/METADATA +794 -0
  2. proxy_scraper_cli-1.7.1.dist-info/RECORD +57 -0
  3. proxy_scraper_cli-1.7.1.dist-info/WHEEL +5 -0
  4. proxy_scraper_cli-1.7.1.dist-info/entry_points.txt +4 -0
  5. proxy_scraper_cli-1.7.1.dist-info/licenses/LICENSE +21 -0
  6. proxy_scraper_cli-1.7.1.dist-info/top_level.txt +1 -0
  7. proxyscraper/__init__.py +12 -0
  8. proxyscraper/__main__.py +7 -0
  9. proxyscraper/agent.py +635 -0
  10. proxyscraper/api.py +139 -0
  11. proxyscraper/app.py +595 -0
  12. proxyscraper/asndb.py +181 -0
  13. proxyscraper/blocklist.py +92 -0
  14. proxyscraper/checker.py +514 -0
  15. proxyscraper/cli.py +269 -0
  16. proxyscraper/compat.py +83 -0
  17. proxyscraper/completion.py +196 -0
  18. proxyscraper/exporters.py +118 -0
  19. proxyscraper/fetchcache.py +103 -0
  20. proxyscraper/geo.py +150 -0
  21. proxyscraper/geodb.py +143 -0
  22. proxyscraper/handshake.py +153 -0
  23. proxyscraper/history.py +106 -0
  24. proxyscraper/judges.py +159 -0
  25. proxyscraper/mcp_entry.py +34 -0
  26. proxyscraper/mcp_server.py +220 -0
  27. proxyscraper/netio.py +167 -0
  28. proxyscraper/options.py +274 -0
  29. proxyscraper/output.py +181 -0
  30. proxyscraper/pages.py +212 -0
  31. proxyscraper/parsing.py +192 -0
  32. proxyscraper/paths.py +55 -0
  33. proxyscraper/pipeline.py +434 -0
  34. proxyscraper/preferences.py +24 -0
  35. proxyscraper/publish.py +236 -0
  36. proxyscraper/server/__init__.py +41 -0
  37. proxyscraper/server/core.py +518 -0
  38. proxyscraper/server/http.py +164 -0
  39. proxyscraper/server/pool.py +195 -0
  40. proxyscraper/server/socks.py +65 -0
  41. proxyscraper/server/status.py +108 -0
  42. proxyscraper/server/upstream.py +119 -0
  43. proxyscraper/site/apple-touch-icon.png +0 -0
  44. proxyscraper/site/googleaac1161b7853c5b5.html +1 -0
  45. proxyscraper/site/index.html +934 -0
  46. proxyscraper/site/logo.png +0 -0
  47. proxyscraper/site/og.png +0 -0
  48. proxyscraper/sources.json +395 -0
  49. proxyscraper/sources.py +491 -0
  50. proxyscraper/targets.py +76 -0
  51. proxyscraper/ui/__init__.py +54 -0
  52. proxyscraper/ui/dashboard.py +344 -0
  53. proxyscraper/ui/keys.py +82 -0
  54. proxyscraper/ui/report.py +209 -0
  55. proxyscraper/ui/serve.py +142 -0
  56. proxyscraper/ui/widgets.py +250 -0
  57. proxyscraper/ui/wizard.py +595 -0
proxyscraper/api.py ADDED
@@ -0,0 +1,139 @@
1
+ """Python API: use proxy-scraper from your own code.
2
+
3
+ from proxyscraper import find_proxies
4
+
5
+ if __name__ == "__main__": # important on macOS/Windows, see below
6
+ for p in find_proxies(want=20, https=True, countries=["DE", "NL"]):
7
+ print(p.url, p.latency, p.country)
8
+
9
+ Behind it runs exactly the same as on the command line (sources, learning, honeypot and
10
+ tampering checks, result files under results/), just without output in the terminal.
11
+
12
+ Large lists are parsed in a process pool. On macOS and Windows it starts the worker processes
13
+ with "spawn", which re-imports the calling script – as with any code that uses multiprocessing,
14
+ the call therefore belongs behind `if __name__ == "__main__":`.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import asyncio
20
+ import contextlib
21
+ import io
22
+ import os
23
+ import tempfile
24
+ import threading
25
+ from typing import Iterable, List, Optional
26
+
27
+ from rich.console import Console
28
+
29
+ from .checker import CheckResult
30
+ from .options import Filters, RunOptions, parse_countries
31
+ from .parsing import PROXY_TYPES
32
+ from .targets import parse_target
33
+ from .ui import widgets
34
+
35
+ __all__ = ["CheckResult", "check_proxies", "check_proxies_async", "find_proxies", "find_proxies_async"]
36
+
37
+
38
+ def _options(types: Iterable[str], want: int, limit: int, https: bool, countries: Iterable[str], anonymity: str,
39
+ max_latency: int, targets: Iterable[str], no_datacenter: bool, no_blocklisted: bool, timeout: float,
40
+ concurrency: int,
41
+ recheck: Optional[str]) -> RunOptions:
42
+ if isinstance(countries, str):
43
+ countries = parse_countries(countries)
44
+ if anonymity not in ("", "anonymous", "elite"):
45
+ raise ValueError(f"anonymity must be '', 'anonymous' or 'elite', not {anonymity!r}")
46
+ # like the CLI: "google.com" -> "https://google.com/", duplicates dropped, invalid targets -> ValueError
47
+ targets = list(dict.fromkeys(parse_target(t).url for t in targets))
48
+ return RunOptions(
49
+ types=list(types),
50
+ filters=Filters(countries={c.upper() for c in countries}, https_only=https, min_anonymity=anonymity,
51
+ max_latency=max_latency, targets=targets, no_datacenter=no_datacenter,
52
+ no_blocklisted=no_blocklisted),
53
+ want=want, limit=limit, timeout=timeout, concurrency=concurrency, recheck=recheck,
54
+ )
55
+
56
+
57
+ # one run after the other: console, history and source statistics are global, and a run uses thousands
58
+ # of connections at once anyway. A threading.Lock, so this also holds across threads and event loops.
59
+ _RUN_LOCK = threading.Lock()
60
+
61
+
62
+ @contextlib.asynccontextmanager
63
+ async def _one_at_a_time():
64
+ # wait without blocking: the event loop keeps running, and cancelling while waiting leaves no lock behind
65
+ while not _RUN_LOCK.acquire(blocking=False):
66
+ await asyncio.sleep(0.05)
67
+ try:
68
+ yield
69
+ finally:
70
+ _RUN_LOCK.release()
71
+
72
+
73
+ @contextlib.contextmanager
74
+ def _quiet(verbose: bool):
75
+ """The UI writes to widgets.console – for the API into a buffer instead of the terminal."""
76
+ if verbose:
77
+ yield
78
+ return
79
+ original = widgets.console
80
+ widgets.console = Console(file=io.StringIO(), width=120)
81
+ try:
82
+ yield
83
+ finally:
84
+ widgets.console = original
85
+
86
+
87
+ async def find_proxies_async(*, types: Iterable[str] = PROXY_TYPES, want: int = 0, limit: int = 0,
88
+ https: bool = False, countries: Iterable[str] = (), anonymity: str = "",
89
+ max_latency: int = 0, targets: Iterable[str] = (), no_datacenter: bool = False,
90
+ no_blocklisted: bool = False,
91
+ timeout: float = 8.0, concurrency: int = 2000, verbose: bool = False,
92
+ _recheck: Optional[str] = None) -> List[CheckResult]:
93
+ """Collect and check proxies; returns the hits that pass every filter, fastest first.
94
+
95
+ want stop as soon as this many matching proxies are found (0 = check everything)
96
+ limit only check the N most promising candidates (0 = all)
97
+ https only proxies that tunnel HTTPS with verified TLS
98
+ countries e.g. ["DE", "AT"] or "DE,AT"
99
+ anonymity "anonymous" or "elite" as the minimum level
100
+ max_latency in milliseconds (0 = any)
101
+ targets sites every proxy has to reach, e.g. ["google.com"]
102
+ verbose show the normal terminal UI
103
+ """
104
+ from .app import Run # only here: app pulls in the whole UI
105
+
106
+ opts = _options(types, want, limit, https, countries, anonymity, max_latency, targets, no_datacenter,
107
+ no_blocklisted, timeout, concurrency, _recheck)
108
+ async with _one_at_a_time():
109
+ with _quiet(verbose):
110
+ run = Run(opts, show_banner=verbose)
111
+ await run.execute()
112
+ found = sorted(run.kept, key=lambda r: r.latency)
113
+ # when stopping after `want`, the checks still in flight finish – the CLI writes all of them to the
114
+ # files, the API returns exactly as many as requested (the fastest)
115
+ return found[:want] if want else found
116
+
117
+
118
+ def find_proxies(**kwargs) -> List[CheckResult]:
119
+ """Like find_proxies_async, just synchronous (starts its own event loop)."""
120
+ return asyncio.run(find_proxies_async(**kwargs))
121
+
122
+
123
+ async def check_proxies_async(proxies: Iterable[str], **kwargs) -> List[CheckResult]:
124
+ """Check your own proxies ("socks5://1.2.3.4:1080", "http://user:pass@…", "1.2.3.4:8080" = HTTP).
125
+ Takes the same filters as find_proxies; nothing is collected."""
126
+ lines = [p.strip() for p in proxies if p and p.strip()]
127
+ lines = [p if "://" in p else f"http://{p}" for p in lines]
128
+ fd, path = tempfile.mkstemp(prefix="proxy-scraper-", suffix=".txt")
129
+ try:
130
+ with os.fdopen(fd, "w", encoding="utf-8") as fh:
131
+ fh.write("\n".join(lines) + "\n")
132
+ return await find_proxies_async(_recheck=path, **kwargs)
133
+ finally:
134
+ with contextlib.suppress(OSError):
135
+ os.remove(path)
136
+
137
+
138
+ def check_proxies(proxies: Iterable[str], **kwargs) -> List[CheckResult]:
139
+ return asyncio.run(check_proxies_async(proxies, **kwargs))