proxy-scraper-cli 1.20.0__tar.gz → 1.21.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.20.0 → proxy_scraper_cli-1.21.0}/PKG-INFO +60 -3
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/README.md +59 -2
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxy_scraper_cli.egg-info/PKG-INFO +60 -3
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxy_scraper_cli.egg-info/SOURCES.txt +3 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/__init__.py +1 -1
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/app.py +5 -3
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/cli.py +6 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/options.py +9 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/pipeline.py +22 -8
- proxy_scraper_cli-1.21.0/proxyscraper/server/api.py +186 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/server/core.py +28 -7
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/server/pool.py +9 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/sources.py +24 -0
- proxy_scraper_cli-1.21.0/tests/test_own_sources.py +98 -0
- proxy_scraper_cli-1.21.0/tests/test_serve_api.py +153 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_server.py +2 -1
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/LICENSE +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxy_scraper_cli.egg-info/dependency_links.txt +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxy_scraper_cli.egg-info/entry_points.txt +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxy_scraper_cli.egg-info/requires.txt +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxy_scraper_cli.egg-info/top_level.txt +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/__main__.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/action.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/agent.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/api.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/asndb.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/blocklist.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/charts.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/checker.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/compat.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/completion.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/exporters.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/fetchcache.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/geo.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/geodb.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/handshake.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/history.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/judges.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/mcp_entry.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/mcp_server.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/netio.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/output.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/pages.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/parsing.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/paths.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/preferences.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/proxypages.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/publish.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/ranking.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/report.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/runsteps.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/server/__init__.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/server/http.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/server/socks.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/server/status.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/server/upstream.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/site/apple-touch-icon.png +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/site/googleaac1161b7853c5b5.html +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/site/index.html +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/site/logo.png +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/site/og.png +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/sites.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/sources.json +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/speed.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/targets.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/ui/__init__.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/ui/dashboard.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/ui/keys.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/ui/report.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/ui/serve.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/ui/widgets.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/ui/wizard.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/pyproject.toml +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/setup.cfg +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_action.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_agent.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_api.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_app.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_asndb.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_auth.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_blocklist.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_charts.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_checker.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_compat.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_completion.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_discovery_growth.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_exporters.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_fetchcache.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_geodb.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_judges.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_learning.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_live_api.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_mcp_server.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_mine_sources.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_options.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_output_stdout.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_packaging.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_pages.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_parsing.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_parsing_fuzz.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_paths.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_pick.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_pipeline.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_proxy_pages.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_proxy_sources.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_publish.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_report.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_robustness.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_rotator.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_serve_password.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_serve_refill.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_serve_v2.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_server_closed_writer.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_server_fixes.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_sites.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_sources_json.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_speed.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_speed_ranking.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_tamper.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_targets.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_ui.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_uptime.py +0 -0
- {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.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.21.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
|
|
@@ -52,6 +52,7 @@ Dynamic: license-file
|
|
|
52
52
|
[](#live-list)
|
|
53
53
|
[](https://pypi.org/project/proxy-scraper-cli/)
|
|
54
54
|
[](pyproject.toml)
|
|
55
|
+
[](https://github.com/maximilianfeix/proxy-scraper/stargazers)
|
|
55
56
|
[](LICENSE)
|
|
56
57
|
|
|
57
58
|
<a href="https://maximilianfeix.github.io/proxy-scraper/"><img src="https://img.shields.io/badge/Browse_the_live_list-D4F77A?style=for-the-badge&labelColor=121113" alt="Browse the live list"></a>
|
|
@@ -94,7 +95,7 @@ Most free proxy lists are 95 % dead, and a good part of the rest are honeypots o
|
|
|
94
95
|
- [Options](#options)
|
|
95
96
|
- [GitHub Actions](#github-actions)
|
|
96
97
|
- [FAQ](#faq)
|
|
97
|
-
- [Roadmap](#roadmap) · [Contributing](#contributing) · [Acknowledgements](#acknowledgements)
|
|
98
|
+
- [Roadmap](#roadmap) · [Contributing](#contributing) · [Community](#community) · [Acknowledgements](#acknowledgements)
|
|
98
99
|
|
|
99
100
|
</details>
|
|
100
101
|
|
|
@@ -384,6 +385,7 @@ macOS, Linux and Windows, Python 3.9 to 3.13. Only two dependencies: `rich` and
|
|
|
384
385
|
| Only proxies that reach *your* target site | ❌ | ✅ `--target` |
|
|
385
386
|
| Learns which sources are worth it | ❌ | ✅ |
|
|
386
387
|
| Usable as a single rotating proxy | ❌ | ✅ `--serve` |
|
|
388
|
+
| API to fetch a proxy (proxy_pool compatible) | ❌ | ✅ [`/get`](#pool-api) |
|
|
387
389
|
| Ready-made list without running anything | ✅ | ✅ [live list](#live-list) |
|
|
388
390
|
|
|
389
391
|
<a id="examples"></a>
|
|
@@ -519,6 +521,38 @@ Without a password, `--serve-host` means **anyone who reaches the port can use i
|
|
|
519
521
|
|
|
520
522
|
In testing: 20 of 20 HTTPS requests succeeded, over 15 different exit IPs. In the wizard this is **Proxy server right away**.
|
|
521
523
|
|
|
524
|
+
<a id="pool-api"></a>
|
|
525
|
+
|
|
526
|
+
### Proxy pool API
|
|
527
|
+
|
|
528
|
+
Some programs want a proxy *address*, not a proxy – to hand it to a browser, a worker or a queue. The same port answers plain HTTP requests with one:
|
|
529
|
+
|
|
530
|
+
```bash
|
|
531
|
+
curl http://127.0.0.1:8899/get # one proxy as JSON, chosen like a connection would be
|
|
532
|
+
curl "http://127.0.0.1:8899/get?country=DE&https=1&format=txt" # → http://203.0.113.7:8080
|
|
533
|
+
curl "http://127.0.0.1:8899/all?protocol=socks5&limit=20" # the 20 best SOCKS5 proxies
|
|
534
|
+
curl "http://127.0.0.1:8899/report?proxy=203.0.113.7:8080&ok=0" # it failed you – three times and it's out
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
| Endpoint | What it does |
|
|
538
|
+
|---|---|
|
|
539
|
+
| `/get` | one proxy, picked by the `--rotate` strategy |
|
|
540
|
+
| `/pop` | like `/get`, and the proxy leaves the pool |
|
|
541
|
+
| `/all` | every usable proxy, best first (`limit=N`) |
|
|
542
|
+
| `/count` | totals by type and country |
|
|
543
|
+
| `/delete?proxy=IP:PORT` | take a proxy out |
|
|
544
|
+
| `/report?proxy=IP:PORT&ok=0` | feedback from your own requests |
|
|
545
|
+
|
|
546
|
+
Filters work on `/get`, `/pop` and `/all`: `country=DE,AT`, `protocol=socks5`, `https=1`, `anonymity=elite`, `max_latency=1500`, and `format=txt` for plain URLs. With `--serve-password` the API wants it as Basic auth, like the status page.
|
|
547
|
+
|
|
548
|
+
**Coming from [jhao104/proxy_pool](https://github.com/jhao104/proxy_pool)?** The endpoints, `type=https` and the JSON fields (`proxy`, `https`, `region`, `anonymous`, `check_count`, `fail_count`, …) are the same, so point your code at port 8899 and it keeps working – without Redis, and with proxies that passed the honeypot, tampering and TLS checks:
|
|
549
|
+
|
|
550
|
+
```python
|
|
551
|
+
import requests
|
|
552
|
+
proxy = requests.get("http://127.0.0.1:8899/get?type=https").json()["url"] # e.g. socks5://…, type included
|
|
553
|
+
requests.get("https://example.com", proxies={"http": proxy, "https": proxy})
|
|
554
|
+
```
|
|
555
|
+
|
|
522
556
|
<a id="discord-bot"></a>
|
|
523
557
|
|
|
524
558
|
## Discord and Telegram bot
|
|
@@ -558,6 +592,15 @@ flowchart LR
|
|
|
558
592
|
</div>
|
|
559
593
|
</details>
|
|
560
594
|
|
|
595
|
+
### Your own lists
|
|
596
|
+
|
|
597
|
+
```bash
|
|
598
|
+
proxy-scraper --source https://example.com/my-list.txt --source socks5=./socks.txt # on top of the 700+ sources
|
|
599
|
+
proxy-scraper --only-sources --source bought.txt --want 50 # only yours
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
Any text with `ip:port` works; lines like `socks5://user:pass@host:port` keep their type, bare ones are tried as HTTP and SOCKS5 unless you write `http=…`.
|
|
603
|
+
|
|
561
604
|
<a id="output"></a>
|
|
562
605
|
|
|
563
606
|
## Output
|
|
@@ -924,7 +967,7 @@ proxyscraper/
|
|
|
924
967
|
├── targets.py target sites for --target
|
|
925
968
|
├── output.py result files
|
|
926
969
|
├── exporters.py proxychains, Clash, sing-box and curl formats (--export)
|
|
927
|
-
├── server/ rotating proxy server (--serve): pool · http · upstream · socks · status · core
|
|
970
|
+
├── server/ rotating proxy server (--serve): pool · http · upstream · socks · status · api · core
|
|
928
971
|
├── api.py find_proxies() / check_proxies() for Python
|
|
929
972
|
├── agent.py MCP tools without the SDK: live list, filters, fetch through proxies
|
|
930
973
|
├── mcp_server.py MCP server (proxy-scraper-mcp) for AI agents
|
|
@@ -937,6 +980,20 @@ proxyscraper/
|
|
|
937
980
|
|
|
938
981
|
</details>
|
|
939
982
|
|
|
983
|
+
<a id="community"></a>
|
|
984
|
+
|
|
985
|
+
## Community
|
|
986
|
+
|
|
987
|
+
Questions, ideas and things you built with it go to [Discussions](https://github.com/maximilianfeix/proxy-scraper/discussions) – bugs to the [issues](../../issues). If proxy-scraper saves you time, a ⭐ helps others find it.
|
|
988
|
+
|
|
989
|
+
<a href="https://star-history.com/#maximilianfeix/proxy-scraper&Date">
|
|
990
|
+
<picture>
|
|
991
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=maximilianfeix/proxy-scraper&type=Date&theme=dark">
|
|
992
|
+
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=maximilianfeix/proxy-scraper&type=Date">
|
|
993
|
+
<img alt="Star history of proxy-scraper" src="https://api.star-history.com/svg?repos=maximilianfeix/proxy-scraper&type=Date" width="600">
|
|
994
|
+
</picture>
|
|
995
|
+
</a>
|
|
996
|
+
|
|
940
997
|
<a id="acknowledgements"></a>
|
|
941
998
|
|
|
942
999
|
## Acknowledgements
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
[](#live-list)
|
|
12
12
|
[](https://pypi.org/project/proxy-scraper-cli/)
|
|
13
13
|
[](pyproject.toml)
|
|
14
|
+
[](https://github.com/maximilianfeix/proxy-scraper/stargazers)
|
|
14
15
|
[](LICENSE)
|
|
15
16
|
|
|
16
17
|
<a href="https://maximilianfeix.github.io/proxy-scraper/"><img src="https://img.shields.io/badge/Browse_the_live_list-D4F77A?style=for-the-badge&labelColor=121113" alt="Browse the live list"></a>
|
|
@@ -53,7 +54,7 @@ Most free proxy lists are 95 % dead, and a good part of the rest are honeypots o
|
|
|
53
54
|
- [Options](#options)
|
|
54
55
|
- [GitHub Actions](#github-actions)
|
|
55
56
|
- [FAQ](#faq)
|
|
56
|
-
- [Roadmap](#roadmap) · [Contributing](#contributing) · [Acknowledgements](#acknowledgements)
|
|
57
|
+
- [Roadmap](#roadmap) · [Contributing](#contributing) · [Community](#community) · [Acknowledgements](#acknowledgements)
|
|
57
58
|
|
|
58
59
|
</details>
|
|
59
60
|
|
|
@@ -343,6 +344,7 @@ macOS, Linux and Windows, Python 3.9 to 3.13. Only two dependencies: `rich` and
|
|
|
343
344
|
| Only proxies that reach *your* target site | ❌ | ✅ `--target` |
|
|
344
345
|
| Learns which sources are worth it | ❌ | ✅ |
|
|
345
346
|
| Usable as a single rotating proxy | ❌ | ✅ `--serve` |
|
|
347
|
+
| API to fetch a proxy (proxy_pool compatible) | ❌ | ✅ [`/get`](#pool-api) |
|
|
346
348
|
| Ready-made list without running anything | ✅ | ✅ [live list](#live-list) |
|
|
347
349
|
|
|
348
350
|
<a id="examples"></a>
|
|
@@ -478,6 +480,38 @@ Without a password, `--serve-host` means **anyone who reaches the port can use i
|
|
|
478
480
|
|
|
479
481
|
In testing: 20 of 20 HTTPS requests succeeded, over 15 different exit IPs. In the wizard this is **Proxy server right away**.
|
|
480
482
|
|
|
483
|
+
<a id="pool-api"></a>
|
|
484
|
+
|
|
485
|
+
### Proxy pool API
|
|
486
|
+
|
|
487
|
+
Some programs want a proxy *address*, not a proxy – to hand it to a browser, a worker or a queue. The same port answers plain HTTP requests with one:
|
|
488
|
+
|
|
489
|
+
```bash
|
|
490
|
+
curl http://127.0.0.1:8899/get # one proxy as JSON, chosen like a connection would be
|
|
491
|
+
curl "http://127.0.0.1:8899/get?country=DE&https=1&format=txt" # → http://203.0.113.7:8080
|
|
492
|
+
curl "http://127.0.0.1:8899/all?protocol=socks5&limit=20" # the 20 best SOCKS5 proxies
|
|
493
|
+
curl "http://127.0.0.1:8899/report?proxy=203.0.113.7:8080&ok=0" # it failed you – three times and it's out
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
| Endpoint | What it does |
|
|
497
|
+
|---|---|
|
|
498
|
+
| `/get` | one proxy, picked by the `--rotate` strategy |
|
|
499
|
+
| `/pop` | like `/get`, and the proxy leaves the pool |
|
|
500
|
+
| `/all` | every usable proxy, best first (`limit=N`) |
|
|
501
|
+
| `/count` | totals by type and country |
|
|
502
|
+
| `/delete?proxy=IP:PORT` | take a proxy out |
|
|
503
|
+
| `/report?proxy=IP:PORT&ok=0` | feedback from your own requests |
|
|
504
|
+
|
|
505
|
+
Filters work on `/get`, `/pop` and `/all`: `country=DE,AT`, `protocol=socks5`, `https=1`, `anonymity=elite`, `max_latency=1500`, and `format=txt` for plain URLs. With `--serve-password` the API wants it as Basic auth, like the status page.
|
|
506
|
+
|
|
507
|
+
**Coming from [jhao104/proxy_pool](https://github.com/jhao104/proxy_pool)?** The endpoints, `type=https` and the JSON fields (`proxy`, `https`, `region`, `anonymous`, `check_count`, `fail_count`, …) are the same, so point your code at port 8899 and it keeps working – without Redis, and with proxies that passed the honeypot, tampering and TLS checks:
|
|
508
|
+
|
|
509
|
+
```python
|
|
510
|
+
import requests
|
|
511
|
+
proxy = requests.get("http://127.0.0.1:8899/get?type=https").json()["url"] # e.g. socks5://…, type included
|
|
512
|
+
requests.get("https://example.com", proxies={"http": proxy, "https": proxy})
|
|
513
|
+
```
|
|
514
|
+
|
|
481
515
|
<a id="discord-bot"></a>
|
|
482
516
|
|
|
483
517
|
## Discord and Telegram bot
|
|
@@ -517,6 +551,15 @@ flowchart LR
|
|
|
517
551
|
</div>
|
|
518
552
|
</details>
|
|
519
553
|
|
|
554
|
+
### Your own lists
|
|
555
|
+
|
|
556
|
+
```bash
|
|
557
|
+
proxy-scraper --source https://example.com/my-list.txt --source socks5=./socks.txt # on top of the 700+ sources
|
|
558
|
+
proxy-scraper --only-sources --source bought.txt --want 50 # only yours
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
Any text with `ip:port` works; lines like `socks5://user:pass@host:port` keep their type, bare ones are tried as HTTP and SOCKS5 unless you write `http=…`.
|
|
562
|
+
|
|
520
563
|
<a id="output"></a>
|
|
521
564
|
|
|
522
565
|
## Output
|
|
@@ -883,7 +926,7 @@ proxyscraper/
|
|
|
883
926
|
├── targets.py target sites for --target
|
|
884
927
|
├── output.py result files
|
|
885
928
|
├── exporters.py proxychains, Clash, sing-box and curl formats (--export)
|
|
886
|
-
├── server/ rotating proxy server (--serve): pool · http · upstream · socks · status · core
|
|
929
|
+
├── server/ rotating proxy server (--serve): pool · http · upstream · socks · status · api · core
|
|
887
930
|
├── api.py find_proxies() / check_proxies() for Python
|
|
888
931
|
├── agent.py MCP tools without the SDK: live list, filters, fetch through proxies
|
|
889
932
|
├── mcp_server.py MCP server (proxy-scraper-mcp) for AI agents
|
|
@@ -896,6 +939,20 @@ proxyscraper/
|
|
|
896
939
|
|
|
897
940
|
</details>
|
|
898
941
|
|
|
942
|
+
<a id="community"></a>
|
|
943
|
+
|
|
944
|
+
## Community
|
|
945
|
+
|
|
946
|
+
Questions, ideas and things you built with it go to [Discussions](https://github.com/maximilianfeix/proxy-scraper/discussions) – bugs to the [issues](../../issues). If proxy-scraper saves you time, a ⭐ helps others find it.
|
|
947
|
+
|
|
948
|
+
<a href="https://star-history.com/#maximilianfeix/proxy-scraper&Date">
|
|
949
|
+
<picture>
|
|
950
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=maximilianfeix/proxy-scraper&type=Date&theme=dark">
|
|
951
|
+
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=maximilianfeix/proxy-scraper&type=Date">
|
|
952
|
+
<img alt="Star history of proxy-scraper" src="https://api.star-history.com/svg?repos=maximilianfeix/proxy-scraper&type=Date" width="600">
|
|
953
|
+
</picture>
|
|
954
|
+
</a>
|
|
955
|
+
|
|
899
956
|
<a id="acknowledgements"></a>
|
|
900
957
|
|
|
901
958
|
## Acknowledgements
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proxy-scraper-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.21.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
|
|
@@ -52,6 +52,7 @@ Dynamic: license-file
|
|
|
52
52
|
[](#live-list)
|
|
53
53
|
[](https://pypi.org/project/proxy-scraper-cli/)
|
|
54
54
|
[](pyproject.toml)
|
|
55
|
+
[](https://github.com/maximilianfeix/proxy-scraper/stargazers)
|
|
55
56
|
[](LICENSE)
|
|
56
57
|
|
|
57
58
|
<a href="https://maximilianfeix.github.io/proxy-scraper/"><img src="https://img.shields.io/badge/Browse_the_live_list-D4F77A?style=for-the-badge&labelColor=121113" alt="Browse the live list"></a>
|
|
@@ -94,7 +95,7 @@ Most free proxy lists are 95 % dead, and a good part of the rest are honeypots o
|
|
|
94
95
|
- [Options](#options)
|
|
95
96
|
- [GitHub Actions](#github-actions)
|
|
96
97
|
- [FAQ](#faq)
|
|
97
|
-
- [Roadmap](#roadmap) · [Contributing](#contributing) · [Acknowledgements](#acknowledgements)
|
|
98
|
+
- [Roadmap](#roadmap) · [Contributing](#contributing) · [Community](#community) · [Acknowledgements](#acknowledgements)
|
|
98
99
|
|
|
99
100
|
</details>
|
|
100
101
|
|
|
@@ -384,6 +385,7 @@ macOS, Linux and Windows, Python 3.9 to 3.13. Only two dependencies: `rich` and
|
|
|
384
385
|
| Only proxies that reach *your* target site | ❌ | ✅ `--target` |
|
|
385
386
|
| Learns which sources are worth it | ❌ | ✅ |
|
|
386
387
|
| Usable as a single rotating proxy | ❌ | ✅ `--serve` |
|
|
388
|
+
| API to fetch a proxy (proxy_pool compatible) | ❌ | ✅ [`/get`](#pool-api) |
|
|
387
389
|
| Ready-made list without running anything | ✅ | ✅ [live list](#live-list) |
|
|
388
390
|
|
|
389
391
|
<a id="examples"></a>
|
|
@@ -519,6 +521,38 @@ Without a password, `--serve-host` means **anyone who reaches the port can use i
|
|
|
519
521
|
|
|
520
522
|
In testing: 20 of 20 HTTPS requests succeeded, over 15 different exit IPs. In the wizard this is **Proxy server right away**.
|
|
521
523
|
|
|
524
|
+
<a id="pool-api"></a>
|
|
525
|
+
|
|
526
|
+
### Proxy pool API
|
|
527
|
+
|
|
528
|
+
Some programs want a proxy *address*, not a proxy – to hand it to a browser, a worker or a queue. The same port answers plain HTTP requests with one:
|
|
529
|
+
|
|
530
|
+
```bash
|
|
531
|
+
curl http://127.0.0.1:8899/get # one proxy as JSON, chosen like a connection would be
|
|
532
|
+
curl "http://127.0.0.1:8899/get?country=DE&https=1&format=txt" # → http://203.0.113.7:8080
|
|
533
|
+
curl "http://127.0.0.1:8899/all?protocol=socks5&limit=20" # the 20 best SOCKS5 proxies
|
|
534
|
+
curl "http://127.0.0.1:8899/report?proxy=203.0.113.7:8080&ok=0" # it failed you – three times and it's out
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
| Endpoint | What it does |
|
|
538
|
+
|---|---|
|
|
539
|
+
| `/get` | one proxy, picked by the `--rotate` strategy |
|
|
540
|
+
| `/pop` | like `/get`, and the proxy leaves the pool |
|
|
541
|
+
| `/all` | every usable proxy, best first (`limit=N`) |
|
|
542
|
+
| `/count` | totals by type and country |
|
|
543
|
+
| `/delete?proxy=IP:PORT` | take a proxy out |
|
|
544
|
+
| `/report?proxy=IP:PORT&ok=0` | feedback from your own requests |
|
|
545
|
+
|
|
546
|
+
Filters work on `/get`, `/pop` and `/all`: `country=DE,AT`, `protocol=socks5`, `https=1`, `anonymity=elite`, `max_latency=1500`, and `format=txt` for plain URLs. With `--serve-password` the API wants it as Basic auth, like the status page.
|
|
547
|
+
|
|
548
|
+
**Coming from [jhao104/proxy_pool](https://github.com/jhao104/proxy_pool)?** The endpoints, `type=https` and the JSON fields (`proxy`, `https`, `region`, `anonymous`, `check_count`, `fail_count`, …) are the same, so point your code at port 8899 and it keeps working – without Redis, and with proxies that passed the honeypot, tampering and TLS checks:
|
|
549
|
+
|
|
550
|
+
```python
|
|
551
|
+
import requests
|
|
552
|
+
proxy = requests.get("http://127.0.0.1:8899/get?type=https").json()["url"] # e.g. socks5://…, type included
|
|
553
|
+
requests.get("https://example.com", proxies={"http": proxy, "https": proxy})
|
|
554
|
+
```
|
|
555
|
+
|
|
522
556
|
<a id="discord-bot"></a>
|
|
523
557
|
|
|
524
558
|
## Discord and Telegram bot
|
|
@@ -558,6 +592,15 @@ flowchart LR
|
|
|
558
592
|
</div>
|
|
559
593
|
</details>
|
|
560
594
|
|
|
595
|
+
### Your own lists
|
|
596
|
+
|
|
597
|
+
```bash
|
|
598
|
+
proxy-scraper --source https://example.com/my-list.txt --source socks5=./socks.txt # on top of the 700+ sources
|
|
599
|
+
proxy-scraper --only-sources --source bought.txt --want 50 # only yours
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
Any text with `ip:port` works; lines like `socks5://user:pass@host:port` keep their type, bare ones are tried as HTTP and SOCKS5 unless you write `http=…`.
|
|
603
|
+
|
|
561
604
|
<a id="output"></a>
|
|
562
605
|
|
|
563
606
|
## Output
|
|
@@ -924,7 +967,7 @@ proxyscraper/
|
|
|
924
967
|
├── targets.py target sites for --target
|
|
925
968
|
├── output.py result files
|
|
926
969
|
├── exporters.py proxychains, Clash, sing-box and curl formats (--export)
|
|
927
|
-
├── server/ rotating proxy server (--serve): pool · http · upstream · socks · status · core
|
|
970
|
+
├── server/ rotating proxy server (--serve): pool · http · upstream · socks · status · api · core
|
|
928
971
|
├── api.py find_proxies() / check_proxies() for Python
|
|
929
972
|
├── agent.py MCP tools without the SDK: live list, filters, fetch through proxies
|
|
930
973
|
├── mcp_server.py MCP server (proxy-scraper-mcp) for AI agents
|
|
@@ -937,6 +980,20 @@ proxyscraper/
|
|
|
937
980
|
|
|
938
981
|
</details>
|
|
939
982
|
|
|
983
|
+
<a id="community"></a>
|
|
984
|
+
|
|
985
|
+
## Community
|
|
986
|
+
|
|
987
|
+
Questions, ideas and things you built with it go to [Discussions](https://github.com/maximilianfeix/proxy-scraper/discussions) – bugs to the [issues](../../issues). If proxy-scraper saves you time, a ⭐ helps others find it.
|
|
988
|
+
|
|
989
|
+
<a href="https://star-history.com/#maximilianfeix/proxy-scraper&Date">
|
|
990
|
+
<picture>
|
|
991
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=maximilianfeix/proxy-scraper&type=Date&theme=dark">
|
|
992
|
+
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=maximilianfeix/proxy-scraper&type=Date">
|
|
993
|
+
<img alt="Star history of proxy-scraper" src="https://api.star-history.com/svg?repos=maximilianfeix/proxy-scraper&type=Date" width="600">
|
|
994
|
+
</picture>
|
|
995
|
+
</a>
|
|
996
|
+
|
|
940
997
|
<a id="acknowledgements"></a>
|
|
941
998
|
|
|
942
999
|
## Acknowledgements
|
{proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxy_scraper_cli.egg-info/SOURCES.txt
RENAMED
|
@@ -48,6 +48,7 @@ proxyscraper/sources.py
|
|
|
48
48
|
proxyscraper/speed.py
|
|
49
49
|
proxyscraper/targets.py
|
|
50
50
|
proxyscraper/server/__init__.py
|
|
51
|
+
proxyscraper/server/api.py
|
|
51
52
|
proxyscraper/server/core.py
|
|
52
53
|
proxyscraper/server/http.py
|
|
53
54
|
proxyscraper/server/pool.py
|
|
@@ -88,6 +89,7 @@ tests/test_mcp_server.py
|
|
|
88
89
|
tests/test_mine_sources.py
|
|
89
90
|
tests/test_options.py
|
|
90
91
|
tests/test_output_stdout.py
|
|
92
|
+
tests/test_own_sources.py
|
|
91
93
|
tests/test_packaging.py
|
|
92
94
|
tests/test_pages.py
|
|
93
95
|
tests/test_parsing.py
|
|
@@ -101,6 +103,7 @@ tests/test_publish.py
|
|
|
101
103
|
tests/test_report.py
|
|
102
104
|
tests/test_robustness.py
|
|
103
105
|
tests/test_rotator.py
|
|
106
|
+
tests/test_serve_api.py
|
|
104
107
|
tests/test_serve_password.py
|
|
105
108
|
tests/test_serve_refill.py
|
|
106
109
|
tests/test_serve_v2.py
|
|
@@ -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.21.0"
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def __getattr__(name):
|
|
@@ -327,8 +327,10 @@ class Run:
|
|
|
327
327
|
plan = await collect_sources(self.opts, self.quality)
|
|
328
328
|
info("Sources", Text.assemble(
|
|
329
329
|
(fmt(len(plan.sources)), f"bold {ACCENT}"), " active ",
|
|
330
|
-
(f"({fmt(plan.
|
|
331
|
-
f"
|
|
330
|
+
(f"({fmt(plan.n_own)} own)" if self.opts.only_sources else
|
|
331
|
+
f"({fmt(plan.n_curated)} curated · {fmt(plan.n_meta)} from {plan.meta_ok}/{plan.meta_total} "
|
|
332
|
+
f"meta lists · {fmt(plan.n_discovered)} discovered"
|
|
333
|
+
+ (f" · {fmt(plan.n_own)} own)" if plan.n_own else ")"), MUTED),
|
|
332
334
|
))
|
|
333
335
|
if plan.discovery_ran and not plan.discovery_token:
|
|
334
336
|
note("GitHub discovery is limited without a token – run `gh auth login` or set GITHUB_TOKEN.",
|
|
@@ -355,7 +357,7 @@ class Run:
|
|
|
355
357
|
hosts = sorted(INSECURE_HOSTS)
|
|
356
358
|
note(f"Certificate could not be verified (TLS inspection on this network?), loaded anyway: "
|
|
357
359
|
f"{', '.join(hosts[:4])}{' …' if len(hosts) > 4 else ''}", MUTED, "ℹ")
|
|
358
|
-
return prioritize(res, self.quality, self.history, self.opts.types)
|
|
360
|
+
return prioritize(res, self.quality, self.history, self.opts.types, listed_only=self.opts.only_sources)
|
|
359
361
|
|
|
360
362
|
# ------------------------------------------------------------------ phase 3+4: check, learn, report
|
|
361
363
|
|
|
@@ -246,6 +246,12 @@ def parse_args(argv: Optional[List[str]] = None) -> argparse.Namespace:
|
|
|
246
246
|
s.add_argument("--discover-repos", type=non_negative_int, default=DEFAULT_DISCOVER_REPOS,
|
|
247
247
|
help=f"max. repos during discovery (default: {DEFAULT_DISCOVER_REPOS}, 40 without a token)")
|
|
248
248
|
s.add_argument("--all-sources", action="store_true", help="also load dead, outdated and unreachable sources")
|
|
249
|
+
s.add_argument("--source", action="append", metavar="URL|FILE",
|
|
250
|
+
help="also load this proxy list (repeatable): a URL or a local file, one proxy per line or "
|
|
251
|
+
"any text with ip:port in it; TYPE=… fixes the type, e.g. socks5=https://…/list.txt – "
|
|
252
|
+
"without it, lines with a scheme keep theirs and bare ones are tried as HTTP and SOCKS5")
|
|
253
|
+
s.add_argument("--only-sources", action="store_true",
|
|
254
|
+
help="only the lists given with --source – no built-in, meta or discovered sources")
|
|
249
255
|
s.add_argument("--no-cache", action="store_true",
|
|
250
256
|
help="reload every list completely (otherwise unchanged ones are skipped via ETag)")
|
|
251
257
|
s.add_argument("--list-sources", nargs="?", const=50, type=positive_int, metavar="N",
|
|
@@ -115,6 +115,8 @@ class RunOptions:
|
|
|
115
115
|
no_discover: bool = False
|
|
116
116
|
discover_repos: int = DEFAULT_DISCOVER_REPOS
|
|
117
117
|
all_sources: bool = False
|
|
118
|
+
sources: List[str] = field(default_factory=list) # own lists (--source): URLs, files, 'socks5=…'
|
|
119
|
+
only_sources: bool = False # only the own lists – no curated, meta or discovered ones
|
|
118
120
|
no_cache: bool = False # reload every list instead of taking unchanged ones from the cache
|
|
119
121
|
no_dnsbl: bool = False # skip the blocklist lookup of the exit IPs
|
|
120
122
|
serve: int = 0 # port of the rotating proxy server after the run, 0 = off
|
|
@@ -149,6 +151,8 @@ class RunOptions:
|
|
|
149
151
|
raise ValueError("--sticky must not be negative")
|
|
150
152
|
if self.serve_refill < 0:
|
|
151
153
|
raise ValueError("--serve-refill must not be negative")
|
|
154
|
+
if self.only_sources and not self.sources:
|
|
155
|
+
raise ValueError("--only-sources needs at least one --source")
|
|
152
156
|
if not 0 <= self.serve <= 65535:
|
|
153
157
|
raise ValueError("port must be between 1 and 65535")
|
|
154
158
|
|
|
@@ -200,6 +204,8 @@ class RunOptions:
|
|
|
200
204
|
no_discover=args.no_discover,
|
|
201
205
|
discover_repos=args.discover_repos,
|
|
202
206
|
all_sources=args.all_sources,
|
|
207
|
+
sources=list(dict.fromkeys(args.source or [])),
|
|
208
|
+
only_sources=args.only_sources,
|
|
203
209
|
no_cache=args.no_cache,
|
|
204
210
|
no_dnsbl=args.no_dnsbl,
|
|
205
211
|
serve=args.serve,
|
|
@@ -245,6 +251,9 @@ class RunOptions:
|
|
|
245
251
|
_flag(argv, "--no-discover", self.no_discover)
|
|
246
252
|
_opt(argv, "--discover-repos", self.discover_repos, DEFAULT_DISCOVER_REPOS)
|
|
247
253
|
_flag(argv, "--all-sources", self.all_sources)
|
|
254
|
+
for spec in self.sources:
|
|
255
|
+
argv += ["--source", spec]
|
|
256
|
+
_flag(argv, "--only-sources", self.only_sources)
|
|
248
257
|
_flag(argv, "--no-cache", self.no_cache)
|
|
249
258
|
_flag(argv, "--no-dnsbl", self.no_dnsbl)
|
|
250
259
|
if self.serve:
|
|
@@ -9,7 +9,10 @@ import random
|
|
|
9
9
|
from collections import Counter
|
|
10
10
|
from concurrent.futures import ProcessPoolExecutor
|
|
11
11
|
from dataclasses import dataclass, field
|
|
12
|
+
from pathlib import Path
|
|
12
13
|
from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple
|
|
14
|
+
from urllib.parse import urlsplit
|
|
15
|
+
from urllib.request import url2pathname
|
|
13
16
|
|
|
14
17
|
from . import sources as srcs
|
|
15
18
|
from .asndb import ProviderLookup
|
|
@@ -45,12 +48,18 @@ class SourcePlan:
|
|
|
45
48
|
meta_ok: int
|
|
46
49
|
meta_total: int
|
|
47
50
|
n_discovered: int
|
|
51
|
+
n_own: int = 0
|
|
48
52
|
discovery_ran: bool = False
|
|
49
53
|
discovery_token: bool = False
|
|
50
54
|
|
|
51
55
|
|
|
52
56
|
async def collect_sources(opts: RunOptions, quality: srcs.SourceStats) -> SourcePlan:
|
|
53
|
-
"""Curated + meta + discovered sources, minus the ones learned to be bad."""
|
|
57
|
+
"""Curated + meta + discovered sources, minus the ones learned to be bad, plus the own ones (--source)."""
|
|
58
|
+
own = dict(srcs.parse_source_spec(spec) for spec in opts.sources)
|
|
59
|
+
wanted = set(opts.types)
|
|
60
|
+
own = {u: t for u, t in own.items() if t == "auto" or t in wanted}
|
|
61
|
+
if opts.only_sources:
|
|
62
|
+
return SourcePlan(own, Counter(), 0, 0, 0, 0, 0, n_own=len(own))
|
|
54
63
|
sources, meta = srcs.load_source_file()
|
|
55
64
|
n_curated = len(sources)
|
|
56
65
|
|
|
@@ -77,7 +86,6 @@ async def collect_sources(opts: RunOptions, quality: srcs.SourceStats) -> Source
|
|
|
77
86
|
for url, ptype in extra.items():
|
|
78
87
|
sources.setdefault(url, ptype)
|
|
79
88
|
|
|
80
|
-
wanted = set(opts.types)
|
|
81
89
|
sources = {u: t for u, t in sources.items() if t == "auto" or t in wanted}
|
|
82
90
|
skipped: Counter = Counter()
|
|
83
91
|
if not opts.all_sources:
|
|
@@ -89,8 +97,9 @@ async def collect_sources(opts: RunOptions, quality: srcs.SourceStats) -> Source
|
|
|
89
97
|
else:
|
|
90
98
|
active[url] = ptype
|
|
91
99
|
sources = active
|
|
100
|
+
sources.update(own) # asked for by name – never skipped, and the given type wins
|
|
92
101
|
return SourcePlan(
|
|
93
|
-
sources, skipped, n_curated, len(meta_found), meta_ok, len(meta), len(discovered),
|
|
102
|
+
sources, skipped, n_curated, len(meta_found), meta_ok, len(meta), len(discovered), n_own=len(own),
|
|
94
103
|
discovery_ran=run_discovery, discovery_token=token is not None,
|
|
95
104
|
)
|
|
96
105
|
|
|
@@ -123,7 +132,9 @@ async def scrape(sources: Dict[str, str], types, quality: srcs.SourceStats, view
|
|
|
123
132
|
with ProcessPoolExecutor(max_workers=max(1, (os.cpu_count() or 2) - 1)) as pool:
|
|
124
133
|
|
|
125
134
|
async def download(url: str):
|
|
126
|
-
"""-> (data, keys from the cache). Exactly one of
|
|
135
|
+
"""-> (data, headers, keys from the cache). Exactly one of data and keys is set."""
|
|
136
|
+
if url.startswith("file://"): # own list from --source
|
|
137
|
+
return await asyncio.to_thread(Path(url2pathname(urlsplit(url).path)).read_bytes), {}, None
|
|
127
138
|
conditional = cache.conditional_headers(url, sources[url])
|
|
128
139
|
async with sem:
|
|
129
140
|
status, headers, body = await http_request(url, timeout=30, headers=conditional or None)
|
|
@@ -152,7 +163,8 @@ async def scrape(sources: Dict[str, str], types, quality: srcs.SourceStats, view
|
|
|
152
163
|
keys = parse_blob(data, sources[url], parse_types)
|
|
153
164
|
else:
|
|
154
165
|
keys = await loop.run_in_executor(pool, parse_blob, data, sources[url], parse_types)
|
|
155
|
-
|
|
166
|
+
if not url.startswith("file://"):
|
|
167
|
+
cache.store(url, headers, keys, sources[url])
|
|
156
168
|
except Exception: # source unreachable/broken – counts as a failure in the statistics
|
|
157
169
|
pass
|
|
158
170
|
n = parsed = 0
|
|
@@ -180,13 +192,15 @@ async def scrape(sources: Dict[str, str], types, quality: srcs.SourceStats, view
|
|
|
180
192
|
return res
|
|
181
193
|
|
|
182
194
|
|
|
183
|
-
def prioritize(res: ScrapeResult, quality: srcs.SourceStats, history: ProxyHistory, types
|
|
195
|
+
def prioritize(res: ScrapeResult, quality: srcs.SourceStats, history: ProxyHistory, types,
|
|
196
|
+
listed_only: bool = False) -> List[str]:
|
|
184
197
|
"""Order of checking: known working proxies first, then by quality of the sources.
|
|
185
198
|
|
|
186
|
-
Within the same source quality, proxies that appear in more lists win.
|
|
199
|
+
Within the same source quality, proxies that appear in more lists win. listed_only (--only-sources):
|
|
200
|
+
the history only moves proxies of these lists to the front, it doesn't add others.
|
|
187
201
|
"""
|
|
188
202
|
wanted = set(types)
|
|
189
|
-
known = [k for k in history.ranked_keys() if split_key(k)[0] in wanted]
|
|
203
|
+
known = [k for k in history.ranked_keys() if split_key(k)[0] in wanted and (not listed_only or k in res.index)]
|
|
190
204
|
known_set = set(known)
|
|
191
205
|
|
|
192
206
|
scores = [quality.score(u) for u in res.urls]
|