proxy-scraper-cli 1.21.0__tar.gz → 1.22.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.
Files changed (125) hide show
  1. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/PKG-INFO +7 -4
  2. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/README.md +6 -3
  3. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxy_scraper_cli.egg-info/PKG-INFO +7 -4
  4. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxy_scraper_cli.egg-info/SOURCES.txt +1 -0
  5. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/__init__.py +1 -1
  6. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/app.py +5 -0
  7. proxy_scraper_cli-1.22.0/proxyscraper/preferences.py +59 -0
  8. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/site/index.html +46 -13
  9. proxy_scraper_cli-1.22.0/tests/test_preferences.py +37 -0
  10. proxy_scraper_cli-1.21.0/proxyscraper/preferences.py +0 -24
  11. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/LICENSE +0 -0
  12. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxy_scraper_cli.egg-info/dependency_links.txt +0 -0
  13. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxy_scraper_cli.egg-info/entry_points.txt +0 -0
  14. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxy_scraper_cli.egg-info/requires.txt +0 -0
  15. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxy_scraper_cli.egg-info/top_level.txt +0 -0
  16. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/__main__.py +0 -0
  17. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/action.py +0 -0
  18. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/agent.py +0 -0
  19. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/api.py +0 -0
  20. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/asndb.py +0 -0
  21. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/blocklist.py +0 -0
  22. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/charts.py +0 -0
  23. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/checker.py +0 -0
  24. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/cli.py +0 -0
  25. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/compat.py +0 -0
  26. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/completion.py +0 -0
  27. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/exporters.py +0 -0
  28. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/fetchcache.py +0 -0
  29. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/geo.py +0 -0
  30. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/geodb.py +0 -0
  31. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/handshake.py +0 -0
  32. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/history.py +0 -0
  33. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/judges.py +0 -0
  34. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/mcp_entry.py +0 -0
  35. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/mcp_server.py +0 -0
  36. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/netio.py +0 -0
  37. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/options.py +0 -0
  38. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/output.py +0 -0
  39. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/pages.py +0 -0
  40. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/parsing.py +0 -0
  41. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/paths.py +0 -0
  42. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/pipeline.py +0 -0
  43. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/proxypages.py +0 -0
  44. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/publish.py +0 -0
  45. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/ranking.py +0 -0
  46. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/report.py +0 -0
  47. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/runsteps.py +0 -0
  48. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/server/__init__.py +0 -0
  49. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/server/api.py +0 -0
  50. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/server/core.py +0 -0
  51. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/server/http.py +0 -0
  52. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/server/pool.py +0 -0
  53. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/server/socks.py +0 -0
  54. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/server/status.py +0 -0
  55. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/server/upstream.py +0 -0
  56. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/site/apple-touch-icon.png +0 -0
  57. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/site/googleaac1161b7853c5b5.html +0 -0
  58. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/site/logo.png +0 -0
  59. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/site/og.png +0 -0
  60. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/sites.py +0 -0
  61. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/sources.json +0 -0
  62. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/sources.py +0 -0
  63. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/speed.py +0 -0
  64. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/targets.py +0 -0
  65. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/ui/__init__.py +0 -0
  66. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/ui/dashboard.py +0 -0
  67. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/ui/keys.py +0 -0
  68. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/ui/report.py +0 -0
  69. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/ui/serve.py +0 -0
  70. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/ui/widgets.py +0 -0
  71. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/proxyscraper/ui/wizard.py +0 -0
  72. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/pyproject.toml +0 -0
  73. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/setup.cfg +0 -0
  74. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_action.py +0 -0
  75. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_agent.py +0 -0
  76. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_api.py +0 -0
  77. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_app.py +0 -0
  78. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_asndb.py +0 -0
  79. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_auth.py +0 -0
  80. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_blocklist.py +0 -0
  81. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_charts.py +0 -0
  82. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_checker.py +0 -0
  83. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_compat.py +0 -0
  84. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_completion.py +0 -0
  85. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_discovery_growth.py +0 -0
  86. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_exporters.py +0 -0
  87. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_fetchcache.py +0 -0
  88. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_geodb.py +0 -0
  89. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_judges.py +0 -0
  90. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_learning.py +0 -0
  91. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_live_api.py +0 -0
  92. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_mcp_server.py +0 -0
  93. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_mine_sources.py +0 -0
  94. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_options.py +0 -0
  95. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_output_stdout.py +0 -0
  96. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_own_sources.py +0 -0
  97. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_packaging.py +0 -0
  98. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_pages.py +0 -0
  99. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_parsing.py +0 -0
  100. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_parsing_fuzz.py +0 -0
  101. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_paths.py +0 -0
  102. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_pick.py +0 -0
  103. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_pipeline.py +0 -0
  104. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_proxy_pages.py +0 -0
  105. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_proxy_sources.py +0 -0
  106. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_publish.py +0 -0
  107. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_report.py +0 -0
  108. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_robustness.py +0 -0
  109. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_rotator.py +0 -0
  110. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_serve_api.py +0 -0
  111. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_serve_password.py +0 -0
  112. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_serve_refill.py +0 -0
  113. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_serve_v2.py +0 -0
  114. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_server.py +0 -0
  115. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_server_closed_writer.py +0 -0
  116. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_server_fixes.py +0 -0
  117. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_sites.py +0 -0
  118. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_sources_json.py +0 -0
  119. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_speed.py +0 -0
  120. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_speed_ranking.py +0 -0
  121. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_tamper.py +0 -0
  122. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_targets.py +0 -0
  123. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_ui.py +0 -0
  124. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.0}/tests/test_uptime.py +0 -0
  125. {proxy_scraper_cli-1.21.0 → proxy_scraper_cli-1.22.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.21.0
3
+ Version: 1.22.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
@@ -69,6 +69,8 @@ Dynamic: license-file
69
69
 
70
70
  Most free proxy lists are 95 % dead, and a good part of the rest are honeypots or proxies that inject scripts into your pages. **proxy-scraper** collects public HTTP, SOCKS4 and SOCKS5 proxies from 700+ sources and keeps only the ones that pass every check. It learns with every run which sources are worth it, and it can turn the result into a single rotating proxy.
71
71
 
72
+ **In numbers:** in one day it checked 3.6 million free proxies – 1.1 % worked, and more than half of the ones that answered failed a second request. [What's actually out there →](docs/free-proxies-in-numbers.md)
73
+
72
74
  <picture>
73
75
  <source media="(prefers-color-scheme: dark)" srcset="docs/checks-dark.svg">
74
76
  <source media="(prefers-color-scheme: light)" srcset="docs/checks-light.svg">
@@ -148,7 +150,7 @@ proxy-scraper --completion fish > ~/.config/fish/completions/proxy-scraper.fish
148
150
  proxy-scraper --completion powershell | Out-String | Invoke-Expression # in $PROFILE
149
151
  ```
150
152
 
151
- 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.
153
+ 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. After the second run that finds proxies it asks once for a GitHub star; `PROXY_SCRAPER_NO_STAR_HINT=1` turns that off (it never shows in CI).
152
154
 
153
155
  </details>
154
156
 
@@ -918,8 +920,9 @@ Only for things that don't matter. Public proxies are run by strangers who can r
918
920
 
919
921
  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.
920
922
 
921
- - [ ] [Measure throughput, not just latency](../../issues/140)
922
- - [ ] [Daily snapshots as a dataset](../../issues/143)
923
+ - [ ] [Mirror the daily snapshots to a Hugging Face dataset](../../issues/188)
924
+
925
+ Shipped in v1.10 to v1.21: download speed per proxy and "best first" everywhere, daily snapshots, a rotating proxy URL for requests, httpx and Playwright, `--pick` for working proxies without a scan, a proxy pool API compatible with jhao104/proxy_pool, your own lists with `--source`, a GitHub Action, a Telegram bot and an Atom feed for the weekly report.
923
926
 
924
927
  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.
925
928
 
@@ -28,6 +28,8 @@
28
28
 
29
29
  Most free proxy lists are 95 % dead, and a good part of the rest are honeypots or proxies that inject scripts into your pages. **proxy-scraper** collects public HTTP, SOCKS4 and SOCKS5 proxies from 700+ sources and keeps only the ones that pass every check. It learns with every run which sources are worth it, and it can turn the result into a single rotating proxy.
30
30
 
31
+ **In numbers:** in one day it checked 3.6 million free proxies – 1.1 % worked, and more than half of the ones that answered failed a second request. [What's actually out there →](docs/free-proxies-in-numbers.md)
32
+
31
33
  <picture>
32
34
  <source media="(prefers-color-scheme: dark)" srcset="docs/checks-dark.svg">
33
35
  <source media="(prefers-color-scheme: light)" srcset="docs/checks-light.svg">
@@ -107,7 +109,7 @@ proxy-scraper --completion fish > ~/.config/fish/completions/proxy-scraper.fish
107
109
  proxy-scraper --completion powershell | Out-String | Invoke-Expression # in $PROFILE
108
110
  ```
109
111
 
110
- 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.
112
+ 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. After the second run that finds proxies it asks once for a GitHub star; `PROXY_SCRAPER_NO_STAR_HINT=1` turns that off (it never shows in CI).
111
113
 
112
114
  </details>
113
115
 
@@ -877,8 +879,9 @@ Only for things that don't matter. Public proxies are run by strangers who can r
877
879
 
878
880
  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.
879
881
 
880
- - [ ] [Measure throughput, not just latency](../../issues/140)
881
- - [ ] [Daily snapshots as a dataset](../../issues/143)
882
+ - [ ] [Mirror the daily snapshots to a Hugging Face dataset](../../issues/188)
883
+
884
+ Shipped in v1.10 to v1.21: download speed per proxy and "best first" everywhere, daily snapshots, a rotating proxy URL for requests, httpx and Playwright, `--pick` for working proxies without a scan, a proxy pool API compatible with jhao104/proxy_pool, your own lists with `--source`, a GitHub Action, a Telegram bot and an Atom feed for the weekly report.
882
885
 
883
886
  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.
884
887
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proxy-scraper-cli
3
- Version: 1.21.0
3
+ Version: 1.22.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
@@ -69,6 +69,8 @@ Dynamic: license-file
69
69
 
70
70
  Most free proxy lists are 95 % dead, and a good part of the rest are honeypots or proxies that inject scripts into your pages. **proxy-scraper** collects public HTTP, SOCKS4 and SOCKS5 proxies from 700+ sources and keeps only the ones that pass every check. It learns with every run which sources are worth it, and it can turn the result into a single rotating proxy.
71
71
 
72
+ **In numbers:** in one day it checked 3.6 million free proxies – 1.1 % worked, and more than half of the ones that answered failed a second request. [What's actually out there →](docs/free-proxies-in-numbers.md)
73
+
72
74
  <picture>
73
75
  <source media="(prefers-color-scheme: dark)" srcset="docs/checks-dark.svg">
74
76
  <source media="(prefers-color-scheme: light)" srcset="docs/checks-light.svg">
@@ -148,7 +150,7 @@ proxy-scraper --completion fish > ~/.config/fish/completions/proxy-scraper.fish
148
150
  proxy-scraper --completion powershell | Out-String | Invoke-Expression # in $PROFILE
149
151
  ```
150
152
 
151
- 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.
153
+ 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. After the second run that finds proxies it asks once for a GitHub star; `PROXY_SCRAPER_NO_STAR_HINT=1` turns that off (it never shows in CI).
152
154
 
153
155
  </details>
154
156
 
@@ -918,8 +920,9 @@ Only for things that don't matter. Public proxies are run by strangers who can r
918
920
 
919
921
  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.
920
922
 
921
- - [ ] [Measure throughput, not just latency](../../issues/140)
922
- - [ ] [Daily snapshots as a dataset](../../issues/143)
923
+ - [ ] [Mirror the daily snapshots to a Hugging Face dataset](../../issues/188)
924
+
925
+ Shipped in v1.10 to v1.21: download speed per proxy and "best first" everywhere, daily snapshots, a rotating proxy URL for requests, httpx and Playwright, `--pick` for working proxies without a scan, a proxy pool API compatible with jhao104/proxy_pool, your own lists with `--source`, a GitHub Action, a Telegram bot and an Atom feed for the weekly report.
923
926
 
924
927
  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.
925
928
 
@@ -97,6 +97,7 @@ tests/test_parsing_fuzz.py
97
97
  tests/test_paths.py
98
98
  tests/test_pick.py
99
99
  tests/test_pipeline.py
100
+ tests/test_preferences.py
100
101
  tests/test_proxy_pages.py
101
102
  tests/test_proxy_sources.py
102
103
  tests/test_publish.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.21.0"
4
+ __version__ = "1.22.0"
5
5
 
6
6
 
7
7
  def __getattr__(name):
@@ -41,6 +41,7 @@ from .judges import JudgeProbe, JudgeWatch, rank_judges
41
41
  from .netio import INSECURE_HOSTS, http_get
42
42
  from .options import STDOUT, RunOptions
43
43
  from .output import ResultWriter, latest_results
44
+ from .pages import REPO_URL
44
45
  from .parsing import PROXY_TYPES, parse_keys, split_key
45
46
  from .paths import is_checkout
46
47
  from .pipeline import (
@@ -53,6 +54,7 @@ from .pipeline import (
53
54
  run_checks,
54
55
  scrape,
55
56
  )
57
+ from .preferences import star_hint_due
56
58
  from .publish import RAW_BASE
57
59
  from .server import ProxyPool, RotatingServer
58
60
  from .targets import Target, parse_target
@@ -591,6 +593,9 @@ class Run:
591
593
  note(f"{fmt(stats.blocklisted)} of {fmt(len(run.results))} hits ({share}) "
592
594
  "exit from an IP on the SpamCop blocklist – sites that use it show captchas or block them. "
593
595
  "Only the others: --no-blocklisted", MUTED, "ℹ")
596
+ if kept and opts.output != STDOUT and widgets.console.is_terminal and star_hint_due():
597
+ note("Found what you needed? A star on GitHub is how others find this: "
598
+ f"[link={REPO_URL}]{REPO_URL}[/link] [{MUTED}](shown once)[/]", ACCENT, "★")
594
599
  if run.reached_goal:
595
600
  note(f"Goal of {fmt(opts.want)} hits reached – stopped early.", GOOD, "✔")
596
601
  elif run.interrupted:
@@ -0,0 +1,59 @@
1
+ """Remembers the last choice from the setup wizard (as command line arguments), and whether the
2
+ one-time star hint was shown."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ import os
8
+ from typing import List, Mapping, Optional
9
+
10
+ from .paths import DATA_DIR, atomic_write
11
+
12
+ PREFERENCES_FILE = DATA_DIR / "preferences.json"
13
+ STAR_HINT_AFTER = 2 # runs with hits before the hint – the first run is for trying it out
14
+
15
+
16
+ def _load() -> dict:
17
+ try:
18
+ data = json.loads(PREFERENCES_FILE.read_text(encoding="utf-8"))
19
+ except (OSError, ValueError):
20
+ return {}
21
+ return data if isinstance(data, dict) else {}
22
+
23
+
24
+ def _save(data: dict) -> None:
25
+ atomic_write(PREFERENCES_FILE, json.dumps(data, indent=1))
26
+
27
+
28
+ def load_last_argv() -> Optional[List[str]]:
29
+ argv = _load().get("last")
30
+ if isinstance(argv, list) and all(isinstance(a, str) for a in argv):
31
+ return argv
32
+ return None
33
+
34
+
35
+ def save_last_argv(argv: List[str]) -> None:
36
+ data = _load()
37
+ data["last"] = argv
38
+ _save(data)
39
+
40
+
41
+ def star_hint_due(env: Mapping[str, str] = os.environ) -> bool:
42
+ """Counts a run that found proxies; True exactly once, on run STAR_HINT_AFTER.
43
+ Never in CI or with PROXY_SCRAPER_NO_STAR_HINT set."""
44
+ if env.get("PROXY_SCRAPER_NO_STAR_HINT") or env.get("CI"):
45
+ return False
46
+ data = _load()
47
+ if data.get("star_hint_shown"):
48
+ return False
49
+ runs = data.get("runs_with_hits")
50
+ runs = (runs if isinstance(runs, int) else 0) + 1
51
+ data["runs_with_hits"] = runs
52
+ due = runs >= STAR_HINT_AFTER
53
+ if due:
54
+ data["star_hint_shown"] = True
55
+ try:
56
+ _save(data)
57
+ except OSError:
58
+ return False # can't remember it – better never than every time
59
+ return due
@@ -288,19 +288,26 @@
288
288
  .stable-badge { color: var(--signal-ink); font-weight: 500; }
289
289
  .sites { display: inline-flex; gap: 4px; }
290
290
  .site { font-size: 12px; padding: 2px 7px; border-radius: 99px; border: 1px solid var(--line); color: var(--text); white-space: nowrap; }
291
+ .site.more { color: var(--muted); }
291
292
  .th-plain { display: block; padding: 16px 18px; }
292
293
  .uptime { display: inline-flex; align-items: center; gap: 8px; font-variant-numeric: tabular-nums; }
293
294
  .meter { width: 32px; height: 4px; border-radius: 2px; background: var(--line); overflow: hidden; }
294
295
  .meter::after { content: ""; display: block; height: 100%; width: var(--v); background: var(--signal-ink); border-radius: inherit; }
295
- .provider { max-width: 240px; overflow: hidden; text-overflow: ellipsis; color: var(--muted); }
296
+ .provider { max-width: 160px; overflow: hidden; text-overflow: ellipsis; color: var(--muted); }
296
297
  .icon-btn { width: 34px; height: 34px; border-radius: 10px; border: 0; background: transparent; color: var(--muted); cursor: pointer; display: inline-grid; place-items: center;
297
298
  transition: background-color .25s, color .25s; }
298
299
  .icon-btn:hover { background: var(--line); color: var(--text); }
299
300
  .icon-btn svg { width: 16px; height: 16px; }
300
301
  .empty { padding: 64px 24px; text-align: center; color: var(--muted); }
302
+ /* The table fits the page from phone to desktop instead of hiding columns behind a sideways scroll */
303
+ @media (max-width: 1300px) { td { padding: 13px 12px; } .sort, .th-plain { padding: 16px 12px; } }
304
+ @media (max-width: 1100px) { th:nth-child(9), td:nth-child(9) { display: none; } .addr-btn svg { display: none; } }
305
+ @media (max-width: 960px) { th:nth-child(6), td:nth-child(6), th:nth-child(8), td:nth-child(8) { display: none; } }
306
+ @media (max-width: 800px) { th:nth-child(5), td:nth-child(5) { display: none; } }
301
307
  .list-foot { display: flex; justify-content: space-between; align-items: center; gap: 16px; flex-wrap: wrap; margin-top: 20px; }
302
308
  .list-foot .count { color: var(--muted); font-size: 15px; }
303
309
  .list-foot .actions { display: flex; gap: 10px; flex-wrap: wrap; }
310
+ .list-foot .fmt { height: 40px; }
304
311
 
305
312
  /* Route: how a proxy gets on the list */
306
313
  .route { height: 460vh; position: relative; }
@@ -474,12 +481,14 @@
474
481
  .route-svg .gate-label { font-size: 19px; }
475
482
  .filters { position: static; }
476
483
  .t-type .name, th .name { display: none; }
477
- .addr-btn { font-size: 13.5px; }
484
+ table { font-size: 13.5px; }
485
+ .addr-btn { font-size: 12.5px; gap: 0; }
478
486
  .addr-btn svg { display: none; }
479
- .sort { padding: 14px 9px; }
480
- th:first-child .sort { padding-left: 16px; }
481
- td { padding-inline: 9px; }
482
- td:first-child { padding-left: 16px; }
487
+ .detail { margin-left: 0; width: 22px; }
488
+ .sort { padding: 14px 6px; }
489
+ th:first-child .sort { padding-left: 14px; }
490
+ td { padding-inline: 6px; }
491
+ td:first-child { padding-left: 14px; }
483
492
  .steps { min-height: 17em; }
484
493
  .gh .label, .gh .star-count { display: none; }
485
494
  .gh { padding: 0; width: 36px; justify-content: center; }
@@ -490,6 +499,7 @@
490
499
  .hide-sm { display: none; }
491
500
  .btn { height: 48px; padding: 0 20px; }
492
501
  }
502
+ @media (max-width: 380px) { .addr-btn { font-size: 11.5px; } td { padding-inline: 4px; } }
493
503
  @media (prefers-reduced-motion: reduce) {
494
504
  html.js .transcript.pending p { opacity: 1; transform: none; }
495
505
  *, *::before, *::after { animation: none !important; transition: none !important; }
@@ -640,6 +650,12 @@
640
650
  <button class="btn small" id="best" type="button" hidden title="The ones that answer quickly and download fast">Best first</button>
641
651
  <button class="btn small" id="more" type="button" hidden>Show more</button>
642
652
  <button class="btn small" id="share" type="button" title="The filters are in the address, so the link opens this exact view">Share view</button>
653
+ <select class="field fmt" id="fmt" aria-label="Format for copy and download" title="Format for copy and download">
654
+ <option value="url">type://ip:port</option>
655
+ <option value="plain">ip:port</option>
656
+ <option value="json">JSON</option>
657
+ <option value="csv">CSV</option>
658
+ </select>
643
659
  <button class="btn small" id="copy-all" type="button">Copy filtered</button>
644
660
  <button class="btn small primary" id="download" type="button">Download filtered</button>
645
661
  </div>
@@ -867,7 +883,10 @@ proxy-scraper --want 50 --https-only</pre><button class="icon-btn" type="button"
867
883
  const SITE_NAMES = { google: "Google", reddit: "Reddit", amazon: "Amazon", instagram: "Instagram", tiktok: "TikTok" };
868
884
  const through = (r) => {
869
885
  const ok = Object.keys(SITE_NAMES).filter(k => (r.sites || {})[k] === true);
870
- return ok.length ? `<span class="sites">${ok.map(k => `<span class="site">${SITE_NAMES[k]}</span>`).join("")}</span>` : '<span class="quiet">–</span>';
886
+ if (!ok.length) return '<span class="quiet">–</span>';
887
+ // two chips and a count keep the column one line high; the title lists them all
888
+ const names = ok.map(k => SITE_NAMES[k]), more = names.length - 2;
889
+ return `<span class="sites" title="Gets through to ${names.join(", ")}">${names.slice(0, 2).map(n => `<span class="site">${n}</span>`).join("")}${more > 0 ? `<span class="site more">+${more}</span>` : ""}</span>`;
871
890
  };
872
891
  const speed = (k) => !k ? '<span class="quiet">–</span>'
873
892
  : `<span class="${k >= 500 ? "fast" : k >= 100 ? "mid" : "slow"}">${k >= 1024 ? (k / 1024).toFixed(1) + " MB/s" : fmt(k) + " KB/s"}</span>`;
@@ -905,9 +924,23 @@ proxy-scraper --want 50 --https-only</pre><button class="icon-btn" type="button"
905
924
  const toastEl = $("#toast");
906
925
  function toast(msg) { toastEl.textContent = msg; toastEl.classList.add("show"); clearTimeout(toastEl._t); toastEl._t = setTimeout(() => toastEl.classList.remove("show"), 1800); }
907
926
  async function copy(text, msg) { try { await navigator.clipboard.writeText(text); toast(msg); } catch (e) { toast("Copying isn't allowed here. Select the text instead."); } }
908
- function download(rows, name) {
909
- const blob = new Blob([rows.map(r => r.url).join("\n") + "\n"], { type: "text/plain" });
910
- const a = Object.assign(document.createElement("a"), { href: URL.createObjectURL(blob), download: name });
927
+ // Copy and download in the format the viewer picked – plain ip:port is what many tools want
928
+ const CSV_COLS = ["proxy", "ptype", "country", "latency", "speed_kbps", "https", "anonymity", "uptime_7d", "org", "hosting", "blocklisted"];
929
+ const csvCell = (v) => v == null ? "" : /[",\n]/.test(String(v)) ? `"${String(v).replace(/"/g, '""')}"` : String(v);
930
+ const FORMATS = {
931
+ url: { ext: "txt", type: "text/plain", text: (rows) => rows.map(r => r.url).join("\n") + "\n" },
932
+ plain: { ext: "txt", type: "text/plain", text: (rows) => rows.map(r => r.proxy).join("\n") + "\n" },
933
+ json: { ext: "json", type: "application/json", text: (rows) => JSON.stringify(rows.map(({ rank, ...r }) => r), null, 1) + "\n" },
934
+ csv: { ext: "csv", type: "text/csv", text: (rows) => [CSV_COLS.join(","), ...rows.map(r => CSV_COLS.map(c => csvCell(r[c])).join(","))].join("\n") + "\n" },
935
+ };
936
+ const fmtSelect = $("#fmt");
937
+ try { const f = localStorage.getItem("format"); if (FORMATS[f]) fmtSelect.value = f; } catch (e) {}
938
+ fmtSelect.onchange = () => { try { localStorage.setItem("format", fmtSelect.value); } catch (e) {} };
939
+ const format = () => FORMATS[fmtSelect.value] || FORMATS.url;
940
+ function download(rows, base) {
941
+ const f = format();
942
+ const blob = new Blob([f.text(rows)], { type: f.type });
943
+ const a = Object.assign(document.createElement("a"), { href: URL.createObjectURL(blob), download: `${base}.${f.ext}` });
911
944
  document.body.append(a); a.click(); a.remove(); setTimeout(() => URL.revokeObjectURL(a.href), 1000);
912
945
  }
913
946
  function ago(iso) {
@@ -1124,9 +1157,9 @@ proxy-scraper --want 50 --https-only</pre><button class="icon-btn" type="button"
1124
1157
  th.querySelector(".sort").onclick = () => { const k = th.dataset.key; state.dir = state.sort === k ? -state.dir : 1; state.sort = k; morph(render); };
1125
1158
  });
