#!/usr/bin/env python
from concurrent import futures
import multiprocessing as mp
import os
import json
import uuid
from bs4 import BeautifulSoup
from markdown import markdown
import requests
import urllib3
# Ignore security hazard since certs SHOULD be trusted (https)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Avoid rate limiting (tcp)
URL_BOT_ID = f'Bot {str(uuid.uuid4())}'
def extract_urls_from_html(content, all_urls):
soup = BeautifulSoup(content, 'html.parser')
for a in soup.find_all('a', href=True):
url = a['href']
if url.startswith('http'):
all_urls.add(url)
def extract_urls(discover_path):
exclude = ['.git', '.vscode']
all_urls = set()
max_strlen = -1
for root, dirs, files in os.walk(discover_path, topdown=True):
dirs[:] = [d for d in dirs if d not in exclude]
for file in files:
output = f'Currently checking: file={file}'
file_path = os.path.join(root, file)
if max_strlen < len(output):
max_strlen = len(output)
print(output.ljust(max_strlen), end='\r')
if file_path.endswith('.html'):
content = open(file_path)
extract_urls_from_html(content, all_urls)
elif file_path.endswith('.markdown'):
content = markdown(open(file_path).read())
extract_urls_from_html(content, all_urls)
return all_urls
def run_workers(work, data, worker_threads=mp.cpu_count()*4):
with futures.ThreadPoolExecutor(max_workers=worker_threads) as executor:
future_to_result = {
executor.submit(work, arg): arg for arg in data}
for future in futures.as_completed(future_to_result):
yield future.result()
def get_url_status(url):
for local in ('localhost', '127.0.0.1', 'app_server'):
if url.startswith('http://' + local):
return (url, 0)
clean_url = url.strip('?.')
try:
response = requests.get(
clean_url, verify=False, timeout=10.0,
headers={'User-Agent': URL_BOT_ID})
return (clean_url, response.status_code)
except requests.exceptions.Timeout:
return (clean_url, 504)
except requests.exceptions.ConnectionError:
return (clean_url, -1)
except requests.exceptions.TooManyRedirects:
return (clean_url, -1)
def bad_url(url_status):
if url_status == -1:
return True
elif url_status == 401 or url_status == 403:
return False
elif url_status == 503:
return False
elif url_status >= 400:
return True
return False
def main():
print('Extract urls...')
all_urls = extract_urls(os.getcwd())
print('\nCheck urls...')
bad_urls = {}
url_id = 1
max_strlen = -1
for url_path, url_status in run_workers(get_url_status, all_urls):
output = f'Currently checking: id={url_id} host={urllib3.util.parse_url(url_path).host}'
if max_strlen < len(output):
max_strlen = len(output)
print(output.ljust(max_strlen), end='\r')
if bad_url(url_status) is True:
bad_urls[url_path] = url_status
url_id += 1
print(f'\nBad urls: {json.dumps(bad_urls, indent=4)}')
if __name__ == '__main__':
main()