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

429 lines
14 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
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,
2023-08-21 14:13:16 +03:00
FrozenSet,
2023-02-19 21:44:15 +03:00
Iterable,
2023-01-02 00:09:55 +03:00
List,
Optional,
Set,
Tuple,
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
2023-06-27 12:04:44 +03:00
from rich.progress import (
BarColumn,
MofNCompleteColumn,
Progress,
TaskID,
TextColumn,
)
2022-02-13 00:33:49 +03:00
from rich.table import Table
2023-10-25 20:27:48 +03:00
from typing_extensions import Self
2022-02-13 00:33:49 +03:00
2023-02-19 21:44:15 +03:00
from . import sort, validators
2023-01-02 00:09:55 +03:00
from .folder import Folder
2023-02-19 21:44:15 +03:00
from .null_context import AsyncNullContext
2023-08-22 12:23:45 +03:00
from .proxy import HEADERS, 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
2022-06-16 10:22:11 +03:00
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__ = (
2023-01-15 13:05:16 +03:00
"check_website",
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,
2023-01-15 13:05:16 +03:00
check_website: str,
2022-02-13 00:33:49 +03:00
sort_by_speed: bool,
2023-01-15 00:22:17 +03:00
save_path: Path,
2023-02-19 21:44:15 +03:00
folders: Iterable[Folder],
2023-01-15 00:22:17 +03:00
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.
2023-02-19 22:30:47 +03:00
The higher the number, the longer the check will take and the
more proxies you get.
source_timeout: The number of seconds to wait for the proxies 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.
2023-02-19 22:30:47 +03:00
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.
2023-02-19 22:30:47 +03:00
Make sure you have enough RAM first, gradually increasing the
default value.
If set to 0, the maximum value available for your OS will be
used.
2023-01-15 13:05:16 +03:00
check_website: URL to which to send a request to check the proxy.
2023-02-19 22:30:47 +03:00
If not equal to 'default', it will not be possible to determine
the anonymity and geolocation of the proxies.
2022-02-13 16:24:44 +03:00
sort_by_speed: Set to False to sort proxies alphabetically.
2023-02-19 22:30:47 +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-02-19 21:44:15 +03:00
validators.timeout(timeout)
self.timeout = ClientTimeout(total=timeout, sock_connect=float("inf"))
validators.source_timeout(source_timeout)
self.source_timeout = source_timeout
max_conn = validators.max_connections(max_connections)
self.sem: Union[asyncio.Semaphore, AsyncNullContext] = (
asyncio.Semaphore(max_conn) if max_conn else AsyncNullContext()
)
2023-01-15 13:05:16 +03:00
self.check_website = check_website
2023-02-19 21:44:15 +03:00
self.sort_by_speed = sort_by_speed
self.path = save_path
self.folders = folders
2023-01-15 13:05:16 +03:00
if self.check_website != "default":
2023-02-19 21:44:15 +03:00
validators.check_website(check_website)
2023-01-15 13:05:16 +03:00
logger.info(
"CheckWebsite is not 'default', "
2023-02-25 19:31:34 +03:00
"so it will not be possible to determine "
"the anonymity and geolocation of the proxies"
2023-01-15 13:05:16 +03:00
)
for folder in self.folders:
folder.is_enabled = (
not folder.for_anonymous and not folder.for_geolocation
)
2023-02-19 21:44:15 +03:00
else:
validators.folders(self.folders)
2023-01-15 13:05:16 +03:00
2023-08-21 14:13:16 +03:00
self.sources: Dict[ProxyType, FrozenSet[str]] = {
2023-02-19 21:44:15 +03:00
proto: frozenset(filter(None, sources.splitlines()))
for proto, sources in sources.items()
if sources
}
validators.sources(self.sources)
self.proxies: Dict[ProxyType, Set[Proxy]] = {
proto: set() for proto in self.sources
}
self.console = console or Console()
self.cookie_jar = DummyCookieJar()
2022-10-30 12:07:59 +03:00
self.regex = re.compile(
2023-01-04 18:11:51 +03:00
r"(?:^|\D)?("
2023-02-25 19:31:34 +03:00
r"(?:[1-9]|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])" # 1-255
2023-01-04 18:11:51 +03:00
+ 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}"
2023-02-25 19:31:34 +03:00
r"|65[0-4]\d{2}|655[0-2]\d|6553[0-5])"
2022-06-21 13:10:56 +03:00
) # 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
2023-01-02 00:09:55 +03:00
@classmethod
2023-02-06 20:59:03 +03:00
def from_configparser(
2023-10-25 20:27:48 +03:00
cls, cfg: ConfigParser, *, console: Optional[Console] = None
) -> Self:
2023-01-02 00:09:55 +03:00
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-15 13:05:16 +03:00
check_website=general.get("CheckWebsite", "default"),
2023-02-28 06:48:56 +00: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",
2023-02-28 06:48:56 +00:00
is_enabled=folders.getboolean("proxies", True),
2023-01-15 00:22:17 +03:00
for_anonymous=False,
for_geolocation=False,
),
Folder(
path=save_path / "proxies_anonymous",
2023-02-28 06:48:56 +00:00
is_enabled=folders.getboolean("proxies_anonymous", True),
2023-01-15 00:22:17 +03:00
for_anonymous=True,
for_geolocation=False,
),
Folder(
path=save_path / "proxies_geolocation",
2023-02-28 06:48:56 +00:00
is_enabled=folders.getboolean("proxies_geolocation", True),
2023-01-15 00:22:17 +03:00
for_anonymous=False,
for_geolocation=True,
),
Folder(
path=save_path / "proxies_geolocation_anonymous",
is_enabled=folders.getboolean(
2023-02-28 06:48:56 +00:00
"proxies_geolocation_anonymous", True
2023-01-15 00:22:17 +03:00
),
for_anonymous=True,
for_geolocation=True,
),
2023-01-02 00:09:55 +03:00
),
2023-01-15 00:22:17 +03:00
sources={
2023-02-01 18:45:53 +03:00
ProxyType.HTTP: (
2023-06-27 12:04:44 +03:00
http.get("Sources")
if http.getboolean("Enabled", True)
else None
2023-02-01 18:45:53 +03:00
),
ProxyType.SOCKS4: (
socks4.get("Sources")
2023-02-28 06:48:56 +00:00
if socks4.getboolean("Enabled", True)
2023-02-01 18:45:53 +03:00
else None
),
ProxyType.SOCKS5: (
socks5.get("Sources")
2023-02-28 06:48:56 +00:00
if socks5.getboolean("Enabled", True)
2023-02-01 18:45:53 +03:00
else None
),
2023-01-15 00:22:17 +03:00
},
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:
2023-04-16 21:11:46 +03:00
await response.read()
text = await response.text()
except asyncio.TimeoutError:
logger.warning("%s | Timed out", source)
2022-08-08 17:15:47 +03:00
except Exception as e:
2023-02-19 21:44:15 +03:00
e_str = str(e)
args: Tuple[object, ...] = (
(
"%s | %s.%s (%s)",
source,
e.__class__.__module__,
e.__class__.__qualname__,
e_str,
)
if e_str
else (
"%s | %s.%s",
source,
e.__class__.__module__,
e.__class__.__qualname__,
)
2023-02-06 20:59:03 +03:00
)
2023-02-19 21:44:15 +03:00
logger.error(*args)
2022-02-13 00:33:49 +03:00
else:
2023-04-16 21:11:46 +03:00
proxies = self.regex.finditer(text)
try:
proxy = next(proxies)
except StopIteration:
2023-02-19 21:44:15 +03:00
args = (
("%s | No proxies found", source)
2023-04-16 21:11:46 +03:00
if response.status == 200 # noqa: PLR2004
else ("%s | HTTP status code %d", source, response.status)
2023-01-02 00:35:20 +03:00
)
2023-02-19 21:44:15 +03:00
logger.warning(*args)
2023-04-16 21:11:46 +03:00
else:
proxies_set = self.proxies[proto]
2023-06-27 12:04:44 +03:00
proxies_set.add(
Proxy(host=proxy.group(1), port=int(proxy.group(2)))
)
2023-04-16 21:11:46 +03:00
for proxy in proxies:
proxies_set.add(
Proxy(host=proxy.group(1), port=int(proxy.group(2)))
)
2022-02-13 00:33:49 +03:00
progress.update(task, advance=1)
async def check_proxy(
2023-06-27 12:04:44 +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(
2023-01-15 13:05:16 +03:00
website=self.check_website,
2023-01-04 20:24:44 +03:00
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
2023-02-25 19:31:34 +03:00
if isinstance(e, OSError) and e.errno == 24: # noqa: PLR2004
2023-01-04 20:24:44 +03:00
logger.error("Please, set MaxConnections to lower value.")
2022-02-13 11:23:12 +03:00
2023-02-06 20:59:03 +03:00
logger.debug(
2023-06-27 12:04:44 +03:00
"%s.%s | %s",
e.__class__.__module__,
e.__class__.__qualname__,
e,
2023-02-06 20:59:03 +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-06-27 12:04:44 +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()
}
2023-01-04 20:24:44 +03:00
async with ClientSession(
2023-08-22 12:23:45 +03:00
headers=HEADERS,
2023-01-07 11:57:57 +03:00
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-06-27 12:04:44 +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")
2023-04-16 21:49:53 +03:00
logger.info(
2023-06-27 12:04:44 +03:00
"Proxy folders have been created in the %s folder.",
self.path.resolve(),
2023-04-16 21:49:53 +03:00
)
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-04-16 21:49:53 +03:00
2023-01-02 00:09:55 +03:00
logger.info(
2023-06-27 12:04:44 +03:00
"Thank you for using "
"https://github.com/monosans/proxy-scraper-checker :)"
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]]:
2023-06-27 12:04:44 +03:00
key: Union[
Callable[[Proxy], float], Callable[[Proxy], Tuple[int, ...]]
] = (
sort.timeout_sort_key
if self.sort_by_speed
else sort.natural_sort_key
2023-01-02 00:09:55 +03:00
)
2022-02-13 00:33:49 +03:00
return {
2023-06-27 12:04:44 +03:00
proto: sorted(proxies, key=key)
for proto, proxies in self.proxies.items()
2022-02-13 00:33:49 +03:00
}
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-06-27 12:04:44 +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
)