This commit is contained in:
2026-06-16 16:56:27 +03:30
parent a5d09be46e
commit 24155ad6bf
22 changed files with 2485 additions and 762 deletions
View File
+106
View File
@@ -0,0 +1,106 @@
"""
Download and patch ChromeDriver once. Run via: make patch-driver
Uses the official Chrome for Testing API to fetch the exact ChromeDriver
version matching the installed Chrome, then patches it with undetected_chromedriver.
"""
from __future__ import annotations
import io
import shutil
import stat
import subprocess
import sys
import urllib.request
import zipfile
from pathlib import Path
import undetected_chromedriver as uc
from config import config
from logger import get_logger
log = get_logger(__name__)
def _get_chrome_full_version() -> tuple[int, str]:
candidates = ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser"]
if config.chrome_binary:
candidates.insert(0, config.chrome_binary)
for binary in candidates:
if not shutil.which(binary) and not Path(binary).exists():
continue
try:
out = subprocess.check_output(
[binary, "--version"], stderr=subprocess.DEVNULL, text=True, timeout=5
).strip()
full = out.split()[-1]
major = int(full.split(".")[0])
log.info("Detected Chrome %s via '%s'", full, binary)
return major, full
except Exception:
continue
log.critical("Could not detect Chrome version. Set CHROME_BINARY in .env")
sys.exit(1)
def _latest_chromedriver_version(major: int) -> str:
url = f"https://googlechromelabs.github.io/chrome-for-testing/LATEST_RELEASE_{major}"
try:
with urllib.request.urlopen(url, timeout=15) as resp:
version = resp.read().decode().strip()
log.info("Latest ChromeDriver for Chrome %d: %s", major, version)
return version
except Exception as exc:
log.critical("Could not fetch ChromeDriver version for Chrome %d: %s", major, exc)
sys.exit(1)
def _download_chromedriver(version: str, dest: Path) -> None:
zip_url = (
f"https://storage.googleapis.com/chrome-for-testing-public"
f"/{version}/linux64/chromedriver-linux64.zip"
)
log.info("Downloading ChromeDriver %s", version)
try:
with urllib.request.urlopen(zip_url, timeout=60) as resp:
data = resp.read()
except Exception as exc:
log.critical("Download failed: %s", exc)
sys.exit(1)
with zipfile.ZipFile(io.BytesIO(data)) as zf:
binary_name = next(
n for n in zf.namelist() if n.endswith("/chromedriver") or n == "chromedriver"
)
dest.write_bytes(zf.read(binary_name))
dest.chmod(dest.stat().st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)
log.info("Extracted to %s", dest)
def _patch(dest: Path) -> None:
log.info("Patching binary …")
patcher = uc.Patcher(executable_path=str(dest))
patcher.patch_exe()
log.info("Patch applied successfully")
def main() -> None:
dest = Path(config.chromedriver_path)
dest.parent.mkdir(parents=True, exist_ok=True)
major, _ = _get_chrome_full_version()
cd_version = _latest_chromedriver_version(major)
_download_chromedriver(cd_version, dest)
_patch(dest)
log.info("ChromeDriver %s ready at %s", cd_version, dest)
log.info("Re-run 'make patch-driver' only when Chrome updates to a new major version.")
if __name__ == "__main__":
main()