|
|
import random |
|
|
import threading |
|
|
import time |
|
|
from collections import defaultdict |
|
|
|
|
|
|
|
|
class ProxyEntry: |
|
|
__slots__ = ( |
|
|
'proxy', 'protocol', 'proxy_url', 'latency', |
|
|
'proxy_ip', 'verified', 'fail_count', 'total_used', |
|
|
) |
|
|
|
|
|
def __init__(self, proxy, protocol, proxy_url, |
|
|
latency, proxy_ip, verified): |
|
|
self.proxy = proxy |
|
|
self.protocol = protocol |
|
|
self.proxy_url = proxy_url |
|
|
self.latency = latency |
|
|
self.proxy_ip = proxy_ip |
|
|
self.verified = verified |
|
|
self.fail_count = 0 |
|
|
self.total_used = 0 |
|
|
|
|
|
def to_dict(self): |
|
|
return { |
|
|
"proxy": self.proxy, |
|
|
"protocol": self.protocol, |
|
|
"proxy_url": self.proxy_url, |
|
|
"latency": self.latency, |
|
|
"proxy_ip": self.proxy_ip, |
|
|
"verified": self.verified, |
|
|
"total_used": self.total_used, |
|
|
} |
|
|
|
|
|
|
|
|
class ProxyPool: |
|
|
def __init__(self): |
|
|
self._px = [] |
|
|
self._um = {} |
|
|
self._lk = threading.Lock() |
|
|
self._rr = defaultdict(int) |
|
|
self._lr = 0.0 |
|
|
self._rc = 0 |
|
|
|
|
|
def refresh(self, new_proxies): |
|
|
with self._lk: |
|
|
old = dict(self._um) |
|
|
fresh, um = [], {} |
|
|
for px in new_proxies: |
|
|
e = ProxyEntry( |
|
|
px["proxy"], px["protocol"], |
|
|
px["proxy_url"], px["latency"], |
|
|
px.get("proxy_ip", ""), |
|
|
px.get("verified", False), |
|
|
) |
|
|
o = old.get(e.proxy_url) |
|
|
if o: |
|
|
e.total_used = o.total_used |
|
|
e.fail_count = max(0, o.fail_count - 1) |
|
|
fresh.append(e) |
|
|
um[e.proxy_url] = e |
|
|
fresh.sort(key=lambda x: x.latency) |
|
|
self._px = fresh |
|
|
self._um = um |
|
|
self._lr = time.time() |
|
|
self._rc += 1 |
|
|
|
|
|
@property |
|
|
def size(self): |
|
|
return len(self._px) |
|
|
|
|
|
def _alive(self, protocol=None, verified=False): |
|
|
r = [p for p in self._px if p.fail_count < 3] |
|
|
if protocol: |
|
|
r = [p for p in r if p.protocol == protocol] |
|
|
if verified: |
|
|
r = [p for p in r if p.verified] |
|
|
return r |
|
|
|
|
|
def get_round_robin(self, protocol=None, verified=False): |
|
|
with self._lk: |
|
|
a = self._alive(protocol, verified) |
|
|
if not a: |
|
|
return None |
|
|
k = f"{protocol}_{verified}" |
|
|
i = self._rr[k] % len(a) |
|
|
self._rr[k] = i + 1 |
|
|
a[i].total_used += 1 |
|
|
return a[i] |
|
|
|
|
|
def get_random(self, protocol=None, verified=False): |
|
|
with self._lk: |
|
|
a = self._alive(protocol, verified) |
|
|
if not a: |
|
|
return None |
|
|
p = random.choice(a) |
|
|
p.total_used += 1 |
|
|
return p |
|
|
|
|
|
def get_fastest(self, protocol=None, verified=False): |
|
|
with self._lk: |
|
|
a = self._alive(protocol, verified) |
|
|
if not a: |
|
|
return None |
|
|
a[0].total_used += 1 |
|
|
return a[0] |
|
|
|
|
|
def get_least_used(self, protocol=None, verified=False): |
|
|
with self._lk: |
|
|
a = self._alive(protocol, verified) |
|
|
if not a: |
|
|
return None |
|
|
p = min(a, key=lambda x: x.total_used) |
|
|
p.total_used += 1 |
|
|
return p |
|
|
|
|
|
def report_failure(self, url): |
|
|
with self._lk: |
|
|
p = self._um.get(url) |
|
|
if p: |
|
|
p.fail_count += 1 |
|
|
|
|
|
def report_success(self, url): |
|
|
with self._lk: |
|
|
p = self._um.get(url) |
|
|
if p: |
|
|
p.fail_count = 0 |
|
|
|
|
|
def get_all(self, protocol=None, verified=False, limit=500): |
|
|
with self._lk: |
|
|
return [ |
|
|
p.to_dict() |
|
|
for p in self._alive(protocol, verified)[:limit] |
|
|
] |
|
|
|
|
|
def get_stats(self): |
|
|
with self._lk: |
|
|
bp = defaultdict(lambda: {"total": 0, "alive": 0}) |
|
|
for p in self._px: |
|
|
bp[p.protocol]["total"] += 1 |
|
|
if p.fail_count < 3: |
|
|
bp[p.protocol]["alive"] += 1 |
|
|
alive = sum(d["alive"] for d in bp.values()) |
|
|
return { |
|
|
"total": len(self._px), |
|
|
"alive": alive, |
|
|
"by_protocol": dict(bp), |
|
|
"refresh_count": self._rc, |
|
|
"last_refresh_ago": round( |
|
|
time.time() - self._lr, 1 |
|
|
) if self._lr else None, |
|
|
} |
|
|
|
|
|
def export_json(self): |
|
|
with self._lk: |
|
|
return [p.to_dict() for p in self._px] |
|
|
|
|
|
def import_json(self, data): |
|
|
proxies = [] |
|
|
for p in data: |
|
|
proxies.append({ |
|
|
"proxy": p["proxy"], |
|
|
"protocol": p["protocol"], |
|
|
"proxy_url": p["proxy_url"], |
|
|
"latency": p["latency"], |
|
|
"proxy_ip": p.get("proxy_ip", ""), |
|
|
"verified": p.get("verified", False), |
|
|
}) |
|
|
if proxies: |
|
|
self.refresh(proxies) |