├── .dockerignore ├── .github └── workflows │ ├── e2e.yml │ ├── publish.yml │ └── pythonpackage.yml ├── .gitignore ├── CHANGELOG.rst ├── CODE_OF_CONDUCT.md ├── Dockerfile.docs ├── LICENSE.md ├── Makefile ├── README.rst ├── docs ├── Makefile ├── _static │ └── .gitkeep ├── api.rst ├── changes.rst ├── conf.py ├── development.rst ├── index.rst └── usage.rst ├── examples ├── __init__.py ├── antigate.py ├── app_stat.py ├── balance.py ├── callback_sniffer.js ├── captcha_ms.jpeg ├── dot.png ├── funcaptcha_request.py ├── funcaptcha_selenium.py ├── funcaptcha_selenium_callback.py ├── hcaptcha_request.py ├── hcaptcha_request_proxy.py ├── recaptcha3_request.py ├── recaptcha_request.py ├── recaptcha_selenium.py ├── recaptcha_selenium_callback.py ├── remote_image.py ├── text.py └── text_stream.py ├── python_anticaptcha ├── __init__.py ├── base.py ├── compat.py ├── exceptions.py └── tasks.py ├── setup.cfg ├── setup.py ├── tests ├── __init__.py └── test_examples.py ├── tox.ini └── unittest.cfg /.dockerignore: -------------------------------------------------------------------------------- 1 | htmlcov/ 2 | .tox/ 3 | dist/ 4 | .git 5 | python_anticaptcha.egg-info 6 | .travis.yml 7 | -------------------------------------------------------------------------------- /.github/workflows/e2e.yml: -------------------------------------------------------------------------------- 1 | name: E2E test ↔️ 2 | 3 | on: 4 | push: 5 | repository_dispatch: 6 | schedule: 7 | - cron: 0 12 * * * 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 30 13 | steps: 14 | - uses: actions/checkout@v1 15 | 16 | - name: Set up Python 3.7 17 | uses: actions/setup-python@v1 18 | with: 19 | python-version: "3.7" 20 | 21 | - name: Build distribution 22 | run: make install 23 | 24 | - uses: nanasess/setup-chromedriver@v1 25 | 26 | - name: Run integration tests 27 | run: make test 28 | env: 29 | KEY: ${{ secrets.anticaptcha_key }} 30 | PROXY_URL: "${{ secrets.proxy_url }}" 31 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Python 🐍 package 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build-n-publish: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | 11 | - name: Set up Python 3.7 12 | uses: actions/setup-python@v1 13 | with: 14 | python-version: "3.7" 15 | 16 | - name: Install setup dependencies 17 | run: pip install setuptools_scm wheel 18 | 19 | - name: Build distribution 20 | run: make build 21 | 22 | - name: Publish distribution 📦 to PyPI 23 | if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') 24 | uses: pypa/gh-action-pypi-publish@master 25 | with: 26 | user: __token__ 27 | password: ${{ secrets.pypi_password }} 28 | 29 | - name: Publish distribution 📦 to Test PyPI 30 | if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') 31 | uses: pypa/gh-action-pypi-publish@master 32 | with: 33 | user: __token__ 34 | password: ${{ secrets.test_pypi_password }} 35 | repository_url: https://test.pypi.org/legacy/ 36 | -------------------------------------------------------------------------------- /.github/workflows/pythonpackage.yml: -------------------------------------------------------------------------------- 1 | name: Python package 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | max-parallel: 4 11 | fail-fast: false 12 | matrix: 13 | python-version: 14 | - '2.7' 15 | - '3.5' 16 | - '3.6' 17 | - '3.7' 18 | - '3.8' 19 | - '3.9' 20 | - '3.10' 21 | 22 | steps: 23 | - uses: actions/checkout@v1 24 | 25 | - name: Set up Python ${{ matrix.python-version }} 26 | uses: actions/setup-python@v3 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | 30 | - name: Install setup dependencies 31 | run: pip install setuptools_scm wheel 32 | 33 | - name: Install dependencies 34 | run: make install 35 | 36 | - name: Build docs 37 | run: make docs 38 | 39 | - name: Build docs 40 | run: make docs 41 | 42 | - name: Test with pytest 43 | run: make test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/python,pycharm 3 | 4 | ### PyCharm ### 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 7 | 8 | # User-specific stuff: 9 | .idea/**/workspace.xml 10 | .idea/**/tasks.xml 11 | .idea/dictionaries 12 | 13 | # Sensitive or high-churn files: 14 | .idea/**/dataSources/ 15 | .idea/**/dataSources.ids 16 | .idea/**/dataSources.xml 17 | .idea/**/dataSources.local.xml 18 | .idea/**/sqlDataSources.xml 19 | .idea/**/dynamic.xml 20 | .idea/**/uiDesigner.xml 21 | 22 | # Gradle: 23 | .idea/**/gradle.xml 24 | .idea/**/libraries 25 | 26 | # CMake 27 | cmake-build-debug/ 28 | 29 | # Mongo Explorer plugin: 30 | .idea/**/mongoSettings.xml 31 | 32 | ## File-based project format: 33 | *.iws 34 | 35 | ## Plugin-specific files: 36 | 37 | # IntelliJ 38 | /out/ 39 | 40 | # mpeltonen/sbt-idea plugin 41 | .idea_modules/ 42 | 43 | # JIRA plugin 44 | atlassian-ide-plugin.xml 45 | 46 | # Cursive Clojure plugin 47 | .idea/replstate.xml 48 | 49 | # Crashlytics plugin (for Android Studio and IntelliJ) 50 | com_crashlytics_export_strings.xml 51 | crashlytics.properties 52 | crashlytics-build.properties 53 | fabric.properties 54 | 55 | ### PyCharm Patch ### 56 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 57 | 58 | # *.iml 59 | # modules.xml 60 | # .idea/misc.xml 61 | # *.ipr 62 | 63 | # Sonarlint plugin 64 | .idea/sonarlint 65 | 66 | ### Python ### 67 | # Byte-compiled / optimized / DLL files 68 | __pycache__/ 69 | *.py[cod] 70 | *$py.class 71 | 72 | # C extensions 73 | *.so 74 | 75 | # Distribution / packaging 76 | .Python 77 | env/ 78 | build/ 79 | develop-eggs/ 80 | dist/ 81 | downloads/ 82 | eggs/ 83 | .eggs/ 84 | lib/ 85 | lib64/ 86 | parts/ 87 | sdist/ 88 | var/ 89 | wheels/ 90 | *.egg-info/ 91 | .installed.cfg 92 | *.egg 93 | 94 | # PyInstaller 95 | # Usually these files are written by a python script from a template 96 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 97 | *.manifest 98 | *.spec 99 | 100 | # Installer logs 101 | pip-log.txt 102 | pip-delete-this-directory.txt 103 | 104 | # Unit test / coverage reports 105 | htmlcov/ 106 | .tox/ 107 | .coverage 108 | .coverage.* 109 | .cache 110 | nosetests.xml 111 | coverage.xml 112 | *,cover 113 | .hypothesis/ 114 | 115 | # Translations 116 | *.mo 117 | *.pot 118 | 119 | # Django stuff: 120 | *.log 121 | local_settings.py 122 | 123 | # Flask stuff: 124 | instance/ 125 | .webassets-cache 126 | 127 | # Scrapy stuff: 128 | .scrapy 129 | 130 | # Sphinx documentation 131 | docs/_build/ 132 | 133 | # PyBuilder 134 | target/ 135 | 136 | # Jupyter Notebook 137 | .ipynb_checkpoints 138 | 139 | # pyenv 140 | .python-version 141 | 142 | # celery beat schedule file 143 | celerybeat-schedule 144 | 145 | # SageMath parsed files 146 | *.sage.py 147 | 148 | # dotenv 149 | .env 150 | 151 | # virtualenv 152 | .venv 153 | venv/ 154 | ENV/ 155 | 156 | # Spyder project settings 157 | .spyderproject 158 | .spyproject 159 | 160 | # Rope project settings 161 | .ropeproject 162 | 163 | # mkdocs documentation 164 | /site 165 | 166 | # End of https://www.gitignore.io/api/python,pycharm 167 | geckodriver -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 1.0.0 - 2022-03-28 5 | ------------------ 6 | 7 | Added 8 | ##### 9 | 10 | - Add new tasks: 11 | 12 | - ``AntiGateTask`` and ``AntiGateTaskProxyless`` 13 | - ``RecaptchaV2EnterpriseTask`` and ``RecaptchaV2EnterpriseTaskProxyless`` 14 | - ``GeeTestTask`` and ``GeeTestTaskProxyless`` 15 | - ``RecaptchaV2Task`` (alias of ``NoCaptchaTask``) and ``RecaptchaV2TaskProxyless`` (alias of ``NoCaptchaTaskProxyless``) 16 | 17 | - Add example for ``AntiGateTaskProxyless`` 18 | - Add optional parameters ``comment``, ``websiteUrl`` to ``ImageToTextTask`` 19 | - Add optional parameter ``funcaptchaApiJSSubdomain``, ``data`` to ``FunCaptchaTask*`` 20 | - Add optional parameter ``isEnterprise`` to ``RecaptchaV3Task*`` 21 | 22 | Removed 23 | ####### 24 | 25 | - Drop tasks unsupported upstream: ``CustomCaptchaTask``, ``SquareNetTask`` 26 | 27 | Changed 28 | ####### 29 | 30 | - Internal refactor to extract ``UserAgentMixin``, ``CookieMixin`` 31 | - Use nose2 for tests 32 | 33 | 0.7.1 - 2020-07-17 34 | ------------------ 35 | 36 | Added 37 | ##### 38 | 39 | - Added examples for proxy mode including `hcaptcha_request_proxy` 40 | 41 | Changed 42 | ####### 43 | 44 | - Fix inheritance of `FunCaptchaTask` 45 | - Added `FunCaptchaTask` to e2e tests 46 | 47 | 0.7.0 - 2020-06-08 48 | ------------------ 49 | 50 | Added 51 | ##### 52 | 53 | - Added parameter `recaptchaDataSValue` in `NoCaptchaTask*` 54 | 55 | Thanks to this change added support for additional "data-s" used by some custom 56 | ReCaptcha deployments, which is in fact a one-time token and must be grabbed 57 | every time you want to solve a Recaptcha. 58 | `
` 59 | 60 | Changed 61 | ####### 62 | 63 | - Fixed deprecated method `report_incorrect`. 64 | You should currently use `report_incorrect_image` instead already. 65 | 66 | 0.6.0 - 2020-04-13 67 | ------------------ 68 | 69 | Added 70 | ##### 71 | 72 | - Added custom timeout for ``createTaskSmee``. 73 | Use as ``client.createTaskSmee(task, timeout=5*60)``. 74 | Default timeout is 5 minutes. 75 | - Added ``squarenet_validator`` for usage with thread pool 76 | for concurrent execution 77 | 78 | Changed 79 | ####### 80 | 81 | - Default 5 seconds timeout apply to all API request. 82 | 83 | 0.5.1 - 2020-03-31 84 | ------------------ 85 | 86 | Changed 87 | ####### 88 | 89 | - Fix import of package 90 | 91 | 0.5.0 - 2020-03-30 92 | ------------------ 93 | 94 | Added 95 | ##### 96 | 97 | - Added ``HCaptchaTaskProxyless`` and ``HCaptchaTask`` for 98 | support hCaptcha_ . See ``examples/hcaptcha_request.py`` for detailed 99 | usage example. 100 | - Added ``SquareNetTask``. See ``examples/squarenet.py`` for detailed 101 | usage example. 102 | - Added ``Job.report_incorrect_recaptcha`` and ``Job.report_incorrect_image`` . 103 | 104 | Changed 105 | ####### 106 | 107 | - Exposed ``FunCaptchaProxylessTask`` as ``python_anticaptcha.FunCaptchaProxylessTask`` 108 | - Exposed ``CustomCaptchaTask`` as ``python_anticaptcha.CustomCaptchaTask`` 109 | - Formated code via Black 110 | - Move constant monitoring to GitHub Actions 111 | - Deprecated ``Job.report_incorrect``. Use ``report_incorrect_image`` instead. 112 | 113 | 0.4.2 - 2019-10-27 114 | ------------------ 115 | 116 | Added 117 | ##### 118 | 119 | - Added example ``remote_image.py`` 120 | 121 | Changed 122 | ####### 123 | 124 | - Switch CI from TravisCI to GitHub Actions 125 | - Automate PyPI releases 126 | - Use ``use_scm_version`` for versioning 127 | - Drop ``use_2to3`` in ``setup.py`` 128 | 129 | 0.4.1 - 2019-07-09 130 | ------------------ 131 | 132 | Added 133 | ##### 134 | 135 | - Added ``python_anticaptcha.__version__`` to provide version signature (see PEP396) 136 | 137 | Changed 138 | ####### 139 | 140 | - ``python_anticaptcha.AnticaptchaClient.createTaskSmee`` use shared session & keep connection. 141 | 142 | 0.4.0 - 2019-06-28 143 | ------------------ 144 | 145 | Added 146 | ##### 147 | 148 | - Added ``python_anticaptcha.AnticaptchaClient.createTaskSmee`` to receive responses without polling 149 | The method, which is based on the callback result of captcha / task factory to Smee.io, 150 | which immediately transfers it to library. Allows to significantly shorten the waiting time 151 | for a response and to reduce load the network connection. 152 | The method is in beta and the way it works may change. All comments are welcome. 153 | - Recaptcha V3 is now officially supported by Anti-Captcha. Added ``python_anticaptcha.RecaptchaV3TaskProxyless``. 154 | 155 | 0.3.2 - 2018-10-17 156 | ------------------ 157 | 158 | Added 159 | ##### 160 | 161 | - Added support for ``IsInvisible`` flag in ``NoCaptchaTaskProxylessTask`` and ``NoCaptchaTask`` 162 | 163 | 0.3.1 - 2018-03-18 164 | ------------------ 165 | 166 | Changed 167 | ####### 168 | 169 | - Replaced ``python_anticaptcha.AnticatpchaException`` to ``python_anticaptcha.AnticaptchaException`` due typo 170 | 171 | Added 172 | ##### 173 | 174 | - Added ``python_anticaptcha.exceptions.AnticatpchaException`` 175 | - Added docs about error handling 176 | 177 | Removed 178 | ####### 179 | 180 | - Deprecated ``python_anticaptcha.exceptions.AnticatpchaException`` 181 | 182 | .. _hCaptcha: https://www.hcaptcha.com/ 183 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at naczelnik@jawnosc.tk. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /Dockerfile.docs: -------------------------------------------------------------------------------- 1 | FROM python:3-alpine as builder 2 | WORKDIR /src 3 | COPY . . 4 | RUN pip install .[docs] 5 | RUN mkdir /out 6 | RUN sphinx-build -W docs /out 7 | FROM nginx 8 | COPY --from=builder /out/ /usr/share/nginx/html 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Adam Dobrawy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CHROMEDRIVER_VERSION=99.0.4844.17 2 | CHROMEDRIVER_DIR=${PWD}/geckodriver 3 | 4 | .PHONY: lint fmt build docs install test gecko 5 | 6 | build: 7 | python setup.py sdist bdist_wheel 8 | 9 | install: install_test install_docs install_pkg 10 | 11 | install_test: 12 | pip install .[tests] 13 | 14 | install_docs: 15 | pip install .[docs] 16 | 17 | install_pkg: 18 | python -m pip install --upgrade pip wheel 19 | pip install . 20 | 21 | gecko: 22 | mkdir -p ${CHROMEDRIVER_DIR} 23 | wget -q -P ${CHROMEDRIVER_DIR} "http://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" 24 | unzip ${CHROMEDRIVER_DIR}/chromedriver* -d ${CHROMEDRIVER_DIR} 25 | rm ${CHROMEDRIVER_DIR}/chromedriver_linux64.zip 26 | 27 | test: 28 | PATH=$$PWD/geckodriver:$$PATH nose2 --verbose 29 | 30 | clean: 31 | rm -r build geckodriver 32 | 33 | lint: 34 | docker run --rm -v /$$(pwd):/apps alpine/flake8 ./ 35 | docker run --rm -v /$$(pwd):/data cytopia/black --check ./ 36 | 37 | fmt: 38 | docker run --rm -v /$$(pwd):/data cytopia/black ./ 39 | 40 | docs: 41 | sphinx-build -W docs /dev/shm/sphinx -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | python-anticaptcha 2 | ================== 3 | 4 | .. image:: https://github.com/ad-m/python-anticaptcha/workflows/Python%20package/badge.svg 5 | :target: https://github.com/ad-m/python-anticaptcha/actions?workflow=Python+package 6 | 7 | .. image:: https://img.shields.io/pypi/v/python-anticaptcha.svg 8 | :target: https://pypi.org/project/python-anticaptcha/ 9 | :alt: Python package 10 | 11 | .. image:: https://badges.gitter.im/python-anticaptcha/Lobby.svg 12 | :target: https://gitter.im/python-anticaptcha/Lobby?utm_source=share-link&utm_medium=link&utm_campaign=share-link 13 | :alt: Join the chat at https://gitter.im/python-anticaptcha/Lobby 14 | 15 | .. image:: https://img.shields.io/pypi/pyversions/python-anticaptcha.svg 16 | :target: https://github.com/ad-m/python-anticaptcha/blob/master/setup.py 17 | :alt: Python compatibility 18 | 19 | .. introduction-start 20 | 21 | Client library for solve captchas with `Anticaptcha.com support`_. 22 | The library supports both Python 2.7 and Python 3. 23 | 24 | The library is cyclically and automatically tested for proper operation. We are constantly making the best efforts for its effective operation. 25 | 26 | In case of any problems with integration - `read the documentation`_, `create an issue`_, use `Gitter`_ or contact privately. 27 | 28 | .. _read the documentation: http://python-anticaptcha.readthedocs.io/en/latest/ 29 | .. _Anticaptcha.com support: http://getcaptchasolution.com/i1hvnzdymd 30 | .. _create an issue: https://github.com/ad-m/python-anticaptcha/issues/new 31 | .. _Gitter: https://gitter.im/python-anticaptcha/Lobby 32 | 33 | .. introduction-end 34 | 35 | 36 | Getting Started 37 | --------------- 38 | 39 | .. getting-started-start 40 | 41 | Install as standard Python package using:: 42 | 43 | pip install python-anticaptcha 44 | 45 | .. getting-started-end 46 | 47 | 48 | Usage 49 | ----- 50 | 51 | .. usage-start 52 | 53 | To use this library do you need `Anticaptcha.com`_ API key. 54 | 55 | Solve recaptcha 56 | ############### 57 | 58 | Example snippet for Recaptcha: 59 | 60 | .. code:: python 61 | 62 | from python_anticaptcha import AnticaptchaClient, NoCaptchaTaskProxylessTask 63 | 64 | api_key = '174faff8fbc769e94a5862391ecfd010' 65 | site_key = '6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-' # grab from site 66 | url = 'https://www.google.com/recaptcha/api2/demo' 67 | 68 | client = AnticaptchaClient(api_key) 69 | task = NoCaptchaTaskProxylessTask(url, site_key) 70 | job = client.createTask(task) 71 | job.join() 72 | print job.get_solution_response() 73 | 74 | The full integration example is available in file ``examples/recaptcha.py``. 75 | 76 | If you only process few page many times to increase reliability, you can specify 77 | whether the captcha is visible or not. This parameter is not required, as is the 78 | system detects invisible sitekeys automatically, and needs several recursive 79 | measures for automated training and analysis. For provide that pass 80 | ``is_invisible`` parameter to ``NoCaptchaTaskProxylessTask`` or ``NoCaptchaTask`` eg.: 81 | 82 | .. code:: python 83 | 84 | from python_anticaptcha import AnticaptchaClient, NoCaptchaTaskProxylessTask 85 | 86 | api_key = '174faff8fbc769e94a5862391ecfd010' 87 | site_key = '6Lc-0DYUAAAAAOPM3RGobCfKjIE5STmzvZfHbbNx' # grab from site 88 | url = 'https://losangeles.craigslist.org/lac/kid/d/housekeeper-sitting-pet-care/6720136191.html' 89 | 90 | client = AnticaptchaClient(api_key) 91 | task = NoCaptchaTaskProxylessTask(url, site_key, is_invisible=True) 92 | job = client.createTask(task) 93 | job.join() 94 | print job.get_solution_response() 95 | 96 | 97 | Solve text captcha 98 | ################## 99 | 100 | Example snippet for text captcha: 101 | 102 | .. code:: python 103 | 104 | from python_anticaptcha import AnticaptchaClient, ImageToTextTask 105 | 106 | api_key = '174faff8fbc769e94a5862391ecfd010' 107 | captcha_fp = open('examples/captcha_ms.jpeg', 'rb') 108 | client = AnticaptchaClient(api_key) 109 | task = ImageToTextTask(captcha_fp) 110 | job = client.createTask(task) 111 | job.join() 112 | print job.get_captcha_text() 113 | 114 | Solve funcaptcha 115 | ################ 116 | 117 | Example snippet for funcaptcha: 118 | 119 | .. code:: python 120 | 121 | from python_anticaptcha import AnticaptchaClient, FunCaptchaTask, Proxy 122 | UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 ' \ 123 | '(KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36' 124 | 125 | api_key = '174faff8fbc769e94a5862391ecfd010' 126 | site_key = 'DE0B0BB7-1EE4-4D70-1853-31B835D4506B' # grab from site 127 | url = 'https://www.google.com/recaptcha/api2/demo' 128 | proxy = Proxy.parse_url("socks5://login:password@123.123.123.123") 129 | 130 | client = AnticaptchaClient(api_key) 131 | task = FunCaptchaTask(url, site_key, proxy=proxy, user_agent=user_agent) 132 | job = client.createTask(task) 133 | job.join() 134 | print job.get_token_response() 135 | 136 | Report incorrect image 137 | ###################### 138 | 139 | Example snippet for reporting an incorrect image task: 140 | 141 | .. code:: python 142 | 143 | from python_anticaptcha import AnticaptchaClient, ImageToTextTask 144 | 145 | api_key = '174faff8fbc769e94a5862391ecfd010' 146 | captcha_fp = open('examples/captcha_ms.jpeg', 'rb') 147 | client = AnticaptchaClient(api_key) 148 | task = ImageToTextTask(captcha_fp) 149 | job = client.createTask(task) 150 | job.join() 151 | print job.get_captcha_text() 152 | job.report_incorrect() 153 | 154 | Setup proxy 155 | ########### 156 | 157 | The library is not responsible for managing the proxy server. However, we point to 158 | the possibility of simply launching such a server by: 159 | 160 | .. code:: 161 | 162 | pip install mitmproxy 163 | mitmweb -p 9190 -b 0.0.0.0 --ignore '.' --socks 164 | 165 | Next to in your application use something like: 166 | 167 | .. code:: python 168 | 169 | proxy = Proxy.parse_url("socks5://123.123.123.123:9190") 170 | 171 | We recommend entering IP-based access control for incoming addresses to proxy. IP address required by 172 | `Anticaptcha.com`_ is: 173 | 174 | .. code:: 175 | 176 | 69.65.41.21 177 | 209.212.146.168 178 | 179 | .. _Anticaptcha.com: http://getcaptchasolution.com/p9bwplkicx 180 | 181 | Error handling 182 | ############## 183 | 184 | In the event of an application error, the AnticaptchaException exception is thrown. To handle the exception, do the following: 185 | 186 | .. code:: python 187 | 188 | from python_anticaptcha import AnticatpchaException, ImageToTextTask 189 | 190 | try: 191 | # any actions 192 | except AnticatpchaException as e: 193 | if e.error_code == 'ERROR_ZERO_BALANCE': 194 | notify_about_no_funds(e.error_id, e.error_code, e.error_description) 195 | else: 196 | raise 197 | 198 | .. usage-end 199 | 200 | Versioning 201 | ---------- 202 | 203 | We use `SemVer`_ for versioning. For the versions available, see the 204 | `tags on this repository`_. 205 | 206 | Authors 207 | ------- 208 | 209 | - **Adam Dobrawy** - *Initial work* - `ad-m`_ 210 | 211 | See also the list of `contributors`_ who participated in this project. 212 | 213 | License 214 | ------- 215 | 216 | This project is licensed under the MIT License - see the `LICENSE.md`_ 217 | file for details 218 | 219 | .. _SemVer: http://semver.org/ 220 | .. _tags on this repository: https://github.com/ad-m/python-anticaptcha/tags 221 | .. _ad-m: https://github.com/ad-m 222 | .. _contributors: https://github.com/ad-m/python-anticaptcha/contributors 223 | .. _LICENSE.md: LICENSE.md 224 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = python-anticaptcha 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/_static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ad-m/python-anticaptcha/076922ee646483328c580c6623f7cb49a2ea4493/docs/_static/.gitkeep -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | API 2 | === 3 | 4 | Base 5 | ---- 6 | 7 | .. automodule:: python_anticaptcha.base 8 | :members: 9 | :undoc-members: 10 | 11 | Exceptions 12 | ---------- 13 | 14 | .. automodule:: python_anticaptcha.exceptions 15 | :members: 16 | :undoc-members: 17 | 18 | Tasks 19 | ----- 20 | 21 | .. automodule:: python_anticaptcha.tasks 22 | :members: 23 | :undoc-members: -------------------------------------------------------------------------------- /docs/changes.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CHANGELOG.rst -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/stable/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | import os 16 | import sys 17 | 18 | sys.path.insert(0, os.path.abspath("..")) 19 | 20 | 21 | # -- Project information ----------------------------------------------------- 22 | 23 | project = u"python-anticaptcha" 24 | copyright = u"2018, Adam Dobrawy" 25 | author = u"Adam Dobrawy" 26 | 27 | # The short X.Y version 28 | version = u"" 29 | # The full version, including alpha/beta/rc tags 30 | release = u"0.2.0" 31 | 32 | 33 | # -- General configuration --------------------------------------------------- 34 | 35 | # If your documentation needs a minimal Sphinx version, state it here. 36 | # 37 | # needs_sphinx = '1.0' 38 | 39 | # Add any Sphinx extension module names here, as strings. They can be 40 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 41 | # ones. 42 | extensions = [ 43 | "sphinx.ext.autodoc", 44 | "sphinx.ext.viewcode", 45 | ] 46 | 47 | # Add any paths that contain templates here, relative to this directory. 48 | templates_path = ["_templates"] 49 | 50 | # The suffix(es) of source filenames. 51 | # You can specify multiple suffix as a list of string: 52 | # 53 | # source_suffix = ['.rst', '.md'] 54 | source_suffix = ".rst" 55 | 56 | # The master toctree document. 57 | master_doc = "index" 58 | 59 | # The language for content autogenerated by Sphinx. Refer to documentation 60 | # for a list of supported languages. 61 | # 62 | # This is also used if you do content translation via gettext catalogs. 63 | # Usually you set "language" from the command line for these cases. 64 | language = None 65 | 66 | # List of patterns, relative to source directory, that match files and 67 | # directories to ignore when looking for source files. 68 | # This pattern also affects html_static_path and html_extra_path . 69 | exclude_patterns = [u"_build", "Thumbs.db", ".DS_Store"] 70 | 71 | # The name of the Pygments (syntax highlighting) style to use. 72 | pygments_style = "sphinx" 73 | 74 | 75 | # -- Options for HTML output ------------------------------------------------- 76 | 77 | # The theme to use for HTML and HTML Help pages. See the documentation for 78 | # a list of builtin themes. 79 | # 80 | html_theme = "alabaster" 81 | 82 | try: 83 | import sphinx_rtd_theme 84 | 85 | html_theme = "sphinx_rtd_theme" 86 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 87 | except ImportError: 88 | pass 89 | 90 | master_doc = "index" 91 | 92 | # Theme options are theme-specific and customize the look and feel of a theme 93 | # further. For a list of options available for each theme, see the 94 | # documentation. 95 | # 96 | # html_theme_options = {} 97 | 98 | # Add any paths that contain custom static files (such as style sheets) here, 99 | # relative to this directory. They are copied after the builtin static files, 100 | # so a file named "default.css" will overwrite the builtin "default.css". 101 | html_static_path = ["_static"] 102 | 103 | # Custom sidebar templates, must be a dictionary that maps document names 104 | # to template names. 105 | # 106 | # The default sidebars (for documents that don't match any pattern) are 107 | # defined by theme itself. Builtin themes are using these templates by 108 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 109 | # 'searchbox.html']``. 110 | # 111 | # html_sidebars = {} 112 | 113 | 114 | # -- Options for HTMLHelp output --------------------------------------------- 115 | 116 | # Output file base name for HTML help builder. 117 | htmlhelp_basename = "python-anticaptchadoc" 118 | 119 | 120 | # -- Options for LaTeX output ------------------------------------------------ 121 | 122 | latex_elements = { 123 | # The paper size ('letterpaper' or 'a4paper'). 124 | # 125 | # 'papersize': 'letterpaper', 126 | # The font size ('10pt', '11pt' or '12pt'). 127 | # 128 | # 'pointsize': '10pt', 129 | # Additional stuff for the LaTeX preamble. 130 | # 131 | # 'preamble': '', 132 | # Latex figure (float) alignment 133 | # 134 | # 'figure_align': 'htbp', 135 | } 136 | 137 | # Grouping the document tree into LaTeX files. List of tuples 138 | # (source start file, target name, title, 139 | # author, documentclass [howto, manual, or own class]). 140 | latex_documents = [ 141 | ( 142 | master_doc, 143 | "python-anticaptcha.tex", 144 | u"python-anticaptcha Documentation", 145 | u"Adam Dobrawy", 146 | "manual", 147 | ), 148 | ] 149 | 150 | 151 | # -- Options for manual page output ------------------------------------------ 152 | 153 | # One entry per manual page. List of tuples 154 | # (source start file, name, description, authors, manual section). 155 | man_pages = [ 156 | (master_doc, "python-anticaptcha", u"python-anticaptcha Documentation", [author], 1) 157 | ] 158 | 159 | 160 | # -- Options for Texinfo output ---------------------------------------------- 161 | 162 | # Grouping the document tree into Texinfo files. List of tuples 163 | # (source start file, target name, title, author, 164 | # dir menu entry, description, category) 165 | texinfo_documents = [ 166 | ( 167 | master_doc, 168 | "python-anticaptcha", 169 | u"python-anticaptcha Documentation", 170 | author, 171 | "python-anticaptcha", 172 | "One line description of project.", 173 | "Miscellaneous", 174 | ), 175 | ] 176 | 177 | 178 | # -- Extension configuration ------------------------------------------------- 179 | -------------------------------------------------------------------------------- /docs/development.rst: -------------------------------------------------------------------------------- 1 | Development 2 | =========== 3 | 4 | The project is open-source. 5 | 6 | Changes are managed through GitHub. Pull requests are particularly welcome. 7 | 8 | All changes are automatically tested using TravisCI. 9 | 10 | New release 11 | ----------- 12 | 13 | Follow these steps to publish the new release: 14 | 15 | * update changelog - use any text editor 16 | * bump version - use ```bumpversion {major,minor,patch}``` 17 | * build package - use ```python setup.py sdist bdist_wheel --universal``` 18 | * upload release to PyPI - use ```twine upload dist/*``` 19 | * push changes to GitHub - ```git push origin && git push --tags``` 20 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. python-anticaptcha documentation master file, created by 2 | sphinx-quickstart on Mon Feb 26 11:20:08 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to python-anticaptcha's documentation! 7 | ============================================== 8 | 9 | .. include:: ../README.rst 10 | :start-after: introduction-start 11 | :end-before: introduction-end 12 | 13 | .. toctree:: 14 | :maxdepth: 2 15 | :caption: Contents: 16 | 17 | usage 18 | api 19 | changes 20 | development 21 | 22 | Indices and tables 23 | ================== 24 | 25 | * :ref:`genindex` 26 | * :ref:`modindex` 27 | * :ref:`search` 28 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | Usage 2 | ===== 3 | 4 | .. include:: ../README.rst 5 | :start-after: usage-start 6 | :end-before: usage-end 7 | -------------------------------------------------------------------------------- /examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ad-m/python-anticaptcha/076922ee646483328c580c6623f7cb49a2ea4493/examples/__init__.py -------------------------------------------------------------------------------- /examples/antigate.py: -------------------------------------------------------------------------------- 1 | from os import environ 2 | from re import TEMPLATE 3 | 4 | from python_anticaptcha import AnticaptchaClient 5 | from python_anticaptcha.tasks import AntiGateTask, AntiGateTaskProxyless 6 | import json 7 | 8 | api_key = environ["KEY"] 9 | 10 | URL = "https://anti-captcha.com/tutorials/v2-textarea" 11 | 12 | TEMPLATE_NAME = "CloudFlare cookies for a proxy" 13 | 14 | VARIABLES = {} 15 | 16 | 17 | def process(): 18 | client = AnticaptchaClient(api_key) 19 | task = AntiGateTaskProxyless( 20 | website_url=URL, 21 | template_name=TEMPLATE_NAME, 22 | variables=VARIABLES, 23 | ) 24 | job = client.createTaskSmee(task) 25 | solution = job.get_solution() 26 | return solution 27 | 28 | 29 | if __name__ == "__main__": 30 | print("Website URL: " + URL) 31 | solution = process() 32 | print("Result: " + json.dumps(solution, indent=4)) 33 | -------------------------------------------------------------------------------- /examples/app_stat.py: -------------------------------------------------------------------------------- 1 | from os import environ 2 | from pprint import pprint 3 | from python_anticaptcha import AnticaptchaClient, ImageToTextTask 4 | from sys import argv 5 | 6 | api_key = environ["KEY"] 7 | 8 | soft_id = argv[1] 9 | mode = argv[2] 10 | 11 | 12 | def process(soft_id, mode): 13 | client = AnticaptchaClient(api_key) 14 | pprint(client.getAppStats(soft_id, mode)) 15 | 16 | 17 | if __name__ == "__main__": 18 | pprint(process(soft_id, mode)) 19 | -------------------------------------------------------------------------------- /examples/balance.py: -------------------------------------------------------------------------------- 1 | from os import environ 2 | from pprint import pprint 3 | from python_anticaptcha import AnticaptchaClient, ImageToTextTask 4 | from sys import argv 5 | 6 | api_key = environ["KEY"] 7 | 8 | 9 | def process(): 10 | client = AnticaptchaClient(api_key) 11 | pprint(client.getBalance()) 12 | 13 | 14 | if __name__ == "__main__": 15 | pprint(process()) 16 | -------------------------------------------------------------------------------- /examples/callback_sniffer.js: -------------------------------------------------------------------------------- 1 | recaptchaCallback = typeof recaptchaCallback == 'undefined' ? [] : recaptchaCallback; 2 | (function () { 3 | if (typeof grecaptcha == 'undefined') { 4 | console.log('Avoid wrapping before load grecaptcha'); 5 | return; 6 | }; 7 | if (!typeof grecaptcha.recaptchaCallback == 'undefined') { 8 | console.log('Avoid multiple wrapping of grecaptcha'); 9 | return; 10 | }; 11 | const x = grecaptcha.render; 12 | grecaptcha.recaptchaCallback = recaptchaCallback; 13 | grecaptcha.render = function () { 14 | console.log('grecaptcha arguments', arguments); 15 | if (arguments[1] && arguments[1].callback) { 16 | const cb = arguments[1].callback; 17 | recaptchaCallback.push(cb); 18 | console.log('recaptchaCallback', cb); 19 | } 20 | return x(...arguments); 21 | }; 22 | })(); 23 | 24 | funcaptchaCallback = typeof funcaptchaCallback == 'undefined' ? [] : funcaptchaCallback; 25 | (function () { 26 | if (typeof ArkoseEnforcement == 'undefined'){ 27 | console.log('Avoid wrapping before load ArkoseEnforcement'); 28 | return; 29 | }; 30 | if (!typeof ArkoseEnforcement.funcaptchaCallback == 'undefined') { 31 | console.log('Avoid multiple wrapping of ArkoseEnforcement'); 32 | return; 33 | }; 34 | const extendFuncaptcha = function(cls) { 35 | function inner() { 36 | console.log('funcaptcha arguments', arguments); 37 | if (arguments[0] && arguments[0].callback) { 38 | const cb = arguments[0].callback; 39 | recaptchaCallback.push(cb); 40 | console.log('funcaptchaCallback', cb); 41 | } 42 | cls.apply(this, arguments) 43 | } 44 | inner.prototype = Object.create(cls.prototype); 45 | inner.funcaptchaCallback = recaptchaCallback; 46 | return inner; 47 | }; 48 | ArkoseEnforcement = new extendFuncaptcha(FunCaptcha); 49 | FunCaptcha = new extendFuncaptcha(ArkoseEnforcement); 50 | })(); -------------------------------------------------------------------------------- /examples/captcha_ms.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ad-m/python-anticaptcha/076922ee646483328c580c6623f7cb49a2ea4493/examples/captcha_ms.jpeg -------------------------------------------------------------------------------- /examples/dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ad-m/python-anticaptcha/076922ee646483328c580c6623f7cb49a2ea4493/examples/dot.png -------------------------------------------------------------------------------- /examples/funcaptcha_request.py: -------------------------------------------------------------------------------- 1 | from six.moves.urllib import parse 2 | import requests 3 | from os import environ 4 | import re 5 | 6 | from python_anticaptcha import AnticaptchaClient, FunCaptchaTask 7 | 8 | api_key = environ["KEY"] 9 | site_key_pattern = 'public_key: "(.+?)",' 10 | site_key_pattern = 'public_key: "(.+?)",' 11 | surl_pattern = 'surl: "(.+?)",' 12 | url = "https://client-demo.arkoselabs.com/solo-animals" 13 | client = AnticaptchaClient(api_key) 14 | session = requests.Session() 15 | 16 | UA = ( 17 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 " 18 | "(KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36" 19 | ) 20 | session.headers = {"User-Agent": UA} 21 | proxy_url = environ["PROXY_URL"] 22 | 23 | 24 | def parse_url(url): 25 | parsed = parse.urlparse(url) 26 | return dict( 27 | proxy_type=parsed.scheme, 28 | proxy_address=parsed.hostname, 29 | proxy_port=parsed.port, 30 | proxy_login=parsed.username, 31 | proxy_password=parsed.password, 32 | ) 33 | 34 | 35 | def get_form_html(): 36 | return session.get(url).text 37 | 38 | 39 | def get_token(form_html): 40 | proxy = parse_url(proxy_url) 41 | site_key = re.search(site_key_pattern, form_html).group(1) 42 | print("Determined site-key:", site_key) 43 | surl = re.search(surl_pattern, form_html).group(1) 44 | print("Determined surl:", surl) 45 | task = FunCaptchaTask(surl, site_key, user_agent=UA, **proxy) 46 | job = client.createTask(task) 47 | job.join(maximum_time=10**4) 48 | return job.get_token_response() 49 | 50 | 51 | def form_submit(token): 52 | return requests.post( 53 | url="{}/verify".format(url), 54 | data={"name": "xx", "fc-token": token}, 55 | proxies={ 56 | "http": proxy_url, 57 | "https": proxy_url, 58 | }, 59 | ).text 60 | 61 | 62 | def process(): 63 | html = get_form_html() 64 | token = get_token(html) 65 | print("Received token:", token) 66 | return form_submit(token) 67 | 68 | 69 | if __name__ == "__main__": 70 | print("Solved!" in process()) 71 | -------------------------------------------------------------------------------- /examples/funcaptcha_selenium.py: -------------------------------------------------------------------------------- 1 | from six.moves.urllib.parse import quote 2 | from six.moves.urllib import parse 3 | 4 | import requests 5 | from os import environ 6 | import re 7 | from random import choice 8 | from selenium.webdriver.common.by import By 9 | from selenium.webdriver.support.ui import WebDriverWait 10 | from selenium.webdriver.support import expected_conditions as EC 11 | 12 | from python_anticaptcha import AnticaptchaClient, FunCaptchaTask 13 | 14 | api_key = environ["KEY"] 15 | site_key_pattern = 'public_key: "(.+?)",' 16 | url = "https://client-demo.arkoselabs.com/solo-animals" 17 | client = AnticaptchaClient(api_key) 18 | 19 | proxy_urls = environ["PROXY_URL"].split(",") 20 | 21 | 22 | def parse_url(url): 23 | parsed = parse.urlparse(url) 24 | return dict( 25 | proxy_type=parsed.scheme, 26 | proxy_address=parsed.hostname, 27 | proxy_port=parsed.port, 28 | proxy_login=parsed.username, 29 | proxy_password=parsed.password, 30 | ) 31 | 32 | 33 | def get_token(public_key, user_agent): 34 | proxy_url = choice(proxy_urls) 35 | proxy = parse_url(proxy_url) 36 | task = FunCaptchaTask(url, public_key, user_agent=user_agent, **proxy) 37 | job = client.createTask(task) 38 | job.join(maximum_time=10 ** 4) 39 | return job.get_token_response() 40 | 41 | 42 | def process(driver): 43 | driver.get(url) 44 | public_key = re.search(site_key_pattern, driver.page_source).group(1) 45 | print("Found public-key", public_key) 46 | user_agent = driver.execute_script("return navigator.userAgent;") 47 | token = get_token(public_key, user_agent) 48 | print("Received token", token) 49 | script = """ 50 | document.getElementById('FunCaptcha-Token').value = decodeURIComponent('{0}'); 51 | document.getElementById('verification-token').value = decodeURIComponent('{0}'); 52 | document.getElementById('submit-btn').disabled = false; 53 | """.format( 54 | quote(token) 55 | ) 56 | driver.execute_script(script) 57 | driver.find_element(By.ID, "submit-btn").click() 58 | return driver.page_source 59 | 60 | 61 | if __name__ == "__main__": 62 | from selenium.webdriver import Firefox 63 | from selenium.webdriver.firefox.options import Options 64 | 65 | options = Options() 66 | # options.add_argument('-headless') 67 | driver = Firefox(firefox_options=options) 68 | assert "Solved!" in process(driver) 69 | -------------------------------------------------------------------------------- /examples/funcaptcha_selenium_callback.py: -------------------------------------------------------------------------------- 1 | import re 2 | import time 3 | from six.moves.urllib.parse import quote 4 | import os 5 | from os import environ 6 | import gzip 7 | 8 | from python_anticaptcha import AnticaptchaClient, FunCaptchaProxylessTask 9 | from selenium.webdriver.common.by import By 10 | 11 | api_key = environ["KEY"] 12 | site_key_pattern = 'public_key: "(.+?)",' 13 | url = "https://client-demo.arkoselabs.com/solo-animals" 14 | EXPECTED_RESULT = "Solved!" 15 | client = AnticaptchaClient(api_key) 16 | 17 | DIR = os.path.dirname(os.path.abspath(__file__)) 18 | 19 | with open(os.path.join(DIR, "callback_sniffer.js"), "rb") as fp: 20 | wrapper_code = fp.read() 21 | 22 | 23 | def get_token(url, site_key): 24 | task = FunCaptchaProxylessTask(website_url=url, website_key=site_key) 25 | job = client.createTask(task) 26 | job.join(maximum_time=60 * 15) 27 | return job.get_token_response() 28 | 29 | 30 | def process(driver): 31 | driver.get(url) 32 | site_key = get_sitekey(driver) 33 | print("Found site-key", site_key) 34 | token = get_token(url, site_key) 35 | print("Found token", token) 36 | form_submit(driver, token) 37 | return driver.page_source 38 | 39 | 40 | def form_submit(driver, token): 41 | script = """ 42 | document.getElementById('FunCaptcha-Token').value = decodeURIComponent('{0}'); 43 | document.getElementById('verification-token').value = decodeURIComponent('{0}'); 44 | document.getElementById('submit-btn').disabled = false; 45 | """.format( 46 | quote(token) 47 | ) 48 | time.sleep(1) 49 | # as example call callback - not required in that example 50 | driver.execute_script("ArkoseEnforcement.funcaptchaCallback[0]('{}')".format(token)) 51 | driver.find_element(By.ID, "submit-btn").click() 52 | time.sleep(1) 53 | 54 | 55 | def get_sitekey(driver): 56 | return re.search(site_key_pattern, driver.page_source).group(1) 57 | 58 | 59 | if __name__ == "__main__": 60 | from seleniumwire import webdriver # Import from seleniumwire 61 | 62 | def custom(req, req_body, res, res_body): 63 | if not req.path: 64 | return 65 | if not "arkoselabs" in req.path: 66 | return 67 | if not res.headers.get("Content-Type", None) in [ 68 | "text/javascript", 69 | "application/javascript", 70 | ]: 71 | print( 72 | "Skip invalid content type", 73 | req.path, 74 | res.headers.get("Content-Type", None), 75 | ) 76 | return 77 | if res.headers["Content-Encoding"] == "gzip": 78 | del res.headers["Content-Encoding"] 79 | res_body = gzip.decompress(res_body) 80 | return res_body + wrapper_code 81 | 82 | driver = webdriver.Firefox(seleniumwire_options={"custom_response_handler": custom}) 83 | 84 | try: 85 | assert EXPECTED_RESULT in process(driver) 86 | finally: 87 | driver.close() 88 | -------------------------------------------------------------------------------- /examples/hcaptcha_request.py: -------------------------------------------------------------------------------- 1 | import re 2 | import requests 3 | from os import environ 4 | 5 | from python_anticaptcha import AnticaptchaClient, HCaptchaTaskProxyless 6 | 7 | api_key = environ["KEY"] 8 | site_key_pattern = 'data-sitekey="(.+?)"' 9 | url = "http://hcaptcha.jawne.info.pl/" 10 | client = AnticaptchaClient(api_key) 11 | session = requests.Session() 12 | EXPECTED_RESULT = "Your request have submitted successfully." 13 | 14 | 15 | def get_form_html(): 16 | return session.get(url).text 17 | 18 | 19 | def get_token(form_html): 20 | site_key = re.search(site_key_pattern, form_html).group(1) 21 | task = HCaptchaTaskProxyless(website_url=url, website_key=site_key) 22 | job = client.createTask(task) 23 | job.join() 24 | return job.get_solution_response() 25 | 26 | 27 | def form_submit(token): 28 | return requests.post(url, data={"g-recaptcha-response": token}).text 29 | 30 | 31 | def process(): 32 | html = get_form_html() 33 | token = get_token(html) 34 | return form_submit(token) 35 | 36 | 37 | if __name__ == "__main__": 38 | assert EXPECTED_RESULT in process() 39 | -------------------------------------------------------------------------------- /examples/hcaptcha_request_proxy.py: -------------------------------------------------------------------------------- 1 | from six.moves.urllib import parse 2 | 3 | import re 4 | import requests 5 | from os import environ 6 | 7 | from python_anticaptcha import AnticaptchaClient, HCaptchaTask 8 | 9 | api_key = environ["KEY"] 10 | proxy_url = environ["PROXY_URL"] # eg. socks5://user:password/123.123.123.123:8888/ 11 | site_key_pattern = 'data-sitekey="(.+?)"' 12 | url = "http://hcaptcha.jawne.info.pl/" 13 | client = AnticaptchaClient(api_key) 14 | session = requests.Session() 15 | EXPECTED_RESULT = "Your request have submitted successfully." 16 | 17 | UA = ( 18 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 " 19 | "(KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36" 20 | ) 21 | 22 | 23 | def parse_url(url): 24 | parsed = parse.urlparse(url) 25 | return dict( 26 | proxy_type=parsed.scheme, 27 | proxy_address=parsed.hostname, 28 | proxy_port=parsed.port, 29 | proxy_login=parsed.username, 30 | proxy_password=parsed.password, 31 | ) 32 | 33 | 34 | def get_form_html(): 35 | return session.get(url).text 36 | 37 | 38 | def get_token(form_html): 39 | site_key = re.search(site_key_pattern, form_html).group(1) 40 | proxy = parse_url(proxy_url) 41 | task = HCaptchaTask( 42 | website_url=url, 43 | website_key=site_key, 44 | user_agent=UA, 45 | cookies="test=test", 46 | **proxy 47 | ) 48 | job = client.createTask(task) 49 | job.join() 50 | return job.get_solution_response() 51 | 52 | 53 | def form_submit(token): 54 | return requests.post(url, data={"g-recaptcha-response": token}).text 55 | 56 | 57 | def process(): 58 | html = get_form_html() 59 | token = get_token(html) 60 | return form_submit(token) 61 | 62 | 63 | if __name__ == "__main__": 64 | assert EXPECTED_RESULT in process() 65 | -------------------------------------------------------------------------------- /examples/recaptcha3_request.py: -------------------------------------------------------------------------------- 1 | import re 2 | import requests 3 | from os import environ 4 | from six.moves.urllib_parse import urljoin 5 | from python_anticaptcha import AnticaptchaClient, RecaptchaV3TaskProxyless 6 | 7 | api_key = environ["KEY"] 8 | site_key_pattern = "grecaptcha.execute\('(.+?)'" 9 | action_name_pattern = "\{action: '(.+?)'\}" 10 | url = "https://recaptcha-demo.appspot.com/recaptcha-v3-request-scores.php" 11 | client = AnticaptchaClient(api_key) 12 | session = requests.Session() 13 | 14 | 15 | def get_form_html(): 16 | return session.get(url).text 17 | 18 | 19 | def get_token(form_html): 20 | website_key = re.search(site_key_pattern, form_html).group(1) 21 | page_action = re.search(action_name_pattern, form_html).group(1) 22 | task = RecaptchaV3TaskProxyless( 23 | website_url=url, website_key=website_key, page_action=page_action, min_score=0.7 24 | ) 25 | job = client.createTask(task) 26 | job.join(maximum_time=9 * 60) 27 | return [page_action, job.get_solution_response()] 28 | 29 | 30 | def form_submit(page_action, token): 31 | return requests.post( 32 | url=urljoin(url, "recaptcha-v3-verify.php"), 33 | params={"action": page_action, "token": token}, 34 | ).json() 35 | 36 | 37 | def process(): 38 | html = get_form_html() 39 | [page_action, token] = get_token(html) 40 | return form_submit(page_action, token) 41 | 42 | 43 | if __name__ == "__main__": 44 | result = process() 45 | assert result["success"] is True 46 | print("Processed successfully") 47 | -------------------------------------------------------------------------------- /examples/recaptcha_request.py: -------------------------------------------------------------------------------- 1 | import re 2 | import requests 3 | from os import environ 4 | 5 | from python_anticaptcha import AnticaptchaClient, NoCaptchaTaskProxylessTask 6 | 7 | api_key = environ["KEY"] 8 | site_key_pattern = 'data-sitekey="(.+?)"' 9 | url = "https://www.google.com/recaptcha/api2/demo?invisible=false" 10 | EXPECTED_RESULT = "Verification Success... Hooray!" 11 | client = AnticaptchaClient(api_key) 12 | session = requests.Session() 13 | 14 | 15 | def get_form_html(): 16 | return session.get(url).text 17 | 18 | 19 | def get_token(form_html): 20 | site_key = re.search(site_key_pattern, form_html).group(1) 21 | task = NoCaptchaTaskProxylessTask(website_url=url, website_key=site_key) 22 | job = client.createTaskSmee(task, timeout=10 * 60) 23 | return job.get_solution_response() 24 | 25 | 26 | def form_submit(token): 27 | return requests.post(url, data={"g-recaptcha-response": token}).text 28 | 29 | 30 | def process(): 31 | html = get_form_html() 32 | token = get_token(html) 33 | return form_submit(token) 34 | 35 | 36 | if __name__ == "__main__": 37 | assert "Verification Success... Hooray!" in process() 38 | -------------------------------------------------------------------------------- /examples/recaptcha_selenium.py: -------------------------------------------------------------------------------- 1 | import time 2 | from os import environ 3 | 4 | from python_anticaptcha import AnticaptchaClient, NoCaptchaTaskProxylessTask 5 | 6 | api_key = environ["KEY"] 7 | invisible_captcha = True 8 | url = "https://www.google.com/recaptcha/api2/demo?invisible={}".format( 9 | str(invisible_captcha) 10 | ) 11 | EXPECTED_RESULT = "Verification Success... Hooray!" 12 | client = AnticaptchaClient(api_key) 13 | 14 | 15 | def get_token(url, site_key, invisible): 16 | task = NoCaptchaTaskProxylessTask( 17 | website_url=url, website_key=site_key, is_invisible=invisible 18 | ) 19 | job = client.createTask(task) 20 | job.join(maximum_time=60 * 15) 21 | return job.get_solution_response() 22 | 23 | 24 | def process(driver): 25 | driver.get(url) 26 | site_key = get_sitekey(driver) 27 | print("Found site-key", site_key) 28 | token = get_token(url, site_key, invisible_captcha) 29 | print("Found token", token) 30 | form_submit(driver, token) 31 | return driver.find_element_by_class_name("recaptcha-success").text 32 | 33 | 34 | def form_submit(driver, token): 35 | driver.execute_script( 36 | "document.getElementById('g-recaptcha-response').innerHTML='{}';".format(token) 37 | ) 38 | driver.execute_script("onSuccess('{}')".format(token)) 39 | time.sleep(1) 40 | 41 | 42 | def get_sitekey(driver): 43 | return driver.find_element_by_class_name("g-recaptcha").get_attribute( 44 | "data-sitekey" 45 | ) 46 | 47 | 48 | if __name__ == "__main__": 49 | from selenium.webdriver import Chrome 50 | from selenium.webdriver.chrome.options import Options 51 | 52 | options = Options() 53 | options.add_experimental_option('prefs', {'intl.accept_languages': 'en_US'}) 54 | driver = Chrome(options=options) 55 | assert EXPECTED_RESULT in process(driver) 56 | -------------------------------------------------------------------------------- /examples/recaptcha_selenium_callback.py: -------------------------------------------------------------------------------- 1 | import re 2 | import time 3 | import os 4 | from os import environ 5 | import gzip 6 | 7 | from python_anticaptcha import AnticaptchaClient, NoCaptchaTaskProxylessTask 8 | 9 | api_key = environ["KEY"] 10 | site_key_pattern = "'sitekey': '(.+?)'" 11 | url = "http://hcaptcha.jawne.info.pl/recaptcha.php" 12 | EXPECTED_RESULT = '"success": true,' 13 | client = AnticaptchaClient(api_key) 14 | 15 | DIR = os.path.dirname(os.path.abspath(__file__)) 16 | 17 | with open(os.path.join(DIR, "callback_sniffer.js"), "rb") as fp: 18 | wrapper_code = fp.read() 19 | 20 | 21 | def get_token(url, site_key): 22 | task = NoCaptchaTaskProxylessTask(website_url=url, website_key=site_key) 23 | job = client.createTask(task) 24 | job.join(maximum_time=60 * 15) 25 | return job.get_solution_response() 26 | 27 | 28 | def process(driver): 29 | driver.get(url) 30 | site_key = get_sitekey(driver) 31 | print("Found site-key", site_key) 32 | token = get_token(url, site_key) 33 | print("Found token", token) 34 | form_submit(driver, token) 35 | return driver.page_source 36 | 37 | 38 | def form_submit(driver, token): 39 | driver.execute_script( 40 | "document.getElementById('g-recaptcha-response').innerHTML='{}';".format(token) 41 | ) 42 | driver.execute_script("grecaptcha.recaptchaCallback[0]('{}')".format(token)) 43 | time.sleep(1) 44 | 45 | 46 | def get_sitekey(driver): 47 | return re.search(site_key_pattern, driver.page_source).group(1) 48 | 49 | 50 | if __name__ == "__main__": 51 | from seleniumwire import webdriver # Import from seleniumwire 52 | 53 | def custom(req, req_body, res, res_body): 54 | if not req.path or not "recaptcha" in req.path: 55 | return 56 | if not res.headers.get("Content-Type", None) == "text/javascript": 57 | return 58 | if res.headers["Content-Encoding"] == "gzip": 59 | del res.headers["Content-Encoding"] 60 | res_body = gzip.decompress(res_body) 61 | return res_body + wrapper_code 62 | 63 | driver = webdriver.Firefox(seleniumwire_options={"custom_response_handler": custom}) 64 | 65 | try: 66 | assert EXPECTED_RESULT in process(driver) 67 | finally: 68 | driver.close() 69 | -------------------------------------------------------------------------------- /examples/remote_image.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from os import environ 3 | 4 | from python_anticaptcha import AnticaptchaClient, ImageToTextTask 5 | 6 | api_key = environ["KEY"] 7 | 8 | URL = "https://raw.githubusercontent.com/ad-m/python-anticaptcha/master/examples/captcha_ms.jpeg" 9 | EXPECTED_RESULT = "56nn2" 10 | 11 | 12 | def process(url): 13 | session = requests.Session() 14 | client = AnticaptchaClient(api_key) 15 | task = ImageToTextTask(session.get(url, stream=True).raw) 16 | job = client.createTask(task) 17 | job.join() 18 | return job.get_captcha_text() 19 | 20 | 21 | if __name__ == "__main__": 22 | print("URL: " + URL) 23 | print("Result: " + str(process(URL))) 24 | print("Expected: " + str(EXPECTED_RESULT)) 25 | -------------------------------------------------------------------------------- /examples/text.py: -------------------------------------------------------------------------------- 1 | from os import environ 2 | 3 | from python_anticaptcha import AnticaptchaClient, ImageToTextTask 4 | 5 | api_key = environ["KEY"] 6 | 7 | IMAGE = "examples/captcha_ms.jpeg" 8 | EXPECTED_RESULT = "56nn2" 9 | 10 | 11 | def process(path): 12 | captcha_fp = open(path, "rb") 13 | client = AnticaptchaClient(api_key) 14 | task = ImageToTextTask(captcha_fp) 15 | job = client.createTask(task) 16 | job.join() 17 | return job.get_captcha_text() 18 | 19 | 20 | if __name__ == "__main__": 21 | print("Image: " + IMAGE) 22 | print("Result: " + str(process(IMAGE))) 23 | print("Expected: " + str(EXPECTED_RESULT)) 24 | -------------------------------------------------------------------------------- /examples/text_stream.py: -------------------------------------------------------------------------------- 1 | import os 2 | from os import environ 3 | 4 | from python_anticaptcha import AnticaptchaClient, ImageToTextTask 5 | 6 | api_key = environ["KEY"] 7 | DIR = os.path.dirname(os.path.abspath(__file__)) 8 | IMAGE = "{}/captcha_ms.jpeg".format(DIR) 9 | EXPECTED_RESULT = "56nn2" 10 | 11 | 12 | def process(path): 13 | captcha_fp = open(path, "rb") 14 | client = AnticaptchaClient(api_key) 15 | task = ImageToTextTask(captcha_fp) 16 | job = client.createTaskSmee(task) 17 | return job.get_captcha_text() 18 | 19 | 20 | if __name__ == "__main__": 21 | print("Image: " + IMAGE) 22 | print("Result: " + str(process(IMAGE))) 23 | print("Expected: " + str(EXPECTED_RESULT)) 24 | -------------------------------------------------------------------------------- /python_anticaptcha/__init__.py: -------------------------------------------------------------------------------- 1 | from .base import AnticaptchaClient 2 | from pkg_resources import get_distribution, DistributionNotFound 3 | from .tasks import ( 4 | NoCaptchaTaskProxylessTask, 5 | RecaptchaV2TaskProxyless, 6 | NoCaptchaTask, 7 | RecaptchaV2Task, 8 | FunCaptchaProxylessTask, 9 | FunCaptchaTask, 10 | ImageToTextTask, 11 | RecaptchaV3TaskProxyless, 12 | HCaptchaTaskProxyless, 13 | HCaptchaTask, 14 | RecaptchaV2EnterpriseTaskProxyless, 15 | RecaptchaV2EnterpriseTask, 16 | GeeTestTaskProxyless, 17 | GeeTestTask, 18 | AntiGateTaskProxyless, 19 | AntiGateTask 20 | ) 21 | from .exceptions import AnticaptchaException 22 | 23 | AnticatpchaException = AnticaptchaException 24 | 25 | try: 26 | __version__ = get_distribution(__name__).version 27 | except DistributionNotFound: 28 | # package is not installed 29 | pass 30 | -------------------------------------------------------------------------------- /python_anticaptcha/base.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | import json 4 | import warnings 5 | 6 | from .compat import split 7 | from six.moves.urllib_parse import urljoin 8 | from .exceptions import AnticaptchaException 9 | 10 | SLEEP_EVERY_CHECK_FINISHED = 3 11 | MAXIMUM_JOIN_TIME = 60 * 5 12 | 13 | 14 | class Job(object): 15 | client = None 16 | task_id = None 17 | _last_result = None 18 | 19 | def __init__(self, client, task_id): 20 | self.client = client 21 | self.task_id = task_id 22 | 23 | def _update(self): 24 | self._last_result = self.client.getTaskResult(self.task_id) 25 | 26 | def check_is_ready(self): 27 | self._update() 28 | return self._last_result["status"] == "ready" 29 | 30 | def get_solution_response(self): # Recaptcha 31 | return self._last_result["solution"]["gRecaptchaResponse"] 32 | 33 | def get_solution(self): 34 | return self._last_result["solution"] 35 | 36 | def get_token_response(self): # Funcaptcha 37 | return self._last_result["solution"]["token"] 38 | 39 | def get_answers(self): 40 | return self._last_result["solution"]["answers"] 41 | 42 | def get_captcha_text(self): # Image 43 | return self._last_result["solution"]["text"] 44 | 45 | def get_cells_numbers(self): 46 | return self._last_result["solution"]["cellNumbers"] 47 | 48 | def report_incorrect(self): 49 | warnings.warn( 50 | "report_incorrect is deprecated, use report_incorrect_image instead", 51 | DeprecationWarning, 52 | ) 53 | return self.client.reportIncorrectImage() 54 | 55 | def report_incorrect_image(self): 56 | return self.client.reportIncorrectImage(self.task_id) 57 | 58 | def report_incorrect_recaptcha(self): 59 | return self.client.reportIncorrectRecaptcha(self.task_id) 60 | 61 | def join(self, maximum_time=None): 62 | elapsed_time = 0 63 | maximum_time = maximum_time or MAXIMUM_JOIN_TIME 64 | while not self.check_is_ready(): 65 | time.sleep(SLEEP_EVERY_CHECK_FINISHED) 66 | elapsed_time += SLEEP_EVERY_CHECK_FINISHED 67 | if elapsed_time is not None and elapsed_time > maximum_time: 68 | raise AnticaptchaException( 69 | None, 70 | 250, 71 | "The execution time exceeded a maximum time of {} seconds. It takes {} seconds.".format( 72 | maximum_time, elapsed_time 73 | ), 74 | ) 75 | 76 | 77 | class AnticaptchaClient(object): 78 | client_key = None 79 | CREATE_TASK_URL = "/createTask" 80 | TASK_RESULT_URL = "/getTaskResult" 81 | BALANCE_URL = "/getBalance" 82 | REPORT_IMAGE_URL = "/reportIncorrectImageCaptcha" 83 | REPORT_RECAPTCHA_URL = "/reportIncorrectRecaptcha" 84 | APP_STAT_URL = "/getAppStats" 85 | SOFT_ID = 847 86 | language_pool = "en" 87 | response_timeout = 5 88 | 89 | def __init__( 90 | self, client_key, language_pool="en", host="api.anti-captcha.com", use_ssl=True 91 | ): 92 | self.client_key = client_key 93 | self.language_pool = language_pool 94 | self.base_url = "{proto}://{host}/".format( 95 | proto="https" if use_ssl else "http", host=host 96 | ) 97 | self.session = requests.Session() 98 | 99 | @property 100 | def client_ip(self): 101 | if not hasattr(self, "_client_ip"): 102 | self._client_ip = self.session.get( 103 | "https://api.myip.com", timeout=self.response_timeout 104 | ).json()["ip"] 105 | return self._client_ip 106 | 107 | def _check_response(self, response): 108 | if response.get("errorId", False) == 11: 109 | response[ 110 | "errorDescription" 111 | ] = "{} Your missing IP address is propably {}.".format( 112 | response["errorDescription"], self.client_ip 113 | ) 114 | if response.get("errorId", False): 115 | raise AnticaptchaException( 116 | response["errorId"], response["errorCode"], response["errorDescription"] 117 | ) 118 | 119 | def createTask(self, task): 120 | request = { 121 | "clientKey": self.client_key, 122 | "task": task.serialize(), 123 | "softId": self.SOFT_ID, 124 | "languagePool": self.language_pool, 125 | } 126 | response = self.session.post( 127 | urljoin(self.base_url, self.CREATE_TASK_URL), 128 | json=request, 129 | timeout=self.response_timeout, 130 | ).json() 131 | self._check_response(response) 132 | return Job(self, response["taskId"]) 133 | 134 | def createTaskSmee(self, task, timeout=MAXIMUM_JOIN_TIME): 135 | """ 136 | Beta method to stream response from smee.io 137 | """ 138 | response = self.session.head( 139 | "https://smee.io/new", timeout=self.response_timeout 140 | ) 141 | smee_url = response.headers["Location"] 142 | task = task.serialize() 143 | request = { 144 | "clientKey": self.client_key, 145 | "task": task, 146 | "softId": self.SOFT_ID, 147 | "languagePool": self.language_pool, 148 | "callbackUrl": smee_url, 149 | } 150 | r = self.session.get( 151 | url=smee_url, 152 | headers={"Accept": "text/event-stream"}, 153 | stream=True, 154 | timeout=(self.response_timeout, timeout), 155 | ) 156 | response = self.session.post( 157 | url=urljoin(self.base_url, self.CREATE_TASK_URL), 158 | json=request, 159 | timeout=self.response_timeout, 160 | ).json() 161 | self._check_response(response) 162 | for line in r.iter_lines(): 163 | content = line.decode("utf-8") 164 | if '"host":"smee.io"' not in content: 165 | continue 166 | payload = json.loads(split(content, ":", 1)[1].strip()) 167 | if "taskId" not in payload["body"] or str(payload["body"]["taskId"]) != str( 168 | response["taskId"] 169 | ): 170 | continue 171 | r.close() 172 | if task["type"] == "CustomCaptchaTask": 173 | payload["body"]["solution"] = payload["body"]["data"][0] 174 | job = Job(client=self, task_id=response["taskId"]) 175 | job._last_result = payload["body"] 176 | return job 177 | 178 | def getTaskResult(self, task_id): 179 | request = {"clientKey": self.client_key, "taskId": task_id} 180 | response = self.session.post( 181 | urljoin(self.base_url, self.TASK_RESULT_URL), json=request 182 | ).json() 183 | self._check_response(response) 184 | return response 185 | 186 | def getBalance(self): 187 | request = { 188 | "clientKey": self.client_key, 189 | "softId": self.SOFT_ID, 190 | } 191 | response = self.session.post( 192 | urljoin(self.base_url, self.BALANCE_URL), json=request 193 | ).json() 194 | self._check_response(response) 195 | return response["balance"] 196 | 197 | def getAppStats(self, soft_id, mode): 198 | request = {"clientKey": self.client_key, "softId": soft_id, "mode": mode} 199 | response = self.session.post( 200 | urljoin(self.base_url, self.APP_STAT_URL), json=request 201 | ).json() 202 | self._check_response(response) 203 | return response 204 | 205 | def reportIncorrectImage(self, task_id): 206 | request = {"clientKey": self.client_key, "taskId": task_id} 207 | response = self.session.post( 208 | urljoin(self.base_url, self.REPORT_IMAGE_URL), json=request 209 | ).json() 210 | self._check_response(response) 211 | return response.get("status", False) != False 212 | 213 | def reportIncorrectRecaptcha(self, task_id): 214 | request = {"clientKey": self.client_key, "taskId": task_id} 215 | response = self.session.post( 216 | urljoin(self.base_url, self.REPORT_RECAPTCHA_URL), json=request 217 | ).json() 218 | self._check_response(response) 219 | return response["status"] == "success" 220 | -------------------------------------------------------------------------------- /python_anticaptcha/compat.py: -------------------------------------------------------------------------------- 1 | import six 2 | 3 | if six.PY3: 4 | 5 | def split(value, sep, maxsplit): 6 | return value.split(sep, maxsplit=maxsplit) 7 | 8 | 9 | else: 10 | 11 | def split(value, sep, maxsplit): 12 | parts = value.split(sep) 13 | return parts[:maxsplit] + [ 14 | sep.join(parts[maxsplit:]), 15 | ] 16 | -------------------------------------------------------------------------------- /python_anticaptcha/exceptions.py: -------------------------------------------------------------------------------- 1 | class AnticaptchaException(Exception): 2 | def __init__(self, error_id, error_code, error_description, *args): 3 | super(AnticaptchaException, self).__init__( 4 | "[{}:{}]{}".format(error_code, error_id, error_description) 5 | ) 6 | self.error_description = error_description 7 | self.error_id = error_id 8 | self.error_code = error_code 9 | 10 | 11 | AnticatpchaException = AnticaptchaException 12 | 13 | 14 | class InvalidWidthException(AnticaptchaException): 15 | def __init__(self, width): 16 | self.width = width 17 | msg = "Invalid width (%s). Can be one of these: 100, 50, 33, 25." % ( 18 | self.width, 19 | ) 20 | super(InvalidWidthException, self).__init__("AC-1", 1, msg) 21 | 22 | 23 | class MissingNameException(AnticaptchaException): 24 | def __init__(self, cls): 25 | self.cls = cls 26 | msg = 'Missing name data in {0}. Provide {0}.__init__(name="X") or {0}.serialize(name="X")'.format( 27 | str(self.cls) 28 | ) 29 | super(MissingNameException, self).__init__("AC-2", 2, msg) 30 | -------------------------------------------------------------------------------- /python_anticaptcha/tasks.py: -------------------------------------------------------------------------------- 1 | import base64 2 | 3 | 4 | class BaseTask(object): 5 | type = None 6 | 7 | def serialize(self, **result): 8 | result["type"] = self.type 9 | return result 10 | 11 | 12 | class UserAgentMixin(BaseTask): 13 | def __init__(self, *args, **kwargs): 14 | self.userAgent = kwargs.pop("user_agent") 15 | super(UserAgentMixin, self).__init__(*args, **kwargs) 16 | 17 | def serialize(self, **result): 18 | data = super(UserAgentMixin, self).serialize(**result) 19 | data["userAgent"] = self.userAgent 20 | return data 21 | 22 | 23 | class CookieMixin(BaseTask): 24 | def __init__(self, *args, **kwargs): 25 | self.cookies = kwargs.pop("cookies", "") 26 | super(CookieMixin, self).__init__(*args, **kwargs) 27 | 28 | def serialize(self, **result): 29 | data = super(CookieMixin, self).serialize(**result) 30 | if self.cookies: 31 | data["cookies"] = self.cookies 32 | return data 33 | 34 | 35 | class ProxyMixin(BaseTask): 36 | def __init__(self, *args, **kwargs): 37 | self.proxyType = kwargs.pop("proxy_type") 38 | self.proxyAddress = kwargs.pop("proxy_address") 39 | self.proxyPort = kwargs.pop("proxy_port") 40 | self.proxyLogin = kwargs.pop("proxy_login") 41 | self.proxyPassword = kwargs.pop("proxy_password") 42 | super(ProxyMixin, self).__init__(*args, **kwargs) 43 | 44 | def serialize(self, **result): 45 | data = super(ProxyMixin, self).serialize(**result) 46 | data["proxyType"] = self.proxyType 47 | data["proxyAddress"] = self.proxyAddress 48 | data["proxyPort"] = self.proxyPort 49 | if self.proxyLogin: 50 | data["proxyLogin"] = self.proxyLogin 51 | data["proxyPassword"] = self.proxyPassword 52 | if self.cookies: 53 | data["cookies"] = self.cookies 54 | return data 55 | 56 | 57 | class NoCaptchaTaskProxylessTask(BaseTask): 58 | type = "NoCaptchaTaskProxyless" 59 | websiteURL = None 60 | websiteKey = None 61 | websiteSToken = None 62 | recaptchaDataSValue = None 63 | 64 | def __init__( 65 | self, 66 | website_url, 67 | website_key, 68 | website_s_token=None, 69 | is_invisible=None, 70 | recaptcha_data_s_value=None, 71 | *args, 72 | **kwargs 73 | ): 74 | self.websiteURL = website_url 75 | self.websiteKey = website_key 76 | self.websiteSToken = website_s_token 77 | self.recaptchaDataSValue = recaptcha_data_s_value 78 | self.isInvisible = is_invisible 79 | super(NoCaptchaTaskProxylessTask, self).__init__(*args, **kwargs) 80 | 81 | def serialize(self, **result): 82 | data = super(NoCaptchaTaskProxylessTask, self).serialize(**result) 83 | data["websiteURL"] = self.websiteURL 84 | data["websiteKey"] = self.websiteKey 85 | if self.websiteSToken is not None: 86 | data["websiteSToken"] = self.websiteSToken 87 | if self.isInvisible is not None: 88 | data["isInvisible"] = self.isInvisible 89 | if self.recaptchaDataSValue is not None: 90 | data["recaptchaDataSValue"] = self.recaptchaDataSValue 91 | return data 92 | 93 | 94 | class RecaptchaV2TaskProxyless(NoCaptchaTaskProxylessTask): 95 | type = "RecaptchaV2TaskProxyless" 96 | 97 | 98 | class NoCaptchaTask( 99 | ProxyMixin, UserAgentMixin, CookieMixin, NoCaptchaTaskProxylessTask 100 | ): 101 | type = "NoCaptchaTask" 102 | 103 | 104 | class RecaptchaV2Task(NoCaptchaTask): 105 | type = "RecaptchaV2Task" 106 | 107 | 108 | class FunCaptchaProxylessTask(BaseTask): 109 | type = "FunCaptchaTaskProxyless" 110 | websiteURL = None 111 | websiteKey = None 112 | funcaptchaApiJSSubdomain = None 113 | data = None 114 | 115 | def __init__( 116 | self, website_url, website_key, subdomain=None, data=None, *args, **kwargs 117 | ): 118 | self.websiteURL = website_url 119 | self.websiteKey = website_key 120 | self.funcaptchaApiJSSubdomain = subdomain 121 | self.data = data 122 | super(FunCaptchaProxylessTask, self).__init__(*args, **kwargs) 123 | 124 | def serialize(self, **result): 125 | data = super(FunCaptchaProxylessTask, self).serialize(**result) 126 | data["websiteURL"] = self.websiteURL 127 | data["websitePublicKey"] = self.websiteKey 128 | if self.funcaptchaApiJSSubdomain: 129 | data["funcaptchaApiJSSubdomain"] = self.funcaptchaApiJSSubdomain 130 | if self.data: 131 | data["data"] = self.data 132 | return data 133 | 134 | 135 | class FunCaptchaTask(ProxyMixin, UserAgentMixin, CookieMixin, FunCaptchaProxylessTask): 136 | type = "FunCaptchaTask" 137 | 138 | 139 | class ImageToTextTask(BaseTask): 140 | type = "ImageToTextTask" 141 | fp = None 142 | phrase = None 143 | case = None 144 | numeric = None 145 | math = None 146 | minLength = None 147 | maxLength = None 148 | comment = None 149 | websiteUrl = None 150 | 151 | def __init__( 152 | self, 153 | fp, 154 | phrase=None, 155 | case=None, 156 | numeric=None, 157 | math=None, 158 | min_length=None, 159 | max_length=None, 160 | comment=None, 161 | website_url=None, 162 | *args, **kwargs 163 | ): 164 | self.fp = fp 165 | self.phrase = phrase 166 | self.case = case 167 | self.numeric = numeric 168 | self.math = math 169 | self.minLength = min_length 170 | self.maxLength = max_length 171 | self.comment = comment 172 | self.websiteUrl = website_url 173 | super(ImageToTextTask, self).__init__(*args, **kwargs) 174 | 175 | def serialize(self, **result): 176 | data = super(ImageToTextTask, self).serialize(**result) 177 | data["body"] = base64.b64encode(self.fp.read()).decode("utf-8") 178 | data["phrase"] = self.phrase 179 | data["case"] = self.case 180 | data["numeric"] = self.numeric 181 | data["math"] = self.math 182 | data["minLength"] = self.minLength 183 | data["maxLength"] = self.maxLength 184 | data["comment"] = self.comment 185 | data["websiteUrl"] = self.websiteUrl 186 | return data 187 | 188 | 189 | class RecaptchaV3TaskProxyless(BaseTask): 190 | type = "RecaptchaV3TaskProxyless" 191 | websiteURL = None 192 | websiteKey = None 193 | minScore = None 194 | pageAction = None 195 | isEnterprise = False 196 | 197 | def __init__( 198 | self, website_url, website_key, min_score, page_action, is_enterprise=False, *args, **kwargs 199 | ): 200 | self.websiteURL = website_url 201 | self.websiteKey = website_key 202 | self.minScore = min_score 203 | self.pageAction = page_action 204 | self.isEnterprise = is_enterprise 205 | super(RecaptchaV3TaskProxyless, self).__init__(*args, **kwargs) 206 | 207 | def serialize(self, **result): 208 | data = super(RecaptchaV3TaskProxyless, self).serialize(**result) 209 | data["websiteURL"] = self.websiteURL 210 | data["websiteKey"] = self.websiteKey 211 | data["minScore"] = self.minScore 212 | data["pageAction"] = self.pageAction 213 | data["isEnterprise"] = self.isEnterprise 214 | return data 215 | 216 | 217 | class HCaptchaTaskProxyless(BaseTask): 218 | type = "HCaptchaTaskProxyless" 219 | websiteURL = None 220 | websiteKey = None 221 | 222 | def __init__(self, website_url, website_key, *args, **kwargs): 223 | self.websiteURL = website_url 224 | self.websiteKey = website_key 225 | super(HCaptchaTaskProxyless, self).__init__(*args, **kwargs) 226 | 227 | def serialize(self, **result): 228 | data = super(HCaptchaTaskProxyless, self).serialize(**result) 229 | data["websiteURL"] = self.websiteURL 230 | data["websiteKey"] = self.websiteKey 231 | return data 232 | 233 | 234 | class HCaptchaTask(ProxyMixin, UserAgentMixin, CookieMixin, HCaptchaTaskProxyless): 235 | type = "HCaptchaTask" 236 | 237 | 238 | class RecaptchaV2EnterpriseTaskProxyless(BaseTask): 239 | type = "RecaptchaV2EnterpriseTaskProxyless" 240 | websiteURL = None 241 | websiteKey = None 242 | enterprisePayload = None 243 | apiDomain = None 244 | 245 | def __init__(self, website_url, website_key, enterprise_payload, api_domain, *args, **kwargs): 246 | self.websiteURL = website_url 247 | self.websiteKey = website_key 248 | self.enterprisePayload = enterprise_payload 249 | self.apiDomain = api_domain 250 | super(RecaptchaV2EnterpriseTaskProxyless, self).__init__(*args, **kwargs) 251 | 252 | def serialize(self, **result): 253 | data = super(RecaptchaV2EnterpriseTaskProxyless, self).serialize(**result) 254 | data["websiteURL"] = self.websiteURL 255 | data["websiteKey"] = self.websiteKey 256 | if self.enterprisePayload: 257 | data["enterprisePayload"] = self.enterprisePayload 258 | if self.apiDomain: 259 | data["apiDomain"] = self.apiDomain 260 | return data 261 | 262 | 263 | class RecaptchaV2EnterpriseTask(ProxyMixin, UserAgentMixin, CookieMixin, BaseTask): 264 | type = "RecaptchaV2EnterpriseTask" 265 | 266 | 267 | class GeeTestTaskProxyless(BaseTask): 268 | type = "GeeTestTaskProxyless" 269 | websiteURL = None 270 | gt = None 271 | challenge = None 272 | geetestApiServerSubdomain = None 273 | geetestGetLib = None 274 | 275 | def __init__( 276 | self, website_url, gt, challenge, subdomain=None, lib=None, *args, **kwargs 277 | ): 278 | self.websiteURL = website_url 279 | self.gt = gt 280 | self.challenge = challenge 281 | self.geetestApiServerSubdomain = subdomain 282 | self.geetestGetLib = lib 283 | super(GeeTestTaskProxyless).__init__(*args, **kwargs) 284 | 285 | def serialize(self, **result): 286 | data = super(GeeTestTaskProxyless, self).serialize(**result) 287 | data["websiteURL"] = self.websiteURL 288 | data["gt"] = self.gt 289 | data["challenge"] = self.challenge 290 | if self.geetestApiServerSubdomain: 291 | data["geetestApiServerSubdomain"] = self.geetestApiServerSubdomain 292 | if self.geetestGetLib: 293 | data["geetestGetLib"] = self.geetestGetLib 294 | return data 295 | 296 | 297 | class GeeTestTask(ProxyMixin, UserAgentMixin, GeeTestTaskProxyless): 298 | pass 299 | 300 | 301 | class AntiGateTaskProxyless(BaseTask): 302 | type = "AntiGateTask" 303 | websiteURL = None 304 | templateName = None 305 | variables = None 306 | 307 | def __init__(self, website_url, template_name, variables, *args, **kwargs): 308 | self.websiteURL = website_url 309 | self.templateName = template_name 310 | self.variables = variables 311 | super(AntiGateTaskProxyless).__init__(*args, **kwargs) 312 | 313 | def serialize(self, **result): 314 | data = super(AntiGateTaskProxyless, self).serialize(**result) 315 | data["websiteURL"] = self.websiteURL 316 | data["templateName"] = self.templateName 317 | data["variables"] = self.variables 318 | return data 319 | 320 | 321 | class AntiGateTask(ProxyMixin, AntiGateTaskProxyless): 322 | pass 323 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.4.2 3 | commit = True 4 | tag = True 5 | tag_name = {new_version} 6 | 7 | [nosetests] 8 | process-timeout = 600 9 | 10 | [bdist_wheel] 11 | universal = 1 12 | 13 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | from codecs import open 3 | from os import path, system 4 | import sys 5 | 6 | here = path.abspath(path.dirname(__file__)) 7 | 8 | # Get the long description from the README file 9 | with open(path.join(here, "README.rst"), encoding="utf-8") as f: 10 | long_description = f.read() 11 | 12 | 13 | tests_deps = ["retry", "nose2", "selenium"] 14 | 15 | extras = {"tests": tests_deps, "docs": "sphinx"} 16 | 17 | setup( 18 | test_suite='nose2.collector.collector', 19 | name="python-anticaptcha", 20 | description="Client library for solve captchas with Anticaptcha.com support.", 21 | long_description=long_description, 22 | url="https://github.com/ad-m/python-anticaptcha", 23 | author="Adam Dobrawy", 24 | author_email="anticaptcha@jawnosc.tk", 25 | license="MIT", 26 | classifiers=[ 27 | "Development Status :: 4 - Beta", 28 | "Intended Audience :: Developers", 29 | "License :: OSI Approved :: MIT License", 30 | "Programming Language :: Python :: 2", 31 | "Programming Language :: Python :: 2.7", 32 | "Programming Language :: Python :: 3", 33 | "Programming Language :: Python :: 3.4", 34 | "Programming Language :: Python :: 3.5", 35 | "Programming Language :: Python :: 3.6", 36 | "Programming Language :: Python :: 3.7", 37 | "Programming Language :: Python :: 3.8", 38 | "Programming Language :: Python :: 3.9", 39 | "Programming Language :: Python :: 3.10", 40 | ], 41 | use_scm_version=True, 42 | setup_requires=["setuptools_scm", "wheel"], 43 | keywords="recaptcha captcha development", 44 | packages=["python_anticaptcha"], 45 | install_requires=["requests", "six"], 46 | tests_require=tests_deps, 47 | extras_require=extras, 48 | ) 49 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ad-m/python-anticaptcha/076922ee646483328c580c6623f7cb49a2ea4493/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_examples.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from unittest import TestCase, skipIf 3 | from retry import retry 4 | 5 | import os 6 | 7 | from python_anticaptcha import AnticatpchaException 8 | from contextlib import contextmanager 9 | 10 | _multiprocess_can_split_ = True 11 | 12 | 13 | def missing_key(*args, **kwargs): 14 | return skipIf( 15 | "KEY" not in os.environ, 16 | "Missing KEY environment variable. " "Unable to connect Anti-captcha.com", 17 | )(*args, **kwargs) 18 | 19 | 20 | def missing_proxy(*args, **kwargs): 21 | return skipIf( 22 | "PROXY_URL" not in os.environ, "Missing PROXY_URL environment variable" 23 | )(*args, **kwargs) 24 | 25 | 26 | @missing_key 27 | class AntiGateTestCase(TestCase): 28 | @retry(tries=3) 29 | def test_process_antigate(self): 30 | from examples import antigate 31 | 32 | solution = antigate.process() 33 | for key in ["url", "domain", "localStorage", "cookies", "fingerprint"]: 34 | self.assertIn(key, solution) 35 | 36 | 37 | @missing_key 38 | @missing_proxy 39 | class FuncaptchaTestCase(TestCase): 40 | # CI Proxy is unstable. 41 | # Occasionally fails, so I repeat my attempt to have others selected. 42 | @retry(tries=3) 43 | def test_funcaptcha(self): 44 | from examples import funcaptcha_request 45 | 46 | self.assertIn("Solved!", funcaptcha_request.process()) 47 | 48 | 49 | @missing_key 50 | class RecaptchaRequestTestCase(TestCase): 51 | # Anticaptcha responds is not fully reliable. 52 | @retry(tries=6) 53 | def test_process(self): 54 | from examples import recaptcha_request 55 | 56 | self.assertIn(recaptcha_request.EXPECTED_RESULT, recaptcha_request.process()) 57 | 58 | 59 | @missing_key 60 | @skipIf(True, "Anti-captcha unable to provide required score, but we tests via proxy") 61 | class RecaptchaV3ProxylessTestCase(TestCase): 62 | # Anticaptcha responds is not fully reliable. 63 | @retry(tries=3) 64 | def test_process(self): 65 | from examples import recaptcha3_request 66 | 67 | self.assertTrue(recaptcha3_request.process()["success"]) 68 | 69 | 70 | @contextmanager 71 | def open_driver(*args, **kwargs): 72 | from selenium.webdriver import Chrome 73 | 74 | driver = Chrome(*args, **kwargs) 75 | try: 76 | yield driver 77 | finally: 78 | driver.quit() 79 | 80 | 81 | @missing_key 82 | class RecaptchaSeleniumtTestCase(TestCase): 83 | # Anticaptcha responds is not fully reliable. 84 | @retry(tries=6) 85 | def test_process(self): 86 | from examples import recaptcha_selenium 87 | from selenium.webdriver.chrome.options import Options 88 | 89 | options = Options() 90 | options.headless = True 91 | options.add_experimental_option('prefs', {'intl.accept_languages': 'en_US'}) 92 | 93 | with open_driver( 94 | options=options, 95 | ) as driver: 96 | self.assertIn( 97 | recaptcha_selenium.EXPECTED_RESULT, recaptcha_selenium.process(driver) 98 | ) 99 | 100 | 101 | @missing_key 102 | class TextTestCase(TestCase): 103 | def test_process(self): 104 | from examples import text 105 | 106 | self.assertEqual(text.process(text.IMAGE).lower(), text.EXPECTED_RESULT.lower()) 107 | 108 | 109 | @missing_key 110 | @skipIf(True, "We testing via proxy for performance reason.") 111 | class HCaptchaTaskProxylessTestCase(TestCase): 112 | @retry(tries=3) 113 | def test_process(self): 114 | from examples import hcaptcha_request 115 | 116 | self.assertIn(hcaptcha_request.EXPECTED_RESULT, hcaptcha_request.process()) 117 | 118 | 119 | @missing_key 120 | @missing_proxy 121 | class HCaptchaTaskTestCase(TestCase): 122 | @retry(tries=3) 123 | def test_process(self): 124 | from examples import hcaptcha_request_proxy 125 | 126 | self.assertIn( 127 | "Your request have submitted successfully.", 128 | hcaptcha_request_proxy.process(), 129 | ) 130 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27,py34,py35,pypy,pypy3 3 | 4 | [testenv] 5 | passenv=KEY PROXY_URL 6 | deps=. 7 | commands= 8 | - python examples/recaptcha.py 9 | - python examples/text.py 10 | 11 | [bdist_wheel] 12 | universal=1 13 | -------------------------------------------------------------------------------- /unittest.cfg: -------------------------------------------------------------------------------- 1 | [unittest] 2 | plugins = nose2.plugins.mp 3 | 4 | [multiprocess] 5 | always-on = true 6 | processes = 8 7 | test-run-timeout = 5 --------------------------------------------------------------------------------