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.
Files changed (123) hide show
  1. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/PKG-INFO +60 -3
  2. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/README.md +59 -2
  3. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxy_scraper_cli.egg-info/PKG-INFO +60 -3
  4. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxy_scraper_cli.egg-info/SOURCES.txt +3 -0
  5. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/__init__.py +1 -1
  6. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/app.py +5 -3
  7. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/cli.py +6 -0
  8. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/options.py +9 -0
  9. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/pipeline.py +22 -8
  10. proxy_scraper_cli-1.21.0/proxyscraper/server/api.py +186 -0
  11. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/server/core.py +28 -7
  12. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/server/pool.py +9 -0
  13. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/sources.py +24 -0
  14. proxy_scraper_cli-1.21.0/tests/test_own_sources.py +98 -0
  15. proxy_scraper_cli-1.21.0/tests/test_serve_api.py +153 -0
  16. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_server.py +2 -1
  17. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/LICENSE +0 -0
  18. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxy_scraper_cli.egg-info/dependency_links.txt +0 -0
  19. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxy_scraper_cli.egg-info/entry_points.txt +0 -0
  20. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxy_scraper_cli.egg-info/requires.txt +0 -0
  21. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxy_scraper_cli.egg-info/top_level.txt +0 -0
  22. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/__main__.py +0 -0
  23. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/action.py +0 -0
  24. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/agent.py +0 -0
  25. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/api.py +0 -0
  26. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/asndb.py +0 -0
  27. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/blocklist.py +0 -0
  28. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/charts.py +0 -0
  29. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/checker.py +0 -0
  30. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/compat.py +0 -0
  31. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/completion.py +0 -0
  32. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/exporters.py +0 -0
  33. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/fetchcache.py +0 -0
  34. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/geo.py +0 -0
  35. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/geodb.py +0 -0
  36. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/handshake.py +0 -0
  37. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/history.py +0 -0
  38. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/judges.py +0 -0
  39. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/mcp_entry.py +0 -0
  40. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/mcp_server.py +0 -0
  41. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/netio.py +0 -0
  42. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/output.py +0 -0
  43. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/pages.py +0 -0
  44. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/parsing.py +0 -0
  45. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/paths.py +0 -0
  46. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/preferences.py +0 -0
  47. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/proxypages.py +0 -0
  48. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/publish.py +0 -0
  49. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/ranking.py +0 -0
  50. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/report.py +0 -0
  51. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/runsteps.py +0 -0
  52. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/server/__init__.py +0 -0
  53. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/server/http.py +0 -0
  54. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/server/socks.py +0 -0
  55. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/server/status.py +0 -0
  56. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/server/upstream.py +0 -0
  57. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/site/apple-touch-icon.png +0 -0
  58. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/site/googleaac1161b7853c5b5.html +0 -0
  59. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/site/index.html +0 -0
  60. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/site/logo.png +0 -0
  61. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/site/og.png +0 -0
  62. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/sites.py +0 -0
  63. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/sources.json +0 -0
  64. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/speed.py +0 -0
  65. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/targets.py +0 -0
  66. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/ui/__init__.py +0 -0
  67. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/ui/dashboard.py +0 -0
  68. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/ui/keys.py +0 -0
  69. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/ui/report.py +0 -0
  70. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/ui/serve.py +0 -0
  71. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/ui/widgets.py +0 -0
  72. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/proxyscraper/ui/wizard.py +0 -0
  73. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/pyproject.toml +0 -0
  74. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/setup.cfg +0 -0
  75. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_action.py +0 -0
  76. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_agent.py +0 -0
  77. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_api.py +0 -0
  78. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_app.py +0 -0
  79. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_asndb.py +0 -0
  80. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_auth.py +0 -0
  81. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_blocklist.py +0 -0
  82. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_charts.py +0 -0
  83. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_checker.py +0 -0
  84. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_compat.py +0 -0
  85. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_completion.py +0 -0
  86. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_discovery_growth.py +0 -0
  87. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_exporters.py +0 -0
  88. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_fetchcache.py +0 -0
  89. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_geodb.py +0 -0
  90. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_judges.py +0 -0
  91. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_learning.py +0 -0
  92. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_live_api.py +0 -0
  93. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_mcp_server.py +0 -0
  94. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_mine_sources.py +0 -0
  95. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_options.py +0 -0
  96. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_output_stdout.py +0 -0
  97. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_packaging.py +0 -0
  98. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_pages.py +0 -0
  99. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_parsing.py +0 -0
  100. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_parsing_fuzz.py +0 -0
  101. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_paths.py +0 -0
  102. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_pick.py +0 -0
  103. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_pipeline.py +0 -0
  104. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_proxy_pages.py +0 -0
  105. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_proxy_sources.py +0 -0
  106. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_publish.py +0 -0
  107. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_report.py +0 -0
  108. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_robustness.py +0 -0
  109. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_rotator.py +0 -0
  110. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_serve_password.py +0 -0
  111. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_serve_refill.py +0 -0
  112. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_serve_v2.py +0 -0
  113. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_server_closed_writer.py +0 -0
  114. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_server_fixes.py +0 -0
  115. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_sites.py +0 -0
  116. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_sources_json.py +0 -0
  117. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_speed.py +0 -0
  118. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_speed_ranking.py +0 -0
  119. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_tamper.py +0 -0
  120. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_targets.py +0 -0
  121. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_ui.py +0 -0
  122. {proxy_scraper_cli-1.20.0 → proxy_scraper_cli-1.21.0}/tests/test_uptime.py +0 -0
  123. {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.20.0
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 proxies](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fmaximilianfeix%2Fproxy-scraper%2Fproxy-list%2Fbadges%2Ftotal.json&style=flat-square&labelColor=121113)](#live-list)
53
53
  [![PyPI](https://img.shields.io/pypi/v/proxy-scraper-cli?style=flat-square&color=D4F77A&labelColor=121113&label=pypi)](https://pypi.org/project/proxy-scraper-cli/)
54
54
  [![Python](https://img.shields.io/badge/python-3.9–3.13-D4F77A?style=flat-square&labelColor=121113)](pyproject.toml)
55
+ [![Stars](https://img.shields.io/github/stars/maximilianfeix/proxy-scraper?style=flat-square&color=D4F77A&labelColor=121113)](https://github.com/maximilianfeix/proxy-scraper/stargazers)
55
56
  [![License](https://img.shields.io/badge/license-MIT-D4F77A?style=flat-square&labelColor=121113)](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 proxies](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fmaximilianfeix%2Fproxy-scraper%2Fproxy-list%2Fbadges%2Ftotal.json&style=flat-square&labelColor=121113)](#live-list)
12
12
  [![PyPI](https://img.shields.io/pypi/v/proxy-scraper-cli?style=flat-square&color=D4F77A&labelColor=121113&label=pypi)](https://pypi.org/project/proxy-scraper-cli/)
13
13
  [![Python](https://img.shields.io/badge/python-3.9–3.13-D4F77A?style=flat-square&labelColor=121113)](pyproject.toml)
14
+ [![Stars](https://img.shields.io/github/stars/maximilianfeix/proxy-scraper?style=flat-square&color=D4F77A&labelColor=121113)](https://github.com/maximilianfeix/proxy-scraper/stargazers)
14
15
  [![License](https://img.shields.io/badge/license-MIT-D4F77A?style=flat-square&labelColor=121113)](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.20.0
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 proxies](https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fmaximilianfeix%2Fproxy-scraper%2Fproxy-list%2Fbadges%2Ftotal.json&style=flat-square&labelColor=121113)](#live-list)
53
53
  [![PyPI](https://img.shields.io/pypi/v/proxy-scraper-cli?style=flat-square&color=D4F77A&labelColor=121113&label=pypi)](https://pypi.org/project/proxy-scraper-cli/)
54
54
  [![Python](https://img.shields.io/badge/python-3.9–3.13-D4F77A?style=flat-square&labelColor=121113)](pyproject.toml)
55
+ [![Stars](https://img.shields.io/github/stars/maximilianfeix/proxy-scraper?style=flat-square&color=D4F77A&labelColor=121113)](https://github.com/maximilianfeix/proxy-scraper/stargazers)
55
56
  [![License](https://img.shields.io/badge/license-MIT-D4F77A?style=flat-square&labelColor=121113)](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
@@ -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.20.0"
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.n_curated)} curated · {fmt(plan.n_meta)} from {plan.meta_ok}/{plan.meta_total} "
331
- f"meta lists · {fmt(plan.n_discovered)} discovered)", MUTED),
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 the two is set."""
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
- cache.store(url, headers, keys, sources[url])
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) -> List[str]:
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]