Last active
June 13, 2025 14:49
-
-
Save jadsongmatos/5dd89e48a0f9524131902ff0aaa5c730 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import re | |
import base64 | |
import hashlib | |
from bs4 import BeautifulSoup | |
def salvar_com_hash(conteudo_bytes, pasta_destino, extensao): | |
""" | |
Dado o conteúdo em bytes, calcula o hash e salva em `pasta_destino` com | |
nome <hash>.<extensao>. | |
Retorna o caminho relativo do arquivo que foi (ou já estava) gravado. | |
""" | |
hash_ = hashlib.md5(conteudo_bytes).hexdigest() | |
nome_arquivo = f"{hash_}.{extensao}" | |
caminho_arquivo = os.path.join(pasta_destino, nome_arquivo) | |
if not os.path.exists(caminho_arquivo): | |
with open(caminho_arquivo, "wb") as f: | |
f.write(conteudo_bytes) | |
return nome_arquivo | |
def substituir_recursos_css(texto, pasta_images, pasta_fonts): | |
""" | |
Substitui referências base64 por arquivos externos em textos CSS. | |
""" | |
def replace_img(match): | |
tipo = match.group(1) | |
b64_data = match.group(2) | |
extensao = tipo.replace("jpeg", "jpg") | |
if extensao.startswith("svg"): | |
extensao = "svg" | |
try: | |
img_bytes = base64.b64decode(b64_data) | |
except Exception as e: | |
print(f"Erro ao decodificar imagem CSS: {e}") | |
return match.group(0) | |
nome_arq = salvar_com_hash(img_bytes, pasta_images, extensao) | |
return f'url("images/{nome_arq}")' | |
texto = re.sub( | |
r'url\(\s*["\']?data:image/([^;]+);base64,([^"\'()]+)["\']?\)', | |
replace_img, | |
texto, | |
flags=re.IGNORECASE | |
) | |
def replace_woff2(match): | |
b64_data = match.group(1) | |
try: | |
fonte_bytes = base64.b64decode(b64_data) | |
except Exception as e: | |
print(f"Erro ao decodificar fonte WOFF2: {e}") | |
return match.group(0) | |
nome_fonte = salvar_com_hash(fonte_bytes, pasta_fonts, "woff2") | |
return f'url("fonts/{nome_fonte}")' | |
texto = re.sub( | |
r'url\(\s*["\']?data:(?:font/woff2|application/font-woff2);base64,([^"\'()]+)["\']?\)', | |
replace_woff2, | |
texto, | |
flags=re.IGNORECASE | |
) | |
return texto | |
def processar_html(caminho_arquivo, pasta_base): | |
""" | |
Processa um único arquivo HTML: remove 'no-referrer', ajusta links e imagens, e salva recursos embutidos. | |
""" | |
pasta_images = os.path.join(pasta_base, "images") | |
pasta_fonts = os.path.join(pasta_base, "fonts") | |
pasta_js = os.path.join(pasta_base, "js") | |
pasta_css = os.path.join(pasta_base, "css") | |
os.makedirs(pasta_images, exist_ok=True) | |
os.makedirs(pasta_fonts, exist_ok=True) | |
os.makedirs(pasta_js, exist_ok=True) | |
os.makedirs(pasta_css, exist_ok=True) | |
with open(caminho_arquivo, "r", encoding="utf-8") as f: | |
conteudo = f.read() | |
soup = BeautifulSoup(conteudo, "html.parser") | |
# --- REMOVER <base href="..."> --- | |
for base_tag in soup.find_all("base"): | |
base_tag.decompose() | |
# --- REMOVER <meta http-equiv="..."> --- | |
for meta in soup.find_all("meta", attrs={"http-equiv": True}): | |
meta.decompose() | |
# Remover content="no-referrer" das tags <meta> | |
for meta in soup.find_all("meta", content="no-referrer"): | |
del meta["content"] | |
# Processar <img> com src em base64 | |
for img in soup.find_all("img"): | |
src = img.get("src", "") | |
if src.startswith("data:image/"): | |
match = re.match(r"data:image/([^;]+);base64,(.*)", src, re.I) | |
if match: | |
tipo = match.group(1) | |
b64_data = match.group(2) | |
extensao = tipo.replace("jpeg", "jpg") | |
if extensao.startswith("svg"): | |
extensao = "svg" | |
try: | |
imagem_bytes = base64.b64decode(b64_data) | |
except Exception as e: | |
print(f"Erro ao decodificar imagem: {e}") | |
continue | |
nome_arquivo = salvar_com_hash(imagem_bytes, pasta_images, extensao) | |
img["src"] = f"images/{nome_arquivo}" | |
# Atualizar atributos style em todas as tags | |
for tag in soup.find_all(style=True): | |
estilo_antigo = tag["style"] | |
tag["style"] = substituir_recursos_css(estilo_antigo, pasta_images, pasta_fonts) | |
# Processar scripts inline - SEM SRC (normal e module) | |
scripts_inline = soup.find_all("script", src=False) | |
js_normal = [] | |
js_module = [] | |
for script in scripts_inline: | |
script_type = script.attrs.get("type", "").strip().lower() | |
if script.string: | |
conteudo_script = script.string.strip() | |
if not conteudo_script: | |
continue | |
if script_type == "module": | |
js_module.append(conteudo_script) | |
else: | |
js_normal.append(conteudo_script) | |
script.decompose() | |
# Salvar scripts normais | |
if js_normal: | |
js_total = "\n".join(js_normal).strip() | |
if js_total: | |
js_bytes = js_total.encode("utf-8") | |
nome_js = salvar_com_hash(js_bytes, pasta_js, "js") | |
nova_tag_script = soup.new_tag("script", src=f"js/{nome_js}") | |
if soup.body: | |
soup.body.append(nova_tag_script) | |
else: | |
soup.append(nova_tag_script) | |
# Salvar scripts modules | |
if js_module: | |
js_total = "\n".join(js_module).strip() | |
if js_total: | |
js_bytes = js_total.encode("utf-8") | |
nome_js = salvar_com_hash(js_bytes, pasta_js, "js") | |
nova_tag_script = soup.new_tag("script", src=f"js/{nome_js}", type="module") | |
if soup.body: | |
soup.body.append(nova_tag_script) | |
else: | |
soup.append(nova_tag_script) | |
# Processar scripts com src="data:..." | |
for script in soup.find_all("script", src=True): | |
src = script["src"] | |
if src.startswith("data:"): | |
# Exemplo: data:module;base64,BASE64AQUI | |
match = re.match(r"data:[^,]*;base64,([^,]*)", src) | |
if match: | |
b64_data = match.group(1) | |
try: | |
js_content = base64.b64decode(b64_data).decode("utf-8") | |
except Exception as e: | |
print(f"Erro ao decodificar script base64: {e}") | |
continue | |
js_bytes = js_content.encode("utf-8") | |
nome_js = salvar_com_hash(js_bytes, pasta_js, "js") | |
# Criar novo script com caminho externo | |
new_script = soup.new_tag( | |
"script", | |
src=f"js/{nome_js}", | |
type=script.get("type", "text/javascript") | |
) | |
# Copiar atributos importantes | |
if script.has_attr("referrerpolicy"): | |
new_script["referrerpolicy"] = script["referrerpolicy"] | |
script.replace_with(new_script) | |
# Processar estilos internos <style> | |
for style in soup.find_all("style"): | |
if not style.string: | |
continue | |
css_conteudo = style.string | |
css_conteudo = substituir_recursos_css(css_conteudo, pasta_images, pasta_fonts) | |
css_bytes = css_conteudo.encode("utf-8") | |
if len(css_bytes) >= 4096: | |
nome_css = salvar_com_hash(css_bytes, pasta_css, "css") | |
nova_tag_link = soup.new_tag("link", rel="stylesheet", href=f"css/{nome_css}") | |
style.replace_with(nova_tag_link) | |
# Adicionar referrerpolicy="origin" onde aplicável | |
for tag in soup.find_all(["a", "img", "script", "link"]): | |
if tag.has_attr("href") or tag.has_attr("src"): | |
tag["referrerpolicy"] = "origin" | |
# Salva o HTML modificado | |
with open(caminho_arquivo, "w", encoding="utf-8") as f: | |
f.write(str(soup)) | |
print(f"Processado: {caminho_arquivo}") | |
def processar_todos_html(pasta): | |
""" | |
Percorre todos os arquivos .html na pasta especificada e processa cada um. | |
""" | |
for arquivo in os.listdir(pasta): | |
if arquivo.lower().endswith(".html"): | |
caminho_arquivo = os.path.join(pasta, arquivo) | |
print(f"Processando: {caminho_arquivo}") | |
processar_html(caminho_arquivo, pasta) | |
print("Processamento concluído!") | |
if __name__ == "__main__": | |
caminho_da_pasta = "./html" | |
processar_todos_html(caminho_da_pasta) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment