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.
- proxy_scraper_cli-1.7.1.dist-info/METADATA +794 -0
- proxy_scraper_cli-1.7.1.dist-info/RECORD +57 -0
- proxy_scraper_cli-1.7.1.dist-info/WHEEL +5 -0
- proxy_scraper_cli-1.7.1.dist-info/entry_points.txt +4 -0
- proxy_scraper_cli-1.7.1.dist-info/licenses/LICENSE +21 -0
- proxy_scraper_cli-1.7.1.dist-info/top_level.txt +1 -0
- proxyscraper/__init__.py +12 -0
- proxyscraper/__main__.py +7 -0
- proxyscraper/agent.py +635 -0
- proxyscraper/api.py +139 -0
- proxyscraper/app.py +595 -0
- proxyscraper/asndb.py +181 -0
- proxyscraper/blocklist.py +92 -0
- proxyscraper/checker.py +514 -0
- proxyscraper/cli.py +269 -0
- proxyscraper/compat.py +83 -0
- proxyscraper/completion.py +196 -0
- proxyscraper/exporters.py +118 -0
- proxyscraper/fetchcache.py +103 -0
- proxyscraper/geo.py +150 -0
- proxyscraper/geodb.py +143 -0
- proxyscraper/handshake.py +153 -0
- proxyscraper/history.py +106 -0
- proxyscraper/judges.py +159 -0
- proxyscraper/mcp_entry.py +34 -0
- proxyscraper/mcp_server.py +220 -0
- proxyscraper/netio.py +167 -0
- proxyscraper/options.py +274 -0
- proxyscraper/output.py +181 -0
- proxyscraper/pages.py +212 -0
- proxyscraper/parsing.py +192 -0
- proxyscraper/paths.py +55 -0
- proxyscraper/pipeline.py +434 -0
- proxyscraper/preferences.py +24 -0
- proxyscraper/publish.py +236 -0
- proxyscraper/server/__init__.py +41 -0
- proxyscraper/server/core.py +518 -0
- proxyscraper/server/http.py +164 -0
- proxyscraper/server/pool.py +195 -0
- proxyscraper/server/socks.py +65 -0
- proxyscraper/server/status.py +108 -0
- proxyscraper/server/upstream.py +119 -0
- proxyscraper/site/apple-touch-icon.png +0 -0
- proxyscraper/site/googleaac1161b7853c5b5.html +1 -0
- proxyscraper/site/index.html +934 -0
- proxyscraper/site/logo.png +0 -0
- proxyscraper/site/og.png +0 -0
- proxyscraper/sources.json +395 -0
- proxyscraper/sources.py +491 -0
- proxyscraper/targets.py +76 -0
- proxyscraper/ui/__init__.py +54 -0
- proxyscraper/ui/dashboard.py +344 -0
- proxyscraper/ui/keys.py +82 -0
- proxyscraper/ui/report.py +209 -0
- proxyscraper/ui/serve.py +142 -0
- proxyscraper/ui/widgets.py +250 -0
- 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))
|