Compare commits

...

73 Commits

Author SHA1 Message Date
ilike2burnthing
0fe9958afe Update README.md 2025-12-03 08:51:24 +00:00
Prodan Denis
9f8c71131f Resolve turnstile captcha (#1634)
Fixes #804
2025-12-03 08:50:31 +00:00
ilike2burnthing
2405c00521 Add formatting to log file
Resolves #1635
2025-12-03 07:07:54 +00:00
ilike2burnthing
ff65b7cc68 Bump version 3.4.6 (#1633) 2025-11-29 02:42:42 +00:00
Azizul Haque Ananto
409e0844a7 Add disable image, css, fonts option with CDP (#1626)
Co-authored-by: ilike2burnthing <59480337+ilike2burnthing@users.noreply.github.com>
2025-11-29 02:27:05 +00:00
ilike2burnthing
368d5d4e05 Update README.md 2025-11-26 00:15:20 +00:00
ilike2burnthing
c7505e3cbf Revert to Python v3.13 and bump FlareSolverr to 3.4.5 (#1617) 2025-11-11 01:58:14 +00:00
ilike2burnthing
5a27090abe Fix Dockerfile casing 2025-11-06 02:11:28 +00:00
flower
e505ea4fe4 Fix changelog building (#1611) 2025-11-06 00:52:46 +00:00
ilike2burnthing
63b6fc53e3 Bump version 3.4.4 (#1609) 2025-11-04 23:04:55 +00:00
flower
8d72617219 Bump dependencies, Chrome, and some other general fixes (#1607)
Co-authored-by: ilike2burnthing <59480337+ilike2burnthing@users.noreply.github.com>
2025-11-04 22:49:32 +00:00
ilike2burnthing
8a8b9415c3 Bump version 3.4.3 (#1601) 2025-10-28 10:21:38 +00:00
ilike2burnthing
16722ef963 Update proxy extension. Fixes #1534 2025-10-28 00:01:30 +00:00
ilike2burnthing
bbc24e9d86 Bump version 3.4.2 (#1590) 2025-10-09 20:05:32 +01:00
acg5159
7dfdfc5e33 Add log file support (#1480)
Co-authored-by: ilike2burnthing <59480337+ilike2burnthing@users.noreply.github.com>
2025-10-09 19:55:32 +01:00
Esteban Thilliez
136422c85c Add returnScreenshot parameter to screenshot the final web page (#1439) 2025-10-08 10:59:39 +01:00
flower
05a72f2709 bump: dependencies (#1585) 2025-10-05 16:25:13 +01:00
flower
da810830da Bump prometheus-client to 0.23.1 (#1583) 2025-10-02 12:32:01 +01:00
Warrenberberd
d27f57c27c Add quote protection for password containing it (#858)
Co-authored-by: ilike2burnthing <59480337+ilike2burnthing@users.noreply.github.com>
2025-10-01 06:46:08 +01:00
eZ4RK0
a916d93779 Handle empty string and keys without value in postData. resolves #1548 (#1550) 2025-10-01 04:56:57 +01:00
Kishan Joshi
0d889cb0b2 Add proxy envs (#1499)
Co-authored-by: ilike2burnthing <59480337+ilike2burnthing@users.noreply.github.com>
2025-09-20 13:43:58 +01:00
Kennedy Oliveira
d430404de8 Add optional wait time after resolving the challenge before returning (#1046)
Co-authored-by: ilike2burnthing <59480337+ilike2burnthing@users.noreply.github.com>
2025-09-20 04:59:16 +01:00
flower
d3b1ba6e88 Bump dependencies & CI actions (#1578) 2025-09-18 21:20:15 +01:00
ilike2burnthing
75e5b190d6 Bump version 3.4.1 (#1576) 2025-09-15 19:01:04 +01:00
ilike2burnthing
cdc3db3c21 Change access denied title check to use startswith. resolves #1574 2025-09-15 18:55:40 +01:00
ilike2burnthing
2dbb0442e0 Fix regex pattern syntax in utils.py 2025-08-25 04:50:05 +01:00
ilike2burnthing
6faab19533 Bump version 3.4.0 (#1564) 2025-08-25 04:21:44 +01:00
ilike2burnthing
af0a7af757 Remove disable software rasterizer option for ARM builds 2025-08-25 04:17:22 +01:00
Alex Naidis
ff74b50b60 Modernize and upgrade application (#1540)
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
2025-08-25 03:55:06 +01:00
ilike2burnthing
3e51ac1188 Update README.md 2025-06-26 05:30:44 +01:00
ilike2burnthing
6627de4fa6 Bump version 3.3.25 (#1523) 2025-06-14 03:52:28 +01:00
ilike2burnthing
fe649255f2 Revert "Fix Chrome GL erros in ASUSTOR NAS"
This reverts commit 8316350b98.
2025-06-14 03:42:08 +01:00
dependabot[bot]
3e338fce2e Bump requests from 2.32.3 to 2.32.4 (#1516)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-11 02:48:00 +01:00
ilike2burnthing
3dd3e7559d u_c: remove apparent c&p typo
https://github.com/ultrafunkamsterdam/undetected-chromedriver/pull/1933
2025-06-06 08:04:00 +01:00
ilike2burnthing
f21c1d51bc Restore example service file. #1204 2025-06-04 23:13:00 +01:00
ilike2burnthing
957347f73a Bump version 3.3.24 (#1505) 2025-06-04 19:02:06 +01:00
ilike2burnthing
c55080b0ec Remove hidden character 2025-06-04 18:54:48 +01:00
ilike2burnthing
639bfca020 Bump version 3.3.23 (#1504) 2025-06-04 18:51:31 +01:00
ilike2burnthing
237694df76 Update base image to bookworm. resolves #1503 2025-06-04 18:44:17 +01:00
ilike2burnthing
6e5d6f1795 Bump version 3.3.22 (#1500) 2025-06-03 05:54:25 +01:00
ilike2burnthing
30804a86e5 Bump Chromium to v137 for build 2025-06-03 05:39:46 +01:00
ilike2burnthing
e0bdaf7745 Don't open devtools 2025-06-03 05:38:55 +01:00
ilike2burnthing
795365dbe4 Change from click to keys
credit to @sh4dowb - #1497
2025-06-03 05:38:06 +01:00
ilike2burnthing
ce5369dd41 Update bug_report.yml
fix accidental delete
2025-03-04 02:05:23 +00:00
ilike2burnthing
600b09d498 Update bug_report.yml 2025-03-04 02:04:29 +00:00
ilike2burnthing
d1f19405a1 Update README.md. closes #1267 2025-01-21 20:27:36 +00:00
ilike2burnthing
82a1366d34 Remove dead directory link from readme. resolve #1436 2025-01-20 17:37:14 +00:00
dependabot[bot]
a2fe9e7776 Bump waitress from 2.1.2 to 3.0.1 (#1418)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-29 01:53:34 +00:00
ilike2burnthing
6cc628df9e Disable search engine choice screen 2024-11-24 18:31:50 +00:00
Eduard Tykhoniuk
8b1851eeb1 Fix headless=false stalling 2024-11-24 18:30:35 +00:00
ilike2burnthing
54668a11e7 bug_report: lint fix again 2024-09-28 08:08:49 +01:00
ilike2burnthing
701d8fb4ff bug_report: change to input 2024-09-28 08:07:56 +01:00
ilike2burnthing
39a265ccb8 bug_report: lint fix 2024-09-28 07:58:14 +01:00
ilike2burnthing
e32b247014 bug_report: default=0 2024-09-28 07:57:11 +01:00
ilike2burnthing
0d8fe8fe50 bug_report: no booleans allowed 2024-09-28 07:51:57 +01:00
ilike2burnthing
718da3a36f bug_report: add 'no really' drop down
maybe this will help... 🙄
2024-09-28 07:50:11 +01:00
ilike2burnthing
a798561338 Bump requests version
*.0 was yanked
2024-07-30 02:38:13 +01:00
Bogdan
eb680efc90 Don't build docker images for PRs from forks (#1281) 2024-07-20 22:08:40 +03:00
ilike2burnthing
0f8f0bec25 revert and bump action version 2024-07-20 19:41:49 +01:00
ilike2burnthing
3d9bc5627b Change to GITHUB_TOKEN for GHRC login 2024-07-20 14:21:34 +01:00
ilike2burnthing
dd7eaee2e3 Bump requirements
resolves Dependabot alerts
2024-07-12 17:11:40 +01:00
ilike2burnthing
031177bbdb Bump version 3.3.21 (#1240) 2024-06-26 02:14:25 +01:00
Bogdan
a8644532a1 Escape values for generated form used in request.post (#1236)
and build docker images for PRs
2024-06-26 02:04:59 +01:00
ilike2burnthing
e96161c873 Add challenge selector to catch reloading page on non-English systems. resolves #1237 2024-06-25 22:32:06 +01:00
ilike2burnthing
5a1f25cd52 Bump version 3.3.20 (#1229) 2024-06-21 22:21:37 +01:00
tenettow
a2c0e4348e Update Cloudflare challenge and checkbox selectors (#1224) 2024-06-21 22:07:03 +01:00
ilike2burnthing
2ecf88895b Check not running in Docker before logging version_main error 2024-06-15 08:37:42 +01:00
ilike2burnthing
984368edb5 maxTimeout should always be int. resolves #1212 2024-06-15 05:41:45 +01:00
21hsmw
6c1d78cb84 Fix occasional headless issue on Linux when set to "false" (#1199)
* Fix occasional headless issue on Linux when set to "false"

- Add a variable containing the current platform
- Check if the platform is "nt" (Windows) before closing the driver

* Update CHANGELOG.md

---------

Co-authored-by: ilike2burnthing <59480337+ilike2burnthing@users.noreply.github.com>
2024-05-24 17:33:46 +01:00
ilike2burnthing
5a2c61601e Fix Chrome v124+ not closing on Windows. resolves #1161 (#1193) 2024-05-20 00:52:55 +01:00
ilike2burnthing
c304da2964 Update README.md 2024-04-22 23:30:52 +01:00
ilike2burnthing
b811412699 Fix LANG ENV for Linux. #1036 2024-04-20 03:41:53 +01:00
Ross Patterson
0bb8de144f Add Compose V2 command to readme (#1154)
Co-authored-by: root@library.moodysalon.net <root@library.moodysalon.net>
Co-authored-by: ilike2burnthing <59480337+ilike2burnthing@users.noreply.github.com>
2024-04-18 05:07:06 +01:00
25 changed files with 578 additions and 321 deletions

View File

@@ -29,6 +29,13 @@ body:
options:
- label: I have read the Discussions
required: true
- type: input
attributes:
label: Have you ACTUALLY checked all these?
description: Please do not waste our time and yours; these checks are there for a reason, it is not just so you can tick boxes for fun. If you type <b>YES</b> and it is clear you did not or have put in no effort, your issue will be closed and locked without comment. If you type <b>NO</b> but still open this issue, you will be permanently blocked for timewasting.
placeholder: YES or NO
validations:
required: true
- type: textarea
attributes:
label: Environment

View File

@@ -1,4 +1,4 @@
name: autotag
name: Autotag
on:
push:
@@ -9,11 +9,10 @@ jobs:
tag-release:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v3
-
name: Auto Tag
- name: Checkout repository
uses: actions/checkout@v5
- name: Auto Tag
uses: Klemensas/action-autotag@stable
with:
GITHUB_TOKEN: "${{ secrets.GH_PAT }}"

View File

@@ -1,53 +1,67 @@
name: release-docker
name: Docker release
on:
push:
tags:
- 'v*.*.*'
- "v*.*.*"
pull_request:
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build-docker-images:
runs-on: ubuntu-22.04
if: ${{ !github.event.pull_request.head.repo.fork }}
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v3
-
name: Downcase repo
- name: Checkout repository
uses: actions/checkout@v5
- name: Downcase repo
run: echo REPOSITORY=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV
-
name: Docker meta
- name: Docker meta
id: docker_meta
uses: crazy-max/ghaction-docker-meta@v3
uses: docker/metadata-action@v5
with:
images: ${{ env.REPOSITORY }},ghcr.io/${{ env.REPOSITORY }}
tag-sha: false
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
-
name: Login to DockerHub
uses: docker/login-action@v2
images: |
${{ env.REPOSITORY }},enable=${{ github.event_name != 'pull_request' }}
ghcr.io/${{ env.REPOSITORY }}
tags: |
type=semver,pattern={{version}},prefix=v
type=ref,event=pr
flavor: |
latest=auto
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Login to GitHub Container Registry
uses: docker/login-action@v2
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GH_PAT }}
-
name: Build and push
uses: docker/build-push-action@v3
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
platforms: linux/386,linux/amd64,linux/arm/v7,linux/arm64/v8
push: ${{ github.event_name != 'pull_request' }}
push: true
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }}

View File

@@ -1,95 +1,63 @@
name: release
name: Release
on:
push:
tags:
- 'v*.*.*'
- "v*.*.*"
jobs:
create-release:
name: Create release
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 0 # get all commits, branches and tags (required for the changelog)
fetch-depth: 0
- name: Build changelog
id: github_changelog
run: |
changelog=$(git log $(git tag | tail -2 | head -1)..HEAD --no-merges --oneline)
changelog="${changelog//'%'/'%25'}"
changelog="${changelog//$'\n'/'%0A'}"
changelog="${changelog//$'\r'/'%0D'}"
echo "##[set-output name=changelog;]${changelog}"
echo "changelog<<EOF" >> $GITHUB_OUTPUT
echo "$changelog" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
body: ${{ steps.github_changelog.outputs.changelog }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
build-linux-package:
name: Build Linux binary
build-package:
name: Build binaries
needs: create-release
runs-on: ubuntu-22.04
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 0 # get all commits, branches and tags (required for the changelog)
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: '3.11'
python-version: "3.13"
- name: Build artifacts
run: |
python -m pip install -r requirements.txt
python -m pip install pyinstaller==5.13.0
python -m pip install pyinstaller==6.16.0
cd src
python build_package.py
- name: Upload release artifacts
uses: alexellis/upload-assets@0.4.0
uses: softprops/action-gh-release@v2
with:
files: ./dist/flaresolverr_*
env:
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
with:
asset_paths: '["./dist/flaresolverr_*"]'
build-windows-package:
name: Build Windows binary
needs: create-release
runs-on: windows-2022
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
fetch-depth: 0 # get all commits, branches and tags (required for the changelog)
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Build artifacts
run: |
python -m pip install -r requirements.txt
python -m pip install pyinstaller==5.13.0
cd src
python build_package.py
- name: Upload release artifacts
uses: alexellis/upload-assets@0.4.0
env:
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
with:
asset_paths: '["./dist/flaresolverr_*"]'

View File

@@ -1,5 +1,70 @@
# Changelog
## v3.4.6 (2025/11/29)
* Add disable image, css, fonts option with CDP. Thanks @Ananto30
## v3.4.5 (2025/11/11)
* Revert to Python v3.13
## v3.4.4 (2025/11/04)
* Bump dependencies, Chromium, and some other general fixes. Thanks @flowerey
## v3.4.3 (2025/10/28)
* Update proxy extension
## v3.4.2 (2025/10/09)
* Bump dependencies & CI actions. Thanks @flowerey
* Add optional wait time after resolving the challenge before returning. Thanks @kennedyoliveira
* Add proxy ENVs. Thanks @Robokishan
* Handle empty string and keys without value in postData. Thanks @eZ4RK0
* Add quote protection for password containing it. Thanks @warrenberberd
* Add returnScreenshot parameter to screenshot the final web page. Thanks @estebanthi
* Add log file support. Thanks @acg5159
## v3.4.1 (2025/09/15)
* Fix regex pattern syntax in utils.py
* Change access denied title check to use startswith
## v3.4.0 (2025/08/25)
* Modernize and upgrade application. Thanks @TheCrazyLex
* Remove disable software rasterizer option for ARM builds. Thanks @smrodman83
## v3.3.25 (2025/06/14)
* Remove `use-gl` argument. Thanks @qwerty12
* u_c: remove apparent c&p typo. Thanks @ok3721
* Bump requirements
## v3.3.24 (2025/06/04)
* Remove hidden character
## v3.3.23 (2025/06/04)
* Update base image to bookworm. Thanks @rwjack
## v3.3.22 (2025/06/03)
* Disable search engine choice screen
* Fix headless=false stalling. Thanks @MAKMED1337
* Change from click to keys. Thanks @sh4dowb
* Don't open devtools
* Bump Chromium to v137 for build
* Bump requirements
## v3.3.21 (2024/06/26)
* Add challenge selector to catch reloading page on non-English systems
* Escape values for generated form used in request.post. Thanks @mynameisbogdan
## v3.3.20 (2024/06/21)
* maxTimeout should always be int
* Check not running in Docker before logging version_main error
* Update Cloudflare challenge and checkbox selectors. Thanks @tenettow & @21hsmw
## v3.3.19 (2024/05/23)
* Fix occasional headless issue on Linux when set to "false". Thanks @21hsmw
## v3.3.18 (2024/05/20)
* Fix LANG ENV for Linux
* Fix Chrome v124+ not closing on Windows. Thanks @RileyXX
## v3.3.17 (2024/04/09)
* Fix file descriptor leak in service on quit(). Thanks @zkulis

View File

@@ -1,4 +1,4 @@
FROM python:3.11-slim-bullseye as builder
FROM python:3.13-slim-bookworm AS builder
# Build dummy packages to skip installing them and their dependencies
RUN apt-get update \
@@ -12,7 +12,7 @@ RUN apt-get update \
&& equivs-build adwaita-icon-theme \
&& mv adwaita-icon-theme_*.deb /adwaita-icon-theme.deb
FROM python:3.11-slim-bullseye
FROM python:3.13-slim-bookworm
# Copy dummy packages
COPY --from=builder /*.deb /
@@ -38,7 +38,12 @@ RUN dpkg -i /libgl1-mesa-dri.deb \
# Create flaresolverr user
&& useradd --home-dir /app --shell /bin/sh flaresolverr \
&& mv /usr/bin/chromedriver chromedriver \
&& chown -R flaresolverr:flaresolverr .
&& chown -R flaresolverr:flaresolverr . \
# Create config dir
&& mkdir /config \
&& chown flaresolverr:flaresolverr /config
VOLUME /config
# Install Python dependencies
COPY requirements.txt .
@@ -62,17 +67,17 @@ ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["/usr/local/bin/python", "-u", "/app/flaresolverr.py"]
# Local build
# docker build -t ngosang/flaresolverr:3.3.17 .
# docker run -p 8191:8191 ngosang/flaresolverr:3.3.17
# docker build -t ngosang/flaresolverr:3.4.6 .
# docker run -p 8191:8191 ngosang/flaresolverr:3.4.6
# Multi-arch build
# docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
# docker buildx create --use
# docker buildx build -t ngosang/flaresolverr:3.3.17 --platform linux/386,linux/amd64,linux/arm/v7,linux/arm64/v8 .
# docker buildx build -t ngosang/flaresolverr:3.4.6 --platform linux/386,linux/amd64,linux/arm/v7,linux/arm64/v8 .
# add --push to publish in DockerHub
# Test multi-arch build
# docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
# docker buildx create --use
# docker buildx build -t ngosang/flaresolverr:3.3.17 --platform linux/arm/v7 --load .
# docker run -p 8191:8191 --platform linux/arm/v7 ngosang/flaresolverr:3.3.17
# docker buildx build -t ngosang/flaresolverr:3.4.6 --platform linux/arm/v7 --load .
# docker run -p 8191:8191 --platform linux/arm/v7 ngosang/flaresolverr:3.4.6

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2023 Diego Heras (ngosang / ngosang@hotmail.es)
Copyright (c) 2025 Diego Heras (ngosang / ngosang@hotmail.es)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

214
README.md
View File

@@ -33,22 +33,25 @@ It is recommended to install using a Docker container because the project depend
already included within the image.
Docker images are available in:
* GitHub Registry => https://github.com/orgs/FlareSolverr/packages/container/package/flaresolverr
* DockerHub => https://hub.docker.com/r/flaresolverr/flaresolverr
- GitHub Registry => https://github.com/orgs/FlareSolverr/packages/container/package/flaresolverr
- DockerHub => https://hub.docker.com/r/flaresolverr/flaresolverr
Supported architectures are:
| Architecture | Tag |
|--------------|--------------|
| ------------ | ------------ |
| x86 | linux/386 |
| x86-64 | linux/amd64 |
| ARM32 | linux/arm/v7 |
| ARM64 | linux/arm64 |
We provide a `docker-compose.yml` configuration file. Clone this repository and execute `docker-compose up -d` to start
We provide a `docker-compose.yml` configuration file. Clone this repository and execute
`docker-compose up -d` _(Compose V1)_ or `docker compose up -d` _(Compose V2)_ to start
the container.
If you prefer the `docker cli` execute the following command.
```bash
docker run -d \
--name=flaresolverr \
@@ -58,7 +61,7 @@ docker run -d \
ghcr.io/flaresolverr/flaresolverr:latest
```
If your host OS is Debian, make sure `libseccomp2` version is 2.5.x. You can check the version with `sudo apt-cache policy libseccomp2`
If your host OS is Debian, make sure `libseccomp2` version is 2.5.x. You can check the version with `sudo apt-cache policy libseccomp2`
and update the package with `sudo apt install libseccomp2=2.5.1-1~bpo10+1` or `sudo apt install libseccomp2=2.5.1-1+deb11u1`.
Remember to restart the Docker daemon and the container after the update.
@@ -68,27 +71,29 @@ Remember to restart the Docker daemon and the container after the update.
> Precompiled binaries are only available for x64 architecture. For other architectures see Docker images.
This is the recommended way for Windows users.
* Download the [FlareSolverr executable](https://github.com/FlareSolverr/FlareSolverr/releases) from the release's page. It is available for Windows x64 and Linux x64.
* Execute FlareSolverr binary. In the environment variables section you can find how to change the configuration.
- Download the [FlareSolverr executable](https://github.com/FlareSolverr/FlareSolverr/releases) from the release's page. It is available for Windows x64 and Linux x64.
- Execute FlareSolverr binary. In the environment variables section you can find how to change the configuration.
### From source code
> **Warning**
> Installing from source code only works for x64 architecture. For other architectures see Docker images.
* Install [Python 3.11](https://www.python.org/downloads/).
* Install [Chrome](https://www.google.com/intl/en_us/chrome/) (all OS) or [Chromium](https://www.chromium.org/getting-involved/download-chromium/) (just Linux, it doesn't work in Windows) web browser.
* (Only in Linux / macOS) Install [Xvfb](https://en.wikipedia.org/wiki/Xvfb) package.
* Clone this repository and open a shell in that path.
* Run `pip install -r requirements.txt` command to install FlareSolverr dependencies.
* Run `python src/flaresolverr.py` command to start FlareSolverr.
- Install [Python 3.13](https://www.python.org/downloads/).
- Install [Chrome](https://www.google.com/intl/en_us/chrome/) (all OS) or [Chromium](https://www.chromium.org/getting-involved/download-chromium/) (just Linux, it doesn't work in Windows) web browser.
- (Only in Linux) Install [Xvfb](https://en.wikipedia.org/wiki/Xvfb) package.
- (Only in macOS) Install [XQuartz](https://www.xquartz.org/) package.
- Clone this repository and open a shell in that path.
- Run `pip install -r requirements.txt` command to install FlareSolverr dependencies.
- Run `python src/flaresolverr.py` command to start FlareSolverr.
### From source code (FreeBSD/TrueNAS CORE)
* Run `pkg install chromium python39 py39-pip xorg-vfbserver` command to install the required dependencies.
* Clone this repository and open a shell in that path.
* Run `python3.9 -m pip install -r requirements.txt` command to install FlareSolverr dependencies.
* Run `python3.9 src/flaresolverr.py` command to start FlareSolverr.
- Run `pkg install chromium python313 py313-pip xorg-vfbserver` command to install the required dependencies.
- Clone this repository and open a shell in that path.
- Run `python3.13 -m pip install -r requirements.txt` command to install FlareSolverr dependencies.
- Run `python3.13 src/flaresolverr.py` command to start FlareSolverr.
### Systemd service
@@ -97,6 +102,7 @@ We provide an example Systemd unit file `flaresolverr.service` as reference. You
## Usage
Example Bash request:
```bash
curl -L -X POST 'http://localhost:8191/v1' \
-H 'Content-Type: application/json' \
@@ -108,6 +114,7 @@ curl -L -X POST 'http://localhost:8191/v1' \
```
Example Python request:
```py
import requests
@@ -123,6 +130,7 @@ print(response.text)
```
Example PowerShell request:
```ps1
$body = @{
cmd = "request.get"
@@ -143,9 +151,9 @@ cookies for the browser to use.
This also speeds up the requests since it won't have to launch a new browser instance for every request.
| 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. |
| 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. |
| 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`
@@ -158,11 +166,7 @@ Example response:
```json
{
"sessions": [
"session_id_1",
"session_id_2",
"session_id_3..."
]
"sessions": ["session_id_1", "session_id_2", "session_id_3..."]
}
```
@@ -172,20 +176,24 @@ This will properly shutdown a browser instance and remove all files associated w
session. When you no longer need to use a session you should make sure to close it.
| Parameter | Notes |
|-----------|-----------------------------------------------|
| --------- | --------------------------------------------- |
| session | The session ID that you want to be destroyed. |
#### + `request.get`
| Parameter | Notes |
|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| url | Mandatory |
| session | Optional. Will send the request from and existing browser instance. If one is not sent it will create a temporary instance that will be destroyed immediately after the request is completed. |
| session_ttl_minutes | Optional. FlareSolverr will automatically rotate expired sessions based on the TTL provided in minutes. |
| maxTimeout | Optional, default value 60000. Max timeout to solve the challenge in milliseconds. |
| cookies | Optional. Will be used by the headless browser. Eg: `"cookies": [{"name": "cookie1", "value": "value1"}, {"name": "cookie2", "value": "value2"}]`. |
| returnOnlyCookies | Optional, default false. Only returns the cookies. Response data, headers and other parts of the response are removed. |
| returnScreenshot | Optional, default false. Captures a screenshot of the final rendered page after all challenges and waits are completed. The screenshot is returned as a Base64-encoded PNG string in the `screenshot` field of the response. |
| 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 not supported. (When the `session` parameter is set, the proxy is ignored; a session specific proxy can be set in `sessions.create`.) |
| waitInSeconds | Optional, default none. Length to wait in seconds after solving the challenge, and before returning the results. Useful to allow it to load dynamic content. |
| disableMedia | Optional, default false. When true FlareSolverr will prevent media resources (images, CSS, and fonts) from being loaded to speed up navigation. |
| tabs_till_verify | Optional, default none. Number of times the `Tab` button is needed to be pressed to end up on the turnstile captcha, in order to verify it. After verifying the captcha, the result will be stored in the solution under `turnstile_token`. |
> **Warning**
> If you want to use Cloudflare clearance cookie in your scripts, make sure you use the FlareSolverr User-Agent too. If they don't match you will see the challenge.
@@ -194,96 +202,103 @@ Example response from running the `curl` above:
```json
{
"solution": {
"url": "https://www.google.com/?gws_rd=ssl",
"status": 200,
"headers": {
"status": "200",
"date": "Thu, 16 Jul 2020 04:15:49 GMT",
"expires": "-1",
"cache-control": "private, max-age=0",
"content-type": "text/html; charset=UTF-8",
"strict-transport-security": "max-age=31536000",
"p3p": "CP=\"This is not a P3P policy! See g.co/p3phelp for more info.\"",
"content-encoding": "br",
"server": "gws",
"content-length": "61587",
"x-xss-protection": "0",
"x-frame-options": "SAMEORIGIN",
"set-cookie": "1P_JAR=2020-07-16-04; expires=Sat..."
},
"response":"<!DOCTYPE html>...",
"cookies": [
{
"name": "NID",
"value": "204=QE3Ocq15XalczqjuDy52HeseG3zAZuJzID3R57...",
"domain": ".google.com",
"path": "/",
"expires": 1610684149.307722,
"size": 178,
"httpOnly": true,
"secure": true,
"session": false,
"sameSite": "None"
},
{
"name": "1P_JAR",
"value": "2020-07-16-04",
"domain": ".google.com",
"path": "/",
"expires": 1597464949.307626,
"size": 19,
"httpOnly": false,
"secure": true,
"session": false,
"sameSite": "None"
}
],
"userAgent": "Windows NT 10.0; Win64; x64) AppleWebKit/5..."
"solution": {
"url": "https://www.google.com/?gws_rd=ssl",
"status": 200,
"headers": {
"status": "200",
"date": "Thu, 16 Jul 2020 04:15:49 GMT",
"expires": "-1",
"cache-control": "private, max-age=0",
"content-type": "text/html; charset=UTF-8",
"strict-transport-security": "max-age=31536000",
"p3p": "CP=\"This is not a P3P policy! See g.co/p3phelp for more info.\"",
"content-encoding": "br",
"server": "gws",
"content-length": "61587",
"x-xss-protection": "0",
"x-frame-options": "SAMEORIGIN",
"set-cookie": "1P_JAR=2020-07-16-04; expires=Sat..."
},
"status": "ok",
"message": "",
"startTimestamp": 1594872947467,
"endTimestamp": 1594872949617,
"version": "1.0.0"
"response": "<!DOCTYPE html>...",
"cookies": [
{
"name": "NID",
"value": "204=QE3Ocq15XalczqjuDy52HeseG3zAZuJzID3R57...",
"domain": ".google.com",
"path": "/",
"expires": 1610684149.307722,
"size": 178,
"httpOnly": true,
"secure": true,
"session": false,
"sameSite": "None"
},
{
"name": "1P_JAR",
"value": "2020-07-16-04",
"domain": ".google.com",
"path": "/",
"expires": 1597464949.307626,
"size": 19,
"httpOnly": false,
"secure": true,
"session": false,
"sameSite": "None"
}
],
"userAgent": "Windows NT 10.0; Win64; x64) AppleWebKit/5...",
"turnstile_token": "03AGdBq24k3lK7JH2v8uN1T5F..."
},
"status": "ok",
"message": "",
"startTimestamp": 1594872947467,
"endTimestamp": 1594872949617,
"version": "1.0.0"
}
```
### + `request.post`
This is the same as `request.get` but it takes one more param:
This works like `request.get`, with the addition of the postData parameter. Note that `tabs_till_verify` is currently supported only for GET requests and requires one extra argument.
| Parameter | Notes |
|-----------|--------------------------------------------------------------------------|
| --------- | ------------------------------------------------------------------------ |
| postData | Must be a string with `application/x-www-form-urlencoded`. Eg: `a=b&c=d` |
## Environment variables
| Name | Default | Notes |
|--------------------|------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| LOG_LEVEL | info | Verbosity of the logging. Use `LOG_LEVEL=debug` for more information. |
| LOG_HTML | false | Only for debugging. If `true` all HTML that passes through the proxy will be logged to the console in `debug` level. |
| CAPTCHA_SOLVER | none | Captcha solving method. It is used when a captcha is encountered. See the Captcha Solvers section. |
| TZ | UTC | Timezone used in the logs and the web browser. Example: `TZ=Europe/London`. |
| LANG | none | Language used in the web browser. Example: `LANG=en_GB`. |
| HEADLESS | true | Only for debugging. To run the web browser in headless mode or visible. |
| BROWSER_TIMEOUT | 40000 | If you are experiencing errors/timeouts because your system is slow, you can try to increase this value. Remember to increase the `maxTimeout` parameter too. |
| TEST_URL | https://www.google.com | FlareSolverr makes a request on start to make sure the web browser is working. You can change that URL if it is blocked in your country. |
| PORT | 8191 | Listening port. You don't need to change this if you are running on Docker. |
| HOST | 0.0.0.0 | Listening interface. You don't need to change this if you are running on Docker. |
| PROMETHEUS_ENABLED | false | Enable Prometheus exporter. See the Prometheus section below. |
| PROMETHEUS_PORT | 8192 | Listening port for Prometheus exporter. See the Prometheus section below. |
| Name | Default | Notes |
| ------------------ | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| LOG_LEVEL | info | Verbosity of the logging. Use `LOG_LEVEL=debug` for more information. |
| LOG_FILE | none | Path to capture log to file. Example: `/config/flaresolverr.log`. |
| LOG_HTML | false | Only for debugging. If `true` all HTML that passes through the proxy will be logged to the console in `debug` level. |
| PROXY_URL | none | URL for proxy. Will be overwritten by `request` or `sessions` proxy, if used. Example: `http://127.0.0.1:8080`. |
| PROXY_USERNAME | none | Username for proxy. Will be overwritten by `request` or `sessions` proxy, if used. Example: `testuser`. |
| PROXY_PASSWORD | none | Password for proxy. Will be overwritten by `request` or `sessions` proxy, if used. Example: `testpass`. |
| CAPTCHA_SOLVER | none | Captcha solving method. It is used when a captcha is encountered. See the Captcha Solvers section. |
| TZ | UTC | Timezone used in the logs and the web browser. Example: `TZ=Europe/London`. |
| LANG | none | Language used in the web browser. Example: `LANG=en_GB`. |
| HEADLESS | true | Only for debugging. To run the web browser in headless mode or visible. |
| DISABLE_MEDIA | false | To disable loading images, CSS, and other media in the web browser to save network bandwidth. |
| TEST_URL | https://www.google.com | FlareSolverr makes a request on start to make sure the web browser is working. You can change that URL if it is blocked in your country. |
| PORT | 8191 | Listening port. You don't need to change this if you are running on Docker. |
| HOST | 0.0.0.0 | Listening interface. You don't need to change this if you are running on Docker. |
| PROMETHEUS_ENABLED | false | Enable Prometheus exporter. See the Prometheus section below. |
| PROMETHEUS_PORT | 8192 | Listening port for Prometheus exporter. See the Prometheus section below. |
Environment variables are set differently depending on the operating system. Some examples:
* Docker: Take a look at the Docker section in this document. Environment variables can be set in the `docker-compose.yml` file or in the Docker CLI command.
* Linux: Run `export LOG_LEVEL=debug` and then run `flaresolverr` in the same shell.
* Windows: Open `cmd.exe`, run `set LOG_LEVEL=debug` and then run `flaresolverr.exe` in the same shell.
- Docker: Take a look at the Docker section in this document. Environment variables can be set in the `docker-compose.yml` file or in the Docker CLI command.
- Linux: Run `export LOG_LEVEL=debug` and then run `flaresolverr` in the same shell.
- Windows: Open `cmd.exe`, run `set LOG_LEVEL=debug` and then run `flaresolverr.exe` in the same shell.
## Prometheus exporter
The Prometheus exporter for FlareSolverr is disabled by default. It can be enabled with the environment variable `PROMETHEUS_ENABLED`. If you are using Docker make sure you expose the `PROMETHEUS_PORT`.
Example metrics:
```shell
# HELP flaresolverr_request_total Total requests with result
# TYPE flaresolverr_request_total counter
@@ -315,8 +330,9 @@ solve a captcha.
If this is the case, FlareSolverr will return the error `Captcha detected but no automatic solver is configured.`
FlareSolverr can be customized to solve the CAPTCHA automatically by setting the environment variable `CAPTCHA_SOLVER`
to the file name of one of the adapters inside the [/captcha](src/captcha) directory.
to the file name of one of the adapters inside the `/captcha` directory.
## Related projects
* C# implementation => https://github.com/FlareSolverr/FlareSolverrSharp
- C# implementation => https://github.com/FlareSolverr/FlareSolverrSharp

View File

@@ -7,9 +7,12 @@ services:
container_name: flaresolverr
environment:
- LOG_LEVEL=${LOG_LEVEL:-info}
- LOG_FILE=${LOG_FILE:-none}
- LOG_HTML=${LOG_HTML:-false}
- CAPTCHA_SOLVER=${CAPTCHA_SOLVER:-none}
- TZ=Europe/London
ports:
- "${PORT:-8191}:8191"
volumes:
- /var/lib/flaresolver:/config
restart: unless-stopped

19
flaresolverr.service Normal file
View File

@@ -0,0 +1,19 @@
[Unit]
Description=FlareSolverr
After=network.target
[Service]
SyslogIdentifier=flaresolverr
Restart=always
RestartSec=5
Type=simple
User=flaresolverr
Group=flaresolverr
Environment="LOG_LEVEL=info"
Environment="CAPTCHA_SOLVER=none"
WorkingDirectory=/opt/flaresolverr
ExecStart=/opt/flaresolverr/flaresolverr
TimeoutStopSec=30
[Install]
WantedBy=multi-user.target

View File

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

View File

@@ -1,13 +1,14 @@
bottle==0.12.25
waitress==2.1.2
selenium==4.15.2
bottle==0.13.4
waitress==3.0.2
selenium==4.38.0
func-timeout==4.3.5
prometheus-client==0.17.1
# required by undetected_chromedriver
requests==2.31.0
certifi==2023.7.22
websockets==11.0.3
# only required for linux and macos
xvfbwrapper==0.2.9; platform_system != "Windows"
# only required for windows
pefile==2023.2.7; platform_system == "Windows"
prometheus-client==0.23.1
# Required by undetected_chromedriver
requests==2.32.5
certifi==2025.10.5
websockets==15.0.1
packaging==25.0
# Only required for Linux and macOS
xvfbwrapper==0.2.15; platform_system != "Windows"
# Only required for Windows
pefile==2024.8.26; platform_system == "Windows"

View File

@@ -5,7 +5,7 @@ import logging
def logger_plugin(callback):
"""
Bottle plugin to use logging module
http://bottlepy.org/docs/dev/plugindev.html
https://bottlepy.org/docs/dev/plugindev.html
Wrap a Bottle request so that a log line is emitted after it's handled.
(This decorator can be extended to take the desired logger as a param.)

View File

@@ -18,7 +18,7 @@ def setup():
def prometheus_plugin(callback):
"""
Bottle plugin to expose Prometheus metrics
http://bottlepy.org/docs/dev/plugindev.html
https://bottlepy.org/docs/dev/plugindev.html
"""
def wrapper(*args, **kwargs):
actual_response = callback(*args, **kwargs)

View File

@@ -25,7 +25,7 @@ def clean_files():
def download_chromium():
# https://commondatastorage.googleapis.com/chromium-browser-snapshots/index.html?prefix=Linux_x64/
revision = "1260008" if os.name == 'nt' else '1260015'
revision = "1522586" if os.name == 'nt' else '1522586'
arch = 'Win_x64' if os.name == 'nt' else 'Linux_x64'
dl_file = 'chrome-win' if os.name == 'nt' else 'chrome-linux'
dl_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, 'dist_chrome')

View File

@@ -10,6 +10,8 @@ class ChallengeResolutionResultT:
response: str = None
cookies: list = None
userAgent: str = None
screenshot: str | None = None
turnstile_token: str = None
def __init__(self, _dict):
self.__dict__.update(_dict)
@@ -41,8 +43,14 @@ class V1RequestBase(object):
url: str = None
postData: str = None
returnOnlyCookies: bool = None
returnScreenshot: bool = None
download: bool = None # deprecated v2.0.0, not used
returnRawHtml: bool = None # deprecated v2.0.0, not used
waitInSeconds: int = None
# Optional resource blocking flag (blocks images, CSS, and fonts)
disableMedia: bool = None
# Optional when you've got a turnstile captcha that needs to be clicked after X number of Tab presses
tabs_till_verify : int = None
def __init__(self, _dict):
self.__dict__.update(_dict)

View File

@@ -13,6 +13,10 @@ from dtos import V1RequestBase
import flaresolverr_service
import utils
env_proxy_url = os.environ.get('PROXY_URL', None)
env_proxy_username = os.environ.get('PROXY_USERNAME', None)
env_proxy_password = os.environ.get('PROXY_PASSWORD', None)
class JSONErrorBottle(Bottle):
"""
@@ -50,7 +54,14 @@ def controller_v1():
"""
Controller v1
"""
req = V1RequestBase(request.json)
data = request.json or {}
if (('proxy' not in data or not data.get('proxy')) and env_proxy_url is not None and (env_proxy_username is None and env_proxy_password is None)):
logging.info('Using proxy URL ENV')
data['proxy'] = {"url": env_proxy_url}
if (('proxy' not in data or not data.get('proxy')) and env_proxy_url is not None and (env_proxy_username is not None or env_proxy_password is not None)):
logging.info('Using proxy URL, username & password ENVs')
data['proxy'] = {"url": env_proxy_url, "username": env_proxy_username, "password": env_proxy_password}
req = V1RequestBase(data)
res = flaresolverr_service.controller_v1_endpoint(req)
if res.__error_500__:
response.status = 500
@@ -70,12 +81,13 @@ if __name__ == "__main__":
# fix ssl certificates for compiled binaries
# https://github.com/pyinstaller/pyinstaller/issues/7229
# https://stackoverflow.com/questions/55736855/how-to-change-the-cafile-argument-in-the-ssl-module-in-python3
# https://stackoverflow.com/q/55736855
os.environ["REQUESTS_CA_BUNDLE"] = certifi.where()
os.environ["SSL_CERT_FILE"] = certifi.where()
# validate configuration
log_level = os.environ.get('LOG_LEVEL', 'info').upper()
log_file = os.environ.get('LOG_FILE', None)
log_html = utils.get_config_log_html()
headless = utils.get_config_headless()
server_host = os.environ.get('HOST', '0.0.0.0')
@@ -85,14 +97,29 @@ if __name__ == "__main__":
logger_format = '%(asctime)s %(levelname)-8s %(message)s'
if log_level == 'DEBUG':
logger_format = '%(asctime)s %(levelname)-8s ReqId %(thread)s %(message)s'
logging.basicConfig(
format=logger_format,
level=log_level,
datefmt='%Y-%m-%d %H:%M:%S',
handlers=[
logging.StreamHandler(sys.stdout)
]
)
if log_file:
log_file = os.path.realpath(log_file)
log_path = os.path.dirname(log_file)
os.makedirs(log_path, exist_ok=True)
logging.basicConfig(
format=logger_format,
level=log_level,
datefmt='%Y-%m-%d %H:%M:%S',
handlers=[
logging.StreamHandler(sys.stdout),
logging.FileHandler(log_file)
]
)
else:
logging.basicConfig(
format=logger_format,
level=log_level,
datefmt='%Y-%m-%d %H:%M:%S',
handlers=[
logging.StreamHandler(sys.stdout)
]
)
# disable warning traces from urllib3
logging.getLogger('urllib3').setLevel(logging.ERROR)
logging.getLogger('selenium.webdriver.remote.remote_connection').setLevel(logging.WARNING)
@@ -101,6 +128,9 @@ if __name__ == "__main__":
logging.info(f'FlareSolverr {utils.get_flaresolverr_version()}')
logging.debug('Debug log enabled')
# Get current OS for global variable
utils.get_current_platform()
# test browser installation
flaresolverr_service.test_browser_installation()

View File

@@ -3,12 +3,14 @@ import platform
import sys
import time
from datetime import timedelta
from urllib.parse import unquote
from html import escape
from urllib.parse import unquote, quote
from func_timeout import FunctionTimedOut, func_timeout
from selenium.common import TimeoutException
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.expected_conditions import (
presence_of_element_located, staleness_of, title_is)
from selenium.webdriver.common.action_chains import ActionChains
@@ -40,12 +42,17 @@ CHALLENGE_TITLES = [
]
CHALLENGE_SELECTORS = [
# Cloudflare
'#cf-challenge-running', '.ray_id', '.attack-box', '#cf-please-wait', '#challenge-spinner', '#trk_jschal_js',
'#cf-challenge-running', '.ray_id', '.attack-box', '#cf-please-wait', '#challenge-spinner', '#trk_jschal_js', '#turnstile-wrapper', '.lds-ring',
# Custom CloudFlare for EbookParadijs, Film-Paleis, MuziekFabriek and Puur-Hollands
'td.info #js_info',
# Fairlane / pararius.com
'div.vc div.text-box h2'
]
TURNSTILE_SELECTORS = [
"input[name='cf-turnstile-response']"
]
SHORT_TIMEOUT = 1
SESSIONS_STORAGE = SessionsStorage()
@@ -119,7 +126,7 @@ def _controller_v1_handler(req: V1RequestBase) -> V1ResponseBase:
logging.warning("Request parameter 'userAgent' was removed in FlareSolverr v2.")
# set default values
if req.maxTimeout is None or req.maxTimeout < 1:
if req.maxTimeout is None or int(req.maxTimeout) < 1:
req.maxTimeout = 60000
# execute the command
@@ -220,7 +227,7 @@ def _cmd_sessions_destroy(req: V1RequestBase) -> V1ResponseBase:
def _resolve_challenge(req: V1RequestBase, method: str) -> ChallengeResolutionT:
timeout = req.maxTimeout / 1000
timeout = int(req.maxTimeout) / 1000
driver = None
try:
if req.session:
@@ -245,25 +252,23 @@ def _resolve_challenge(req: V1RequestBase, method: str) -> ChallengeResolutionT:
raise Exception('Error solving the challenge. ' + str(e).replace('\n', '\\n'))
finally:
if not req.session and driver is not None:
if utils.PLATFORM_VERSION == "nt":
driver.close()
driver.quit()
logging.debug('A used instance of webdriver has been destroyed')
def click_verify(driver: WebDriver):
def click_verify(driver: WebDriver, num_tabs: int = 1):
try:
logging.debug("Try to find the Cloudflare verify checkbox...")
iframe = driver.find_element(By.XPATH, "//iframe[starts-with(@id, 'cf-chl-widget-')]")
driver.switch_to.frame(iframe)
checkbox = driver.find_element(
by=By.XPATH,
value='//*[@id="challenge-stage"]/div/label/input',
)
if checkbox:
actions = ActionChains(driver)
actions.move_to_element_with_offset(checkbox, 5, 7)
actions.click(checkbox)
actions.perform()
logging.debug("Cloudflare verify checkbox found and clicked!")
actions = ActionChains(driver)
actions.pause(5)
for _ in range(num_tabs):
actions.send_keys(Keys.TAB).pause(0.1)
actions.pause(1)
actions.send_keys(Keys.SPACE).perform()
logging.debug(f"Cloudflare verify checkbox clicked after {num_tabs} tabs!")
except Exception:
logging.debug("Cloudflare verify checkbox not found on the page.")
finally:
@@ -286,36 +291,90 @@ def click_verify(driver: WebDriver):
time.sleep(2)
def _get_turnstile_token(driver: WebDriver, tabs: int):
token_input = driver.find_element(By.CSS_SELECTOR, "input[name='cf-turnstile-response']")
current_value = token_input.get_attribute("value")
while True:
click_verify(driver, num_tabs=tabs)
turnstile_token = token_input.get_attribute("value")
if turnstile_token:
if turnstile_token != current_value:
logging.info(f"Turnstile token: {turnstile_token}")
return turnstile_token
logging.debug(f"Failed to extract token possibly click failed")
def get_correct_window(driver: WebDriver) -> WebDriver:
if len(driver.window_handles) > 1:
for window_handle in driver.window_handles:
driver.switch_to.window(window_handle)
current_url = driver.current_url
if not current_url.startswith("devtools://devtools"):
return driver
return driver
# reset focus
driver.execute_script("""
let el = document.createElement('button');
el.style.position='fixed';
el.style.top='0';
el.style.left='0';
document.body.prepend(el);
el.focus();
""")
time.sleep(1)
def _resolve_turnstile_captcha(req: V1RequestBase, driver: WebDriver):
turnstile_token = None
if req.tabs_till_verify is not None:
logging.debug(f'Navigating to... {req.url} in order to pass the turnstile challenge')
driver.get(req.url)
def access_page(driver: WebDriver, url: str) -> None:
driver.get(url)
driver.start_session()
driver.start_session() # required to bypass Cloudflare
turnstile_challenge_found = False
for selector in TURNSTILE_SELECTORS:
found_elements = driver.find_elements(By.CSS_SELECTOR, selector)
if len(found_elements) > 0:
turnstile_challenge_found = True
logging.info("Turnstile challenge detected. Selector found: " + selector)
break
if turnstile_challenge_found:
turnstile_token = _get_turnstile_token(driver=driver, tabs=req.tabs_till_verify)
else:
logging.debug(f'Turnstile challenge not found')
return turnstile_token
def _evil_logic(req: V1RequestBase, driver: WebDriver, method: str) -> ChallengeResolutionT:
res = ChallengeResolutionT({})
res.status = STATUS_OK
res.message = ""
# optionally block resources like images/css/fonts using CDP
disable_media = utils.get_config_disable_media()
if req.disableMedia is not None:
disable_media = req.disableMedia
if disable_media:
block_urls = [
# Images
"*.png", "*.jpg", "*.jpeg", "*.gif", "*.webp", "*.bmp", "*.svg", "*.ico",
"*.PNG", "*.JPG", "*.JPEG", "*.GIF", "*.WEBP", "*.BMP", "*.SVG", "*.ICO",
"*.tiff", "*.tif", "*.jpe", "*.apng", "*.avif", "*.heic", "*.heif",
"*.TIFF", "*.TIF", "*.JPE", "*.APNG", "*.AVIF", "*.HEIC", "*.HEIF",
# Stylesheets
"*.css",
"*.CSS",
# Fonts
"*.woff", "*.woff2", "*.ttf", "*.otf", "*.eot",
"*.WOFF", "*.WOFF2", "*.TTF", "*.OTF", "*.EOT"
]
try:
logging.debug("Network.setBlockedURLs: %s", block_urls)
driver.execute_cdp_cmd("Network.enable", {})
driver.execute_cdp_cmd("Network.setBlockedURLs", {"urls": block_urls})
except Exception:
# if CDP commands are not available or fail, ignore and continue
logging.debug("Network.setBlockedURLs failed or unsupported on this webdriver")
# navigate to the page
logging.debug(f'Navigating to... {req.url}')
if method == 'POST':
logging.debug(f"Navigating to... {req.url}")
turnstile_token = None
if method == "POST":
_post_request(req, driver)
else:
access_page(driver, req.url)
driver = get_correct_window(driver)
if req.tabs_till_verify is None:
driver.get(req.url)
else:
turnstile_token = _resolve_turnstile_captcha(req, driver)
# set cookies if required
if req.cookies is not None and len(req.cookies) > 0:
@@ -327,8 +386,7 @@ def _evil_logic(req: V1RequestBase, driver: WebDriver, method: str) -> Challenge
if method == 'POST':
_post_request(req, driver)
else:
access_page(driver, req.url)
driver = get_correct_window(driver)
driver.get(req.url)
# wait for the page
if utils.get_config_log_html():
@@ -338,7 +396,7 @@ def _evil_logic(req: V1RequestBase, driver: WebDriver, method: str) -> Challenge
# find access denied titles
for title in ACCESS_DENIED_TITLES:
if title == page_title:
if page_title.startswith(title):
raise Exception('Cloudflare has blocked this request. '
'Probably your IP is banned for this site, check in your web browser.')
# find access denied selectors
@@ -410,21 +468,30 @@ def _evil_logic(req: V1RequestBase, driver: WebDriver, method: str) -> Challenge
challenge_res.status = 200 # todo: fix, selenium not provides this info
challenge_res.cookies = driver.get_cookies()
challenge_res.userAgent = utils.get_user_agent(driver)
challenge_res.turnstile_token = turnstile_token
if not req.returnOnlyCookies:
challenge_res.headers = {} # todo: fix, selenium not provides this info
if req.waitInSeconds and req.waitInSeconds > 0:
logging.info("Waiting " + str(req.waitInSeconds) + " seconds before returning the response...")
time.sleep(req.waitInSeconds)
challenge_res.response = driver.page_source
if req.returnScreenshot:
challenge_res.screenshot = driver.get_screenshot_as_base64()
res.result = challenge_res
return res
def _post_request(req: V1RequestBase, driver: WebDriver):
post_form = f'<form id="hackForm" action="{req.url}" method="POST">'
query_string = req.postData if req.postData[0] != '?' else req.postData[1:]
query_string = req.postData if req.postData and req.postData[0] != '?' else req.postData[1:] if req.postData else ''
pairs = query_string.split('&')
for pair in pairs:
parts = pair.split('=')
parts = pair.split('=', 1)
# noinspection PyBroadException
try:
name = unquote(parts[0])
@@ -434,10 +501,12 @@ def _post_request(req: V1RequestBase, driver: WebDriver):
continue
# noinspection PyBroadException
try:
value = unquote(parts[1])
value = unquote(parts[1]) if len(parts) > 1 else ''
except Exception:
value = parts[1]
post_form += f'<input type="text" name="{name}" value="{value}"><br>'
value = parts[1] if len(parts) > 1 else ''
# Protection of " character, for syntax
value=value.replace('"','&quot;')
post_form += f'<input type="text" name="{escape(quote(name))}" value="{escape(quote(value))}"><br>'
post_form += '</form>'
html_content = f"""
<!DOCTYPE html>
@@ -447,6 +516,4 @@ def _post_request(req: V1RequestBase, driver: WebDriver):
<script>document.getElementById('hackForm').submit();</script>
</body>
</html>"""
driver.get("data:text/html;charset=utf-8," + html_content)
driver.start_session()
driver.start_session() # required to bypass Cloudflare
driver.get("data:text/html;charset=utf-8,{html_content}".format(html_content=html_content))

View File

@@ -66,6 +66,8 @@ class SessionsStorage:
return False
session = self.sessions.pop(session_id)
if utils.PLATFORM_VERSION == "nt":
session.driver.close()
session.driver.quit()
return True

View File

@@ -21,11 +21,11 @@ class TestFlareSolverr(unittest.TestCase):
proxy_socks_url = "socks5://127.0.0.1:1080"
google_url = "https://www.google.com"
post_url = "https://httpbin.org/post"
cloudflare_url = "https://nowsecure.nl"
cloudflare_url = "https://nowsecure.nl/"
cloudflare_url_2 = "https://idope.se/torrent-list/harry/"
ddos_guard_url = "https://anidex.info/"
ddos_guard_url = "https://www.litres.ru/"
fairlane_url = "https://www.pararius.com/apartments/amsterdam"
custom_cloudflare_url = "https://www.muziekfabriek.org"
custom_cloudflare_url = "https://www.muziekfabriek.org/"
cloudflare_blocked_url = "https://cpasbiens3.fr/index.php?do=search&subaction=search"
app = TestApp(flaresolverr.app)
@@ -92,6 +92,29 @@ class TestFlareSolverr(unittest.TestCase):
self.assertGreater(len(solution.cookies), 0)
self.assertIn("Chrome/", solution.userAgent)
def test_v1_endpoint_request_get_disable_resources(self):
res = self.app.post_json("/v1", {
"cmd": "request.get",
"url": self.google_url,
"disableMedia": True
})
self.assertEqual(res.status_code, 200)
body = V1ResponseBase(res.json)
self.assertEqual(STATUS_OK, body.status)
self.assertEqual("Challenge not detected!", body.message)
self.assertGreater(body.startTimestamp, 10000)
self.assertGreaterEqual(body.endTimestamp, body.startTimestamp)
self.assertEqual(utils.get_flaresolverr_version(), body.version)
solution = body.solution
self.assertIn(self.google_url, solution.url)
self.assertEqual(solution.status, 200)
self.assertIs(len(solution.headers), 0)
self.assertIn("<title>Google</title>", solution.response)
self.assertGreater(len(solution.cookies), 0)
self.assertIn("Chrome/", solution.userAgent)
def test_v1_endpoint_request_get_cloudflare_js_1(self):
res = self.app.post_json('/v1', {
"cmd": "request.get",
@@ -162,7 +185,7 @@ class TestFlareSolverr(unittest.TestCase):
self.assertIn(self.ddos_guard_url, solution.url)
self.assertEqual(solution.status, 200)
self.assertIs(len(solution.headers), 0)
self.assertIn("<title>AniDex</title>", solution.response)
self.assertIn("<title>Литрес", solution.response)
self.assertGreater(len(solution.cookies), 0)
self.assertIn("Chrome/", solution.userAgent)

View File

@@ -471,7 +471,7 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
self.patcher.executable_path
)
super(Chrome, self).__init__(
super().__init__(
service=service,
options=options,
keep_alive=keep_alive,
@@ -507,8 +507,6 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
"Page.addScriptToEvaluateOnNewDocument",
{
"source": """
Object.defineProperty(window, "navigator", {
Object.defineProperty(window, "navigator", {
value: new Proxy(navigator, {
has: (target, key) => (key === "webdriver" ? false : key in target),
@@ -729,10 +727,8 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
def start_session(self, capabilities=None, browser_profile=None):
if not capabilities:
capabilities = self.options.to_capabilities()
super(selenium.webdriver.chrome.webdriver.WebDriver, self).start_session(
capabilities
)
# super(Chrome, self).start_session(capabilities, browser_profile)
super().start_session(capabilities)
# super(Chrome, self).start_session(capabilities, browser_profile) # Original explicit call commented out
def find_elements_recursive(self, by, value):
"""
@@ -788,15 +784,6 @@ class Chrome(selenium.webdriver.chrome.webdriver.WebDriver):
logger.debug("gracefully closed browser")
except Exception as e: # noqa
pass
# Force kill Chrome process in Windows
# https://github.com/FlareSolverr/FlareSolverr/issues/772
if os.name == 'nt':
try:
subprocess.call(['taskkill', '/f', '/pid', str(self.browser_pid)],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
except Exception:
pass
if (
hasattr(self, "keep_user_data_dir")
and hasattr(self, "user_data_dir")

View File

@@ -2,6 +2,7 @@ import asyncio
from collections.abc import Mapping
from collections.abc import Sequence
from functools import wraps
import os
import logging
import threading
import time
@@ -187,4 +188,6 @@ def test():
time.sleep(10)
if os.name == "nt":
driver.close()
driver.quit()

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# this module is part of undetected_chromedriver
from distutils.version import LooseVersion
from packaging.version import Version as LooseVersion
import io
import json
import logging
@@ -12,6 +12,7 @@ import random
import re
import shutil
import string
import subprocess
import sys
import time
from urllib.request import urlopen
@@ -68,8 +69,10 @@ class Patcher(object):
# 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")
# Check not running inside Docker
if not os.path.exists("/app/chromedriver"):
# If the conversion fails, log an error message
logging.info("version_main cannot be converted to an integer")
# Set self.is_old_chromedriver to False if the conversion fails
self.is_old_chromedriver = False
@@ -220,7 +223,7 @@ class Patcher(object):
pass
release = self.fetch_release_number()
self.version_main = release.version[0]
self.version_main = release.major
self.version_full = release
self.unzip_package(self.fetch_package())
@@ -325,11 +328,11 @@ class Patcher(object):
"""
zip_name = f"chromedriver_{self.platform_name}.zip"
if self.is_old_chromedriver:
download_url = "%s/%s/%s" % (self.url_repo, self.version_full.vstring, zip_name)
download_url = "%s/%s/%s" % (self.url_repo, str(self.version_full), zip_name)
else:
zip_name = zip_name.replace("_", "-", 1)
download_url = "https://storage.googleapis.com/chrome-for-testing-public/%s/%s/%s"
download_url %= (self.version_full.vstring, self.platform_name, zip_name)
download_url %= (str(self.version_full), self.platform_name, zip_name)
logger.debug("downloading from %s" % download_url)
return urlretrieve(download_url)[0]
@@ -371,10 +374,31 @@ class Patcher(object):
"""
exe_name = os.path.basename(exe_name)
if IS_POSIX:
r = os.system("kill -f -9 $(pidof %s)" % exe_name)
# Using shell=True for pidof, consider a more robust pid finding method if issues arise.
# pgrep can be an alternative: ["pgrep", "-f", exe_name]
# Or psutil if adding a dependency is acceptable.
command = f"pidof {exe_name}"
try:
result = subprocess.run(command, shell=True, capture_output=True, text=True, check=True)
pids = result.stdout.strip().split()
if pids:
subprocess.run(["kill", "-9"] + pids, check=False) # Changed from -f -9 to -9 as -f is not standard for kill
return True
return False # No PIDs found
except subprocess.CalledProcessError: # pidof returns 1 if no process found
return False # No process found
except Exception as e:
logger.debug(f"Error killing process on POSIX: {e}")
return False
else:
r = os.system("taskkill /f /im %s" % exe_name)
return not r
try:
# TASKKILL /F /IM chromedriver.exe
result = subprocess.run(["taskkill", "/f", "/im", exe_name], check=False, capture_output=True)
# taskkill returns 0 if process was killed, 128 if not found.
return result.returncode == 0
except Exception as e:
logger.debug(f"Error killing process on Windows: {e}")
return False
@staticmethod
def gen_random_cdc():

View File

@@ -1,16 +1,18 @@
import json
import logging
import os
import platform
import re
import shutil
import urllib.parse
import tempfile
import sys
import tempfile
import urllib.parse
from selenium.webdriver.chrome.webdriver import WebDriver
import undetected_chromedriver as uc
FLARESOLVERR_VERSION = None
PLATFORM_VERSION = None
CHROME_EXE_PATH = None
CHROME_MAJOR_VERSION = None
USER_AGENT = None
@@ -26,6 +28,10 @@ def get_config_headless() -> bool:
return os.environ.get('HEADLESS', 'true').lower() == 'true'
def get_config_disable_media() -> bool:
return os.environ.get('DISABLE_MEDIA', 'false').lower() == 'true'
def get_flaresolverr_version() -> str:
global FLARESOLVERR_VERSION
if FLARESOLVERR_VERSION is not None:
@@ -38,6 +44,13 @@ def get_flaresolverr_version() -> str:
FLARESOLVERR_VERSION = json.loads(f.read())['version']
return FLARESOLVERR_VERSION
def get_current_platform() -> str:
global PLATFORM_VERSION
if PLATFORM_VERSION is not None:
return PLATFORM_VERSION
PLATFORM_VERSION = os.name
return PLATFORM_VERSION
def create_proxy_extension(proxy: dict) -> str:
parsed_url = urllib.parse.urlparse(proxy['url'])
@@ -49,18 +62,21 @@ def create_proxy_extension(proxy: dict) -> str:
manifest_json = """
{
"version": "1.0.0",
"manifest_version": 2,
"manifest_version": 3,
"name": "Chrome Proxy",
"permissions": [
"proxy",
"tabs",
"unlimitedStorage",
"storage",
"<all_urls>",
"webRequest",
"webRequestBlocking"
"webRequestAuthProvider"
],
"background": {"scripts": ["background.js"]},
"host_permissions": [
"<all_urls>"
],
"background": {
"service_worker": "background.js"
},
"minimum_chrome_version": "76.0.0"
}
"""
@@ -121,25 +137,22 @@ def get_webdriver(proxy: dict = None) -> WebDriver:
options = uc.ChromeOptions()
options.add_argument('--no-sandbox')
options.add_argument('--window-size=1920,1080')
options.add_argument('--disable-search-engine-choice-screen')
# todo: this param shows a warning in chrome head-full
options.add_argument('--disable-setuid-sandbox')
options.add_argument('--disable-dev-shm-usage')
# this option removes the zygote sandbox (it seems that the resolution is a bit faster)
options.add_argument('--no-zygote')
# attempt to fix Docker ARM32 build
options.add_argument('--disable-gpu-sandbox')
options.add_argument('--disable-software-rasterizer')
IS_ARMARCH = platform.machine().startswith(('arm', 'aarch'))
if IS_ARMARCH:
options.add_argument('--disable-gpu-sandbox')
options.add_argument('--ignore-certificate-errors')
options.add_argument('--ignore-ssl-errors')
# fix GL errors in ASUSTOR NAS
# https://github.com/FlareSolverr/FlareSolverr/issues/782
# https://github.com/microsoft/vscode/issues/127800#issuecomment-873342069
# https://peter.sh/experiments/chromium-command-line-switches/#use-gl
options.add_argument('--use-gl=swiftshader')
language = os.environ.get('LANG', None)
if language is not None:
options.add_argument('--lang=%s' % language)
options.add_argument('--accept-lang=%s' % language)
# Fix for Chrome 117 | https://github.com/FlareSolverr/FlareSolverr/issues/910
if USER_AGENT is not None:
@@ -148,6 +161,7 @@ def get_webdriver(proxy: dict = None) -> WebDriver:
proxy_extension_dir = None
if proxy and all(key in proxy for key in ['url', 'username', 'password']):
proxy_extension_dir = create_proxy_extension(proxy)
options.add_argument("--disable-features=DisableLoadExtensionCommandLineSwitch")
options.add_argument("--load-extension=%s" % os.path.abspath(proxy_extension_dir))
elif proxy and 'url' in proxy:
proxy_url = proxy['url']
@@ -165,8 +179,6 @@ def get_webdriver(proxy: dict = None) -> WebDriver:
# For normal headless mode:
# options.add_argument('--headless')
options.add_argument("--auto-open-devtools-for-tabs")
# if we are inside the Docker container, we avoid downloading the driver
driver_exe_path = None
version_main = None
@@ -189,6 +201,8 @@ def get_webdriver(proxy: dict = None) -> WebDriver:
windows_headless=windows_headless, headless=get_config_headless())
except Exception as e:
logging.error("Error starting Chrome: %s" % e)
# No point in continuing if we cannot retrieve the driver
raise e
# save the patched driver to avoid re-downloads
if driver_exe_path is None:
@@ -290,7 +304,7 @@ def extract_version_nt_folder() -> str:
paths = [f.path for f in os.scandir(path) if f.is_dir()]
for path in paths:
filename = os.path.basename(path)
pattern = '\d+\.\d+\.\d+\.\d+'
pattern = r'\d+\.\d+\.\d+\.\d+'
match = re.search(pattern, filename)
if match and match.group():
# Found a Chrome version.
@@ -314,6 +328,8 @@ def get_user_agent(driver=None) -> str:
raise Exception("Error getting browser User-Agent. " + str(e))
finally:
if driver is not None:
if PLATFORM_VERSION == "nt":
driver.close()
driver.quit()

View File

@@ -1 +1 @@
WebTest==3.0.0
WebTest==3.0.7