Files
proxy-scraper-checker/proxy_scraper_checker/proxy_scraper_checker.py
T

395 lines
13 KiB
Python
Raw Normal View History

2022-07-10 19:43:43 +03:00
from __future__ import annotations
2022-02-13 00:33:49 +03:00
import asyncio
2023-01-02 00:09:55 +03:00
import logging
2022-02-13 00:33:49 +03:00
import re
import sys
2022-04-13 18:24:56 +03:00
from configparser import ConfigParser
2022-02-13 00:33:49 +03:00
from pathlib import Path
from random import shuffle
2023-01-02 00:09:55 +03:00
from typing import (
Callable,
Dict,
List,
Optional,
Set,
Tuple,
Type,
TypeVar,
Union,
)
2022-02-13 00:33:49 +03:00
2023-01-07 11:57:57 +03:00
from aiohttp import ClientSession, ClientTimeout, DummyCookieJar
2023-01-15 00:22:17 +03:00
from aiohttp_socks import ProxyType
2022-02-13 00:33:49 +03:00
from rich.console import Console
from rich.progress import (
BarColumn,
2022-10-20 09:37:43 +03:00
MofNCompleteColumn,
2022-02-13 00:33:49 +03:00
Progress,
TaskID,
TextColumn,
)
from rich.table import Table
2023-01-02 00:09:55 +03:00
from . import sort
from .folder import Folder
from .proxy import Proxy
2022-02-13 00:33:49 +03:00
2023-01-02 00:09:55 +03:00
logger = logging.getLogger(__name__)
2022-04-11 12:06:00 +03:00
2023-01-02 00:09:55 +03:00
TProxyScraperChecker = TypeVar(
"TProxyScraperChecker", bound="ProxyScraperChecker"
)
2022-06-16 10:22:11 +03:00
def validate_max_connections(value: int) -> int:
if sys.platform != "win32":
2023-01-05 20:05:10 +03:00
import resource
soft_limit, hard_limit = resource.getrlimit(resource.RLIMIT_NOFILE)
if soft_limit < hard_limit:
resource.setrlimit(
resource.RLIMIT_NOFILE, (hard_limit, hard_limit)
)
elif value > 512 and isinstance(
asyncio.get_event_loop_policy(), asyncio.WindowsSelectorEventLoopPolicy
):
logger.warning(
"MaxConnections value is too high. "
+ "Windows supports a maximum of 512. "
+ "The config value will be ignored and 512 will be used."
)
return 512
return value
2022-02-13 00:33:49 +03:00
class ProxyScraperChecker:
"""HTTP, SOCKS4, SOCKS5 proxies scraper and checker."""
2022-04-11 12:06:00 +03:00
__slots__ = (
2022-07-11 18:20:34 +03:00
"console",
2023-01-04 20:24:44 +03:00
"cookie_jar",
2023-01-15 00:22:17 +03:00
"folders",
2022-07-11 18:20:34 +03:00
"path",
"proxies_count",
"proxies",
2022-04-11 12:06:00 +03:00
"regex",
2022-07-11 18:20:34 +03:00
"sem",
2022-04-11 12:06:00 +03:00
"sort_by_speed",
2023-01-07 12:10:25 +03:00
"source_timeout",
2022-04-11 12:06:00 +03:00
"sources",
2022-07-11 18:20:34 +03:00
"timeout",
2022-04-11 12:06:00 +03:00
)
2022-02-13 00:33:49 +03:00
def __init__(
self,
*,
timeout: float,
2023-01-07 12:10:25 +03:00
source_timeout: float,
2022-02-13 00:33:49 +03:00
max_connections: int,
sort_by_speed: bool,
2023-01-15 00:22:17 +03:00
save_path: Path,
folders: Tuple[Folder, ...],
sources: Dict[ProxyType, Optional[str]],
2022-08-13 12:17:45 +03:00
console: Optional[Console] = None,
2022-02-13 00:33:49 +03:00
) -> None:
2022-02-13 16:24:44 +03:00
"""HTTP, SOCKS4, SOCKS5 proxies scraper and checker.
2022-02-13 00:33:49 +03:00
Args:
2023-01-07 12:10:25 +03:00
timeout: The number of seconds to wait for a proxied request.
The higher the number, the longer the check will take
and the more proxies you get.
2023-01-07 12:26:32 +03:00
source_timeout: The number of seconds to wait for the proxies
2023-01-07 12:10:25 +03:00
to be downloaded from the source.
2023-01-05 20:24:01 +03:00
max_connections: Maximum concurrent connections.
Windows supports maximum of 512.
On *nix operating systems, this restriction is much looser.
The limit on *nix can be seen with the command ulimit -Hn.
2023-01-07 14:53:21 +03:00
Don't be in a hurry to set high values.
Make sure you have enough RAM first, gradually
increasing the default value.
2022-02-13 16:24:44 +03:00
sort_by_speed: Set to False to sort proxies alphabetically.
2022-07-11 18:20:34 +03:00
save_path: Path to the folder where the proxy folders will
be saved. Leave empty to save the proxies to the current
directory.
2022-02-13 00:33:49 +03:00
"""
2023-01-15 00:22:17 +03:00
self.path = save_path
self.folders = folders
if not any(folder for folder in self.folders if folder.is_enabled):
2022-02-13 11:23:12 +03:00
raise ValueError("all folders are disabled in the config")
2022-10-30 12:07:59 +03:00
self.regex = re.compile(
2023-01-04 18:11:51 +03:00
r"(?:^|\D)?("
+ r"(?:[1-9]|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])" # 1-255
+ r"\.(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])" * 3 # 0-255
2022-06-21 13:10:56 +03:00
+ r"):"
+ (
2023-01-04 17:49:45 +03:00
r"(\d|[1-9]\d{1,3}|[1-5]\d{4}|6[0-4]\d{3}"
2022-06-21 13:10:56 +03:00
+ r"|65[0-4]\d{2}|655[0-2]\d|6553[0-5])"
) # 0-65535
2023-01-04 17:49:45 +03:00
+ r"(?:\D|$)"
2022-02-13 00:33:49 +03:00
)
2022-02-13 11:23:12 +03:00
self.sort_by_speed = sort_by_speed
2023-01-07 14:50:42 +03:00
self.timeout = ClientTimeout(total=timeout, sock_connect=float("inf"))
2023-01-07 12:10:25 +03:00
self.source_timeout = source_timeout
2022-02-13 11:23:12 +03:00
self.sources = {
2022-04-13 18:24:56 +03:00
proto: frozenset(filter(None, sources.splitlines()))
2023-01-15 00:22:17 +03:00
for proto, sources in sources.items()
2022-02-13 00:33:49 +03:00
if sources
}
2023-01-15 00:22:17 +03:00
self.proxies: Dict[ProxyType, Set[Proxy]] = {
2022-02-13 11:23:12 +03:00
proto: set() for proto in self.sources
2022-02-13 00:33:49 +03:00
}
2023-01-04 20:24:44 +03:00
self.cookie_jar = DummyCookieJar()
2022-07-11 18:20:34 +03:00
self.console = console or Console()
max_connections = validate_max_connections(max_connections)
2022-02-13 11:23:12 +03:00
self.sem = asyncio.Semaphore(max_connections)
2022-02-13 00:33:49 +03:00
2023-01-02 00:09:55 +03:00
@classmethod
def from_ini(
cls: Type[TProxyScraperChecker],
file_name: str,
*,
console: Optional[Console] = None,
) -> TProxyScraperChecker:
cfg = ConfigParser(interpolation=None)
cfg.read(file_name, encoding="utf-8")
general = cfg["General"]
folders = cfg["Folders"]
http = cfg["HTTP"]
socks4 = cfg["SOCKS4"]
socks5 = cfg["SOCKS5"]
2023-01-15 00:22:17 +03:00
save_path = Path(general.get("SavePath", ""))
2023-01-02 00:09:55 +03:00
return cls(
2023-01-05 20:24:01 +03:00
timeout=general.getfloat("Timeout", 5),
2023-01-07 12:10:25 +03:00
source_timeout=general.getfloat("SourceTimeout", 15),
2023-01-05 20:24:01 +03:00
max_connections=general.getint("MaxConnections", 512),
2023-01-02 00:09:55 +03:00
sort_by_speed=general.getboolean("SortBySpeed", True),
2023-01-15 00:22:17 +03:00
save_path=save_path,
folders=(
Folder(
path=save_path / "proxies",
is_enabled=folders.getboolean("proxies", True),
for_anonymous=False,
for_geolocation=False,
),
Folder(
path=save_path / "proxies_anonymous",
is_enabled=folders.getboolean("proxies_anonymous", True),
for_anonymous=True,
for_geolocation=False,
),
Folder(
path=save_path / "proxies_geolocation",
is_enabled=folders.getboolean("proxies_geolocation", True),
for_anonymous=False,
for_geolocation=True,
),
Folder(
path=save_path / "proxies_geolocation_anonymous",
is_enabled=folders.getboolean(
"proxies_geolocation_anonymous", True
),
for_anonymous=True,
for_geolocation=True,
),
2023-01-02 00:09:55 +03:00
),
2023-01-15 00:22:17 +03:00
sources={
ProxyType.HTTP: http.get("Sources")
if http.getboolean("Enabled", True)
else None,
ProxyType.SOCKS4: socks4.get("Sources")
if socks4.getboolean("Enabled", True)
else None,
ProxyType.SOCKS5: socks5.get("Sources")
if socks5.getboolean("Enabled", True)
else None,
},
2023-01-02 00:09:55 +03:00
console=console,
)
2022-02-13 00:33:49 +03:00
async def fetch_source(
self,
2022-12-31 09:25:47 +03:00
*,
2022-02-13 00:33:49 +03:00
session: ClientSession,
source: str,
2023-01-15 00:22:17 +03:00
proto: ProxyType,
2022-02-13 00:33:49 +03:00
progress: Progress,
task: TaskID,
) -> None:
"""Get proxies from source.
Args:
2022-02-13 16:24:44 +03:00
source: Proxy list URL.
2022-02-13 00:33:49 +03:00
"""
try:
2023-01-07 11:57:57 +03:00
async with session.get(source) as response:
2022-08-08 17:15:47 +03:00
status = response.status
text = await response.text()
except Exception as e:
2023-01-02 09:44:39 +03:00
logger.error("%s | %s | %s", source, e.__class__.__qualname__, e)
2022-02-13 00:33:49 +03:00
else:
proxies = tuple(self.regex.finditer(text))
if proxies:
for proxy in proxies:
2022-12-31 09:25:47 +03:00
proxy_obj = Proxy(
2023-01-04 17:49:45 +03:00
host=proxy.group(1), port=int(proxy.group(2))
2022-12-31 09:25:47 +03:00
)
2022-08-08 17:15:47 +03:00
self.proxies[proto].add(proxy_obj)
else:
2023-01-02 00:35:20 +03:00
logger.warning(
"%s | No proxies found | HTTP status code %d",
source,
status,
)
2022-02-13 00:33:49 +03:00
progress.update(task, advance=1)
async def check_proxy(
2023-01-15 00:22:17 +03:00
self,
*,
proxy: Proxy,
proto: ProxyType,
progress: Progress,
task: TaskID,
2022-02-13 00:33:49 +03:00
) -> None:
"""Check if proxy is alive."""
try:
2023-01-04 20:24:44 +03:00
await proxy.check(
sem=self.sem,
cookie_jar=self.cookie_jar,
proto=proto,
timeout=self.timeout,
)
2022-02-13 00:33:49 +03:00
except Exception as e:
# Too many open files
if isinstance(e, OSError) and e.errno == 24:
2023-01-04 20:24:44 +03:00
logger.error("Please, set MaxConnections to lower value.")
2022-02-13 11:23:12 +03:00
2022-02-13 00:33:49 +03:00
self.proxies[proto].remove(proxy)
progress.update(task, advance=1)
2022-07-11 18:20:34 +03:00
async def fetch_all_sources(self, progress: Progress) -> None:
tasks = {
proto: progress.add_task(
2023-01-15 00:22:17 +03:00
f"[yellow]Scraper [red]:: [green]{proto.name}",
total=len(sources),
2022-07-11 18:20:34 +03:00
)
for proto, sources in self.sources.items()
}
headers = {
"User-Agent": (
2022-12-31 09:01:41 +03:00
"Mozilla/5.0 (Windows NT 10.0; rv:108.0)"
+ " Gecko/20100101 Firefox/108.0"
2022-07-11 18:20:34 +03:00
)
}
2023-01-04 20:24:44 +03:00
async with ClientSession(
2023-01-07 11:57:57 +03:00
headers=headers,
cookie_jar=self.cookie_jar,
2023-01-07 12:10:25 +03:00
timeout=ClientTimeout(total=self.source_timeout),
2023-01-04 20:24:44 +03:00
) as session:
2022-07-11 18:20:34 +03:00
coroutines = (
self.fetch_source(
2022-12-31 09:25:47 +03:00
session=session,
source=source,
proto=proto,
progress=progress,
task=tasks[proto],
2022-02-13 00:33:49 +03:00
)
2022-02-13 11:23:12 +03:00
for proto, sources in self.sources.items()
2022-07-11 18:20:34 +03:00
for source in sources
)
await asyncio.gather(*coroutines)
2022-02-13 00:33:49 +03:00
2023-01-15 00:22:17 +03:00
self.proxies_count = {
proto: len(proxies) for proto, proxies in self.proxies.items()
}
2022-02-13 00:33:49 +03:00
2022-07-11 18:20:34 +03:00
async def check_all_proxies(self, progress: Progress) -> None:
tasks = {
proto: progress.add_task(
2023-01-15 00:22:17 +03:00
f"[yellow]Checker [red]:: [green]{proto.name}",
total=len(proxies),
2022-07-11 18:20:34 +03:00
)
for proto, proxies in self.proxies.items()
}
coroutines = [
2022-12-31 09:25:47 +03:00
self.check_proxy(
proxy=proxy, proto=proto, progress=progress, task=tasks[proto]
)
2022-07-11 18:20:34 +03:00
for proto, proxies in self.proxies.items()
for proxy in proxies
]
shuffle(coroutines)
await asyncio.gather(*coroutines)
2022-02-13 00:33:49 +03:00
def save_proxies(self) -> None:
"""Delete old proxies and save new ones."""
2023-01-02 00:09:55 +03:00
sorted_proxies = self.get_sorted_proxies().items()
2023-01-15 00:22:17 +03:00
for folder in self.folders:
2022-02-13 11:23:12 +03:00
folder.remove()
2023-01-15 00:22:17 +03:00
for folder in self.folders:
if not folder.is_enabled:
continue
2022-02-13 11:23:12 +03:00
folder.create()
2022-02-13 00:33:49 +03:00
for proto, proxies in sorted_proxies:
text = "\n".join(
2023-01-04 17:49:45 +03:00
proxy.as_str(include_geolocation=folder.for_geolocation)
2022-02-13 00:33:49 +03:00
for proxy in proxies
2022-02-13 11:23:12 +03:00
if (proxy.is_anonymous if folder.for_anonymous else True)
)
2023-01-15 00:22:17 +03:00
file = folder.path / f"{proto.name.lower()}.txt"
2022-05-28 14:34:46 +03:00
file.write_text(text, encoding="utf-8")
2022-02-13 00:33:49 +03:00
2023-01-02 00:09:55 +03:00
async def run(self) -> None:
with self._get_progress_bar() as progress:
2022-07-11 18:20:34 +03:00
await self.fetch_all_sources(progress)
await self.check_all_proxies(progress)
2022-02-13 00:33:49 +03:00
2023-01-02 00:09:55 +03:00
table = self._get_results_table()
2022-07-11 18:20:34 +03:00
self.console.print(table)
2022-02-13 00:33:49 +03:00
self.save_proxies()
2023-01-02 00:09:55 +03:00
logger.info(
"Proxy folders have been created in the %s folder."
+ "\nThank you for using proxy-scraper-checker :)",
self.path.resolve(),
2022-02-13 00:33:49 +03:00
)
2023-01-15 00:22:17 +03:00
def get_sorted_proxies(self) -> Dict[ProxyType, List[Proxy]]:
2022-08-13 12:17:45 +03:00
key: Union[
Callable[[Proxy], float], Callable[[Proxy], Tuple[int, ...]]
2023-01-02 00:09:55 +03:00
] = (
sort.timeout_sort_key
if self.sort_by_speed
else sort.natural_sort_key
)
2022-02-13 00:33:49 +03:00
return {
proto: sorted(proxies, key=key)
for proto, proxies in self.proxies.items()
}
2023-01-02 00:09:55 +03:00
def _get_results_table(self) -> Table:
table = Table()
table.add_column("Protocol", style="cyan")
table.add_column("Working", style="magenta")
table.add_column("Total", style="green")
for proto, proxies in self.proxies.items():
working = len(proxies)
total = self.proxies_count[proto]
2023-01-02 09:44:39 +03:00
percentage = working / total if total else 0
2023-01-15 00:22:17 +03:00
table.add_row(
proto.name, f"{working} ({percentage:.1%})", str(total)
)
2023-01-02 00:09:55 +03:00
return table
def _get_progress_bar(self) -> Progress:
2022-02-13 00:33:49 +03:00
return Progress(
TextColumn("[progress.description]{task.description}"),
BarColumn(),
2022-10-20 09:37:43 +03:00
MofNCompleteColumn(),
2022-07-11 18:20:34 +03:00
console=self.console,
2022-02-13 00:33:49 +03:00
)