proxy-scraper-cli 1.9.0__tar.gz → 1.10.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.9.0 → proxy_scraper_cli-1.10.0}/PKG-INFO +32 -12
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/README.md +30 -11
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxy_scraper_cli.egg-info/PKG-INFO +32 -12
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxy_scraper_cli.egg-info/SOURCES.txt +5 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxy_scraper_cli.egg-info/requires.txt +1 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/__init__.py +1 -1
- proxy_scraper_cli-1.10.0/proxyscraper/action.py +96 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/cli.py +20 -5
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/completion.py +61 -3
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/exporters.py +43 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/parsing.py +6 -1
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/report.py +2 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/server/core.py +23 -11
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/site/index.html +157 -15
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/sites.py +45 -6
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/pyproject.toml +1 -1
- proxy_scraper_cli-1.10.0/tests/test_action.py +69 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_completion.py +35 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_exporters.py +22 -0
- proxy_scraper_cli-1.10.0/tests/test_parsing_fuzz.py +83 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_report.py +6 -0
- proxy_scraper_cli-1.10.0/tests/test_server_closed_writer.py +72 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_sites.py +31 -1
- proxy_scraper_cli-1.10.0/tests/test_sources_json.py +36 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/LICENSE +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxy_scraper_cli.egg-info/dependency_links.txt +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxy_scraper_cli.egg-info/entry_points.txt +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxy_scraper_cli.egg-info/top_level.txt +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/__main__.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/agent.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/api.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/app.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/asndb.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/blocklist.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/charts.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/checker.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/compat.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/fetchcache.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/geo.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/geodb.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/handshake.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/history.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/judges.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/mcp_entry.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/mcp_server.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/netio.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/options.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/output.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/pages.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/paths.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/pipeline.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/preferences.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/proxypages.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/publish.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/server/__init__.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/server/http.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/server/pool.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/server/socks.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/server/status.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/server/upstream.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/site/apple-touch-icon.png +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/site/googleaac1161b7853c5b5.html +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/site/logo.png +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/site/og.png +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/sources.json +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/sources.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/targets.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/ui/__init__.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/ui/dashboard.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/ui/keys.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/ui/report.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/ui/serve.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/ui/widgets.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/proxyscraper/ui/wizard.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/setup.cfg +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_agent.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_api.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_app.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_asndb.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_auth.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_blocklist.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_charts.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_checker.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_compat.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_discovery_growth.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_fetchcache.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_geodb.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_judges.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_learning.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_live_api.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_mcp_server.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_mine_sources.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_options.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_output_stdout.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_packaging.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_pages.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_parsing.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_paths.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_pipeline.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_proxy_pages.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_proxy_sources.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_publish.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_robustness.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_serve_password.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_serve_refill.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_serve_v2.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_server.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_server_fixes.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_tamper.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_targets.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_ui.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.0}/tests/test_uptime.py +0 -0
- {proxy_scraper_cli-1.9.0 → proxy_scraper_cli-1.10.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.10.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
|
|
@@ -35,6 +35,7 @@ Provides-Extra: mcp
|
|
|
35
35
|
Requires-Dist: mcp<3,>=2.2; python_version >= "3.10" and extra == "mcp"
|
|
36
36
|
Provides-Extra: dev
|
|
37
37
|
Requires-Dist: pytest; extra == "dev"
|
|
38
|
+
Requires-Dist: hypothesis; extra == "dev"
|
|
38
39
|
Requires-Dist: ruff==0.16.8; extra == "dev"
|
|
39
40
|
Dynamic: license-file
|
|
40
41
|
|
|
@@ -138,11 +139,12 @@ python3 proxy_scraper.py
|
|
|
138
139
|
|
|
139
140
|
Every [release](https://github.com/maximilianfeix/proxy-scraper/releases/latest) also ships a wheel you can install with `pip install <file>.whl`, and a multi-arch image (amd64/arm64) on `ghcr.io`. In the container the wizard never shows up, it runs straight away. For the proxy server use `--serve --serve-host 0.0.0.0` with `-p 127.0.0.1:8899:8899`, so the port is only open on your own machine.
|
|
140
141
|
|
|
141
|
-
Tab completion for bash, zsh and
|
|
142
|
+
Tab completion for bash, zsh, fish and PowerShell:
|
|
142
143
|
|
|
143
144
|
```bash
|
|
144
145
|
eval "$(proxy-scraper --completion zsh)" # in ~/.zshrc (after compinit), same for bash in ~/.bashrc
|
|
145
146
|
proxy-scraper --completion fish > ~/.config/fish/completions/proxy-scraper.fish
|
|
147
|
+
proxy-scraper --completion powershell | Out-String | Invoke-Expression # in $PROFILE
|
|
146
148
|
```
|
|
147
149
|
|
|
148
150
|
Installed, the learned state lives in your user data folder (`~/Library/Application Support/proxy-scraper`, `%LOCALAPPDATA%\proxy-scraper` or `~/.local/share/proxy-scraper`; override with `PROXY_SCRAPER_HOME`) and results go to `./results`. Run from a clone, both stay inside the project.
|
|
@@ -656,14 +658,14 @@ if __name__ == "__main__":
|
|
|
656
658
|
|
|
657
659
|
In a Scrapy project, put `RotatingProxy` in `middlewares.py` and add it to `DOWNLOADER_MIDDLEWARES` in `settings.py`. For long crawls, reload the pool now and then – the list changes every hour.
|
|
658
660
|
|
|
659
|
-
**proxychains, Clash / Mihomo** – ready-made configs with `--export`
|
|
661
|
+
**proxychains, Clash / Mihomo, sing-box** – ready-made configs with `--export`
|
|
660
662
|
|
|
661
663
|
```bash
|
|
662
|
-
proxy-scraper --want 30 -y --export proxychains,clash
|
|
664
|
+
proxy-scraper --want 30 -y --export proxychains,clash,singbox
|
|
663
665
|
proxychains4 -f results/latest/proxychains.conf curl https://api.ipify.org
|
|
664
666
|
```
|
|
665
667
|
|
|
666
|
-
`clash.yaml` has all HTTP and SOCKS5 proxies plus a `url-test` group that always picks the fastest.
|
|
668
|
+
`clash.yaml` has all HTTP and SOCKS5 proxies plus a `url-test` group that always picks the fastest. `singbox.json` does the same for sing-box, SOCKS4 included, and opens a local proxy: `sing-box run -c results/latest/singbox.json`, then use `127.0.0.1:2080` as HTTP or SOCKS5 proxy. All three leave out HTTP proxies that can't tunnel (`CONNECT`), because these tools tunnel everything.
|
|
667
669
|
|
|
668
670
|
**In a pipe** – `-o -` prints the hits to stdout, the interface moves to stderr
|
|
669
671
|
|
|
@@ -679,6 +681,22 @@ export HTTPS_PROXY=http://127.0.0.1:8899 HTTP_PROXY=http://127.0.0.1:8899
|
|
|
679
681
|
pip download requests # git, pip, npm & co. now go through the pool
|
|
680
682
|
```
|
|
681
683
|
|
|
684
|
+
**In a GitHub workflow** – the action picks working proxies for the next steps
|
|
685
|
+
|
|
686
|
+
```yaml
|
|
687
|
+
- id: proxies
|
|
688
|
+
uses: maximilianfeix/proxy-scraper@v1
|
|
689
|
+
with:
|
|
690
|
+
types: socks5
|
|
691
|
+
https: true
|
|
692
|
+
min-uptime: 90 # on the list for 90 %+ of the week
|
|
693
|
+
works-on: google # optional: google, reddit, amazon
|
|
694
|
+
recheck: true # optional: check them again from the runner
|
|
695
|
+
- run: curl -x "${{ steps.proxies.outputs.proxy }}" https://api.ipify.org
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
`proxy` is the fastest one, `file` a text file with all of them (up to `limit`, default 20), `count` how many. Without a match the step fails, unless `fail-if-empty: false`.
|
|
699
|
+
|
|
682
700
|
**Without installing anything** – straight from the live list
|
|
683
701
|
|
|
684
702
|
```bash
|
|
@@ -722,16 +740,16 @@ curl.exe -x (Get-Content "$run\all.txt" -TotalCount 1) http://api.ipify.org
|
|
|
722
740
|
| `-t`, `--timeout S` | timeout per proxy (default: 8 s) |
|
|
723
741
|
| `--discover` | search GitHub for new sources right now |
|
|
724
742
|
| `--no-cache` | download every list again (unchanged ones are normally skipped via ETag) |
|
|
725
|
-
| `--list-sources [N]` | show the source ranking |
|
|
743
|
+
| `--list-sources [N]` | show the source ranking (add `--json` for scripts: url, status, hit rate, checks, last change) |
|
|
726
744
|
| `--serve-host ADDR` | where the proxy server listens (default `127.0.0.1`; `0.0.0.0` for Docker, with a warning) |
|
|
727
745
|
| `--serve-password SECRET` | clients must send this password in the proxy login; better set `PROXY_SCRAPER_SERVE_PASSWORD` |
|
|
728
746
|
| `--rotate STRATEGY` · `--sticky SEC` | how the proxy server picks proxies, see [above](#proxy-server) |
|
|
729
747
|
| `--serve-refill HOURS` | while serving, check fresh proxies every HOURS and add the hits to the pool |
|
|
730
748
|
| `--serve [PORT]` | afterwards serve as a rotating proxy on `127.0.0.1:PORT` (default: 8899) |
|
|
731
749
|
| `-o FILE` | also write all hits to this file; `-o -` prints them to stdout |
|
|
732
|
-
| `--export FORMATS` | extra files for other tools: `proxychains`, `clash`, `curl` or `all` |
|
|
750
|
+
| `--export FORMATS` | extra files for other tools: `proxychains`, `clash`, `singbox`, `curl` or `all` |
|
|
733
751
|
| `-V`, `--version` | print the version |
|
|
734
|
-
| `--completion SHELL` | print the tab completion script for bash, zsh or
|
|
752
|
+
| `--completion SHELL` | print the tab completion script for bash, zsh, fish or PowerShell |
|
|
735
753
|
|
|
736
754
|
Everything else: `proxy-scraper --help`
|
|
737
755
|
|
|
@@ -813,10 +831,12 @@ Only for things that don't matter. Public proxies are run by strangers who can r
|
|
|
813
831
|
|
|
814
832
|
## Roadmap
|
|
815
833
|
|
|
816
|
-
What's next is
|
|
834
|
+
What's next is in the [open issues](../../issues) – ideas and wishes are welcome as an [issue](../../issues/new/choose). During Hacktoberfest there are [beginner-friendly issues](../../issues?q=is%3Aopen+label%3Ahacktoberfest) with pointers on where to start.
|
|
835
|
+
|
|
836
|
+
- [ ] [Measure throughput, not just latency](../../issues/140)
|
|
837
|
+
- [ ] [Daily snapshots as a dataset](../../issues/143)
|
|
817
838
|
|
|
818
|
-
|
|
819
|
-
- [ ] [sing-box export](../../issues/113), [PowerShell completion](../../issues/114), [shareable filters on the website](../../issues/116)
|
|
839
|
+
Shipped in v1.8 and v1.9: uptime per proxy and `stable.txt`, which proxies get through to Google, Reddit and Amazon, `live_proxies()` in Python, a page per proxy with its week of checks, a weekly report, live charts in this README, shareable filters on the website, sources mined from other scrapers' lists, untyped lists tried as HTTP and SOCKS5, `--export singbox`, PowerShell completion and recipes for httpx, aiohttp and Scrapy.
|
|
820
840
|
|
|
821
841
|
Shipped in [v1.7](../../milestone/7): an MCP server so AI agents get working proxies and can load pages through them, 28 new sources and a GitHub search that runs daily and keeps what it found, and a cleaner website.
|
|
822
842
|
|
|
@@ -861,7 +881,7 @@ proxyscraper/
|
|
|
861
881
|
├── geodb.py DB-IP country database (monthly, binary search)
|
|
862
882
|
├── targets.py target sites for --target
|
|
863
883
|
├── output.py result files
|
|
864
|
-
├── exporters.py proxychains, Clash and curl formats (--export)
|
|
884
|
+
├── exporters.py proxychains, Clash, sing-box and curl formats (--export)
|
|
865
885
|
├── server/ rotating proxy server (--serve): pool · http · upstream · socks · status · core
|
|
866
886
|
├── api.py find_proxies() / check_proxies() for Python
|
|
867
887
|
├── agent.py MCP tools without the SDK: live list, filters, fetch through proxies
|
|
@@ -98,11 +98,12 @@ python3 proxy_scraper.py
|
|
|
98
98
|
|
|
99
99
|
Every [release](https://github.com/maximilianfeix/proxy-scraper/releases/latest) also ships a wheel you can install with `pip install <file>.whl`, and a multi-arch image (amd64/arm64) on `ghcr.io`. In the container the wizard never shows up, it runs straight away. For the proxy server use `--serve --serve-host 0.0.0.0` with `-p 127.0.0.1:8899:8899`, so the port is only open on your own machine.
|
|
100
100
|
|
|
101
|
-
Tab completion for bash, zsh and
|
|
101
|
+
Tab completion for bash, zsh, fish and PowerShell:
|
|
102
102
|
|
|
103
103
|
```bash
|
|
104
104
|
eval "$(proxy-scraper --completion zsh)" # in ~/.zshrc (after compinit), same for bash in ~/.bashrc
|
|
105
105
|
proxy-scraper --completion fish > ~/.config/fish/completions/proxy-scraper.fish
|
|
106
|
+
proxy-scraper --completion powershell | Out-String | Invoke-Expression # in $PROFILE
|
|
106
107
|
```
|
|
107
108
|
|
|
108
109
|
Installed, the learned state lives in your user data folder (`~/Library/Application Support/proxy-scraper`, `%LOCALAPPDATA%\proxy-scraper` or `~/.local/share/proxy-scraper`; override with `PROXY_SCRAPER_HOME`) and results go to `./results`. Run from a clone, both stay inside the project.
|
|
@@ -616,14 +617,14 @@ if __name__ == "__main__":
|
|
|
616
617
|
|
|
617
618
|
In a Scrapy project, put `RotatingProxy` in `middlewares.py` and add it to `DOWNLOADER_MIDDLEWARES` in `settings.py`. For long crawls, reload the pool now and then – the list changes every hour.
|
|
618
619
|
|
|
619
|
-
**proxychains, Clash / Mihomo** – ready-made configs with `--export`
|
|
620
|
+
**proxychains, Clash / Mihomo, sing-box** – ready-made configs with `--export`
|
|
620
621
|
|
|
621
622
|
```bash
|
|
622
|
-
proxy-scraper --want 30 -y --export proxychains,clash
|
|
623
|
+
proxy-scraper --want 30 -y --export proxychains,clash,singbox
|
|
623
624
|
proxychains4 -f results/latest/proxychains.conf curl https://api.ipify.org
|
|
624
625
|
```
|
|
625
626
|
|
|
626
|
-
`clash.yaml` has all HTTP and SOCKS5 proxies plus a `url-test` group that always picks the fastest.
|
|
627
|
+
`clash.yaml` has all HTTP and SOCKS5 proxies plus a `url-test` group that always picks the fastest. `singbox.json` does the same for sing-box, SOCKS4 included, and opens a local proxy: `sing-box run -c results/latest/singbox.json`, then use `127.0.0.1:2080` as HTTP or SOCKS5 proxy. All three leave out HTTP proxies that can't tunnel (`CONNECT`), because these tools tunnel everything.
|
|
627
628
|
|
|
628
629
|
**In a pipe** – `-o -` prints the hits to stdout, the interface moves to stderr
|
|
629
630
|
|
|
@@ -639,6 +640,22 @@ export HTTPS_PROXY=http://127.0.0.1:8899 HTTP_PROXY=http://127.0.0.1:8899
|
|
|
639
640
|
pip download requests # git, pip, npm & co. now go through the pool
|
|
640
641
|
```
|
|
641
642
|
|
|
643
|
+
**In a GitHub workflow** – the action picks working proxies for the next steps
|
|
644
|
+
|
|
645
|
+
```yaml
|
|
646
|
+
- id: proxies
|
|
647
|
+
uses: maximilianfeix/proxy-scraper@v1
|
|
648
|
+
with:
|
|
649
|
+
types: socks5
|
|
650
|
+
https: true
|
|
651
|
+
min-uptime: 90 # on the list for 90 %+ of the week
|
|
652
|
+
works-on: google # optional: google, reddit, amazon
|
|
653
|
+
recheck: true # optional: check them again from the runner
|
|
654
|
+
- run: curl -x "${{ steps.proxies.outputs.proxy }}" https://api.ipify.org
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
`proxy` is the fastest one, `file` a text file with all of them (up to `limit`, default 20), `count` how many. Without a match the step fails, unless `fail-if-empty: false`.
|
|
658
|
+
|
|
642
659
|
**Without installing anything** – straight from the live list
|
|
643
660
|
|
|
644
661
|
```bash
|
|
@@ -682,16 +699,16 @@ curl.exe -x (Get-Content "$run\all.txt" -TotalCount 1) http://api.ipify.org
|
|
|
682
699
|
| `-t`, `--timeout S` | timeout per proxy (default: 8 s) |
|
|
683
700
|
| `--discover` | search GitHub for new sources right now |
|
|
684
701
|
| `--no-cache` | download every list again (unchanged ones are normally skipped via ETag) |
|
|
685
|
-
| `--list-sources [N]` | show the source ranking |
|
|
702
|
+
| `--list-sources [N]` | show the source ranking (add `--json` for scripts: url, status, hit rate, checks, last change) |
|
|
686
703
|
| `--serve-host ADDR` | where the proxy server listens (default `127.0.0.1`; `0.0.0.0` for Docker, with a warning) |
|
|
687
704
|
| `--serve-password SECRET` | clients must send this password in the proxy login; better set `PROXY_SCRAPER_SERVE_PASSWORD` |
|
|
688
705
|
| `--rotate STRATEGY` · `--sticky SEC` | how the proxy server picks proxies, see [above](#proxy-server) |
|
|
689
706
|
| `--serve-refill HOURS` | while serving, check fresh proxies every HOURS and add the hits to the pool |
|
|
690
707
|
| `--serve [PORT]` | afterwards serve as a rotating proxy on `127.0.0.1:PORT` (default: 8899) |
|
|
691
708
|
| `-o FILE` | also write all hits to this file; `-o -` prints them to stdout |
|
|
692
|
-
| `--export FORMATS` | extra files for other tools: `proxychains`, `clash`, `curl` or `all` |
|
|
709
|
+
| `--export FORMATS` | extra files for other tools: `proxychains`, `clash`, `singbox`, `curl` or `all` |
|
|
693
710
|
| `-V`, `--version` | print the version |
|
|
694
|
-
| `--completion SHELL` | print the tab completion script for bash, zsh or
|
|
711
|
+
| `--completion SHELL` | print the tab completion script for bash, zsh, fish or PowerShell |
|
|
695
712
|
|
|
696
713
|
Everything else: `proxy-scraper --help`
|
|
697
714
|
|
|
@@ -773,10 +790,12 @@ Only for things that don't matter. Public proxies are run by strangers who can r
|
|
|
773
790
|
|
|
774
791
|
## Roadmap
|
|
775
792
|
|
|
776
|
-
What's next is
|
|
793
|
+
What's next is in the [open issues](../../issues) – ideas and wishes are welcome as an [issue](../../issues/new/choose). During Hacktoberfest there are [beginner-friendly issues](../../issues?q=is%3Aopen+label%3Ahacktoberfest) with pointers on where to start.
|
|
794
|
+
|
|
795
|
+
- [ ] [Measure throughput, not just latency](../../issues/140)
|
|
796
|
+
- [ ] [Daily snapshots as a dataset](../../issues/143)
|
|
777
797
|
|
|
778
|
-
|
|
779
|
-
- [ ] [sing-box export](../../issues/113), [PowerShell completion](../../issues/114), [shareable filters on the website](../../issues/116)
|
|
798
|
+
Shipped in v1.8 and v1.9: uptime per proxy and `stable.txt`, which proxies get through to Google, Reddit and Amazon, `live_proxies()` in Python, a page per proxy with its week of checks, a weekly report, live charts in this README, shareable filters on the website, sources mined from other scrapers' lists, untyped lists tried as HTTP and SOCKS5, `--export singbox`, PowerShell completion and recipes for httpx, aiohttp and Scrapy.
|
|
780
799
|
|
|
781
800
|
Shipped in [v1.7](../../milestone/7): an MCP server so AI agents get working proxies and can load pages through them, 28 new sources and a GitHub search that runs daily and keeps what it found, and a cleaner website.
|
|
782
801
|
|
|
@@ -821,7 +840,7 @@ proxyscraper/
|
|
|
821
840
|
├── geodb.py DB-IP country database (monthly, binary search)
|
|
822
841
|
├── targets.py target sites for --target
|
|
823
842
|
├── output.py result files
|
|
824
|
-
├── exporters.py proxychains, Clash and curl formats (--export)
|
|
843
|
+
├── exporters.py proxychains, Clash, sing-box and curl formats (--export)
|
|
825
844
|
├── server/ rotating proxy server (--serve): pool · http · upstream · socks · status · core
|
|
826
845
|
├── api.py find_proxies() / check_proxies() for Python
|
|
827
846
|
├── agent.py MCP tools without the SDK: live list, filters, fetch through proxies
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: proxy-scraper-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.10.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
|
|
@@ -35,6 +35,7 @@ Provides-Extra: mcp
|
|
|
35
35
|
Requires-Dist: mcp<3,>=2.2; python_version >= "3.10" and extra == "mcp"
|
|
36
36
|
Provides-Extra: dev
|
|
37
37
|
Requires-Dist: pytest; extra == "dev"
|
|
38
|
+
Requires-Dist: hypothesis; extra == "dev"
|
|
38
39
|
Requires-Dist: ruff==0.16.8; extra == "dev"
|
|
39
40
|
Dynamic: license-file
|
|
40
41
|
|
|
@@ -138,11 +139,12 @@ python3 proxy_scraper.py
|
|
|
138
139
|
|
|
139
140
|
Every [release](https://github.com/maximilianfeix/proxy-scraper/releases/latest) also ships a wheel you can install with `pip install <file>.whl`, and a multi-arch image (amd64/arm64) on `ghcr.io`. In the container the wizard never shows up, it runs straight away. For the proxy server use `--serve --serve-host 0.0.0.0` with `-p 127.0.0.1:8899:8899`, so the port is only open on your own machine.
|
|
140
141
|
|
|
141
|
-
Tab completion for bash, zsh and
|
|
142
|
+
Tab completion for bash, zsh, fish and PowerShell:
|
|
142
143
|
|
|
143
144
|
```bash
|
|
144
145
|
eval "$(proxy-scraper --completion zsh)" # in ~/.zshrc (after compinit), same for bash in ~/.bashrc
|
|
145
146
|
proxy-scraper --completion fish > ~/.config/fish/completions/proxy-scraper.fish
|
|
147
|
+
proxy-scraper --completion powershell | Out-String | Invoke-Expression # in $PROFILE
|
|
146
148
|
```
|
|
147
149
|
|
|
148
150
|
Installed, the learned state lives in your user data folder (`~/Library/Application Support/proxy-scraper`, `%LOCALAPPDATA%\proxy-scraper` or `~/.local/share/proxy-scraper`; override with `PROXY_SCRAPER_HOME`) and results go to `./results`. Run from a clone, both stay inside the project.
|
|
@@ -656,14 +658,14 @@ if __name__ == "__main__":
|
|
|
656
658
|
|
|
657
659
|
In a Scrapy project, put `RotatingProxy` in `middlewares.py` and add it to `DOWNLOADER_MIDDLEWARES` in `settings.py`. For long crawls, reload the pool now and then – the list changes every hour.
|
|
658
660
|
|
|
659
|
-
**proxychains, Clash / Mihomo** – ready-made configs with `--export`
|
|
661
|
+
**proxychains, Clash / Mihomo, sing-box** – ready-made configs with `--export`
|
|
660
662
|
|
|
661
663
|
```bash
|
|
662
|
-
proxy-scraper --want 30 -y --export proxychains,clash
|
|
664
|
+
proxy-scraper --want 30 -y --export proxychains,clash,singbox
|
|
663
665
|
proxychains4 -f results/latest/proxychains.conf curl https://api.ipify.org
|
|
664
666
|
```
|
|
665
667
|
|
|
666
|
-
`clash.yaml` has all HTTP and SOCKS5 proxies plus a `url-test` group that always picks the fastest.
|
|
668
|
+
`clash.yaml` has all HTTP and SOCKS5 proxies plus a `url-test` group that always picks the fastest. `singbox.json` does the same for sing-box, SOCKS4 included, and opens a local proxy: `sing-box run -c results/latest/singbox.json`, then use `127.0.0.1:2080` as HTTP or SOCKS5 proxy. All three leave out HTTP proxies that can't tunnel (`CONNECT`), because these tools tunnel everything.
|
|
667
669
|
|
|
668
670
|
**In a pipe** – `-o -` prints the hits to stdout, the interface moves to stderr
|
|
669
671
|
|
|
@@ -679,6 +681,22 @@ export HTTPS_PROXY=http://127.0.0.1:8899 HTTP_PROXY=http://127.0.0.1:8899
|
|
|
679
681
|
pip download requests # git, pip, npm & co. now go through the pool
|
|
680
682
|
```
|
|
681
683
|
|
|
684
|
+
**In a GitHub workflow** – the action picks working proxies for the next steps
|
|
685
|
+
|
|
686
|
+
```yaml
|
|
687
|
+
- id: proxies
|
|
688
|
+
uses: maximilianfeix/proxy-scraper@v1
|
|
689
|
+
with:
|
|
690
|
+
types: socks5
|
|
691
|
+
https: true
|
|
692
|
+
min-uptime: 90 # on the list for 90 %+ of the week
|
|
693
|
+
works-on: google # optional: google, reddit, amazon
|
|
694
|
+
recheck: true # optional: check them again from the runner
|
|
695
|
+
- run: curl -x "${{ steps.proxies.outputs.proxy }}" https://api.ipify.org
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
`proxy` is the fastest one, `file` a text file with all of them (up to `limit`, default 20), `count` how many. Without a match the step fails, unless `fail-if-empty: false`.
|
|
699
|
+
|
|
682
700
|
**Without installing anything** – straight from the live list
|
|
683
701
|
|
|
684
702
|
```bash
|
|
@@ -722,16 +740,16 @@ curl.exe -x (Get-Content "$run\all.txt" -TotalCount 1) http://api.ipify.org
|
|
|
722
740
|
| `-t`, `--timeout S` | timeout per proxy (default: 8 s) |
|
|
723
741
|
| `--discover` | search GitHub for new sources right now |
|
|
724
742
|
| `--no-cache` | download every list again (unchanged ones are normally skipped via ETag) |
|
|
725
|
-
| `--list-sources [N]` | show the source ranking |
|
|
743
|
+
| `--list-sources [N]` | show the source ranking (add `--json` for scripts: url, status, hit rate, checks, last change) |
|
|
726
744
|
| `--serve-host ADDR` | where the proxy server listens (default `127.0.0.1`; `0.0.0.0` for Docker, with a warning) |
|
|
727
745
|
| `--serve-password SECRET` | clients must send this password in the proxy login; better set `PROXY_SCRAPER_SERVE_PASSWORD` |
|
|
728
746
|
| `--rotate STRATEGY` · `--sticky SEC` | how the proxy server picks proxies, see [above](#proxy-server) |
|
|
729
747
|
| `--serve-refill HOURS` | while serving, check fresh proxies every HOURS and add the hits to the pool |
|
|
730
748
|
| `--serve [PORT]` | afterwards serve as a rotating proxy on `127.0.0.1:PORT` (default: 8899) |
|
|
731
749
|
| `-o FILE` | also write all hits to this file; `-o -` prints them to stdout |
|
|
732
|
-
| `--export FORMATS` | extra files for other tools: `proxychains`, `clash`, `curl` or `all` |
|
|
750
|
+
| `--export FORMATS` | extra files for other tools: `proxychains`, `clash`, `singbox`, `curl` or `all` |
|
|
733
751
|
| `-V`, `--version` | print the version |
|
|
734
|
-
| `--completion SHELL` | print the tab completion script for bash, zsh or
|
|
752
|
+
| `--completion SHELL` | print the tab completion script for bash, zsh, fish or PowerShell |
|
|
735
753
|
|
|
736
754
|
Everything else: `proxy-scraper --help`
|
|
737
755
|
|
|
@@ -813,10 +831,12 @@ Only for things that don't matter. Public proxies are run by strangers who can r
|
|
|
813
831
|
|
|
814
832
|
## Roadmap
|
|
815
833
|
|
|
816
|
-
What's next is
|
|
834
|
+
What's next is in the [open issues](../../issues) – ideas and wishes are welcome as an [issue](../../issues/new/choose). During Hacktoberfest there are [beginner-friendly issues](../../issues?q=is%3Aopen+label%3Ahacktoberfest) with pointers on where to start.
|
|
835
|
+
|
|
836
|
+
- [ ] [Measure throughput, not just latency](../../issues/140)
|
|
837
|
+
- [ ] [Daily snapshots as a dataset](../../issues/143)
|
|
817
838
|
|
|
818
|
-
|
|
819
|
-
- [ ] [sing-box export](../../issues/113), [PowerShell completion](../../issues/114), [shareable filters on the website](../../issues/116)
|
|
839
|
+
Shipped in v1.8 and v1.9: uptime per proxy and `stable.txt`, which proxies get through to Google, Reddit and Amazon, `live_proxies()` in Python, a page per proxy with its week of checks, a weekly report, live charts in this README, shareable filters on the website, sources mined from other scrapers' lists, untyped lists tried as HTTP and SOCKS5, `--export singbox`, PowerShell completion and recipes for httpx, aiohttp and Scrapy.
|
|
820
840
|
|
|
821
841
|
Shipped in [v1.7](../../milestone/7): an MCP server so AI agents get working proxies and can load pages through them, 28 new sources and a GitHub search that runs daily and keeps what it found, and a cleaner website.
|
|
822
842
|
|
|
@@ -861,7 +881,7 @@ proxyscraper/
|
|
|
861
881
|
├── geodb.py DB-IP country database (monthly, binary search)
|
|
862
882
|
├── targets.py target sites for --target
|
|
863
883
|
├── output.py result files
|
|
864
|
-
├── exporters.py proxychains, Clash and curl formats (--export)
|
|
884
|
+
├── exporters.py proxychains, Clash, sing-box and curl formats (--export)
|
|
865
885
|
├── server/ rotating proxy server (--serve): pool · http · upstream · socks · status · core
|
|
866
886
|
├── api.py find_proxies() / check_proxies() for Python
|
|
867
887
|
├── agent.py MCP tools without the SDK: live list, filters, fetch through proxies
|
|
@@ -9,6 +9,7 @@ proxy_scraper_cli.egg-info/requires.txt
|
|
|
9
9
|
proxy_scraper_cli.egg-info/top_level.txt
|
|
10
10
|
proxyscraper/__init__.py
|
|
11
11
|
proxyscraper/__main__.py
|
|
12
|
+
proxyscraper/action.py
|
|
12
13
|
proxyscraper/agent.py
|
|
13
14
|
proxyscraper/api.py
|
|
14
15
|
proxyscraper/app.py
|
|
@@ -62,6 +63,7 @@ proxyscraper/ui/report.py
|
|
|
62
63
|
proxyscraper/ui/serve.py
|
|
63
64
|
proxyscraper/ui/widgets.py
|
|
64
65
|
proxyscraper/ui/wizard.py
|
|
66
|
+
tests/test_action.py
|
|
65
67
|
tests/test_agent.py
|
|
66
68
|
tests/test_api.py
|
|
67
69
|
tests/test_app.py
|
|
@@ -86,6 +88,7 @@ tests/test_output_stdout.py
|
|
|
86
88
|
tests/test_packaging.py
|
|
87
89
|
tests/test_pages.py
|
|
88
90
|
tests/test_parsing.py
|
|
91
|
+
tests/test_parsing_fuzz.py
|
|
89
92
|
tests/test_paths.py
|
|
90
93
|
tests/test_pipeline.py
|
|
91
94
|
tests/test_proxy_pages.py
|
|
@@ -97,8 +100,10 @@ tests/test_serve_password.py
|
|
|
97
100
|
tests/test_serve_refill.py
|
|
98
101
|
tests/test_serve_v2.py
|
|
99
102
|
tests/test_server.py
|
|
103
|
+
tests/test_server_closed_writer.py
|
|
100
104
|
tests/test_server_fixes.py
|
|
101
105
|
tests/test_sites.py
|
|
106
|
+
tests/test_sources_json.py
|
|
102
107
|
tests/test_tamper.py
|
|
103
108
|
tests/test_targets.py
|
|
104
109
|
tests/test_ui.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.10.0"
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def __getattr__(name):
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""The GitHub Action (action.yml): working proxies from the hourly list for the next steps of a workflow.
|
|
2
|
+
|
|
3
|
+
Inputs come in as PS_* environment variables, results go to $GITHUB_OUTPUT:
|
|
4
|
+
proxy the fastest one, ready to use (socks5://1.2.3.4:1080)
|
|
5
|
+
file a text file with all of them, one per line, fastest first
|
|
6
|
+
count how many
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
import tempfile
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import List
|
|
16
|
+
|
|
17
|
+
from .api import check_proxies, live_proxies
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class InputError(ValueError):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _flag(name: str, default: bool = False) -> bool:
|
|
25
|
+
value = os.environ.get(name, "").strip().lower()
|
|
26
|
+
if not value:
|
|
27
|
+
return default
|
|
28
|
+
if value in ("true", "yes", "1"):
|
|
29
|
+
return True
|
|
30
|
+
if value in ("false", "no", "0"):
|
|
31
|
+
return False
|
|
32
|
+
raise InputError(f"{name[3:].lower().replace('_', '-')} must be true or false, not {value!r}")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _number(name: str, default: int) -> int:
|
|
36
|
+
value = os.environ.get(name, "").strip()
|
|
37
|
+
if not value:
|
|
38
|
+
return default
|
|
39
|
+
if not value.isdigit():
|
|
40
|
+
raise InputError(f"{name[3:].lower().replace('_', '-')} must be a whole number, not {value!r}")
|
|
41
|
+
return int(value)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _list(name: str) -> List[str]:
|
|
45
|
+
return [part.strip().lower() for part in os.environ.get(name, "").split(",") if part.strip()]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _output(**values: str) -> None:
|
|
49
|
+
path = os.environ.get("GITHUB_OUTPUT")
|
|
50
|
+
lines = "".join(f"{key}={value}\n" for key, value in values.items())
|
|
51
|
+
if path:
|
|
52
|
+
with open(path, "a", encoding="utf-8") as fh:
|
|
53
|
+
fh.write(lines)
|
|
54
|
+
else:
|
|
55
|
+
sys.stdout.write(lines)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def main() -> int:
|
|
59
|
+
try:
|
|
60
|
+
limit = _number("PS_LIMIT", 20)
|
|
61
|
+
recheck = _flag("PS_RECHECK")
|
|
62
|
+
fail_if_empty = _flag("PS_FAIL_IF_EMPTY", True)
|
|
63
|
+
filters = {"types": _list("PS_TYPES") or ["http", "socks4", "socks5"],
|
|
64
|
+
"countries": os.environ.get("PS_COUNTRIES", "").strip(),
|
|
65
|
+
"https": _flag("PS_HTTPS"), "min_uptime": _number("PS_MIN_UPTIME", 0),
|
|
66
|
+
"works_on": _list("PS_WORKS_ON")}
|
|
67
|
+
# a recheck starts from all of them – the limit applies to what still works from here
|
|
68
|
+
found = live_proxies(**filters, limit=0 if recheck else limit)
|
|
69
|
+
except (InputError, ValueError) as e:
|
|
70
|
+
print(f"::error::{e}")
|
|
71
|
+
return 1
|
|
72
|
+
except ConnectionError as e:
|
|
73
|
+
print(f"::error::The live list couldn't be loaded: {e}")
|
|
74
|
+
return 1
|
|
75
|
+
urls = [p.url for p in found]
|
|
76
|
+
if recheck and urls:
|
|
77
|
+
print(f"Checking {len(urls)} proxies again from this runner …")
|
|
78
|
+
urls = [r.url for r in sorted(check_proxies(urls, https=filters["https"]), key=lambda r: r.latency)]
|
|
79
|
+
if limit:
|
|
80
|
+
urls = urls[:limit]
|
|
81
|
+
# a file of its own per step: a job may use the action twice (socks5 in one step, http in the next)
|
|
82
|
+
fd, name = tempfile.mkstemp(prefix="proxies-", suffix=".txt", dir=os.environ.get("RUNNER_TEMP") or None)
|
|
83
|
+
with os.fdopen(fd, "w", encoding="utf-8") as fh:
|
|
84
|
+
fh.write("".join(f"{u}\n" for u in urls))
|
|
85
|
+
file = Path(name)
|
|
86
|
+
_output(proxy=urls[0] if urls else "", file=str(file), count=str(len(urls)))
|
|
87
|
+
print(f"{len(urls)} working {'proxy' if len(urls) == 1 else 'proxies'}" + (f", fastest: {urls[0]}" if urls else ""))
|
|
88
|
+
if not urls and fail_if_empty:
|
|
89
|
+
print("::error::No proxy matches these filters right now – loosen them (min-uptime, works-on, countries) "
|
|
90
|
+
"or set fail-if-empty: false")
|
|
91
|
+
return 1
|
|
92
|
+
return 0
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if __name__ == "__main__":
|
|
96
|
+
sys.exit(main())
|
|
@@ -4,9 +4,11 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
6
|
import asyncio
|
|
7
|
+
import json
|
|
7
8
|
import os
|
|
8
9
|
import sys
|
|
9
10
|
from collections import Counter
|
|
11
|
+
from datetime import datetime, timezone
|
|
10
12
|
from typing import List, Optional
|
|
11
13
|
|
|
12
14
|
from rich.console import Console
|
|
@@ -38,13 +40,22 @@ from .ui.keys import is_interactive
|
|
|
38
40
|
from .ui.wizard import run_wizard
|
|
39
41
|
|
|
40
42
|
|
|
41
|
-
def list_sources(limit: int) -> int:
|
|
42
|
-
"""Ranking of every known source by learned hit rate."""
|
|
43
|
+
def list_sources(limit: int, as_json: bool = False) -> int:
|
|
44
|
+
"""Ranking of every known source by learned hit rate – as a table, or as JSON on stdout for scripts."""
|
|
43
45
|
quality = srcs.SourceStats()
|
|
44
46
|
curated, _meta = srcs.load_source_file()
|
|
45
47
|
urls = set(curated) | set(srcs.load_discovered()) | set(quality.records)
|
|
46
48
|
ranking = quality.ranking(urls)
|
|
47
49
|
rows = [(u, r, quality.skip_reason(u) or "active") for u, r in ranking if r.runs][:limit]
|
|
50
|
+
if as_json:
|
|
51
|
+
def stamp(t: float) -> Optional[str]:
|
|
52
|
+
return datetime.fromtimestamp(t, timezone.utc).isoformat(timespec="seconds") if t else None
|
|
53
|
+
sys.stdout.write(json.dumps([
|
|
54
|
+
{"url": u, "status": status, "hit_rate": round(r.score, 4), "checked": round(r.checked),
|
|
55
|
+
"working": round(r.working), "proxies": r.count, "runs": r.runs,
|
|
56
|
+
"first_seen": stamp(r.first_seen), "last_change": stamp(r.last_change)}
|
|
57
|
+
for u, r, status in rows], indent=1) + "\n")
|
|
58
|
+
return 0
|
|
48
59
|
reasons = Counter(quality.skip_reason(u) or "active" for u, _ in ranking)
|
|
49
60
|
widgets.console.print(banner())
|
|
50
61
|
render_source_ranking(rows, len(urls), reasons)
|
|
@@ -181,7 +192,7 @@ def parse_args(argv: Optional[List[str]] = None) -> argparse.Namespace:
|
|
|
181
192
|
"(the interface moves to stderr then)")
|
|
182
193
|
o.add_argument("--export", type=export_list, metavar="FORMATS",
|
|
183
194
|
help=f"extra formats in the results folder: {', '.join(EXPORTERS)} or all "
|
|
184
|
-
"(e.g. --export proxychains,
|
|
195
|
+
"(e.g. --export proxychains,singbox)")
|
|
185
196
|
|
|
186
197
|
s = p.add_argument_group("Sources")
|
|
187
198
|
s.add_argument("--discover", action="store_true",
|
|
@@ -194,7 +205,11 @@ def parse_args(argv: Optional[List[str]] = None) -> argparse.Namespace:
|
|
|
194
205
|
help="reload every list completely (otherwise unchanged ones are skipped via ETag)")
|
|
195
206
|
s.add_argument("--list-sources", nargs="?", const=50, type=positive_int, metavar="N",
|
|
196
207
|
help="show the source ranking by hit rate (default: top 50) and exit")
|
|
197
|
-
|
|
208
|
+
s.add_argument("--json", action="store_true", help="with --list-sources: print the ranking as JSON to stdout")
|
|
209
|
+
args = p.parse_args(argv)
|
|
210
|
+
if args.json and args.list_sources is None:
|
|
211
|
+
p.error("--json only works together with --list-sources")
|
|
212
|
+
return args
|
|
198
213
|
|
|
199
214
|
|
|
200
215
|
def last_options() -> Optional[RunOptions]:
|
|
@@ -244,7 +259,7 @@ def run(argv: Optional[List[str]] = None) -> int:
|
|
|
244
259
|
from .mcp_entry import main as mcp_main
|
|
245
260
|
return mcp_main()
|
|
246
261
|
if args.list_sources is not None:
|
|
247
|
-
return list_sources(args.list_sources)
|
|
262
|
+
return list_sources(args.list_sources, args.json)
|
|
248
263
|
opts = RunOptions.from_args(args)
|
|
249
264
|
if opts.output == STDOUT:
|
|
250
265
|
widgets.console = Console(highlight=False, stderr=True) # stdout only carries the hits
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Tab completion for bash, zsh and
|
|
1
|
+
"""Tab completion for bash, zsh, fish and PowerShell – generated straight from the argparse parser.
|
|
2
2
|
|
|
3
3
|
That way the script can never go stale: every new option shows up automatically, with its choices
|
|
4
4
|
(--types, --rotate, …) and the start of its help text as the description (zsh, fish).
|
|
@@ -12,7 +12,7 @@ import sys
|
|
|
12
12
|
from dataclasses import dataclass
|
|
13
13
|
from typing import List, Optional, Tuple
|
|
14
14
|
|
|
15
|
-
SHELLS = ("bash", "zsh", "fish")
|
|
15
|
+
SHELLS = ("bash", "zsh", "fish", "powershell")
|
|
16
16
|
PROG = "proxy-scraper"
|
|
17
17
|
FILE_OPTIONS = {"--recheck": ("live",), "--output": ()} # the value is a file path (or one of these words)
|
|
18
18
|
OWN_HELP = {"help": "show help", "version": "show the version"} # shorter than argparse's own texts
|
|
@@ -181,8 +181,66 @@ def fish(opts: List[Option]) -> str:
|
|
|
181
181
|
return "\n".join(lines) + "\n"
|
|
182
182
|
|
|
183
183
|
|
|
184
|
+
def _ps_quote(text: str) -> str:
|
|
185
|
+
return "'" + text.replace("'", "''") + "'"
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def powershell(opts: List[Option]) -> str:
|
|
189
|
+
"""A native argument completer: options with their help as tooltip, the choices after an option (also for
|
|
190
|
+
several values in a row, --types http socks5), and PowerShell's own path completion for file options."""
|
|
191
|
+
rows = []
|
|
192
|
+
for o in opts:
|
|
193
|
+
# an optional value without choices (--serve, --list-sources): options may follow right away, like in bash
|
|
194
|
+
expects_value = o.takes_value and not (o.optional_value and not o.is_file and not o.choices)
|
|
195
|
+
fields = [f"Flags = @({', '.join(_ps_quote(f) for f in o.flags)})", f"Help = {_ps_quote(o.help)}",
|
|
196
|
+
f"Choices = @({', '.join(_ps_quote(c) for c in (*o.choices, *o.extra))})",
|
|
197
|
+
f"Value = ${str(expects_value).lower()}", f"Multi = ${str(o.multi).lower()}",
|
|
198
|
+
f"File = ${str(o.is_file).lower()}"]
|
|
199
|
+
rows.append(" @{ " + "; ".join(fields) + " }")
|
|
200
|
+
table = ",\n".join(rows)
|
|
201
|
+
return f"""# PowerShell completion for {PROG}
|
|
202
|
+
# enable: {PROG} --completion powershell | Out-String | Invoke-Expression (add that line to $PROFILE)
|
|
203
|
+
Register-ArgumentCompleter -Native -CommandName '{PROG}' -ScriptBlock {{
|
|
204
|
+
param($wordToComplete, $commandAst, $cursorPosition)
|
|
205
|
+
$options = @(
|
|
206
|
+
{table}
|
|
207
|
+
)
|
|
208
|
+
$words = @($commandAst.CommandElements | Where-Object {{ $_.Extent.EndOffset -le $cursorPosition }} |
|
|
209
|
+
ForEach-Object {{ $_.ToString() }})
|
|
210
|
+
if ($wordToComplete -and $words.Count -gt 0 -and $words[-1] -eq $wordToComplete) {{
|
|
211
|
+
$words = @($words | Select-Object -SkipLast 1)
|
|
212
|
+
}}
|
|
213
|
+
# the last option before the cursor, and whether the cursor is right after it
|
|
214
|
+
$last = $null; $distance = 0
|
|
215
|
+
for ($i = $words.Count - 1; $i -ge 1; $i--) {{
|
|
216
|
+
if ($words[$i].StartsWith('-')) {{ $last = $options | Where-Object {{ $_.Flags -contains $words[$i] }} |
|
|
217
|
+
Select-Object -First 1; break }}
|
|
218
|
+
$distance++
|
|
219
|
+
}}
|
|
220
|
+
if ($last -and $last.Value -and -not $wordToComplete.StartsWith('-') -and ($distance -eq 0 -or $last.Multi)) {{
|
|
221
|
+
if ($last.Choices.Count -gt 0) {{
|
|
222
|
+
$last.Choices | Where-Object {{ $_ -like "$wordToComplete*" }} | ForEach-Object {{
|
|
223
|
+
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
|
|
224
|
+
}}
|
|
225
|
+
if ($last.File) {{ # a file works too (--recheck live or a path)
|
|
226
|
+
[System.Management.Automation.CompletionCompleters]::CompleteFilename($wordToComplete)
|
|
227
|
+
}}
|
|
228
|
+
}}
|
|
229
|
+
return # nothing to suggest (a number, a path): PowerShell falls back to its own completion
|
|
230
|
+
}}
|
|
231
|
+
foreach ($option in $options) {{
|
|
232
|
+
foreach ($flag in $option.Flags) {{
|
|
233
|
+
if ($flag -like "$wordToComplete*") {{
|
|
234
|
+
[System.Management.Automation.CompletionResult]::new($flag, $flag, 'ParameterName', $option.Help)
|
|
235
|
+
}}
|
|
236
|
+
}}
|
|
237
|
+
}}
|
|
238
|
+
}}
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
|
|
184
242
|
def script(parser: argparse.ArgumentParser, shell: str) -> str:
|
|
185
|
-
return {"bash": bash, "zsh": zsh, "fish": fish}[shell](options(parser))
|
|
243
|
+
return {"bash": bash, "zsh": zsh, "fish": fish, "powershell": powershell}[shell](options(parser))
|
|
186
244
|
|
|
187
245
|
|
|
188
246
|
class CompletionAction(argparse.Action):
|