proxyherd 1.0.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.
@@ -0,0 +1,17 @@
1
+ # The MIT License (MIT)
2
+
3
+ Copyright (c) 2016-present Fabien Vauchelles
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software
6
+ and associated documentation files (the "Software"), to deal in the Software without restriction,
7
+ including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
8
+ and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
9
+ subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
14
+ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
15
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
16
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
17
+ THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,4 @@
1
+ include README.md
2
+ include MANIFEST.in
3
+ recursive-include proxyherd *
4
+ recursive-include scrapoxy *
@@ -0,0 +1,40 @@
1
+ Metadata-Version: 2.4
2
+ Name: proxyherd
3
+ Version: 1.0.0
4
+ Summary: Python API and Scrapy middlewares for ProxyHerd, the open-source super proxy manager
5
+ Home-page: https://github.com/proxyherd/proxyherd
6
+ Author: ProxyHerd maintainers
7
+ License: MIT
8
+ Keywords: crawler,crawling,proxyherd,scrapoxy,scrapy,scraper,scraping,webscraping,proxy
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Framework :: Scrapy
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENCE
14
+ Dynamic: author
15
+ Dynamic: classifier
16
+ Dynamic: description
17
+ Dynamic: description-content-type
18
+ Dynamic: home-page
19
+ Dynamic: keywords
20
+ Dynamic: license
21
+ Dynamic: license-file
22
+ Dynamic: summary
23
+
24
+ # Python API for ProxyHerd
25
+
26
+ Scrapy middlewares and a small REST client for [ProxyHerd](https://proxyherd.github.io/proxyherd/),
27
+ the open-source super proxy manager.
28
+
29
+ ```shell
30
+ pip install proxyherd
31
+ ```
32
+
33
+ Documentation: https://proxyherd.github.io/proxyherd/integration/python/scrapy/guide
34
+
35
+ ## Migrating from the `scrapoxy` package
36
+
37
+ `proxyherd` is the continuation of the `scrapoxy` Python package (community fork of Scrapoxy 4.17.4).
38
+ The module `proxyherd` exposes the same middlewares; the settings are now prefixed `PROXYHERD_`
39
+ (`PROXYHERD_MASTER`, `PROXYHERD_API`, `PROXYHERD_USERNAME`, `PROXYHERD_PASSWORD`, ...).
40
+ The old `SCRAPOXY_` settings and the `scrapoxy` module keep working as deprecated aliases.
@@ -0,0 +1,17 @@
1
+ # Python API for ProxyHerd
2
+
3
+ Scrapy middlewares and a small REST client for [ProxyHerd](https://proxyherd.github.io/proxyherd/),
4
+ the open-source super proxy manager.
5
+
6
+ ```shell
7
+ pip install proxyherd
8
+ ```
9
+
10
+ Documentation: https://proxyherd.github.io/proxyherd/integration/python/scrapy/guide
11
+
12
+ ## Migrating from the `scrapoxy` package
13
+
14
+ `proxyherd` is the continuation of the `scrapoxy` Python package (community fork of Scrapoxy 4.17.4).
15
+ The module `proxyherd` exposes the same middlewares; the settings are now prefixed `PROXYHERD_`
16
+ (`PROXYHERD_MASTER`, `PROXYHERD_API`, `PROXYHERD_USERNAME`, `PROXYHERD_PASSWORD`, ...).
17
+ The old `SCRAPOXY_` settings and the `scrapoxy` module keep working as deprecated aliases.
@@ -0,0 +1,5 @@
1
+ from proxyherd.api import ProxyHerdApi
2
+ from proxyherd.blacklist import BlacklistDownloaderMiddleware
3
+ from proxyherd.proxy import ProxyDownloaderMiddleware
4
+ from proxyherd.scale import ScaleSpiderMiddleware
5
+ from proxyherd.sticky import StickySpiderMiddleware
@@ -0,0 +1,72 @@
1
+ from base64 import b64encode
2
+ from urllib.request import Request, urlopen
3
+ from typing import TypedDict, List
4
+
5
+ import json
6
+
7
+
8
+ def is_proxy_online(proxy):
9
+ return (
10
+ proxy['status'] == 'STARTED' and
11
+ proxy['fingerprint'] and
12
+ not proxy['removing']
13
+ )
14
+
15
+
16
+ class IProxyToRemove(TypedDict):
17
+ id: str
18
+ force: bool
19
+
20
+
21
+ class ProxyHerdApi:
22
+ def __init__(self, url: str, username: str, password: str):
23
+ self._url = f"{url}/scraper"
24
+ self._token = b64encode(f"{username}:{password}".encode("utf-8")).decode("utf-8")
25
+
26
+ def get_project(self):
27
+ req = Request(
28
+ url=f"{self._url}/project",
29
+ headers={
30
+ "Authorization": f"Basic {self._token}"
31
+ }
32
+ )
33
+ contents = urlopen(req).read()
34
+ return json.loads(contents)
35
+
36
+ def set_project_status(self, status: str):
37
+ payload = {"status": status}
38
+ req = Request(
39
+ url = f"{self._url}/project/status",
40
+ method="POST",
41
+ headers={
42
+ "Authorization": f"Basic {self._token}",
43
+ "Content-Type": "application/json"
44
+ },
45
+ data=json.dumps(payload).encode("utf-8")
46
+ )
47
+ urlopen(req).read()
48
+
49
+ def get_all_project_connectors_and_proxies(self):
50
+ req = Request(
51
+ url=f"{self._url}/project/connectors",
52
+ headers={
53
+ "Authorization": f"Basic {self._token}"
54
+ }
55
+ )
56
+ contents = urlopen(req).read()
57
+ return json.loads(contents)
58
+
59
+ def ask_proxies_to_remove(self, proxies_ids: List[IProxyToRemove]):
60
+ req = Request(
61
+ url=f"{self._url}/project/proxies/remove",
62
+ method="POST",
63
+ headers={
64
+ "Authorization": f"Basic {self._token}",
65
+ "Content-Type": "application/json"
66
+ },
67
+ data=json.dumps(proxies_ids).encode("utf-8")
68
+ )
69
+ urlopen(req).read()
70
+
71
+ # Backward-compatible alias (the package was named scrapoxy before 1.0.0)
72
+ ScrapoxyApi = ProxyHerdApi
@@ -0,0 +1,76 @@
1
+ from random import randrange
2
+ from scrapy.exceptions import IgnoreRequest
3
+ from proxyherd.api import ProxyHerdApi, is_proxy_online
4
+ from time import sleep
5
+
6
+
7
+ class BlacklistError(Exception):
8
+ def __init__(self, response, message, *args, **kwargs):
9
+ super(BlacklistError, self).__init__(*args, **kwargs)
10
+
11
+ self.response = response
12
+ self.message = message
13
+
14
+ def __str__(self):
15
+ return self.message
16
+
17
+
18
+ class BlacklistDownloaderMiddleware(object):
19
+
20
+ @classmethod
21
+ def from_crawler(cls, crawler):
22
+ return cls(crawler)
23
+
24
+ def __init__(self, crawler):
25
+ """Access the settings of the crawler to connect to ProxyHerd.
26
+ """
27
+ api = crawler.settings.get("PROXYHERD_API", crawler.settings.get("SCRAPOXY_API"))
28
+ assert api, "PROXYHERD_API is required"
29
+
30
+ username = crawler.settings.get("PROXYHERD_USERNAME", crawler.settings.get("SCRAPOXY_USERNAME"))
31
+ assert username, "PROXYHERD_USERNAME is required"
32
+
33
+ password = crawler.settings.get("PROXYHERD_PASSWORD", crawler.settings.get("SCRAPOXY_PASSWORD"))
34
+ assert password, "PROXYHERD_PASSWORD is required"
35
+
36
+ self._api = ProxyHerdApi(api, username, password)
37
+
38
+ self._http_status_codes = crawler.settings.get("PROXYHERD_BLACKLIST_HTTP_STATUS_CODES", crawler.settings.get("SCRAPOXY_BLACKLIST_HTTP_STATUS_CODES", [429, 503]))
39
+ self._force = crawler.settings.get("PROXYHERD_BLACKLIST_FORCE", crawler.settings.get("SCRAPOXY_BLACKLIST_FORCE", True))
40
+ self._sleep_min = crawler.settings.get("PROXYHERD_SLEEP_MIN", crawler.settings.get("SCRAPOXY_SLEEP_MIN", 60))
41
+ self._sleep_max = crawler.settings.get("PROXYHERD_SLEEP_MAX", crawler.settings.get("SCRAPOXY_SLEEP_MAX", 180))
42
+
43
+ def process_response(self, request, response, spider):
44
+ if response.status not in self._http_status_codes:
45
+ return response
46
+
47
+ spider.logger.info("Ignoring Blacklisted response %s: HTTP status %d" % (response.url, response.status))
48
+
49
+ id_raw = response.headers.get("x-scrapoxy-proxyname")
50
+ if not id_raw:
51
+ raise BlacklistError(response, "No header 'X-Scrapoxy-Proxyname' name in response headers. MITM must be enabled")
52
+
53
+ id = id_raw.decode("utf-8")
54
+
55
+ alive_count = 0
56
+ views = self._api.get_all_project_connectors_and_proxies()
57
+ for view in views:
58
+ for proxy in view["proxies"]:
59
+ if (proxy["id"] != id and is_proxy_online(proxy)):
60
+ alive_count += 1
61
+
62
+ spider.logger.error("Remove instance %s (remaining %d instances)." % (response.url, response.status))
63
+
64
+ self._api.ask_proxies_to_remove([
65
+ {
66
+ "id": id,
67
+ "force": self._force
68
+ }
69
+ ])
70
+
71
+ if alive_count <= 0:
72
+ delay = randrange(self._sleep_min, self._sleep_max)
73
+ spider.logger.warn("No instance remaining. Sleep for %d seconds..." % delay)
74
+ sleep(delay)
75
+
76
+ raise IgnoreRequest()
@@ -0,0 +1,40 @@
1
+ from base64 import b64encode
2
+
3
+
4
+ class ProxyDownloaderMiddleware(object):
5
+
6
+ @classmethod
7
+ def from_crawler(cls, crawler):
8
+ return cls(crawler)
9
+
10
+ def __init__(self, crawler):
11
+ master = crawler.settings.get("PROXYHERD_MASTER", crawler.settings.get("SCRAPOXY_MASTER"))
12
+ if master:
13
+ self._proxy = f"{master}/?noconnect"
14
+
15
+ username = crawler.settings.get("PROXYHERD_USERNAME", crawler.settings.get("SCRAPOXY_USERNAME"))
16
+ password = crawler.settings.get("PROXYHERD_PASSWORD", crawler.settings.get("SCRAPOXY_PASSWORD"))
17
+ if username and password:
18
+ auth_b64 = b64encode(f"{username}:{password}".encode("utf-8")).decode("utf-8")
19
+ self._proxy_auth = (username, password)
20
+ self._proxy_auth_b64 = f"Basic {auth_b64}"
21
+
22
+ def process_request(self, request, spider):
23
+ if request.meta.get("no-proxy"):
24
+ return
25
+
26
+ # Don"t overwrite with a random one (server-side state for IP)
27
+ if hasattr(self, "_proxy"):
28
+ request.meta["proxy"] = self._proxy
29
+
30
+ if hasattr(self, "_proxy_auth"):
31
+ request.headers["proxy-authorization"] = self._proxy_auth_b64
32
+
33
+ if request.meta.get("impersonate"):
34
+ impersonate_args = request.meta.get("impersonate_args", {})
35
+ impersonate_args["verify"] = False
36
+
37
+ if hasattr(self, "_proxy_auth"):
38
+ impersonate_args["proxy_auth"] = self._proxy_auth
39
+
40
+ request.meta["impersonate_args"] = impersonate_args
@@ -0,0 +1,42 @@
1
+ from scrapy import signals
2
+ from proxyherd.api import ProxyHerdApi
3
+ from time import sleep
4
+
5
+
6
+ class ScaleSpiderMiddleware(object):
7
+
8
+ @classmethod
9
+ def from_crawler(cls, crawler):
10
+ return cls(crawler)
11
+
12
+ def __init__(self, crawler):
13
+ api = crawler.settings.get("PROXYHERD_API", crawler.settings.get("SCRAPOXY_API"))
14
+ assert api, "PROXYHERD_API is required"
15
+
16
+ username = crawler.settings.get("PROXYHERD_USERNAME", crawler.settings.get("SCRAPOXY_USERNAME"))
17
+ assert username, "PROXYHERD_USERNAME is required"
18
+
19
+ password = crawler.settings.get("PROXYHERD_PASSWORD", crawler.settings.get("SCRAPOXY_PASSWORD"))
20
+ assert password, "PROXYHERD_PASSWORD is required"
21
+
22
+ self._api = ProxyHerdApi(api, username, password)
23
+
24
+ self._delay = crawler.settings.get("PROXYHERD_WAIT_FOR_SCALE", crawler.settings.get("SCRAPOXY_WAIT_FOR_SCALE", 120))
25
+
26
+ crawler.signals.connect(self.spider_opened, signals.spider_opened)
27
+ crawler.signals.connect(self.spider_closed, signals.spider_closed)
28
+
29
+
30
+ def spider_opened(self, spider):
31
+ spider.logger.info("[ScaleSpiderMiddleware] Upscale ProxyHerd")
32
+
33
+ self._api.set_project_status("HOT")
34
+
35
+ spider.logger.info("[ScaleSpiderMiddleware] Sleeping %d seconds to finish upscale" % self._delay)
36
+ sleep(self._delay)
37
+
38
+
39
+ def spider_closed(self, spider):
40
+ spider.logger.info("[ScaleSpiderMiddleware] Downscale ProxyHerd")
41
+
42
+ self._api.set_project_status("OFF")
@@ -0,0 +1,14 @@
1
+ from scrapy import Request
2
+
3
+
4
+ class StickySpiderMiddleware:
5
+ # Propagate proxyname between responses and requests to have sticky session
6
+ def process_spider_output(self, response, result, spider):
7
+ proxyname = response.headers.get("x-scrapoxy-proxyname")
8
+ if proxyname:
9
+ for r in result:
10
+ if isinstance(r, Request):
11
+ r.headers["x-scrapoxy-proxyname"] = proxyname
12
+ yield r
13
+ else:
14
+ yield from result
@@ -0,0 +1,40 @@
1
+ Metadata-Version: 2.4
2
+ Name: proxyherd
3
+ Version: 1.0.0
4
+ Summary: Python API and Scrapy middlewares for ProxyHerd, the open-source super proxy manager
5
+ Home-page: https://github.com/proxyherd/proxyherd
6
+ Author: ProxyHerd maintainers
7
+ License: MIT
8
+ Keywords: crawler,crawling,proxyherd,scrapoxy,scrapy,scraper,scraping,webscraping,proxy
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Framework :: Scrapy
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENCE
14
+ Dynamic: author
15
+ Dynamic: classifier
16
+ Dynamic: description
17
+ Dynamic: description-content-type
18
+ Dynamic: home-page
19
+ Dynamic: keywords
20
+ Dynamic: license
21
+ Dynamic: license-file
22
+ Dynamic: summary
23
+
24
+ # Python API for ProxyHerd
25
+
26
+ Scrapy middlewares and a small REST client for [ProxyHerd](https://proxyherd.github.io/proxyherd/),
27
+ the open-source super proxy manager.
28
+
29
+ ```shell
30
+ pip install proxyherd
31
+ ```
32
+
33
+ Documentation: https://proxyherd.github.io/proxyherd/integration/python/scrapy/guide
34
+
35
+ ## Migrating from the `scrapoxy` package
36
+
37
+ `proxyherd` is the continuation of the `scrapoxy` Python package (community fork of Scrapoxy 4.17.4).
38
+ The module `proxyherd` exposes the same middlewares; the settings are now prefixed `PROXYHERD_`
39
+ (`PROXYHERD_MASTER`, `PROXYHERD_API`, `PROXYHERD_USERNAME`, `PROXYHERD_PASSWORD`, ...).
40
+ The old `SCRAPOXY_` settings and the `scrapoxy` module keep working as deprecated aliases.
@@ -0,0 +1,21 @@
1
+ LICENCE
2
+ MANIFEST.in
3
+ README.md
4
+ setup.cfg
5
+ setup.py
6
+ proxyherd/__init__.py
7
+ proxyherd/api.py
8
+ proxyherd/blacklist.py
9
+ proxyherd/proxy.py
10
+ proxyherd/scale.py
11
+ proxyherd/sticky.py
12
+ proxyherd.egg-info/PKG-INFO
13
+ proxyherd.egg-info/SOURCES.txt
14
+ proxyherd.egg-info/dependency_links.txt
15
+ proxyherd.egg-info/top_level.txt
16
+ scrapoxy/__init__.py
17
+ scrapoxy/api.py
18
+ scrapoxy/blacklist.py
19
+ scrapoxy/proxy.py
20
+ scrapoxy/scale.py
21
+ scrapoxy/sticky.py
@@ -0,0 +1,2 @@
1
+ proxyherd
2
+ scrapoxy
@@ -0,0 +1,18 @@
1
+ """Compatibility shim.
2
+
3
+ The ``scrapoxy`` Python package continues as ``proxyherd`` (community fork of Scrapoxy 4.17.4).
4
+ Importing ``scrapoxy`` still works but is deprecated; import ``proxyherd`` instead.
5
+ """
6
+ import warnings
7
+
8
+ from proxyherd.api import ProxyHerdApi, ProxyHerdApi as ScrapoxyApi, is_proxy_online # noqa: F401
9
+ from proxyherd.blacklist import BlacklistDownloaderMiddleware # noqa: F401
10
+ from proxyherd.proxy import ProxyDownloaderMiddleware # noqa: F401
11
+ from proxyherd.scale import ScaleSpiderMiddleware # noqa: F401
12
+ from proxyherd.sticky import StickySpiderMiddleware # noqa: F401
13
+
14
+ warnings.warn(
15
+ "The 'scrapoxy' module is deprecated; import 'proxyherd' instead.",
16
+ DeprecationWarning,
17
+ stacklevel=2,
18
+ )
@@ -0,0 +1,2 @@
1
+ from proxyherd.api import * # noqa: F401,F403
2
+ from proxyherd.api import ProxyHerdApi as ScrapoxyApi # noqa: F401
@@ -0,0 +1 @@
1
+ from proxyherd.blacklist import * # noqa: F401,F403
@@ -0,0 +1 @@
1
+ from proxyherd.proxy import * # noqa: F401,F403
@@ -0,0 +1 @@
1
+ from proxyherd.scale import * # noqa: F401,F403
@@ -0,0 +1 @@
1
+ from proxyherd.sticky import * # noqa: F401,F403
@@ -0,0 +1,7 @@
1
+ [metadata]
2
+ description-file = README.md
3
+
4
+ [egg_info]
5
+ tag_build =
6
+ tag_date = 0
7
+
@@ -0,0 +1,24 @@
1
+ try:
2
+ from setuptools import setup, find_packages
3
+ except ImportError:
4
+ from distutils.core import setup, find_packages
5
+
6
+
7
+ setup(
8
+ name='proxyherd',
9
+ packages=find_packages(),
10
+ install_requires=[],
11
+ version='1.0.0',
12
+ description='Python API and Scrapy middlewares for ProxyHerd, the open-source super proxy manager',
13
+ long_description=open('README.md').read(),
14
+ long_description_content_type='text/markdown',
15
+ author='ProxyHerd maintainers',
16
+ url='https://github.com/proxyherd/proxyherd',
17
+ license='MIT',
18
+ keywords=['crawler', 'crawling', 'proxyherd', 'scrapoxy', 'scrapy', 'scraper', 'scraping', 'webscraping', 'proxy'],
19
+ classifiers=[
20
+ 'License :: OSI Approved :: MIT License',
21
+ 'Programming Language :: Python :: 3',
22
+ 'Framework :: Scrapy',
23
+ ],
24
+ )