1126
1159
  $("#rows").onclick = (e) => { const b = e.target.closest(".addr-btn"); if (b) copy(b.dataset.url, `Copied ${b.dataset.url}`); };
1127
- $("#copy-all").onclick = () => { const rows = filtered(); copy(rows.map(r => r.url).join("\n") + "\n", `Copied ${fmt(rows.length)} proxies`); };
1128
- $("#download").onclick = () => download(filtered(), "proxies.txt");
1129
- $("#dl-all").onclick = () => state.rows.length ? download(state.rows, "all.txt") : toast("The list is still loading");
1160
+ $("#copy-all").onclick = () => { const rows = filtered(); copy(format().text(rows), `Copied ${fmt(rows.length)} proxies`); };
1161
+ $("#download").onclick = () => download(filtered(), "proxies");
1162
+ $("#dl-all").onclick = () => state.rows.length ? download(state.rows, "all") : toast("The list is still loading");
1130
1163
  // the transcript plays once when it comes into view
1131
1164
  const transcript = $("#transcript");
1132
1165
  if (calm || !("IntersectionObserver" in window)) transcript.classList.remove("pending");
@@ -0,0 +1,37 @@
1
+ import json
2
+
3
+ import pytest
4
+
5
+ from proxyscraper import preferences
6
+
7
+
8
+ @pytest.fixture(autouse=True)
9
+ def prefs_file(tmp_path, monkeypatch):
10
+ path = tmp_path / "preferences.json"
11
+ monkeypatch.setattr(preferences, "PREFERENCES_FILE", path)
12
+ return path
13
+
14
+
15
+ def test_star_hint_once_on_the_second_run_with_hits():
16
+ assert [preferences.star_hint_due(env={}) for _ in range(4)] == [False, True, False, False]
17
+
18
+
19
+ @pytest.mark.parametrize("env", [{"CI": "true"}, {"PROXY_SCRAPER_NO_STAR_HINT": "1"}])
20
+ def test_star_hint_never_in_ci_or_when_turned_off(env, prefs_file):
21
+ assert not any(preferences.star_hint_due(env=env) for _ in range(3))
22
+ assert not prefs_file.exists()
23
+
24
+
25
+ def test_star_hint_and_last_argv_share_the_file(prefs_file):
26
+ preferences.save_last_argv(["--want", "20"])
27
+ preferences.star_hint_due(env={})
28
+ preferences.save_last_argv(["--want", "30"])
29
+ assert preferences.load_last_argv() == ["--want", "30"]
30
+ assert json.loads(prefs_file.read_text())["runs_with_hits"] == 1
31
+
32
+
33
+ def test_broken_file_starts_over(prefs_file):
34
+ prefs_file.write_text("[1, 2")
35
+ assert preferences.load_last_argv() is None
36
+ assert preferences.star_hint_due(env={}) is False
37
+ assert preferences.star_hint_due(env={}) is True
@@ -1,24 +0,0 @@
1
- """Remembers the last choice from the setup wizard (as command line arguments)."""
2
-
3
- from __future__ import annotations
4
-
5
- import json
6
- from typing import List, Optional
7
-
8
- from .paths import DATA_DIR, atomic_write
9
-
10
- PREFERENCES_FILE = DATA_DIR / "preferences.json"
11
-
12
-
13
- def load_last_argv() -> Optional[List[str]]:
14
- try:
15
- argv = json.loads(PREFERENCES_FILE.read_text(encoding="utf-8")).get("last")
16
- except (OSError, ValueError, AttributeError):
17
- return None
18
- if isinstance(argv, list) and all(isinstance(a, str) for a in argv):
19
- return argv
20
- return None
21
-
22
-
23
- def save_last_argv(argv: List[str]) -> None:
24
- atomic_write(PREFERENCES_FILE, json.dumps({"last": argv}, indent=1))