Compare commits

...

18 Commits

Author SHA1 Message Date
ilike2burnthing
220f2599ae Bump version 3.3.9 (#963) 2023-11-13 07:17:28 +00:00
ilike2burnthing
d772cf3f50 Fix for Docker build, capture TypeError. Fixes #962 2023-11-13 07:14:13 +00:00
ilike2burnthing
ab4365894b Bump version 3.3.8 (#961) 2023-11-13 04:55:49 +00:00
ilike2burnthing
3fa9631559 Fix "OSError: [WinError 6] The handle is invalid" on exit 2023-11-13 04:28:19 +00:00
ilike2burnthing
04858c22fd Support running Chrome 119 from source (#960) 2023-11-13 04:23:06 +00:00
Nabi KaramAliZadeh
5085ca6990 Fix headless=true for Chrome 117+. Fixes #910 (#921) 2023-11-13 04:03:56 +00:00
ilike2burnthing
cd4df1e061 Bump version 3.3.7 (#944) 2023-11-05 14:41:12 +00:00
ilike2burnthing
6c79783f7c Bump version 3.3.6 (#905) 2023-09-15 20:40:56 +01:00
ilike2burnthing
4139e8d47c Update checkbox selector, again 2023-09-15 20:37:26 +01:00
zax2002
1942eb5fdc Typo in README (#901) 2023-09-14 07:41:59 +01:00
ilike2burnthing
401bf5be76 Bump version 3.3.5 (#902) 2023-09-13 10:28:02 +01:00
ilike2burnthing
d8ffdd3061 Change checkbox selector, support language other than English. resolves #891 2023-09-13 10:19:19 +01:00
ilike2burnthing
2d66590b08 Bump version 3.3.4 (#884) 2023-09-02 12:30:21 +01:00
Zachary Hampton
a217510dc7 Update checkbox selector (#882) 2023-09-02 12:24:25 +01:00
ilike2burnthing
553bd8ab4f Bump version 3.3.3 (#879) 2023-08-31 20:02:17 +01:00
ilike2burnthing
1b197c3e53 Update undetected_chromedriver to v3.5.3 (#860) 2023-08-31 19:56:06 +01:00
ngosang
fd308f01be Bump version 3.3.2 2023-08-03 10:00:16 +02:00
ngosang
b5eef32615 Fix URL domain in Prometheus exporter 2023-08-03 09:02:46 +02:00
9 changed files with 180 additions and 59 deletions

View File

@@ -1,5 +1,39 @@
# Changelog # Changelog
## v3.3.9 (2023/11/13)
* Fix for Docker build, capture TypeError
## v3.3.8 (2023/11/13)
* Fix headless=true for Chrome 117+. Thanks @NabiKAZ
* Support running Chrome 119 from source. Thanks @koleg and @Chris7X
* Fix "OSError: [WinError 6] The handle is invalid" on exit. Thanks @enesgorkemgenc
## v3.3.7 (2023/11/05)
* Bump to rebuild. Thanks @JoachimDorchies
## v3.3.6 (2023/09/15)
* Update checkbox selector, again
## v3.3.5 (2023/09/13)
* Change checkbox selector, support languages other than English
## v3.3.4 (2023/09/02)
* Update checkbox selector
## v3.3.3 (2023/08/31)
* Update undetected_chromedriver to v3.5.3
## v3.3.2 (2023/08/03)
* Fix URL domain in Prometheus exporter
## v3.3.1 (2023/08/03) ## v3.3.1 (2023/08/03)
* Fix for Cloudflare verify checkbox * Fix for Cloudflare verify checkbox

View File

@@ -62,17 +62,17 @@ ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/usr/local/bin/python", "-u", "/app/flaresolverr.py"] CMD ["/usr/local/bin/python", "-u", "/app/flaresolverr.py"]
# Local build # Local build
# docker build -t ngosang/flaresolverr:3.3.1 . # docker build -t ngosang/flaresolverr:3.3.9 .
# docker run -p 8191:8191 ngosang/flaresolverr:3.3.1 # docker run -p 8191:8191 ngosang/flaresolverr:3.3.9
# Multi-arch build # Multi-arch build
# docker run --rm --privileged multiarch/qemu-user-static --reset -p yes # docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
# docker buildx create --use # docker buildx create --use
# docker buildx build -t ngosang/flaresolverr:3.3.1 --platform linux/386,linux/amd64,linux/arm/v7,linux/arm64/v8 . # docker buildx build -t ngosang/flaresolverr:3.3.9 --platform linux/386,linux/amd64,linux/arm/v7,linux/arm64/v8 .
# add --push to publish in DockerHub # add --push to publish in DockerHub
# Test multi-arch build # Test multi-arch build
# docker run --rm --privileged multiarch/qemu-user-static --reset -p yes # docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
# docker buildx create --use # docker buildx create --use
# docker buildx build -t ngosang/flaresolverr:3.3.1 --platform linux/arm/v7 --load . # docker buildx build -t ngosang/flaresolverr:3.3.9 --platform linux/arm/v7 --load .
# docker run -p 8191:8191 --platform linux/arm/v7 ngosang/flaresolverr:3.3.1 # docker run -p 8191:8191 --platform linux/arm/v7 ngosang/flaresolverr:3.3.9

View File

@@ -113,7 +113,7 @@ This also speeds up the requests since it won't have to launch a new browser ins
| Parameter | Notes | | Parameter | Notes |
|-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| session | Optional. The session ID that you want to be assigned to the instance. If isn't set a random UUID will be assigned. | | session | Optional. The session ID that you want to be assigned to the instance. If isn't set a random UUID will be assigned. |
| proxy | Optional, default disabled. Eg: `"proxy": {"url": "http://127.0.0.1:8888"}`. You must include the proxy schema in the URL: `http://`, `socks4://` or `socks5://`. Authorization (username/password) is supported. Eg: `"proxy": {"url": "http://127.0.0.1:8888", username": "testuser", "password": "testpass"}` | | proxy | Optional, default disabled. Eg: `"proxy": {"url": "http://127.0.0.1:8888"}`. You must include the proxy schema in the URL: `http://`, `socks4://` or `socks5://`. Authorization (username/password) is supported. Eg: `"proxy": {"url": "http://127.0.0.1:8888", "username": "testuser", "password": "testpass"}` |
#### + `sessions.list` #### + `sessions.list`

View File

@@ -1,6 +1,6 @@
{ {
"name": "flaresolverr", "name": "flaresolverr",
"version": "3.3.1", "version": "3.3.9",
"description": "Proxy server to bypass Cloudflare protection", "description": "Proxy server to bypass Cloudflare protection",
"author": "Diego Heras (ngosang / ngosang@hotmail.es)", "author": "Diego Heras (ngosang / ngosang@hotmail.es)",
"license": "MIT" "license": "MIT"

View File

@@ -2,7 +2,8 @@ import logging
import os import os
import urllib.parse import urllib.parse
from dtos import V1ResponseBase from bottle import request
from dtos import V1RequestBase, V1ResponseBase
from metrics import start_metrics_http_server, REQUEST_COUNTER, REQUEST_DURATION from metrics import start_metrics_http_server, REQUEST_COUNTER, REQUEST_DURATION
PROMETHEUS_ENABLED = os.environ.get('PROMETHEUS_ENABLED', 'false').lower() == 'true' PROMETHEUS_ENABLED = os.environ.get('PROMETHEUS_ENABLED', 'false').lower() == 'true'
@@ -39,8 +40,12 @@ def prometheus_plugin(callback):
domain = "unknown" domain = "unknown"
if res.solution and res.solution.url: if res.solution and res.solution.url:
parsed_url = urllib.parse.urlparse(res.solution.url) domain = parse_domain_url(res.solution.url)
domain = parsed_url.hostname else:
# timeout error
req = V1RequestBase(request.json)
if req.url:
domain = parse_domain_url(req.url)
run_time = (res.endTimestamp - res.startTimestamp) / 1000 run_time = (res.endTimestamp - res.startTimestamp) / 1000
REQUEST_DURATION.labels(domain=domain).observe(run_time) REQUEST_DURATION.labels(domain=domain).observe(run_time)
@@ -54,4 +59,8 @@ def prometheus_plugin(callback):
result = "error" result = "error"
REQUEST_COUNTER.labels(domain=domain, result=result).inc() REQUEST_COUNTER.labels(domain=domain, result=result).inc()
def parse_domain_url(url):
parsed_url = urllib.parse.urlparse(url)
return parsed_url.hostname
return wrapper return wrapper

View File

@@ -252,11 +252,11 @@ def _resolve_challenge(req: V1RequestBase, method: str) -> ChallengeResolutionT:
def click_verify(driver: WebDriver): def click_verify(driver: WebDriver):
try: try:
logging.debug("Try to find the Cloudflare verify checkbox...") logging.debug("Try to find the Cloudflare verify checkbox...")
iframe = driver.find_element(By.XPATH, "//iframe[@title='Widget containing a Cloudflare security challenge']") iframe = driver.find_element(By.XPATH, "//iframe[starts-with(@id, 'cf-chl-widget-')]")
driver.switch_to.frame(iframe) driver.switch_to.frame(iframe)
checkbox = driver.find_element( checkbox = driver.find_element(
by=By.XPATH, by=By.XPATH,
value='//*[@id="cf-stage"]//label[@class="ctp-checkbox-label"]/input', value='//*[@id="challenge-stage"]/div/label/input',
) )
if checkbox: if checkbox:
actions = ActionChains(driver) actions = ActionChains(driver)

View File

@@ -17,11 +17,12 @@ by UltrafunkAmsterdam (https://github.com/ultrafunkamsterdam)
from __future__ import annotations from __future__ import annotations
__version__ = "3.5.0" __version__ = "3.5.3"
import json import json
import logging import logging
import os import os
import pathlib
import re import re
import shutil import shutil
import subprocess import subprocess
@@ -373,6 +374,18 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
browser_executable_path or find_chrome_executable() browser_executable_path or find_chrome_executable()
) )
if not options.binary_location or not \
pathlib.Path(options.binary_location).exists():
raise FileNotFoundError(
"\n---------------------\n"
"Could not determine browser executable."
"\n---------------------\n"
"Make sure your browser is installed in the default location (path).\n"
"If you are sure about the browser executable, you can specify it using\n"
"the `browser_executable_path='{}` parameter.\n\n"
.format("/path/to/browser/executable" if IS_POSIX else "c:/path/to/your/browser.exe")
)
self._delay = 3 self._delay = 3
self.user_data_dir = user_data_dir self.user_data_dir = user_data_dir
@@ -451,11 +464,9 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
) )
self.browser_pid = browser.pid self.browser_pid = browser.pid
# Fix for Chrome 115
# https://github.com/seleniumbase/SeleniumBase/pull/1967
service = selenium.webdriver.chromium.service.ChromiumService( service = selenium.webdriver.chromium.service.ChromiumService(
executable_path=self.patcher.executable_path, self.patcher.executable_path
service_args=["--disable-build-check"]
) )
super(Chrome, self).__init__( super(Chrome, self).__init__(
@@ -800,7 +811,11 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
else: else:
logger.debug("successfully removed %s" % self.user_data_dir) logger.debug("successfully removed %s" % self.user_data_dir)
break break
time.sleep(0.1)
try:
time.sleep(0.1)
except OSError:
pass
# dereference patcher, so patcher can start cleaning up as well. # dereference patcher, so patcher can start cleaning up as well.
# this must come last, otherwise it will throw 'in use' errors # this must come last, otherwise it will throw 'in use' errors
@@ -895,8 +910,6 @@ def find_chrome_executable():
if item is not None: if item is not None:
for subitem in ( for subitem in (
"Google/Chrome/Application", "Google/Chrome/Application",
"Google/Chrome Beta/Application",
"Google/Chrome Canary/Application",
): ):
candidates.add(os.sep.join((item, subitem, "chrome.exe"))) candidates.add(os.sep.join((item, subitem, "chrome.exe")))
for candidate in candidates: for candidate in candidates:

View File

@@ -3,9 +3,11 @@
from distutils.version import LooseVersion from distutils.version import LooseVersion
import io import io
import json
import logging import logging
import os import os
import pathlib import pathlib
import platform
import random import random
import re import re
import shutil import shutil
@@ -24,21 +26,9 @@ IS_POSIX = sys.platform.startswith(("darwin", "cygwin", "linux", "linux2"))
class Patcher(object): class Patcher(object):
lock = Lock() lock = Lock()
url_repo = "https://chromedriver.storage.googleapis.com"
zip_name = "chromedriver_%s.zip"
exe_name = "chromedriver%s" exe_name = "chromedriver%s"
platform = sys.platform platform = sys.platform
if platform.endswith("win32"):
zip_name %= "win32"
exe_name %= ".exe"
if platform.endswith(("linux", "linux2")):
zip_name %= "linux64"
exe_name %= ""
if platform.endswith("darwin"):
zip_name %= "mac64"
exe_name %= ""
if platform.endswith("win32"): if platform.endswith("win32"):
d = "~/appdata/roaming/undetected_chromedriver" d = "~/appdata/roaming/undetected_chromedriver"
elif "LAMBDA_TASK_ROOT" in os.environ: elif "LAMBDA_TASK_ROOT" in os.environ:
@@ -72,6 +62,20 @@ class Patcher(object):
prefix = "undetected" prefix = "undetected"
self.user_multi_procs = user_multi_procs self.user_multi_procs = user_multi_procs
try:
# Try to convert version_main into an integer
version_main_int = int(version_main)
# check if version_main_int is less than or equal to e.g 114
self.is_old_chromedriver = version_main and version_main_int <= 114
except (ValueError,TypeError):
# If the conversion fails, print an error message
print("version_main cannot be converted to an integer")
# Set self.is_old_chromedriver to False if the conversion fails
self.is_old_chromedriver = False
# Needs to be called before self.exe_name is accessed
self._set_platform_name()
if not os.path.exists(self.data_path): if not os.path.exists(self.data_path):
os.makedirs(self.data_path, exist_ok=True) os.makedirs(self.data_path, exist_ok=True)
@@ -97,9 +101,33 @@ class Patcher(object):
self._custom_exe_path = True self._custom_exe_path = True
self.executable_path = executable_path self.executable_path = executable_path
# Set the correct repository to download the Chromedriver from
if self.is_old_chromedriver:
self.url_repo = "https://chromedriver.storage.googleapis.com"
else:
self.url_repo = "https://googlechromelabs.github.io/chrome-for-testing"
self.version_main = version_main self.version_main = version_main
self.version_full = None self.version_full = None
def _set_platform_name(self):
"""
Set the platform and exe name based on the platform undetected_chromedriver is running on
in order to download the correct chromedriver.
"""
if self.platform.endswith("win32"):
self.platform_name = "win32"
self.exe_name %= ".exe"
if self.platform.endswith(("linux", "linux2")):
self.platform_name = "linux64"
self.exe_name %= ""
if self.platform.endswith("darwin"):
if self.is_old_chromedriver:
self.platform_name = "mac64"
else:
self.platform_name = "mac-x64"
self.exe_name %= ""
def auto(self, executable_path=None, force=False, version_main=None, _=None): def auto(self, executable_path=None, force=False, version_main=None, _=None):
""" """
@@ -111,16 +139,15 @@ class Patcher(object):
Returns: Returns:
""" """
# if self.user_multi_procs and \
# self.user_multi_procs != -1:
# # -1 being a skip value used later in this block
#
p = pathlib.Path(self.data_path) p = pathlib.Path(self.data_path)
with Lock(): if self.user_multi_procs:
files = list(p.rglob("*chromedriver*?")) with Lock():
for file in files: files = list(p.rglob("*chromedriver*"))
if self.is_binary_patched(file): most_recent = max(files, key=lambda f: f.stat().st_mtime)
self.executable_path = str(file) files.remove(most_recent)
list(map(lambda f: f.unlink(), files))
if self.is_binary_patched(most_recent):
self.executable_path = str(most_recent)
return True return True
if executable_path: if executable_path:
@@ -202,7 +229,11 @@ class Patcher(object):
def cleanup_unused_files(self): def cleanup_unused_files(self):
p = pathlib.Path(self.data_path) p = pathlib.Path(self.data_path)
items = list(p.glob("*undetected*")) items = list(p.glob("*undetected*"))
print(items) for item in items:
try:
item.unlink()
except:
pass
def patch(self): def patch(self):
self.patch_exe() self.patch_exe()
@@ -214,12 +245,32 @@ class Patcher(object):
:return: version string :return: version string
:rtype: LooseVersion :rtype: LooseVersion
""" """
path = "/latest_release" # Endpoint for old versions of Chromedriver (114 and below)
if self.version_main: if self.is_old_chromedriver:
path += f"_{self.version_main}" path = f"/latest_release_{self.version_main}"
path = path.upper() path = path.upper()
logger.debug("getting release number from %s" % path)
return LooseVersion(urlopen(self.url_repo + path).read().decode())
# Endpoint for new versions of Chromedriver (115+)
if not self.version_main:
# Fetch the latest version
path = "/last-known-good-versions-with-downloads.json"
logger.debug("getting release number from %s" % path)
with urlopen(self.url_repo + path) as conn:
response = conn.read().decode()
last_versions = json.loads(response)
return LooseVersion(last_versions["channels"]["Stable"]["version"])
# Fetch the latest minor version of the major version provided
path = "/latest-versions-per-milestone-with-downloads.json"
logger.debug("getting release number from %s" % path) logger.debug("getting release number from %s" % path)
return LooseVersion(urlopen(self.url_repo + path).read().decode()) with urlopen(self.url_repo + path) as conn:
response = conn.read().decode()
major_versions = json.loads(response)
return LooseVersion(major_versions["milestones"][str(self.version_main)]["version"])
def parse_exe_version(self): def parse_exe_version(self):
with io.open(self.executable_path, "rb") as f: with io.open(self.executable_path, "rb") as f:
@@ -234,10 +285,16 @@ class Patcher(object):
:return: path to downloaded file :return: path to downloaded file
""" """
u = "%s/%s/%s" % (self.url_repo, self.version_full.vstring, self.zip_name) zip_name = f"chromedriver_{self.platform_name}.zip"
logger.debug("downloading from %s" % u) if self.is_old_chromedriver:
# return urlretrieve(u, filename=self.data_path)[0] download_url = "%s/%s/%s" % (self.url_repo, self.version_full.vstring, zip_name)
return urlretrieve(u)[0] else:
zip_name = zip_name.replace("_", "-", 1)
download_url = "https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/%s/%s/%s"
download_url %= (self.version_full.vstring, self.platform_name, zip_name)
logger.debug("downloading from %s" % download_url)
return urlretrieve(download_url)[0]
def unzip_package(self, fp): def unzip_package(self, fp):
""" """
@@ -245,6 +302,12 @@ class Patcher(object):
:return: path to unpacked executable :return: path to unpacked executable
""" """
exe_path = self.exe_name
if not self.is_old_chromedriver:
# The new chromedriver unzips into its own folder
zip_name = f"chromedriver-{self.platform_name}"
exe_path = os.path.join(zip_name, self.exe_name)
logger.debug("unzipping %s" % fp) logger.debug("unzipping %s" % fp)
try: try:
os.unlink(self.zip_path) os.unlink(self.zip_path)
@@ -253,10 +316,10 @@ class Patcher(object):
os.makedirs(self.zip_path, mode=0o755, exist_ok=True) os.makedirs(self.zip_path, mode=0o755, exist_ok=True)
with zipfile.ZipFile(fp, mode="r") as zf: with zipfile.ZipFile(fp, mode="r") as zf:
zf.extract(self.exe_name, self.zip_path) zf.extractall(self.zip_path)
os.rename(os.path.join(self.zip_path, self.exe_name), self.executable_path) os.rename(os.path.join(self.zip_path, exe_path), self.executable_path)
os.remove(fp) os.remove(fp)
os.rmdir(self.zip_path) shutil.rmtree
os.chmod(self.executable_path, 0o755) os.chmod(self.executable_path, 0o755)
return self.executable_path return self.executable_path

View File

@@ -113,7 +113,7 @@ def create_proxy_extension(proxy: dict) -> str:
def get_webdriver(proxy: dict = None) -> WebDriver: def get_webdriver(proxy: dict = None) -> WebDriver:
global PATCHED_DRIVER_PATH global PATCHED_DRIVER_PATH, USER_AGENT
logging.debug('Launching web browser...') logging.debug('Launching web browser...')
# undetected_chromedriver # undetected_chromedriver
@@ -136,6 +136,10 @@ def get_webdriver(proxy: dict = None) -> WebDriver:
# https://peter.sh/experiments/chromium-command-line-switches/#use-gl # https://peter.sh/experiments/chromium-command-line-switches/#use-gl
options.add_argument('--use-gl=swiftshader') options.add_argument('--use-gl=swiftshader')
# Fix for Chrome 117 | https://github.com/FlareSolverr/FlareSolverr/issues/910
if USER_AGENT is not None:
options.add_argument('--user-agent=%s' % USER_AGENT)
proxy_extension_dir = None proxy_extension_dir = None
if proxy and all(key in proxy for key in ['url', 'username', 'password']): if proxy and all(key in proxy for key in ['url', 'username', 'password']):
proxy_extension_dir = create_proxy_extension(proxy) proxy_extension_dir = create_proxy_extension(proxy)
@@ -162,10 +166,6 @@ def get_webdriver(proxy: dict = None) -> WebDriver:
driver_exe_path = "/app/chromedriver" driver_exe_path = "/app/chromedriver"
else: else:
version_main = get_chrome_major_version() version_main = get_chrome_major_version()
# Fix for Chrome 115
# https://github.com/seleniumbase/SeleniumBase/pull/1967
if int(version_main) > 114:
version_main = 114
if PATCHED_DRIVER_PATH is not None: if PATCHED_DRIVER_PATH is not None:
driver_exe_path = PATCHED_DRIVER_PATH driver_exe_path = PATCHED_DRIVER_PATH
@@ -295,6 +295,8 @@ def get_user_agent(driver=None) -> str:
if driver is None: if driver is None:
driver = get_webdriver() driver = get_webdriver()
USER_AGENT = driver.execute_script("return navigator.userAgent") USER_AGENT = driver.execute_script("return navigator.userAgent")
# Fix for Chrome 117 | https://github.com/FlareSolverr/FlareSolverr/issues/910
USER_AGENT = re.sub('HEADLESS', '', USER_AGENT, flags=re.IGNORECASE)
return USER_AGENT return USER_AGENT
except Exception as e: except Exception as e:
raise Exception("Error getting browser User-Agent. " + str(e)) raise Exception("Error getting browser User-Agent. " + str(e))