Skip to content
This repository was archived by the owner on May 25, 2022. It is now read-only.

Commit cb73efa

Browse files
authored
Merge pull request #80 from IngoKl/development
Add cli_proxy_tester project
2 parents e445aed + 7963b98 commit cb73efa

File tree

7 files changed

+365
-2
lines changed

7 files changed

+365
-2
lines changed

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# mini-projects-python
2-
A collection of simple python mini projects to enhance your python skills
32

3+
A collection of simple python mini projects to enhance your python skills
44

55
If you want to learn about python visit [Here](https://github.com/chavarera/PythonScript)
6+
67
## Steps To Follow
8+
79
- Select an issue
810
- Open [project here](https://github.com/chavarera/python-mini-projects)
911
- Create a fork of the project
@@ -31,7 +33,7 @@ Sr no | Project Name | Author
3133
9 | [WishList App Using Django](https://github.com/chavarera/python-mini-projects/tree/master/projects/WishList) | [Ravi Chavare](https://github.com/chavarera)
3234
10 | [Split Folders into Subfolders](https://github.com/chavarera/python-mini-projects/tree/master/projects/split%20folder%20into%20subfolders) | [Ravi Chavare](https://github.com/chavarera)
3335
11 | [Download bulk images](https://github.com/chavarera/python-mini-projects/tree/master/projects/download%20images%20from%20website) | [Mitesh](https://github.com/Mitesh2499)
34-
12 | [Ranom word from file](https://github.com/chavarera/python-mini-projects/tree/master/projects/Random_word_from_list) |
36+
12 | [Random word from file](https://github.com/chavarera/python-mini-projects/tree/master/projects/Random_word_from_list) |
3537
13 | [Battery notification](https://github.com/chavarera/python-mini-projects/tree/master/projects/battery%20notification) | [Mitesh](https://github.com/Mitesh2499)
3638
14 | [Calculate age](https://github.com/chavarera/python-mini-projects/tree/master/projects/Calculate%20age) | [Gaodong](https://github.com/xlgd)
3739
15 | [Text file analysis](https://github.com/chavarera/python-mini-projects/tree/master/projects/Textfile%20analysis) | [m044de](https://github.com/m044de/)
@@ -53,3 +55,4 @@ Sr no | Project Name | Author
5355
31 | [Cli Based Todo Application](https://github.com/chavarera/python-mini-projects/tree/master/projects/CLI%20Todo) | [Audrey Yang](https://github.com/audrey-yang)
5456
32 | [Currency Convertor cli app](https://github.com/chavarera/python-mini-projects/tree/master/projects/Currency%20Converter) | [github-of-wone](https://github.com/github-of-wone/)
5557
33 | [Stopwatch Application](https://github.com/chavarera/python-mini-projects/tree/master/projects/create%20a%20simple%20stopwatch) | [Gaodong](https://github.com/xlgd)
58+
34 | [CLI Proxy Tester](https://github.com/chavarera/python-mini-projects/tree/master/projects/cli_proxy_tester) | [Ingo Kleiber](https://github.com/IngoKl)
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Project
2+
proxies.csv
3+
4+
# Byte-compiled / optimized / DLL files
5+
__pycache__/
6+
*.py[cod]
7+
*$py.class
8+
9+
# C extensions
10+
*.so
11+
12+
# Distribution / packaging
13+
.Python
14+
build/
15+
develop-eggs/
16+
dist/
17+
downloads/
18+
eggs/
19+
.eggs/
20+
lib/
21+
lib64/
22+
parts/
23+
sdist/
24+
var/
25+
wheels/
26+
share/python-wheels/
27+
*.egg-info/
28+
.installed.cfg
29+
*.egg
30+
MANIFEST
31+
32+
# PyInstaller
33+
# Usually these files are written by a python script from a template
34+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
35+
*.manifest
36+
*.spec
37+
38+
# Installer logs
39+
pip-log.txt
40+
pip-delete-this-directory.txt
41+
42+
# Unit test / coverage reports
43+
htmlcov/
44+
.tox/
45+
.nox/
46+
.coverage
47+
.coverage.*
48+
.cache
49+
nosetests.xml
50+
coverage.xml
51+
*.cover
52+
*.py,cover
53+
.hypothesis/
54+
.pytest_cache/
55+
cover/
56+
57+
# Translations
58+
*.mo
59+
*.pot
60+
61+
# Django stuff:
62+
*.log
63+
local_settings.py
64+
db.sqlite3
65+
db.sqlite3-journal
66+
67+
# Flask stuff:
68+
instance/
69+
.webassets-cache
70+
71+
# Scrapy stuff:
72+
.scrapy
73+
74+
# Sphinx documentation
75+
docs/_build/
76+
77+
# PyBuilder
78+
.pybuilder/
79+
target/
80+
81+
# Jupyter Notebook
82+
.ipynb_checkpoints
83+
84+
# IPython
85+
profile_default/
86+
ipython_config.py
87+
88+
# pyenv
89+
# For a library or package, you might want to ignore these files since the code is
90+
# intended to run in multiple environments; otherwise, check them in:
91+
# .python-version
92+
93+
# pipenv
94+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
95+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
96+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
97+
# install all needed dependencies.
98+
#Pipfile.lock
99+
100+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
101+
__pypackages__/
102+
103+
# Celery stuff
104+
celerybeat-schedule
105+
celerybeat.pid
106+
107+
# SageMath parsed files
108+
*.sage.py
109+
110+
# Environments
111+
.env
112+
.venv
113+
env/
114+
venv/
115+
ENV/
116+
env.bak/
117+
venv.bak/
118+
119+
# Spyder project settings
120+
.spyderproject
121+
.spyproject
122+
123+
# Rope project settings
124+
.ropeproject
125+
126+
# mkdocs documentation
127+
/site
128+
129+
# mypy
130+
.mypy_cache/
131+
.dmypy.json
132+
dmypy.json
133+
134+
# Pyre type checker
135+
.pyre/
136+
137+
# pytype static type analyzer
138+
.pytype/
139+
140+
# Cython debug symbols
141+
cython_debug/
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# mini-projects-python - cli based proxy tester (#77)
2+
3+
*Author:* Ingo Kleiber (ingo@kleiber.me)
4+
5+
This mini project is a proxy tester based on `requests`. It utilized `pandas` for handling csv files and
6+
`click` for the CLI.
7+
8+
## Usage
9+
10+
This script tests proxies by querying (GET request) a testing website that returns the IP of the client. If the returned IP matches the IP of the proxy, we consider the proxy to be good.
11+
12+
### Testing Single Proxies
13+
14+
`python cli.py single http://1.1.1.1`
15+
16+
This will test the HTTP proxy 1.1.1.1 against the default testing website [iptest.ingokleiber.de](http://iptest.ingokleiber.de).
17+
You can run your own testing service using the PHP script in `/ipinfo`. This service should be offered both via HTTP and HTTPs.
18+
19+
`python cli.py single http://1.1.1.1 --iptest iptest.yourdomain.com`
20+
21+
### (Re)Testing a CSV File
22+
23+
`python cli.py csv-file proxies.csv`
24+
25+
This will (re)test all proxies in the given file.
26+
27+
### Adding and Testing Proxies From a Text File
28+
29+
`python cli.py add-from-txt-file proxy_candidates.txt`
30+
31+
This will add and test each proxy (one per line) in `proxy_candidates.txt`.

projects/cli_proxy_tester/cli.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import re
2+
3+
import click
4+
5+
from proxytest import add_from_text_file
6+
from proxytest import test_csv_file
7+
from proxytest import test_single_proxy
8+
9+
10+
def validate_proxy(ctx, param, value):
11+
'''Validate proxy input. The RegEx crudely matches both IPv4 and URLs.'''
12+
validator = re.compile(r'(https|http|socks4|socks5):\/\/'
13+
r'((?:[0-9]{1,3}\.){3}[0-9]{1,3}(:[0-9]{2,5})?'
14+
r'|([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?)')
15+
16+
if not validator.match(value):
17+
raise click.BadParameter('Please provide a proxy in the format'
18+
'type://address (e.g., https://42.42.42.42)')
19+
else:
20+
return value
21+
22+
23+
@click.group()
24+
def cli():
25+
pass
26+
27+
28+
@cli.command()
29+
@click.argument('proxy', callback=validate_proxy)
30+
@click.option('--iptest', default='iptest.ingokleiber.de',
31+
help='iptest address')
32+
@click.option('--csv', default='proxies.csv', help='CSV path')
33+
def single(proxy, iptest, csv):
34+
test_single_proxy(proxy, iptest, csv)
35+
36+
37+
@cli.command()
38+
@click.argument('csv')
39+
@click.option('--iptest', default='iptest.ingokleiber.de',
40+
help='iptest address')
41+
def csv_file(iptest, csv):
42+
test_csv_file(iptest, csv)
43+
44+
45+
@cli.command()
46+
@click.argument('txt')
47+
@click.option('--iptest', default='iptest.ingokleiber.de',
48+
help='iptest address')
49+
@click.option('--csv', default='proxies.csv', help='CSV path')
50+
def add_from_txt_file(iptest, txt, csv):
51+
add_from_text_file(iptest, txt, csv)
52+
53+
54+
if __name__ == '__main__':
55+
cli()
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
header('Content-type: application/json');
3+
4+
$client_information = array(
5+
"ip" => $_SERVER['REMOTE_ADDR'],
6+
"xff" => $_SERVER['HTTP_X_FORWARDED_FOR'],
7+
"useragent" => $_SERVER['HTTP_USER_AGENT']
8+
);
9+
10+
echo json_encode($client_information);
11+
?>
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import logging
2+
from json.decoder import JSONDecodeError
3+
from pathlib import Path
4+
5+
import pandas as pd
6+
import requests
7+
from requests.exceptions import ProxyError
8+
9+
logging.basicConfig(level=logging.INFO)
10+
11+
12+
def add_proxies_to_file(csv_path: str, proxies: list):
13+
'''This function will add one or multiple proxies to the CSV file.'''
14+
15+
if not csv_path.exists():
16+
pr_file: pd.DataFrame = pd.DataFrame(
17+
columns=['proxy_type', 'proxy_address', 'proxy_status'])
18+
logging.info('New CSV file will be created')
19+
else:
20+
pr_file: pd.DataFrame = pd.read_csv(csv_path)
21+
logging.info('Existing CSV file has been loaded')
22+
23+
for proxy in proxies:
24+
if len(pr_file) == 0:
25+
# First proxy in the file
26+
pr_file = pr_file.append(proxy, ignore_index=True)
27+
else:
28+
if len(pr_file.loc[(pr_file['proxy_type'] == proxy['proxy_type']) &
29+
(pr_file['proxy_address'] == proxy['proxy_address'])]) > 0:
30+
# Proxy is already in the file
31+
pr_file.loc[(pr_file['proxy_type'] == proxy['proxy_type']) &
32+
(pr_file['proxy_address'] == proxy['proxy_address']),
33+
['proxy_status']] = proxy['proxy_status']
34+
else:
35+
# Proxy is not yet in the file
36+
pr_file = pr_file.append(proxy, ignore_index=True)
37+
38+
pr_file = pr_file.drop_duplicates()
39+
pr_file.to_csv(csv_path, index=False)
40+
logging.info('CSV file has been written')
41+
42+
43+
def test_proxy(proxy_type: str, proxy_address: str, iptest: str):
44+
'''This function takes a proxy (type, address)
45+
and tests it against a given iptest adress.'''
46+
47+
logging.info(f'Testing proxy: {proxy_address}')
48+
49+
try:
50+
proxies = {proxy_type: proxy_address}
51+
proxy_status: str = ''
52+
53+
if proxy_type == 'https':
54+
r = requests.get(f'https://{iptest}', proxies=proxies)
55+
else:
56+
r = requests.get(f'http://{iptest}', proxies=proxies)
57+
58+
try:
59+
json_response: dict = r.json()
60+
61+
if json_response["ip"] in proxy_address:
62+
proxy_status = 'Proxy functional'
63+
else:
64+
logging.warning(f'Proxy "{proxy_address}"'
65+
f'returned {json_response}')
66+
proxy_status = 'Proxy not functional'
67+
except JSONDecodeError:
68+
proxy_status = 'Invalid response'
69+
except ProxyError:
70+
proxy_status = 'Proxy error'
71+
72+
logging.info(f'Proxy {proxy_address}: {proxy_status}')
73+
return {'proxy_type': proxy_type,
74+
'proxy_address': proxy_address,
75+
'proxy_status': proxy_status}
76+
77+
78+
def test_single_proxy(proxy: str, iptest: str, csv_path: str):
79+
'''This function tests an individual proxy and adds it to the CSV file.'''
80+
proxy_type, proxy_address = proxy.split('://')
81+
result: dict = test_proxy(proxy_type, proxy_address, iptest)
82+
83+
add_proxies_to_file(Path(csv_path), [result])
84+
85+
86+
def test_csv_file(iptest: str, csv_path: str):
87+
'''This function (re)tests every proxy in a given CSV file.'''
88+
89+
csv_path: Path = Path(csv_path)
90+
91+
if csv_path.exists():
92+
pr_file: pd.DataFrame = pd.read_csv(csv_path)
93+
else:
94+
raise FileNotFoundError
95+
96+
proxies: list = []
97+
98+
for index, proxy in pr_file.iterrows():
99+
proxies.append(test_proxy(proxy['proxy_type'],
100+
proxy['proxy_address'],
101+
iptest))
102+
103+
add_proxies_to_file(csv_path, proxies)
104+
105+
106+
def add_from_text_file(iptest: str, text_path: str, csv_path: str):
107+
''' This function adds a list of proxies
108+
from a text file (line by line).'''
109+
text_path: Path = Path(text_path)
110+
111+
if text_path.exists():
112+
proxies: list = text_path.read_text().splitlines()
113+
114+
for proxy in proxies:
115+
'''We will treat each proxy as a single proxy
116+
and leverage the existing function'''
117+
test_single_proxy(proxy, iptest, csv_path)
118+
else:
119+
raise FileNotFoundError
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
click==7.1.2
2+
requests[socks]==2.24.0
3+
pandas==1.0.5

0 commit comments

Comments
 (0)