proxy-scraper-cli 1.16.0__tar.gz → 1.17.0__tar.gz
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.16.0 → proxy_scraper_cli-1.17.0}/PKG-INFO +1 -1
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxy_scraper_cli.egg-info/PKG-INFO +1 -1
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/__init__.py +1 -1
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/pages.py +116 -9
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_pages.py +42 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/LICENSE +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/README.md +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxy_scraper_cli.egg-info/SOURCES.txt +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxy_scraper_cli.egg-info/dependency_links.txt +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxy_scraper_cli.egg-info/entry_points.txt +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxy_scraper_cli.egg-info/requires.txt +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxy_scraper_cli.egg-info/top_level.txt +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/__main__.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/action.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/agent.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/api.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/app.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/asndb.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/blocklist.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/charts.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/checker.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/cli.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/compat.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/completion.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/exporters.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/fetchcache.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/geo.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/geodb.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/handshake.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/history.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/judges.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/mcp_entry.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/mcp_server.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/netio.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/options.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/output.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/parsing.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/paths.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/pipeline.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/preferences.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/proxypages.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/publish.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/report.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/runsteps.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/server/__init__.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/server/core.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/server/http.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/server/pool.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/server/socks.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/server/status.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/server/upstream.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/site/apple-touch-icon.png +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/site/googleaac1161b7853c5b5.html +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/site/index.html +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/site/logo.png +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/site/og.png +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/sites.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/sources.json +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/sources.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/speed.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/targets.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/ui/__init__.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/ui/dashboard.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/ui/keys.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/ui/report.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/ui/serve.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/ui/widgets.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/ui/wizard.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/pyproject.toml +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/setup.cfg +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_action.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_agent.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_api.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_app.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_asndb.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_auth.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_blocklist.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_charts.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_checker.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_compat.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_completion.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_discovery_growth.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_exporters.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_fetchcache.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_geodb.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_judges.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_learning.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_live_api.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_mcp_server.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_mine_sources.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_options.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_output_stdout.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_packaging.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_parsing.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_parsing_fuzz.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_paths.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_pick.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_pipeline.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_proxy_pages.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_proxy_sources.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_publish.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_report.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_robustness.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_rotator.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_serve_password.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_serve_refill.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_serve_v2.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_server.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_server_closed_writer.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_server_fixes.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_sites.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_sources_json.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_speed.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_tamper.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_targets.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_ui.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_uptime.py +0 -0
- {proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/tests/test_wizard.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proxy-scraper-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.17.0
|
|
4
4
|
Summary: Finds free HTTP, SOCKS4 and SOCKS5 proxies that actually work: 700+ sources, real checks, honeypot filtering and a rotating local proxy server.
|
|
5
5
|
Author: Maximilian Feix
|
|
6
6
|
License-Expression: MIT
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proxy-scraper-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.17.0
|
|
4
4
|
Summary: Finds free HTTP, SOCKS4 and SOCKS5 proxies that actually work: 700+ sources, real checks, honeypot filtering and a rotating local proxy server.
|
|
5
5
|
Author: Maximilian Feix
|
|
6
6
|
License-Expression: MIT
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""proxy-scraper: collects public HTTP/SOCKS4/SOCKS5 proxies from hundreds of sources, checks them
|
|
2
2
|
in parallel with its own protocol handshakes and learns which sources deliver good proxies."""
|
|
3
3
|
|
|
4
|
-
__version__ = "1.
|
|
4
|
+
__version__ = "1.17.0"
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def __getattr__(name):
|
|
@@ -7,6 +7,8 @@ that answers exactly that, and every page links back to the full, filterable lis
|
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
import json
|
|
11
|
+
import statistics
|
|
10
12
|
from datetime import datetime
|
|
11
13
|
from html import escape
|
|
12
14
|
from pathlib import Path
|
|
@@ -100,6 +102,22 @@ footer { color: var(--muted); font-size: 14px; display: grid; gap: 6px; }
|
|
|
100
102
|
"""
|
|
101
103
|
|
|
102
104
|
|
|
105
|
+
PAGE_CSS = """
|
|
106
|
+
.wrap > * { min-width: 0; } /* grid items default to their content's width: the wide table would push the page */
|
|
107
|
+
.facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 150px), 1fr)); gap: 1px; margin: 0;
|
|
108
|
+
background: var(--line); border: 1px solid var(--line); border-radius: 16px; overflow: hidden; }
|
|
109
|
+
.facts div { background: var(--panel); padding: 16px 18px; }
|
|
110
|
+
.facts dt { font-size: 26px; font-weight: 600; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
|
|
111
|
+
.facts dd { margin: 2px 0 0; color: var(--muted); font-size: 14px; }
|
|
112
|
+
h2 { font-size: 24px; letter-spacing: -.02em; margin: 0 0 12px; font-weight: 600; }
|
|
113
|
+
.use pre { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 14px 16px;
|
|
114
|
+
overflow-x: auto; font-size: 14px; margin: 0 0 10px; }
|
|
115
|
+
.note { color: var(--muted); font-size: 14px; }
|
|
116
|
+
.faq h3 { font-size: 18px; font-weight: 500; margin: 22px 0 6px; }
|
|
117
|
+
.faq p { margin: 0; color: var(--muted); max-width: 70ch; }
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
|
|
103
121
|
def _list_query(path: str) -> str:
|
|
104
122
|
"""The same filter on the full list: the website reads its filters from the address."""
|
|
105
123
|
kind = path.strip("/")
|
|
@@ -109,6 +127,61 @@ def _list_query(path: str) -> str:
|
|
|
109
127
|
"elite": "?elite=1"}.get(kind, "")
|
|
110
128
|
|
|
111
129
|
|
|
130
|
+
SITE_NAMES = {"tiktok": "TikTok"}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _pick_flags(path: str) -> str:
|
|
134
|
+
"""The --pick filters that give the same selection as this page."""
|
|
135
|
+
kind = path.strip("/")
|
|
136
|
+
if kind.startswith("country/"):
|
|
137
|
+
return f"--country {kind.split('/')[1].upper()}"
|
|
138
|
+
return {"http": "--types http", "socks4": "--types socks4", "socks5": "--types socks5", "https": "--https-only",
|
|
139
|
+
"elite": "--anonymity elite"}.get(kind, "")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _short(path: str) -> str:
|
|
143
|
+
"""How a page's proxies are called in a question: "proxies in Germany", "SOCKS5 proxies"."""
|
|
144
|
+
kind = path.strip("/")
|
|
145
|
+
if kind.startswith("country/"):
|
|
146
|
+
return f"proxies in {_short_name(COUNTRIES.get(kind.split('/')[1].upper(), kind.split('/')[1].upper()))}"
|
|
147
|
+
return {"https": "HTTPS proxies", "elite": "elite proxies"}.get(kind, f"{kind.upper()} proxies")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _facts(rows: List[dict]) -> Dict[str, object]:
|
|
151
|
+
speeds = [r["speed_kbps"] for r in rows if r.get("speed_kbps")]
|
|
152
|
+
return {"total": len(rows), "https": sum(1 for r in rows if r.get("https")),
|
|
153
|
+
"stable": sum(1 for r in rows if (r.get("uptime_7d") or 0) >= 90),
|
|
154
|
+
# None when none of them was tried: the site check only runs on HTTPS-capable ones, and not at all
|
|
155
|
+
# when it couldn't resolve the site – "0 get through" would blame proxies for a check that didn't run
|
|
156
|
+
"google": (sum(1 for r in rows if (r.get("sites") or {}).get("google"))
|
|
157
|
+
if any("google" in (r.get("sites") or {}) for r in rows) else None),
|
|
158
|
+
"speed": round(statistics.median(speeds)) if speeds else None}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _faq(path: str, facts: Dict[str, object], when: str) -> List[Tuple[str, str]]:
|
|
162
|
+
short, flags = _short(path), _pick_flags(path)
|
|
163
|
+
if facts["google"] is None:
|
|
164
|
+
got = "These proxies weren't part of the last site check – it only tries the ones that tunnel HTTPS."
|
|
165
|
+
elif facts["google"]:
|
|
166
|
+
got = f"In the last check, {facts['google']:,} of them got through to Google search without a captcha."
|
|
167
|
+
else:
|
|
168
|
+
got = "In the last check, none of them got through to Google search without a captcha."
|
|
169
|
+
return [
|
|
170
|
+
(f"How many free {short} work right now?",
|
|
171
|
+
f"{facts['total']:,} passed every check in the last run ({when}): a real handshake, a honeypot check on two "
|
|
172
|
+
f"sites and a content check. {facts['https']:,} of them tunnel HTTPS with verified TLS, and "
|
|
173
|
+
f"{facts['stable']:,} were on the list in 90 % or more of this week's hourly checks. The list is checked "
|
|
174
|
+
"again every hour."),
|
|
175
|
+
(f"Which free {short} get through to Google?",
|
|
176
|
+
f"{got} The site check also tries Reddit, Amazon, Instagram and TikTok – the full list "
|
|
177
|
+
"on the website can be filtered by it."),
|
|
178
|
+
(f"How do I get working {short} in code or the terminal?",
|
|
179
|
+
f"Without installing anything, download proxies.txt from this page. With the tool: pipx install "
|
|
180
|
+
f"proxy-scraper-cli, then proxy-scraper --pick 5 {flags} prints five from the hourly list. From Python, "
|
|
181
|
+
"live_proxies() takes the same filters."),
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
|
|
112
185
|
def _render(path: str, title: str, what: str, rows: List[dict], updated: datetime, nav: str) -> str:
|
|
113
186
|
depth = path.count("/")
|
|
114
187
|
root = "../" * depth
|
|
@@ -120,19 +193,49 @@ def _render(path: str, title: str, what: str, rows: List[dict], updated: datetim
|
|
|
120
193
|
else:
|
|
121
194
|
lede = (f"No {what} passed every check in the last run ({when}). The list is checked again every hour, "
|
|
122
195
|
"so look again later or try the full list.")
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
f"{escape(r['proxy']
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
196
|
+
def cells(r: dict) -> str:
|
|
197
|
+
through = ", ".join(SITE_NAMES.get(n, n.title()) for n, ok in (r.get("sites") or {}).items() if ok) or "–"
|
|
198
|
+
return (f"<tr><td class=\"mono\"><a href=\"{root}proxy/{escape(r['proxy'].replace(':', '-'))}/\">"
|
|
199
|
+
f"{escape(r['proxy'])}</a></td><td>{escape(r['ptype'].upper())}</td>"
|
|
200
|
+
f"<td>{escape(r.get('country') or '–')}</td><td>{r['latency']:,} ms</td>"
|
|
201
|
+
f"<td>{(str(r['speed_kbps']) + ' KB/s') if r.get('speed_kbps') else '–'}</td>"
|
|
202
|
+
f"<td>{(str(r['uptime_7d']) + ' %') if r.get('uptime_7d') is not None else '–'}</td>"
|
|
203
|
+
f"<td>{'yes' if r.get('https') else 'no'}</td><td>{escape(through)}</td>"
|
|
204
|
+
f"<td>{escape((r.get('org') or '–')[:40])}</td></tr>")
|
|
205
|
+
body_rows = "\n".join(cells(r) for r in rows[:ROWS_PER_PAGE])
|
|
130
206
|
table = (f"<div class=\"table\"><table><caption>The {min(total, ROWS_PER_PAGE):,} fastest, as ip:port. "
|
|
131
207
|
f"The download has all {total:,} as type://ip:port.</caption>"
|
|
132
208
|
"<thead><tr><th scope=\"col\">Proxy</th><th scope=\"col\">Type</th><th scope=\"col\">Country</th>"
|
|
133
|
-
"<th scope=\"col\">Latency</th><th scope=\"col\">
|
|
209
|
+
"<th scope=\"col\">Latency</th><th scope=\"col\">Speed</th><th scope=\"col\">Uptime</th>"
|
|
210
|
+
"<th scope=\"col\">HTTPS</th><th scope=\"col\">Gets through</th>"
|
|
134
211
|
f"<th scope=\"col\">Provider</th></tr></thead><tbody>{body_rows}</tbody></table></div>"
|
|
135
212
|
if total else "")
|
|
213
|
+
facts = _facts(rows)
|
|
214
|
+
facts_html = "" if not total else (
|
|
215
|
+
"<dl class=\"facts\">"
|
|
216
|
+
f"<div><dt>{facts['total']:,}</dt><dd>working right now</dd></div>"
|
|
217
|
+
f"<div><dt>{facts['https']:,}</dt><dd>tunnel HTTPS</dd></div>"
|
|
218
|
+
f"<div><dt>{facts['stable']:,}</dt><dd>on the list 90 %+ of the week</dd></div>"
|
|
219
|
+
+ (f"<div><dt>{facts['google']:,}</dt><dd>get through to Google</dd></div>"
|
|
220
|
+
if facts["google"] is not None else "")
|
|
221
|
+
+ (f"<div><dt>{facts['speed']:,} KB/s</dt><dd>median download speed</dd></div>" if facts["speed"] else "")
|
|
222
|
+
+ "</dl>")
|
|
223
|
+
flags = _pick_flags(path)
|
|
224
|
+
# curl goes to an https:// address, so only proxies that can tunnel it
|
|
225
|
+
curl_flags = flags if "--https-only" in flags else f"{flags} --https-only".strip()
|
|
226
|
+
commands = (
|
|
227
|
+
"<section class=\"use\"><h2>In the terminal or in code</h2>"
|
|
228
|
+
f"<pre class=\"mono\">pipx install proxy-scraper-cli\nproxy-scraper --pick 5 {escape(flags)}\n"
|
|
229
|
+
f"curl -x \"$(proxy-scraper --pick {escape(curl_flags)})\" https://api.ipify.org</pre>"
|
|
230
|
+
"<p class=\"note\">--pick takes proxies from the same hourly list in about half a second. "
|
|
231
|
+
f"From Python: <code>live_proxies()</code>, see the <a href=\"{REPO_URL}#from-python\">README</a>."
|
|
232
|
+
"</p></section>")
|
|
233
|
+
faq = _faq(path, facts, when)
|
|
234
|
+
faq_html = "<section class=\"faq\"><h2>Questions</h2>" + "".join(
|
|
235
|
+
f"<h3>{escape(q)}</h3><p>{escape(a)}</p>" for q, a in faq) + "</section>"
|
|
236
|
+
faq_ld = json.dumps({"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [
|
|
237
|
+
{"@type": "Question", "name": q, "acceptedAnswer": {"@type": "Answer", "text": a}} for q, a in faq]},
|
|
238
|
+
ensure_ascii=False).replace("</", "<\\/")
|
|
136
239
|
description = (f"{total:,} free {what}, checked every hour: real handshake, honeypot and content check. "
|
|
137
240
|
"Download as text or filter the full list.")
|
|
138
241
|
return f"""<!doctype html>
|
|
@@ -154,7 +257,8 @@ def _render(path: str, title: str, what: str, rows: List[dict], updated: datetim
|
|
|
154
257
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
155
258
|
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@400..700&family=Geist+Mono:wght@400;500&display=swap"
|
|
156
259
|
rel="stylesheet">
|
|
157
|
-
<style>{CSS}</style>
|
|
260
|
+
<style>{CSS}{PAGE_CSS}</style>
|
|
261
|
+
<script type="application/ld+json">{faq_ld}</script>
|
|
158
262
|
</head>
|
|
159
263
|
<body>
|
|
160
264
|
<div class="wrap">
|
|
@@ -171,7 +275,10 @@ def _render(path: str, title: str, what: str, rows: List[dict], updated: datetim
|
|
|
171
275
|
<a class="btn" href="{REPO_URL}">Check them from your network</a>
|
|
172
276
|
</div>
|
|
173
277
|
</main>
|
|
278
|
+
{facts_html}
|
|
174
279
|
{table}
|
|
280
|
+
{commands}
|
|
281
|
+
{faq_html}
|
|
175
282
|
{nav}
|
|
176
283
|
<footer>
|
|
177
284
|
<span>Free proxies are run by strangers. Never send passwords or personal data through them.</span>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Static pages per protocol and country for search engines, plus the sitemap (#121)."""
|
|
2
2
|
|
|
3
|
+
import html
|
|
3
4
|
from datetime import datetime, timezone
|
|
4
5
|
|
|
5
6
|
from proxyscraper import pages
|
|
@@ -58,3 +59,44 @@ def test_filter_button_opens_the_list_with_the_same_filter(tmp_path):
|
|
|
58
59
|
for path, query in expected.items():
|
|
59
60
|
html = (tmp_path / path / "index.html").read_text(encoding="utf-8")
|
|
60
61
|
assert f'href="{"../" * (path.count("/") + 1)}{query}#list">Filter the full list' in html
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_a_country_page_has_the_numbers_commands_and_answers(tmp_path):
|
|
65
|
+
import json
|
|
66
|
+
import re
|
|
67
|
+
rows = [row(1, "socks5", https=True, uptime_7d=95, speed_kbps=400, sites={"google": True}),
|
|
68
|
+
row(2, "http", https=False, uptime_7d=20),
|
|
69
|
+
row(3, "http", country="US")]
|
|
70
|
+
pages.write_pages(rows, tmp_path, NOW)
|
|
71
|
+
de = (tmp_path / "country" / "de" / "index.html").read_text(encoding="utf-8")
|
|
72
|
+
facts = re.sub(r"\s+", " ", re.sub(r"<[^>]+>", " ", de))
|
|
73
|
+
assert "2 working right now" in facts and "1 tunnel HTTPS" in facts and "1 on the list 90 %+ of the week" in facts
|
|
74
|
+
assert "1 get through to Google" in facts and "400 KB/s" in facts
|
|
75
|
+
assert "proxy-scraper --pick 5 --country DE" in de and 'curl -x "$(proxy-scraper --pick --country DE' in de
|
|
76
|
+
assert ">Speed<" in de and ">Uptime<" in de
|
|
77
|
+
ld = [json.loads(b) for b in re.findall(r'<script type="application/ld\+json">(.*?)</script>', de, re.S)]
|
|
78
|
+
faq = next(d for d in ld if d["@type"] == "FAQPage")
|
|
79
|
+
assert any("Germany" in q["name"] for q in faq["mainEntity"])
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_protocol_pages_pick_by_protocol(tmp_path):
|
|
83
|
+
pages.write_pages([row(1, "socks5", https=True)], tmp_path, NOW)
|
|
84
|
+
assert "proxy-scraper --pick 5 --types socks5" in (tmp_path / "socks5" / "index.html").read_text()
|
|
85
|
+
assert "proxy-scraper --pick 5 --https-only" in (tmp_path / "https" / "index.html").read_text()
|
|
86
|
+
assert "proxy-scraper --pick 5 --anonymity elite" in (tmp_path / "elite" / "index.html").read_text()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_an_empty_page_still_renders(tmp_path):
|
|
90
|
+
pages.write_pages([], tmp_path, NOW)
|
|
91
|
+
assert "No proxies in Germany" in (tmp_path / "country" / "de" / "index.html").read_text()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_the_median_speed_is_the_real_median():
|
|
95
|
+
assert pages._facts([row(1, speed_kbps=100), row(2, speed_kbps=400)])["speed"] == 250
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_pages_dont_claim_google_blocked_proxies_that_were_never_checked(tmp_path):
|
|
99
|
+
pages.write_pages([row(1, "socks4"), row(2, "socks4")], tmp_path, NOW)
|
|
100
|
+
page = html.unescape((tmp_path / "socks4" / "index.html").read_text(encoding="utf-8"))
|
|
101
|
+
assert "none of them got through" not in page and "<dd>get through to Google</dd>" not in page
|
|
102
|
+
assert "weren't part of the last site check" in page
|
|
File without changes
|
|
File without changes
|
{proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxy_scraper_cli.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxy_scraper_cli.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxy_scraper_cli.egg-info/requires.txt
RENAMED
|
File without changes
|
{proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxy_scraper_cli.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/site/apple-touch-icon.png
RENAMED
|
File without changes
|
{proxy_scraper_cli-1.16.0 → proxy_scraper_cli-1.17.0}/proxyscraper/site/googleaac1161b7853c5b5.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|