├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .readthedocs.yaml ├── HISTORY.md ├── LICENSE ├── MANIFEST.in ├── Pipfile ├── Pipfile.lock ├── README.md ├── README.md.erb ├── docs ├── Makefile ├── _static │ └── serpapi-python.png ├── conf.py ├── index.rst └── requirements.txt ├── ext └── serpapi-python.svg ├── pylint.rc ├── pyproject.toml ├── serpapi ├── __init__.py ├── __version__.py ├── core.py ├── exceptions.py ├── http.py ├── models.py ├── textui.py └── utils.py ├── setup.py └── tests ├── __init__.py ├── conftest.py ├── example_search_apple_app_store_test.py ├── example_search_baidu_test.py ├── example_search_bing_test.py ├── example_search_duckduckgo_test.py ├── example_search_ebay_test.py ├── example_search_google_autocomplete_test.py ├── example_search_google_events_test.py ├── example_search_google_images_test.py ├── example_search_google_jobs_test.py ├── example_search_google_local_services_test.py ├── example_search_google_maps_test.py ├── example_search_google_play_test.py ├── example_search_google_product_test.py ├── example_search_google_reverse_image_test.py ├── example_search_google_scholar_test.py ├── example_search_google_test.py ├── example_search_home_depot_test.py ├── example_search_naver_test.py ├── example_search_walmart_test.py ├── example_search_yahoo_test.py ├── example_search_youtube_test.py └── test_integration.py /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: serpapi-python 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] 19 | 20 | steps: 21 | - name: Checkout repository 22 | uses: actions/checkout@v3 23 | 24 | - name: Set up Python ${{ matrix.python-version }} 25 | uses: actions/setup-python@v4 26 | with: 27 | python-version: ${{ matrix.python-version }} 28 | 29 | 30 | - name: Install dependencies 31 | run: pip install -e .[test] 32 | 33 | - name: Test with pytest 34 | run: pytest 35 | env: 36 | API_KEY: ${{secrets.API_KEY}} 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | 3 | script/ 4 | .coverage 5 | docs/build 6 | dist/ 7 | build/ 8 | .pytest_cache/ 9 | serpapi.egg-info/ 10 | 11 | 12 | __pycache__ 13 | *.pyc 14 | .DS_Store 15 | .envrc 16 | 17 | .vscode 18 | t.py 19 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the OS, Python version and other tools you might need 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.11" 13 | # You can also specify other tool versions: 14 | # nodejs: "19" 15 | # rust: "1.64" 16 | # golang: "1.19" 17 | 18 | # Build documentation in the "docs/" directory with Sphinx 19 | sphinx: 20 | configuration: docs/conf.py 21 | 22 | # Optionally build your docs in additional formats such as PDF and ePub 23 | formats: 24 | - pdf 25 | - epub 26 | 27 | # Optional but recommended, declare the Python requirements required 28 | # to build your documentation 29 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 30 | python: 31 | install: 32 | - requirements: docs/requirements.txt 33 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | Release History 2 | =============== 3 | 4 | 0.1.5 (2023-11-01) 5 | ------------------ 6 | 7 | - Python 3.12 support. 8 | 9 | 0.1.4 (2023-10-11) 10 | ------------------ 11 | 12 | - Add README documentation for various engines. 13 | 14 | 0.1.3 (2023-10-06) 15 | ------------------ 16 | 17 | - Replace deprecated serpapi_pagination.next_link with 'next'. 18 | - Improve documentation: how to use the client directly for pagination searches. 19 | 20 | 0.1.2 (2023-10-03) 21 | ------------------ 22 | 23 | - Update project status to Production/Stable. 24 | 25 | 0.1.1 (2023-10-03) 26 | ------------------ 27 | 28 | - Update documentation link to point to Read the Docs. 29 | 30 | 0.1.0 (2023-10-03) 31 | ------------------ 32 | 33 | - First release on PyPI. 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2023 SerpApi 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 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md HISTORY.md LICENSE 2 | recursive-include tests *.py -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | serpapi = {editable = true, path = "."} 8 | 9 | [dev-packages] 10 | alabaster = "*" 11 | sphinx = "*" 12 | pytest = "*" 13 | black = "*" 14 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "af8f25e5f407e8511d4991ef1bea16153306344c1b3f417c2b005dce48c36352" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": {}, 8 | "sources": [ 9 | { 10 | "name": "pypi", 11 | "url": "https://pypi.org/simple", 12 | "verify_ssl": true 13 | } 14 | ] 15 | }, 16 | "default": { 17 | "certifi": { 18 | "hashes": [ 19 | "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", 20 | "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" 21 | ], 22 | "markers": "python_version >= '3.6'", 23 | "version": "==2023.7.22" 24 | }, 25 | "charset-normalizer": { 26 | "hashes": [ 27 | "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96", 28 | "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c", 29 | "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710", 30 | "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706", 31 | "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020", 32 | "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252", 33 | "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad", 34 | "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329", 35 | "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a", 36 | "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f", 37 | "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6", 38 | "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4", 39 | "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a", 40 | "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46", 41 | "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2", 42 | "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23", 43 | "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace", 44 | "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd", 45 | "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982", 46 | "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10", 47 | "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2", 48 | "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea", 49 | "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09", 50 | "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5", 51 | "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149", 52 | "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489", 53 | "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9", 54 | "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80", 55 | "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592", 56 | "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3", 57 | "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6", 58 | "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed", 59 | "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c", 60 | "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200", 61 | "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a", 62 | "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e", 63 | "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d", 64 | "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6", 65 | "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623", 66 | "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669", 67 | "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3", 68 | "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa", 69 | "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9", 70 | "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2", 71 | "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f", 72 | "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1", 73 | "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4", 74 | "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a", 75 | "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8", 76 | "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3", 77 | "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029", 78 | "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f", 79 | "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959", 80 | "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22", 81 | "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7", 82 | "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952", 83 | "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346", 84 | "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e", 85 | "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d", 86 | "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299", 87 | "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd", 88 | "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a", 89 | "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3", 90 | "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037", 91 | "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94", 92 | "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c", 93 | "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858", 94 | "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a", 95 | "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449", 96 | "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c", 97 | "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918", 98 | "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1", 99 | "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c", 100 | "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac", 101 | "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa" 102 | ], 103 | "markers": "python_full_version >= '3.7.0'", 104 | "version": "==3.2.0" 105 | }, 106 | "idna": { 107 | "hashes": [ 108 | "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", 109 | "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" 110 | ], 111 | "markers": "python_version >= '3.5'", 112 | "version": "==3.4" 113 | }, 114 | "pygments": { 115 | "hashes": [ 116 | "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c", 117 | "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1" 118 | ], 119 | "markers": "python_version >= '3.7'", 120 | "version": "==2.15.1" 121 | }, 122 | "requests": { 123 | "hashes": [ 124 | "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", 125 | "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" 126 | ], 127 | "markers": "python_version >= '3.7'", 128 | "version": "==2.31.0" 129 | }, 130 | "serpapi": { 131 | "editable": true, 132 | "path": "." 133 | }, 134 | "urllib3": { 135 | "hashes": [ 136 | "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11", 137 | "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4" 138 | ], 139 | "markers": "python_version >= '3.7'", 140 | "version": "==2.0.4" 141 | } 142 | }, 143 | "develop": { 144 | "alabaster": { 145 | "hashes": [ 146 | "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3", 147 | "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2" 148 | ], 149 | "index": "pypi", 150 | "version": "==0.7.13" 151 | }, 152 | "babel": { 153 | "hashes": [ 154 | "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610", 155 | "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455" 156 | ], 157 | "markers": "python_version >= '3.7'", 158 | "version": "==2.12.1" 159 | }, 160 | "black": { 161 | "hashes": [ 162 | "sha256:01ede61aac8c154b55f35301fac3e730baf0c9cf8120f65a9cd61a81cfb4a0c3", 163 | "sha256:022a582720b0d9480ed82576c920a8c1dde97cc38ff11d8d8859b3bd6ca9eedb", 164 | "sha256:25cc308838fe71f7065df53aedd20327969d05671bac95b38fdf37ebe70ac087", 165 | "sha256:27eb7a0c71604d5de083757fbdb245b1a4fae60e9596514c6ec497eb63f95320", 166 | "sha256:327a8c2550ddc573b51e2c352adb88143464bb9d92c10416feb86b0f5aee5ff6", 167 | "sha256:47e56d83aad53ca140da0af87678fb38e44fd6bc0af71eebab2d1f59b1acf1d3", 168 | "sha256:501387a9edcb75d7ae8a4412bb8749900386eaef258f1aefab18adddea1936bc", 169 | "sha256:552513d5cd5694590d7ef6f46e1767a4df9af168d449ff767b13b084c020e63f", 170 | "sha256:5c4bc552ab52f6c1c506ccae05681fab58c3f72d59ae6e6639e8885e94fe2587", 171 | "sha256:642496b675095d423f9b8448243336f8ec71c9d4d57ec17bf795b67f08132a91", 172 | "sha256:6d1c6022b86f83b632d06f2b02774134def5d4d4f1dac8bef16d90cda18ba28a", 173 | "sha256:7f3bf2dec7d541b4619b8ce526bda74a6b0bffc480a163fed32eb8b3c9aed8ad", 174 | "sha256:831d8f54c3a8c8cf55f64d0422ee875eecac26f5f649fb6c1df65316b67c8926", 175 | "sha256:8417dbd2f57b5701492cd46edcecc4f9208dc75529bcf76c514864e48da867d9", 176 | "sha256:86cee259349b4448adb4ef9b204bb4467aae74a386bce85d56ba4f5dc0da27be", 177 | "sha256:893695a76b140881531062d48476ebe4a48f5d1e9388177e175d76234ca247cd", 178 | "sha256:9fd59d418c60c0348505f2ddf9609c1e1de8e7493eab96198fc89d9f865e7a96", 179 | "sha256:ad0014efc7acf0bd745792bd0d8857413652979200ab924fbf239062adc12491", 180 | "sha256:b5b0ee6d96b345a8b420100b7d71ebfdd19fab5e8301aff48ec270042cd40ac2", 181 | "sha256:c333286dc3ddca6fdff74670b911cccedacb4ef0a60b34e491b8a67c833b343a", 182 | "sha256:f9062af71c59c004cd519e2fb8f5d25d39e46d3af011b41ab43b9c74e27e236f", 183 | "sha256:fb074d8b213749fa1d077d630db0d5f8cc3b2ae63587ad4116e8a436e9bbe995" 184 | ], 185 | "index": "pypi", 186 | "version": "==23.7.0" 187 | }, 188 | "certifi": { 189 | "hashes": [ 190 | "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", 191 | "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" 192 | ], 193 | "markers": "python_version >= '3.6'", 194 | "version": "==2023.7.22" 195 | }, 196 | "charset-normalizer": { 197 | "hashes": [ 198 | "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96", 199 | "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c", 200 | "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710", 201 | "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706", 202 | "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020", 203 | "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252", 204 | "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad", 205 | "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329", 206 | "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a", 207 | "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f", 208 | "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6", 209 | "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4", 210 | "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a", 211 | "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46", 212 | "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2", 213 | "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23", 214 | "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace", 215 | "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd", 216 | "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982", 217 | "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10", 218 | "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2", 219 | "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea", 220 | "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09", 221 | "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5", 222 | "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149", 223 | "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489", 224 | "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9", 225 | "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80", 226 | "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592", 227 | "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3", 228 | "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6", 229 | "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed", 230 | "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c", 231 | "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200", 232 | "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a", 233 | "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e", 234 | "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d", 235 | "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6", 236 | "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623", 237 | "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669", 238 | "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3", 239 | "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa", 240 | "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9", 241 | "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2", 242 | "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f", 243 | "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1", 244 | "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4", 245 | "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a", 246 | "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8", 247 | "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3", 248 | "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029", 249 | "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f", 250 | "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959", 251 | "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22", 252 | "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7", 253 | "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952", 254 | "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346", 255 | "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e", 256 | "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d", 257 | "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299", 258 | "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd", 259 | "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a", 260 | "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3", 261 | "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037", 262 | "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94", 263 | "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c", 264 | "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858", 265 | "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a", 266 | "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449", 267 | "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c", 268 | "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918", 269 | "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1", 270 | "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c", 271 | "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac", 272 | "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa" 273 | ], 274 | "markers": "python_full_version >= '3.7.0'", 275 | "version": "==3.2.0" 276 | }, 277 | "click": { 278 | "hashes": [ 279 | "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd", 280 | "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5" 281 | ], 282 | "markers": "python_version >= '3.7'", 283 | "version": "==8.1.6" 284 | }, 285 | "docutils": { 286 | "hashes": [ 287 | "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6", 288 | "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b" 289 | ], 290 | "markers": "python_version >= '3.7'", 291 | "version": "==0.20.1" 292 | }, 293 | "idna": { 294 | "hashes": [ 295 | "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", 296 | "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" 297 | ], 298 | "markers": "python_version >= '3.5'", 299 | "version": "==3.4" 300 | }, 301 | "imagesize": { 302 | "hashes": [ 303 | "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", 304 | "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a" 305 | ], 306 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 307 | "version": "==1.4.1" 308 | }, 309 | "iniconfig": { 310 | "hashes": [ 311 | "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", 312 | "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" 313 | ], 314 | "markers": "python_version >= '3.7'", 315 | "version": "==2.0.0" 316 | }, 317 | "jinja2": { 318 | "hashes": [ 319 | "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", 320 | "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" 321 | ], 322 | "markers": "python_version >= '3.7'", 323 | "version": "==3.1.2" 324 | }, 325 | "markupsafe": { 326 | "hashes": [ 327 | "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e", 328 | "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e", 329 | "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431", 330 | "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686", 331 | "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559", 332 | "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc", 333 | "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c", 334 | "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0", 335 | "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4", 336 | "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9", 337 | "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575", 338 | "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba", 339 | "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d", 340 | "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3", 341 | "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00", 342 | "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155", 343 | "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac", 344 | "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52", 345 | "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f", 346 | "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8", 347 | "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b", 348 | "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24", 349 | "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea", 350 | "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198", 351 | "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0", 352 | "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee", 353 | "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be", 354 | "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2", 355 | "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707", 356 | "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6", 357 | "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58", 358 | "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779", 359 | "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636", 360 | "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c", 361 | "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad", 362 | "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee", 363 | "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc", 364 | "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2", 365 | "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48", 366 | "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7", 367 | "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e", 368 | "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b", 369 | "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa", 370 | "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5", 371 | "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e", 372 | "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb", 373 | "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9", 374 | "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57", 375 | "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc", 376 | "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2" 377 | ], 378 | "markers": "python_version >= '3.7'", 379 | "version": "==2.1.3" 380 | }, 381 | "mypy-extensions": { 382 | "hashes": [ 383 | "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", 384 | "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" 385 | ], 386 | "markers": "python_version >= '3.5'", 387 | "version": "==1.0.0" 388 | }, 389 | "packaging": { 390 | "hashes": [ 391 | "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", 392 | "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" 393 | ], 394 | "markers": "python_version >= '3.7'", 395 | "version": "==23.1" 396 | }, 397 | "pathspec": { 398 | "hashes": [ 399 | "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687", 400 | "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293" 401 | ], 402 | "markers": "python_version >= '3.7'", 403 | "version": "==0.11.1" 404 | }, 405 | "platformdirs": { 406 | "hashes": [ 407 | "sha256:1b42b450ad933e981d56e59f1b97495428c9bd60698baab9f3eb3d00d5822421", 408 | "sha256:ad8291ae0ae5072f66c16945166cb11c63394c7a3ad1b1bc9828ca3162da8c2f" 409 | ], 410 | "markers": "python_version >= '3.7'", 411 | "version": "==3.9.1" 412 | }, 413 | "pluggy": { 414 | "hashes": [ 415 | "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849", 416 | "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3" 417 | ], 418 | "markers": "python_version >= '3.7'", 419 | "version": "==1.2.0" 420 | }, 421 | "pygments": { 422 | "hashes": [ 423 | "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c", 424 | "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1" 425 | ], 426 | "markers": "python_version >= '3.7'", 427 | "version": "==2.15.1" 428 | }, 429 | "pytest": { 430 | "hashes": [ 431 | "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32", 432 | "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a" 433 | ], 434 | "index": "pypi", 435 | "version": "==7.4.0" 436 | }, 437 | "requests": { 438 | "hashes": [ 439 | "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", 440 | "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" 441 | ], 442 | "markers": "python_version >= '3.7'", 443 | "version": "==2.31.0" 444 | }, 445 | "snowballstemmer": { 446 | "hashes": [ 447 | "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", 448 | "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a" 449 | ], 450 | "version": "==2.2.0" 451 | }, 452 | "sphinx": { 453 | "hashes": [ 454 | "sha256:8f336d0221c3beb23006b3164ed1d46db9cebcce9cb41cdb9c5ecd4bcc509be0", 455 | "sha256:9bdfb5a2b28351d4fdf40a63cd006dbad727f793b243e669fc950d7116c634af" 456 | ], 457 | "index": "pypi", 458 | "version": "==7.1.0" 459 | }, 460 | "sphinxcontrib-applehelp": { 461 | "hashes": [ 462 | "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228", 463 | "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e" 464 | ], 465 | "markers": "python_version >= '3.8'", 466 | "version": "==1.0.4" 467 | }, 468 | "sphinxcontrib-devhelp": { 469 | "hashes": [ 470 | "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e", 471 | "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4" 472 | ], 473 | "markers": "python_version >= '3.5'", 474 | "version": "==1.0.2" 475 | }, 476 | "sphinxcontrib-htmlhelp": { 477 | "hashes": [ 478 | "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff", 479 | "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903" 480 | ], 481 | "markers": "python_version >= '3.8'", 482 | "version": "==2.0.1" 483 | }, 484 | "sphinxcontrib-jsmath": { 485 | "hashes": [ 486 | "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", 487 | "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8" 488 | ], 489 | "markers": "python_version >= '3.5'", 490 | "version": "==1.0.1" 491 | }, 492 | "sphinxcontrib-qthelp": { 493 | "hashes": [ 494 | "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72", 495 | "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6" 496 | ], 497 | "markers": "python_version >= '3.5'", 498 | "version": "==1.0.3" 499 | }, 500 | "sphinxcontrib-serializinghtml": { 501 | "hashes": [ 502 | "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd", 503 | "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952" 504 | ], 505 | "markers": "python_version >= '3.5'", 506 | "version": "==1.1.5" 507 | }, 508 | "urllib3": { 509 | "hashes": [ 510 | "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11", 511 | "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4" 512 | ], 513 | "markers": "python_version >= '3.7'", 514 | "version": "==2.0.4" 515 | } 516 | } 517 | } 518 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 |

SerpApi Python Library & Package

4 | serpapi python library logo 5 | 6 | ![Package](https://badge.fury.io/py/serpapi.svg) 7 | 8 | [![serpapi-python](https://github.com/serpapi/serpapi-python/actions/workflows/ci.yml/badge.svg)](https://github.com/serpapi/serpapi-python/actions/workflows/ci.yml) 9 |
10 | 11 | This repository is the home of the *soon–to–be* official Python API wrapper for [SerpApi](https://serpapi.com). This `serpapi` module allows you to access search data in your Python application. 12 | 13 | [SerpApi](https://serpapi.com) supports Google, Google Maps, Google Shopping, Bing, Baidu, Yandex, Yahoo, eBay, App Stores, and more. Check out the [documentation](https://serpapi.com/search-api) for a full list. 14 | 15 | 16 | ## Installation 17 | 18 | To install the `serpapi` package, simply run the following command: 19 | 20 | ```bash 21 | $ pip install serpapi 22 | ``` 23 | 24 | Please note that this package is separate from the legacy `serpapi` module, which is available on PyPi as `google-search-results`. This package is maintained by SerpApi, and is the recommended way to access the SerpApi service from Python. 25 | 26 | ## Usage 27 | 28 | Let's start by searching for Coffee on Google: 29 | 30 | ```pycon 31 | >>> import serpapi 32 | >>> s = serpapi.search(q="Coffee", engine="google", location="Austin, Texas", hl="en", gl="us") 33 | ``` 34 | 35 | The `s` variable now contains a `SerpResults` object, which acts just like a standard dictionary, with some convenient functions added on top. 36 | 37 | Let's print the first result: 38 | 39 | ```pycon 40 | >>> s["organic_results"][0]["link"] 41 | 'https://en.wikipedia.org/wiki/Coffee' 42 | ``` 43 | 44 | Let's print the title of the first result, but in a more Pythonic way: 45 | 46 | ```pycon 47 | >>> s["organic_results"][0].get("title") 48 | 'Coffee - Wikipedia' 49 | ``` 50 | 51 | The [SerpApi.com API Documentation](https://serpapi.com/search-api) contains a list of all the possible parameters that can be passed to the API. 52 | 53 | ## Documentation 54 | 55 | Documentation is [available on Read the Docs](https://serpapi-python.readthedocs.io/en/latest/). 56 | 57 | ## Basic Examples in Python 58 | 59 | ### Search Bing 60 | ```python 61 | import os 62 | import serpapi 63 | 64 | client = serpapi.Client(api_key=os.getenv("API_KEY")) 65 | results = client.search({ 66 | 'engine': 'bing', 67 | 'q': 'coffee' 68 | }) 69 | ``` 70 | - API Documentation: [serpapi.com/bing-search-api](https://serpapi.com/bing-search-api) 71 | 72 | ### Search Baidu 73 | ```python 74 | import os 75 | import serpapi 76 | 77 | client = serpapi.Client(api_key=os.getenv("API_KEY")) 78 | results = client.search({ 79 | 'engine': 'baidu', 80 | 'q': 'coffee', 81 | }) 82 | ``` 83 | - API Documentation: [serpapi.com/baidu-search-api](https://serpapi.com/baidu-search-api) 84 | 85 | ### Search Yahoo 86 | ```python 87 | import os 88 | import serpapi 89 | 90 | client = serpapi.Client(api_key=os.getenv("API_KEY")) 91 | results = client.search({ 92 | 'engine': 'yahoo', 93 | 'p': 'coffee', 94 | }) 95 | ``` 96 | - API Documentation: [serpapi.com/yahoo-search-api](https://serpapi.com/yahoo-search-api) 97 | 98 | ### Search YouTube 99 | ```python 100 | import os 101 | import serpapi 102 | 103 | client = serpapi.Client(api_key=os.getenv("API_KEY")) 104 | results = client.search({ 105 | 'engine': 'youtube', 106 | 'search_query': 'coffee', 107 | }) 108 | ``` 109 | - API Documentation: [serpapi.com/youtube-search-api](https://serpapi.com/youtube-search-api) 110 | 111 | ### Search Walmart 112 | ```python 113 | import os 114 | import serpapi 115 | 116 | client = serpapi.Client(api_key=os.getenv("API_KEY")) 117 | results = client.search({ 118 | 'engine': 'walmart', 119 | 'query': 'coffee', 120 | }) 121 | ``` 122 | - API Documentation: [serpapi.com/walmart-search-api](https://serpapi.com/walmart-search-api) 123 | 124 | ### Search eBay 125 | ```python 126 | import os 127 | import serpapi 128 | 129 | client = serpapi.Client(api_key=os.getenv("API_KEY")) 130 | results = client.search({ 131 | 'engine': 'ebay', 132 | '_nkw': 'coffee', 133 | }) 134 | ``` 135 | - API Documentation: [serpapi.com/ebay-search-api](https://serpapi.com/ebay-search-api) 136 | 137 | ### Search Naver 138 | ```python 139 | import os 140 | import serpapi 141 | 142 | client = serpapi.Client(api_key=os.getenv("API_KEY")) 143 | results = client.search({ 144 | 'engine': 'naver', 145 | 'query': 'coffee', 146 | }) 147 | ``` 148 | - API Documentation: [serpapi.com/naver-search-api](https://serpapi.com/naver-search-api) 149 | 150 | ### Search Home Depot 151 | ```python 152 | import os 153 | import serpapi 154 | 155 | client = serpapi.Client(api_key=os.getenv("API_KEY")) 156 | results = client.search({ 157 | 'engine': 'home_depot', 158 | 'q': 'table', 159 | }) 160 | ``` 161 | - API Documentation: [serpapi.com/home-depot-search-api](https://serpapi.com/home-depot-search-api) 162 | 163 | ### Search Apple App Store 164 | ```python 165 | import os 166 | import serpapi 167 | 168 | client = serpapi.Client(api_key=os.getenv("API_KEY")) 169 | results = client.search({ 170 | 'engine': 'apple_app_store', 171 | 'term': 'coffee', 172 | }) 173 | ``` 174 | - API Documentation: [serpapi.com/apple-app-store](https://serpapi.com/apple-app-store) 175 | 176 | ### Search DuckDuckGo 177 | ```python 178 | import os 179 | import serpapi 180 | 181 | client = serpapi.Client(api_key=os.getenv("API_KEY")) 182 | results = client.search({ 183 | 'engine': 'duckduckgo', 184 | 'q': 'coffee', 185 | }) 186 | ``` 187 | - API Documentation: [serpapi.com/duckduckgo-search-api](https://serpapi.com/duckduckgo-search-api) 188 | 189 | ### Search Google 190 | ```python 191 | import os 192 | import serpapi 193 | 194 | client = serpapi.Client(api_key=os.getenv("API_KEY")) 195 | results = client.search({ 196 | 'engine': 'google', 197 | 'q': 'coffee' 198 | }) 199 | ``` 200 | - API Documentation: [serpapi.com/search-api](https://serpapi.com/search-api) 201 | 202 | ### Search Google Scholar 203 | ```python 204 | import os 205 | import serpapi 206 | 207 | client = serpapi.Client(api_key=os.getenv("API_KEY")) 208 | results = client.search({ 209 | 'engine': 'google_scholar', 210 | 'q': 'coffee', 211 | }) 212 | ``` 213 | - API Documentation: [serpapi.com/google-scholar-api](https://serpapi.com/google-scholar-api) 214 | 215 | ### Search Google Autocomplete 216 | ```python 217 | import os 218 | import serpapi 219 | 220 | client = serpapi.Client(api_key=os.getenv("API_KEY")) 221 | results = client.search({ 222 | 'engine': 'google_autocomplete', 223 | 'q': 'coffee', 224 | }) 225 | ``` 226 | - API Documentation: [serpapi.com/google-autocomplete-api](https://serpapi.com/google-autocomplete-api) 227 | 228 | ### Search Google Product 229 | ```python 230 | import os 231 | import serpapi 232 | 233 | client = serpapi.Client(api_key=os.getenv("API_KEY")) 234 | results = client.search({ 235 | 'engine': 'google_product', 236 | 'q': 'coffee', 237 | 'product_id': '4887235756540435899', 238 | }) 239 | ``` 240 | - API Documentation: [serpapi.com/google-product-api](https://serpapi.com/google-product-api) 241 | 242 | ### Search Google Reverse Image 243 | ```python 244 | import os 245 | import serpapi 246 | 247 | client = serpapi.Client(api_key=os.getenv("API_KEY")) 248 | results = client.search({ 249 | 'engine': 'google_reverse_image', 250 | 'image_url': 'https://i.imgur.com/5bGzZi7.jpg', 251 | 'max_results': '1', 252 | }) 253 | ``` 254 | - API Documentation: [serpapi.com/google-reverse-image](https://serpapi.com/google-reverse-image) 255 | 256 | ### Search Google Events 257 | ```python 258 | import os 259 | import serpapi 260 | 261 | client = serpapi.Client(api_key=os.getenv("API_KEY")) 262 | results = client.search({ 263 | 'engine': 'google_events', 264 | 'q': 'coffee', 265 | }) 266 | ``` 267 | - API Documentation: [serpapi.com/google-events-api](https://serpapi.com/google-events-api) 268 | 269 | ### Search Google Local Services 270 | ```python 271 | import os 272 | import serpapi 273 | 274 | client = serpapi.Client(api_key=os.getenv("API_KEY")) 275 | results = client.search({ 276 | 'engine': 'google_local_services', 277 | 'q': 'electrician', 278 | 'data_cid': '6745062158417646970', 279 | }) 280 | ``` 281 | - API Documentation: [serpapi.com/google-local-services-api](https://serpapi.com/google-local-services-api) 282 | 283 | ### Search Google Maps 284 | ```python 285 | import os 286 | import serpapi 287 | 288 | 289 | client = serpapi.Client(api_key=os.getenv("API_KEY")) 290 | results = client.search({ 291 | 'engine': 'google_maps', 292 | 'q': 'pizza', 293 | 'll': '@40.7455096,-74.0083012,15.1z', 294 | 'type': 'search', 295 | }) 296 | ``` 297 | - API Documentation: [serpapi.com/google-maps-api](https://serpapi.com/google-maps-api) 298 | 299 | ### Search Google Jobs 300 | ```python 301 | import os 302 | import serpapi 303 | 304 | 305 | client = serpapi.Client(api_key=os.getenv("API_KEY")) 306 | results = client.search({ 307 | 'engine': 'google_jobs', 308 | 'q': 'coffee', 309 | }) 310 | ``` 311 | - API Documentation: [serpapi.com/google-jobs-api](https://serpapi.com/google-jobs-api) 312 | 313 | ### Search Google Play 314 | ```python 315 | import os 316 | import serpapi 317 | 318 | client = serpapi.Client(api_key=os.getenv("API_KEY")) 319 | results = client.search({ 320 | 'engine': 'google_play', 321 | 'q': 'kite', 322 | 'store': 'apps', 323 | 'max_results': '2', 324 | }) 325 | ``` 326 | - API Documentation: [serpapi.com/google-play-api](https://serpapi.com/google-play-api) 327 | 328 | ### Search Google Images 329 | ```python 330 | import os 331 | import serpapi 332 | 333 | client = serpapi.Client(api_key=os.getenv("API_KEY")) 334 | results = client.search({ 335 | 'engine': 'google_images', 336 | 'tbm': 'isch', 337 | 'q': 'coffee', 338 | }) 339 | ``` 340 | - API Documentation: [serpapi.com/images-results](https://serpapi.com/images-results) 341 | 342 | 343 | ## License 344 | 345 | MIT License. 346 | 347 | ## Contributing 348 | 349 | Bug reports and pull requests are welcome on GitHub. Once dependencies are installed, you can run the tests with `pytest`. 350 | -------------------------------------------------------------------------------- /README.md.erb: -------------------------------------------------------------------------------- 1 | <%- 2 | def snippet(format, path) 3 | lines = File.new(path).readlines 4 | stop = lines.size - 1 5 | slice = lines[7..stop] 6 | slice.reject! { |l| l.match?(/(^# |assert )/) } 7 | buf = slice.map { |l| l.gsub(/(^\s{2})/, '').gsub(/^\s*$/, '') }.join 8 | url = 'https://github.com/serpapi/serpapi-python/blob/master/' + path 9 | %Q(```#{format}\nimport serpapi\nimport pprint\nimport os\n\n#{buf}```\ntest: [#{path}](#{url})) 10 | end 11 | -%> 12 | 13 |
14 |

SerpApi Python Library & Package

15 | serpapi python library logo 16 | 17 | 19 | [![serpapi-python](https://github.com/serpapi/serpapi-python/actions/workflows/ci.yml/badge.svg)](https://github.com/serpapi/serpapi-python/actions/workflows/ci.yml) 20 |
21 | 22 | This repository is the home of the *soon–to–be* official Python API wrapper for [SerpApi](https://serpapi.com). This `serpapi` module allows you to access search data in your Python application. 23 | 24 | [SerpApi](https://serpapi.com) supports Google, Google Maps, Google Shopping, Bing, Baidu, Yandex, Yahoo, eBay, App Stores, and more. Check out the [documentation](https://serpapi.com/search-api) for a full list. 25 | 26 | ## Current Status 27 | 28 | This project is under development, and will be released to the public on PyPi soon. 29 | 30 | ## Installation 31 | 32 | To install the `serpapi` package, simply run the following command: 33 | 34 | ```bash 35 | $ pip install serpapi 36 | ``` 37 | 38 | Please note that this package is separate from the *soon–to–be* legacy `serpapi` module, which is currently available on PyPi as `google-search-results`. 39 | 40 | ## Usage 41 | 42 | Let's start by searching for Coffee on Google: 43 | 44 | ```pycon 45 | >>> import serpapi 46 | >>> s = serpapi.search(q="Coffee", engine="google", location="Austin, Texas", hl="en", gl="us") 47 | ``` 48 | 49 | The `s` variable now contains a `SerpResults` object, which acts just like a standard dictionary, with some convenient functions added on top. 50 | 51 | Let's print the first result: 52 | 53 | ```pycon 54 | >>> s["organic_results"][0]["link"] 55 | 'https://en.wikipedia.org/wiki/Coffee' 56 | ``` 57 | 58 | Let's print the title of the first result, but in a more Pythonic way: 59 | 60 | ```pycon 61 | >>> s["organic_results"][0].get("title") 62 | 'Coffee - Wikipedia' 63 | ``` 64 | 65 | The [SerpApi.com API Documentation](https://serpapi.com/search-api) contains a list of all the possible parameters that can be passed to the API. 66 | 67 | ## Documentation 68 | 69 | Documentation is [available on Read the Docs](https://serpapi-python.readthedocs.io/en/latest/). 70 | 71 | ## Examples in python 72 | Here is how to calls the APIs. 73 | 74 | ### Search bing 75 | <%= snippet('python', 'tests/example_search_bing_test.py') %> 76 | see: [serpapi.com/bing-search-api](https://serpapi.com/bing-search-api) 77 | 78 | ### Search baidu 79 | <%= snippet('python', 'tests/example_search_baidu_test.py') %> 80 | see: [serpapi.com/baidu-search-api](https://serpapi.com/baidu-search-api) 81 | 82 | ### Search yahoo 83 | <%= snippet('python', 'tests/example_search_yahoo_test.py') %> 84 | see: [serpapi.com/yahoo-search-api](https://serpapi.com/yahoo-search-api) 85 | 86 | ### Search youtube 87 | <%= snippet('python', 'tests/example_search_youtube_test.py') %> 88 | see: [serpapi.com/youtube-search-api](https://serpapi.com/youtube-search-api) 89 | 90 | ### Search walmart 91 | <%= snippet('python', 'tests/example_search_walmart_test.py') %> 92 | see: [serpapi.com/walmart-search-api](https://serpapi.com/walmart-search-api) 93 | 94 | ### Search ebay 95 | <%= snippet('python', 'tests/example_search_ebay_test.py') %> 96 | see: [serpapi.com/ebay-search-api](https://serpapi.com/ebay-search-api) 97 | 98 | ### Search naver 99 | <%= snippet('python', 'tests/example_search_naver_test.py') %> 100 | see: [serpapi.com/naver-search-api](https://serpapi.com/naver-search-api) 101 | 102 | ### Search home depot 103 | <%= snippet('python', 'tests/example_search_home_depot_test.py') %> 104 | see: [serpapi.com/home-depot-search-api](https://serpapi.com/home-depot-search-api) 105 | 106 | ### Search apple app store 107 | <%= snippet('python', 'tests/example_search_apple_app_store_test.py') %> 108 | see: [serpapi.com/apple-app-store](https://serpapi.com/apple-app-store) 109 | 110 | ### Search duckduckgo 111 | <%= snippet('python', 'tests/example_search_duckduckgo_test.py') %> 112 | see: [serpapi.com/duckduckgo-search-api](https://serpapi.com/duckduckgo-search-api) 113 | 114 | ### Search google 115 | <%= snippet('python', 'tests/example_search_google_test.py') %> 116 | see: [serpapi.com/search-api](https://serpapi.com/search-api) 117 | 118 | ### Search google scholar 119 | <%= snippet('python', 'tests/example_search_google_scholar_test.py') %> 120 | see: [serpapi.com/google-scholar-api](https://serpapi.com/google-scholar-api) 121 | 122 | ### Search google autocomplete 123 | <%= snippet('python', 'tests/example_search_google_autocomplete_test.py') %> 124 | see: [serpapi.com/google-autocomplete-api](https://serpapi.com/google-autocomplete-api) 125 | 126 | ### Search google product 127 | <%= snippet('python', 'tests/example_search_google_product_test.py') %> 128 | see: [serpapi.com/google-product-api](https://serpapi.com/google-product-api) 129 | 130 | ### Search google reverse image 131 | <%= snippet('python', 'tests/example_search_google_reverse_image_test.py') %> 132 | see: [serpapi.com/google-reverse-image](https://serpapi.com/google-reverse-image) 133 | 134 | ### Search google events 135 | <%= snippet('python', 'tests/example_search_google_events_test.py') %> 136 | see: [serpapi.com/google-events-api](https://serpapi.com/google-events-api) 137 | 138 | ### Search google local services 139 | <%= snippet('python', 'tests/example_search_google_local_services_test.py') %> 140 | see: [serpapi.com/google-local-services-api](https://serpapi.com/google-local-services-api) 141 | 142 | ### Search google maps 143 | <%= snippet('python', 'tests/example_search_google_maps_test.py') %> 144 | see: [serpapi.com/google-maps-api](https://serpapi.com/google-maps-api) 145 | 146 | ### Search google jobs 147 | <%= snippet('python', 'tests/example_search_google_jobs_test.py') %> 148 | see: [serpapi.com/google-jobs-api](https://serpapi.com/google-jobs-api) 149 | 150 | ### Search google play 151 | <%= snippet('python', 'tests/example_search_google_play_test.py') %> 152 | see: [serpapi.com/google-play-api](https://serpapi.com/google-play-api) 153 | 154 | ### Search google images 155 | <%= snippet('python', 'tests/example_search_google_images_test.py') %> 156 | see: [serpapi.com/images-results](https://serpapi.com/images-results) 157 | 158 | 159 | ## License 160 | 161 | MIT License. 162 | 163 | ## Contributing 164 | 165 | Bug reports and pull requests are welcome on GitHub. Once dependencies are installed, you can run the tests with `pytest`. 166 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 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) 21 | -------------------------------------------------------------------------------- /docs/_static/serpapi-python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serpapi/serpapi-python/cdcfb0ee8f7421f2fd5591519eac80cd64425751/docs/_static/serpapi-python.png -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | 16 | sys.path.insert(0, os.path.abspath("../..")) 17 | 18 | import serpapi 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = "serpapi-python" 23 | copyright = "2023 SerpApi, LLC" 24 | author = "SerpApi, LLC" 25 | 26 | # The full version, including alpha/beta/rc tags 27 | release = serpapi.__version__ 28 | 29 | 30 | # -- General configuration --------------------------------------------------- 31 | 32 | # Add any Sphinx extension module names here, as strings. They can be 33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 34 | # ones. 35 | extensions = ["sphinx.ext.githubpages", "sphinx.ext.autodoc"] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ["_templates"] 39 | 40 | # List of patterns, relative to source directory, that match files and 41 | # directories to ignore when looking for source files. 42 | # This pattern also affects html_static_path and html_extra_path. 43 | exclude_patterns = [] 44 | 45 | 46 | # -- Options for HTML output ------------------------------------------------- 47 | 48 | # The theme to use for HTML and HTML Help pages. See the documentation for 49 | # a list of builtin themes. 50 | # 51 | html_theme = "alabaster" 52 | 53 | # Add any paths that contain custom static files (such as style sheets) here, 54 | # relative to this directory. They are copied after the builtin static files, 55 | # so a file named "default.css" will overwrite the builtin "default.css". 56 | html_static_path = ["_static"] 57 | 58 | 59 | # -- Extension configuration ------------------------------------------------- 60 | html_theme_options = { 61 | "logo": "serpapi-python.png", 62 | "logo_name": "serapi-python", 63 | } 64 | 65 | html_sidebars = { 66 | "**": [ 67 | "about.html", 68 | ] 69 | } 70 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. serpapi-python documentation master file, created by 2 | sphinx-quickstart on Sun Apr 3 21:09:40 2022. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | **serpapi-python** 7 | ================== 8 | 9 | an official Python client library for `SerpApi `_. 10 | 11 | -------------- 12 | 13 | Installation 14 | ------------ 15 | 16 | To install ``serpapi-python``, simply use `pip`:: 17 | 18 | $ pip install serpapi 19 | 20 | 21 | Please note that Python 3.6+ is required. 22 | 23 | 24 | Usage 25 | ----- 26 | 27 | Usage of this module is fairly straight-forward. In general, this module attempts to be as close to the actual API as possible, while still being Pythonic. 28 | 29 | For example, the API endpoint ``https://serpapi.com/search.json`` is represented by the method ``serpapi.search()``. 30 | 31 | .. code-block:: python 32 | 33 | >>> import serpapi 34 | >>> s = serpapi.search(q="Coffee", engine="google", location="Austin, Texas", hl="en", gl="us") 35 | >>> s["organic_results"][0]["link"] 36 | 'https://en.wikipedia.org/wiki/Coffee' 37 | 38 | Any parameters that you pass to ``search()`` will be passed to the API. This includes the ``api_key`` parameter, which is required for all requests. 39 | 40 | .. _using-api-client-directly: 41 | 42 | Using the API Client directly 43 | ^^^^^^^^^ 44 | 45 | To make this less repetitive, and gain the benefit of connection pooling, let's start using the API Client directly:: 46 | 47 | >>> client = serpapi.Client(api_key="secret_api_key") 48 | >>> s = client.search(q="Coffee", engine="google", location="Austin, Texas", hl="en", gl="us") 49 | 50 | The ``api_key`` parameter is now automatically passed to all requests made by the client. 51 | 52 | 53 | Concise Tutorial 54 | ---------------- 55 | 56 | Let's start by searching for ``Coffee`` on Google:: 57 | 58 | >>> import serpapi 59 | >>> s = serpapi.search(q="Coffee", engine="google", location="Austin, Texas", hl="en", gl="us") 60 | 61 | The ``s`` variable now contains a :class:`SerpResults ` object, which acts just like a standard dictionary, with some convenient functions added on top. 62 | 63 | Let's print the first result:: 64 | 65 | >>> print(s["organic_results"][0]["link"]) 66 | https://en.wikipedia.org/wiki/Coffee 67 | 68 | Let's print the title of the first result, but in a more Pythonic way:: 69 | 70 | >>> print(s["organic_results"][0].get("title")) 71 | Coffee - Wikipedia 72 | 73 | The `SerpApi.com API Documentation `_ contains a list of all the possible parameters that can be passed to the API. 74 | 75 | 76 | API Reference 77 | ------------- 78 | 79 | .. _api-reference: 80 | 81 | This part of the documentation covers all the interfaces of :class:`serpapi` Python module. 82 | 83 | .. module:: serpapi 84 | :platform: Unix, Windows 85 | :synopsis: SerpApi Python Library 86 | 87 | .. autofunction:: serpapi.search 88 | .. autofunction:: serpapi.search_archive 89 | .. autofunction:: serpapi.locations 90 | .. autofunction:: serpapi.account 91 | 92 | 93 | 94 | Results from SerpApi.com 95 | ------------------------ 96 | 97 | When a successful search has been executed, the method returns 98 | a :class:`SerpResults ` object, which acts just like a standard dictionary, 99 | with some convenient functions added on top. 100 | 101 | 102 | .. code-block:: python 103 | 104 | >>> s = serpapi.search(q="Coffee", engine="google", location="Austin, Texas", hl="en", gl="us") 105 | >>> type(s) 106 | 107 | 108 | >>> s["organic_results"][0]["link"] 109 | 'https://en.wikipedia.org/wiki/Coffee' 110 | 111 | >>> s["search_metadata"] 112 | {'id': '64c148d35119a60ab1e00cc9', 'status': 'Success', 'json_endpoint': 'https://serpapi.com/searches/a15e1b92727f292c/64c148d35119a60ab1e00cc9.json', 'created_at': '2023-07-26 16:24:51 UTC', 'processed_at': '2023-07-26 16:24:51 UTC', 'google_url': 'https://www.google.com/search?q=Coffee&oq=Coffee&uule=w+CAIQICIdQXVzdGluLFRYLFRleGFzLFVuaXRlZCBTdGF0ZXM&hl=en&gl=us&sourceid=chrome&ie=UTF-8', 'raw_html_file': 'https://serpapi.com/searches/a15e1b92727f292c/64c148d35119a60ab1e00cc9.html', 'total_time_taken': 1.55} 113 | 114 | Optionally, if you want exactly a dictionary of the entire response, you can use the ``as_dict()`` method:: 115 | 116 | >>> type(s.as_dict()) 117 | 118 | 119 | You can get the next page of results:: 120 | 121 | >>> type(s.next_page()) 122 | 123 | 124 | To iterate over all pages of results, it's recommended to :ref:`use the API Client directly `:: 125 | 126 | >>> client = serpapi.Client(api_key="secret_api_key") 127 | >>> search = client.search(q="Coffee", engine="google", location="Austin, Texas", hl="en", gl="us") 128 | >>> for page in search.yield_pages(): 129 | ... print(page["search_metadata"]["page_number"]) 130 | 1 131 | 2 132 | 3 133 | 4 134 | 5 135 | 6 136 | 7 137 | 8 138 | 9 139 | 10 140 | 141 | 142 | Here's documentation of the class itself and its methods: 143 | 144 | .. autoclass:: serpapi.SerpResults 145 | 146 | .. automethod:: SerpResults.next_page 147 | .. automethod:: SerpResults.yield_pages 148 | .. autoproperty:: SerpResults.next_page_url 149 | 150 | 151 | API Client 152 | ---------- 153 | 154 | The primary interface to `serpapi-python` is through the :class:`serpapi.Client` class. 155 | The primary benefit of using this class is to benefit from Requests' HTTP Connection Pooling. 156 | This class also alleviates the need to pass an ``api_key``` along with every search made to the platform. 157 | 158 | .. autoclass:: serpapi.Client 159 | 160 | .. automethod:: Client.search 161 | .. automethod:: Client.search_archive 162 | .. automethod:: Client.account 163 | .. automethod:: Client.locations 164 | 165 | 166 | 167 | Exceptions 168 | ---------- 169 | 170 | .. autoexception:: serpapi.SerpApiError 171 | :members: 172 | 173 | .. autoexception:: serpapi.SearchIDNotProvided 174 | :members: 175 | 176 | .. autoexception:: serpapi.HTTPError 177 | :members: 178 | 179 | .. autoexception:: serpapi.HTTPConnectionError 180 | :members: 181 | 182 | 183 | 184 | 185 | 186 | Indices and tables 187 | ================== 188 | 189 | * :ref:`genindex` 190 | * :ref:`modindex` 191 | * :ref:`search` 192 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.13 2 | astroid==2.15.5 3 | Babel==2.12.1 4 | bleach==6.0.0 5 | certifi==2023.5.7 6 | charset-normalizer==3.2.0 7 | coverage==7.2.7 8 | dill==0.3.6 9 | docutils==0.20.1 10 | idna==3.4 11 | imagesize==1.4.1 12 | importlib-metadata==6.7.0 13 | iniconfig==2.0.0 14 | isort==5.12.0 15 | jaraco.classes==3.2.3 16 | Jinja2==3.1.2 17 | keyring==24.2.0 18 | lazy-object-proxy==1.9.0 19 | markdown-it-py==3.0.0 20 | MarkupSafe==2.1.3 21 | mccabe==0.7.0 22 | mdurl==0.1.2 23 | more-itertools==9.1.0 24 | packaging==23.1 25 | pkginfo==1.9.6 26 | platformdirs==3.8.0 27 | pluggy==1.2.0 28 | pycodestyle==2.10.0 29 | Pygments==2.15.1 30 | pylint==2.17.4 31 | pytest==7.4.0 32 | pytest-cov==4.1.0 33 | readme-renderer==40.0 34 | requests==2.31.0 35 | requests-toolbelt==1.0.0 36 | rfc3986==2.0.0 37 | rich==13.4.2 38 | six==1.16.0 39 | snowballstemmer==2.2.0 40 | Sphinx==7.0.1 41 | sphinxcontrib-applehelp==1.0.4 42 | sphinxcontrib-devhelp==1.0.2 43 | sphinxcontrib-htmlhelp==2.0.1 44 | sphinxcontrib-jsmath==1.0.1 45 | sphinxcontrib-qthelp==1.0.3 46 | sphinxcontrib-serializinghtml==1.1.5 47 | tomlkit==0.11.8 48 | twine==4.0.2 49 | urllib3==2.0.3 50 | webencodings==0.5.1 51 | wrapt==1.15.0 52 | zipp==3.15.0 53 | -e . 54 | -------------------------------------------------------------------------------- /pylint.rc: -------------------------------------------------------------------------------- 1 | [MAIN] 2 | 3 | # Analyse import fallback blocks. This can be used to support both Python 2 and 4 | # 3 compatible code, which means that the block might have code that exists 5 | # only in one or another interpreter, leading to false positives when analysed. 6 | analyse-fallback-blocks=no 7 | 8 | # Clear in-memory caches upon conclusion of linting. Useful if running pylint 9 | # in a server-like mode. 10 | clear-cache-post-run=no 11 | 12 | # Load and enable all available extensions. Use --list-extensions to see a list 13 | # all available extensions. 14 | #enable-all-extensions= 15 | 16 | # In error mode, messages with a category besides ERROR or FATAL are 17 | # suppressed, and no reports are done by default. Error mode is compatible with 18 | # disabling specific errors. 19 | #errors-only= 20 | 21 | # Always return a 0 (non-error) status code, even if lint errors are found. 22 | # This is primarily useful in continuous integration scripts. 23 | #exit-zero= 24 | 25 | # A comma-separated list of package or module names from where C extensions may 26 | # be loaded. Extensions are loading into the active Python interpreter and may 27 | # run arbitrary code. 28 | extension-pkg-allow-list= 29 | 30 | # A comma-separated list of package or module names from where C extensions may 31 | # be loaded. Extensions are loading into the active Python interpreter and may 32 | # run arbitrary code. (This is an alternative name to extension-pkg-allow-list 33 | # for backward compatibility.) 34 | extension-pkg-whitelist= 35 | 36 | # Return non-zero exit code if any of these messages/categories are detected, 37 | # even if score is above --fail-under value. Syntax same as enable. Messages 38 | # specified are enabled, while categories only check already-enabled messages. 39 | fail-on= 40 | 41 | # Specify a score threshold under which the program will exit with error. 42 | fail-under=10 43 | 44 | # Interpret the stdin as a python script, whose filename needs to be passed as 45 | # the module_or_package argument. 46 | #from-stdin= 47 | 48 | # Files or directories to be skipped. They should be base names, not paths. 49 | ignore=CVS 50 | 51 | # Add files or directories matching the regular expressions patterns to the 52 | # ignore-list. The regex matches against paths and can be in Posix or Windows 53 | # format. Because '\\' represents the directory delimiter on Windows systems, 54 | # it can't be used as an escape character. 55 | ignore-paths= 56 | 57 | # Files or directories matching the regular expression patterns are skipped. 58 | # The regex matches against base names, not paths. The default value ignores 59 | # Emacs file locks 60 | ignore-patterns=^\.# 61 | 62 | # List of module names for which member attributes should not be checked 63 | # (useful for modules/projects where namespaces are manipulated during runtime 64 | # and thus existing member attributes cannot be deduced by static analysis). It 65 | # supports qualified module names, as well as Unix pattern matching. 66 | ignored-modules= 67 | 68 | # Python code to execute, usually for sys.path manipulation such as 69 | # pygtk.require(). 70 | #init-hook= 71 | 72 | # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the 73 | # number of processors available to use, and will cap the count on Windows to 74 | # avoid hangs. 75 | jobs=1 76 | 77 | # Control the amount of potential inferred values when inferring a single 78 | # object. This can help the performance when dealing with large functions or 79 | # complex, nested conditions. 80 | limit-inference-results=100 81 | 82 | # List of plugins (as comma separated values of python module names) to load, 83 | # usually to register additional checkers. 84 | load-plugins= 85 | 86 | # Pickle collected data for later comparisons. 87 | persistent=yes 88 | 89 | # Minimum Python version to use for version dependent checks. Will default to 90 | # the version used to run pylint. 91 | py-version=3.11 92 | 93 | # Discover python modules and packages in the file system subtree. 94 | recursive=no 95 | 96 | # Add paths to the list of the source roots. Supports globbing patterns. The 97 | # source root is an absolute path or a path relative to the current working 98 | # directory used to determine a package namespace for modules located under the 99 | # source root. 100 | source-roots= 101 | 102 | # When enabled, pylint would attempt to guess common misconfiguration and emit 103 | # user-friendly hints instead of false-positive error messages. 104 | suggestion-mode=yes 105 | 106 | # Allow loading of arbitrary C extensions. Extensions are imported into the 107 | # active Python interpreter and may run arbitrary code. 108 | unsafe-load-any-extension=no 109 | 110 | # In verbose mode, extra non-checker-related info will be displayed. 111 | #verbose= 112 | 113 | 114 | [BASIC] 115 | 116 | # Naming style matching correct argument names. 117 | argument-naming-style=snake_case 118 | 119 | # Regular expression matching correct argument names. Overrides argument- 120 | # naming-style. If left empty, argument names will be checked with the set 121 | # naming style. 122 | #argument-rgx= 123 | 124 | # Naming style matching correct attribute names. 125 | attr-naming-style=snake_case 126 | 127 | # Regular expression matching correct attribute names. Overrides attr-naming- 128 | # style. If left empty, attribute names will be checked with the set naming 129 | # style. 130 | #attr-rgx= 131 | 132 | # Bad variable names which should always be refused, separated by a comma. 133 | bad-names=foo, 134 | bar, 135 | baz, 136 | toto, 137 | tutu, 138 | tata 139 | victor 140 | kenneth 141 | bart 142 | 143 | # Bad variable names regexes, separated by a comma. If names match any regex, 144 | # they will always be refused 145 | bad-names-rgxs= 146 | 147 | # Naming style matching correct class attribute names. 148 | class-attribute-naming-style=any 149 | 150 | # Regular expression matching correct class attribute names. Overrides class- 151 | # attribute-naming-style. If left empty, class attribute names will be checked 152 | # with the set naming style. 153 | #class-attribute-rgx= 154 | 155 | # Naming style matching correct class constant names. 156 | class-const-naming-style=UPPER_CASE 157 | 158 | # Regular expression matching correct class constant names. Overrides class- 159 | # const-naming-style. If left empty, class constant names will be checked with 160 | # the set naming style. 161 | #class-const-rgx= 162 | 163 | # Naming style matching correct class names. 164 | class-naming-style=PascalCase 165 | 166 | # Regular expression matching correct class names. Overrides class-naming- 167 | # style. If left empty, class names will be checked with the set naming style. 168 | #class-rgx= 169 | 170 | # Naming style matching correct constant names. 171 | const-naming-style=UPPER_CASE 172 | 173 | # Regular expression matching correct constant names. Overrides const-naming- 174 | # style. If left empty, constant names will be checked with the set naming 175 | # style. 176 | #const-rgx= 177 | 178 | # Minimum line length for functions/classes that require docstrings, shorter 179 | # ones are exempt. 180 | docstring-min-length=-1 181 | 182 | # Naming style matching correct function names. 183 | function-naming-style=snake_case 184 | 185 | # Regular expression matching correct function names. Overrides function- 186 | # naming-style. If left empty, function names will be checked with the set 187 | # naming style. 188 | #function-rgx= 189 | 190 | # Good variable names which should always be accepted, separated by a comma. 191 | good-names=i, 192 | j, 193 | k, 194 | ex, 195 | Run, 196 | _ 197 | 198 | # Good variable names regexes, separated by a comma. If names match any regex, 199 | # they will always be accepted 200 | good-names-rgxs= 201 | 202 | # Include a hint for the correct naming format with invalid-name. 203 | include-naming-hint=no 204 | 205 | # Naming style matching correct inline iteration names. 206 | inlinevar-naming-style=any 207 | 208 | # Regular expression matching correct inline iteration names. Overrides 209 | # inlinevar-naming-style. If left empty, inline iteration names will be checked 210 | # with the set naming style. 211 | #inlinevar-rgx= 212 | 213 | # Naming style matching correct method names. 214 | method-naming-style=snake_case 215 | 216 | # Regular expression matching correct method names. Overrides method-naming- 217 | # style. If left empty, method names will be checked with the set naming style. 218 | #method-rgx= 219 | 220 | # Naming style matching correct module names. 221 | module-naming-style=snake_case 222 | 223 | # Regular expression matching correct module names. Overrides module-naming- 224 | # style. If left empty, module names will be checked with the set naming style. 225 | #module-rgx= 226 | 227 | # Colon-delimited sets of names that determine each other's naming style when 228 | # the name regexes allow several styles. 229 | name-group= 230 | 231 | # Regular expression which should only match function or class names that do 232 | # not require a docstring. 233 | no-docstring-rgx=^_ 234 | 235 | # List of decorators that produce properties, such as abc.abstractproperty. Add 236 | # to this list to register other decorators that produce valid properties. 237 | # These decorators are taken in consideration only for invalid-name. 238 | property-classes=abc.abstractproperty 239 | 240 | # Regular expression matching correct type alias names. If left empty, type 241 | # alias names will be checked with the set naming style. 242 | #typealias-rgx= 243 | 244 | # Regular expression matching correct type variable names. If left empty, type 245 | # variable names will be checked with the set naming style. 246 | #typevar-rgx= 247 | 248 | # Naming style matching correct variable names. 249 | variable-naming-style=snake_case 250 | 251 | # Regular expression matching correct variable names. Overrides variable- 252 | # naming-style. If left empty, variable names will be checked with the set 253 | # naming style. 254 | #variable-rgx= 255 | 256 | 257 | [CLASSES] 258 | 259 | # Warn about protected attribute access inside special methods 260 | check-protected-access-in-special-methods=no 261 | 262 | # List of method names used to declare (i.e. assign) instance attributes. 263 | defining-attr-methods=__init__, 264 | __new__, 265 | setUp, 266 | asyncSetUp, 267 | __post_init__ 268 | 269 | # List of member names, which should be excluded from the protected access 270 | # warning. 271 | exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit 272 | 273 | # List of valid names for the first argument in a class method. 274 | valid-classmethod-first-arg=cls 275 | 276 | # List of valid names for the first argument in a metaclass class method. 277 | valid-metaclass-classmethod-first-arg=mcs 278 | 279 | 280 | [DESIGN] 281 | 282 | # List of regular expressions of class ancestor names to ignore when counting 283 | # public methods (see R0903) 284 | exclude-too-few-public-methods= 285 | 286 | # List of qualified class names to ignore when counting class parents (see 287 | # R0901) 288 | ignored-parents= 289 | 290 | # Maximum number of arguments for function / method. 291 | max-args=5 292 | 293 | # Maximum number of attributes for a class (see R0902). 294 | max-attributes=7 295 | 296 | # Maximum number of boolean expressions in an if statement (see R0916). 297 | max-bool-expr=5 298 | 299 | # Maximum number of branch for function / method body. 300 | max-branches=12 301 | 302 | # Maximum number of locals for function / method body. 303 | max-locals=15 304 | 305 | # Maximum number of parents for a class (see R0901). 306 | max-parents=7 307 | 308 | # Maximum number of public methods for a class (see R0904). 309 | max-public-methods=20 310 | 311 | # Maximum number of return / yield for function / method body. 312 | max-returns=6 313 | 314 | # Maximum number of statements in function / method body. 315 | max-statements=50 316 | 317 | # Minimum number of public methods for a class (see R0903). 318 | min-public-methods=2 319 | 320 | 321 | [EXCEPTIONS] 322 | 323 | # Exceptions that will emit a warning when caught. 324 | overgeneral-exceptions=builtins.BaseException,builtins.Exception 325 | 326 | 327 | [FORMAT] 328 | 329 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 330 | expected-line-ending-format= 331 | 332 | # Regexp for a line that is allowed to be longer than the limit. 333 | ignore-long-lines=^\s*(# )??$ 334 | 335 | # Number of spaces of indent required inside a hanging or continued line. 336 | indent-after-paren=4 337 | 338 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 339 | # tab). 340 | indent-string=' ' 341 | 342 | # Maximum number of characters on a single line. 343 | max-line-length=100 344 | 345 | # Maximum number of lines in a module. 346 | max-module-lines=1000 347 | 348 | # Allow the body of a class to be on the same line as the declaration if body 349 | # contains single statement. 350 | single-line-class-stmt=no 351 | 352 | # Allow the body of an if to be on the same line as the test if there is no 353 | # else. 354 | single-line-if-stmt=no 355 | 356 | 357 | [IMPORTS] 358 | 359 | # List of modules that can be imported at any level, not just the top level 360 | # one. 361 | allow-any-import-level= 362 | 363 | # Allow explicit reexports by alias from a package __init__. 364 | allow-reexport-from-package=no 365 | 366 | # Allow wildcard imports from modules that define __all__. 367 | allow-wildcard-with-all=no 368 | 369 | # Deprecated modules which should not be used, separated by a comma. 370 | deprecated-modules= 371 | 372 | # Output a graph (.gv or any supported image format) of external dependencies 373 | # to the given file (report RP0402 must not be disabled). 374 | ext-import-graph= 375 | 376 | # Output a graph (.gv or any supported image format) of all (i.e. internal and 377 | # external) dependencies to the given file (report RP0402 must not be 378 | # disabled). 379 | import-graph= 380 | 381 | # Output a graph (.gv or any supported image format) of internal dependencies 382 | # to the given file (report RP0402 must not be disabled). 383 | int-import-graph= 384 | 385 | # Force import order to recognize a module as part of the standard 386 | # compatibility libraries. 387 | known-standard-library= 388 | 389 | # Force import order to recognize a module as part of a third party library. 390 | known-third-party=enchant 391 | 392 | # Couples of modules and preferred modules, separated by a comma. 393 | preferred-modules= 394 | 395 | 396 | [LOGGING] 397 | 398 | # The type of string formatting that logging methods do. `old` means using % 399 | # formatting, `new` is for `{}` formatting. 400 | logging-format-style=old 401 | 402 | # Logging modules to check that the string format arguments are in logging 403 | # function parameter format. 404 | logging-modules=logging 405 | 406 | 407 | [MESSAGES CONTROL] 408 | 409 | # Only show warnings with the listed confidence levels. Leave empty to show 410 | # all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, 411 | # UNDEFINED. 412 | confidence=HIGH, 413 | CONTROL_FLOW, 414 | INFERENCE, 415 | INFERENCE_FAILURE, 416 | UNDEFINED 417 | 418 | # Disable the message, report, category or checker with the given id(s). You 419 | # can either give multiple identifiers separated by comma (,) or put this 420 | # option multiple times (only on the command line, not in the configuration 421 | # file where it should appear only once). You can also use "--disable=all" to 422 | # disable everything first and then re-enable specific checks. For example, if 423 | # you want to run only the similarities checker, you can use "--disable=all 424 | # --enable=similarities". If you want to run only the classes checker, but have 425 | # no Warning level messages displayed, use "--disable=all --enable=classes 426 | # --disable=W". 427 | disable=raw-checker-failed, 428 | bad-inline-option, 429 | locally-disabled, 430 | file-ignored, 431 | suppressed-message, 432 | useless-suppression, 433 | deprecated-pragma, 434 | use-symbolic-message-instead 435 | unnecessary-pass 436 | invalid-name 437 | 438 | 439 | # Enable the message, report, category or checker with the given id(s). You can 440 | # either give multiple identifier separated by comma (,) or put this option 441 | # multiple time (only on the command line, not in the configuration file where 442 | # it should appear only once). See also the "--disable" option for examples. 443 | enable=c-extension-no-member 444 | 445 | 446 | [METHOD_ARGS] 447 | 448 | # List of qualified names (i.e., library.method) which require a timeout 449 | # parameter e.g. 'requests.api.get,requests.api.post' 450 | timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request 451 | 452 | 453 | [MISCELLANEOUS] 454 | 455 | # List of note tags to take in consideration, separated by a comma. 456 | notes=FIXME, 457 | XXX, 458 | TODO 459 | 460 | # Regular expression of note tags to take in consideration. 461 | notes-rgx= 462 | 463 | 464 | [REFACTORING] 465 | 466 | # Maximum number of nested blocks for function / method body 467 | max-nested-blocks=5 468 | 469 | # Complete name of functions that never returns. When checking for 470 | # inconsistent-return-statements if a never returning function is called then 471 | # it will be considered as an explicit return statement and no message will be 472 | # printed. 473 | never-returning-functions=sys.exit,argparse.parse_error 474 | 475 | 476 | [REPORTS] 477 | 478 | # Python expression which should return a score less than or equal to 10. You 479 | # have access to the variables 'fatal', 'error', 'warning', 'refactor', 480 | # 'convention', and 'info' which contain the number of messages in each 481 | # category, as well as 'statement' which is the total number of statements 482 | # analyzed. This score is used by the global evaluation report (RP0004). 483 | evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) 484 | 485 | # Template used to display messages. This is a python new-style format string 486 | # used to format the message information. See doc for all details. 487 | msg-template= 488 | 489 | # Set the output format. Available formats are text, parseable, colorized, json 490 | # and msvs (visual studio). You can also give a reporter class, e.g. 491 | # mypackage.mymodule.MyReporterClass. 492 | #output-format= 493 | 494 | # Tells whether to display a full report or only the messages. 495 | reports=no 496 | 497 | # Activate the evaluation score. 498 | score=yes 499 | 500 | 501 | [SIMILARITIES] 502 | 503 | # Comments are removed from the similarity computation 504 | ignore-comments=yes 505 | 506 | # Docstrings are removed from the similarity computation 507 | ignore-docstrings=yes 508 | 509 | # Imports are removed from the similarity computation 510 | ignore-imports=yes 511 | 512 | # Signatures are removed from the similarity computation 513 | ignore-signatures=yes 514 | 515 | # Minimum lines number of a similarity. 516 | min-similarity-lines=4 517 | 518 | 519 | [SPELLING] 520 | 521 | # Limits count of emitted suggestions for spelling mistakes. 522 | max-spelling-suggestions=4 523 | 524 | # Spelling dictionary name. No available dictionaries : You need to install 525 | # both the python package and the system dependency for enchant to work.. 526 | spelling-dict= 527 | 528 | # List of comma separated words that should be considered directives if they 529 | # appear at the beginning of a comment and should not be checked. 530 | spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: 531 | 532 | # List of comma separated words that should not be checked. 533 | spelling-ignore-words= 534 | 535 | # A path to a file that contains the private dictionary; one word per line. 536 | spelling-private-dict-file= 537 | 538 | # Tells whether to store unknown words to the private dictionary (see the 539 | # --spelling-private-dict-file option) instead of raising a message. 540 | spelling-store-unknown-words=no 541 | 542 | 543 | [STRING] 544 | 545 | # This flag controls whether inconsistent-quotes generates a warning when the 546 | # character used as a quote delimiter is used inconsistently within a module. 547 | check-quote-consistency=no 548 | 549 | # This flag controls whether the implicit-str-concat should generate a warning 550 | # on implicit string concatenation in sequences defined over several lines. 551 | check-str-concat-over-line-jumps=no 552 | 553 | 554 | [TYPECHECK] 555 | 556 | # List of decorators that produce context managers, such as 557 | # contextlib.contextmanager. Add to this list to register other decorators that 558 | # produce valid context managers. 559 | contextmanager-decorators=contextlib.contextmanager 560 | 561 | # List of members which are set dynamically and missed by pylint inference 562 | # system, and so shouldn't trigger E1101 when accessed. Python regular 563 | # expressions are accepted. 564 | generated-members= 565 | 566 | # Tells whether to warn about missing members when the owner of the attribute 567 | # is inferred to be None. 568 | ignore-none=yes 569 | 570 | # This flag controls whether pylint should warn about no-member and similar 571 | # checks whenever an opaque object is returned when inferring. The inference 572 | # can return multiple potential results while evaluating a Python object, but 573 | # some branches might not be evaluated, which results in partial inference. In 574 | # that case, it might be useful to still emit no-member and other checks for 575 | # the rest of the inferred objects. 576 | ignore-on-opaque-inference=yes 577 | 578 | # List of symbolic message names to ignore for Mixin members. 579 | ignored-checks-for-mixins=no-member, 580 | not-async-context-manager, 581 | not-context-manager, 582 | attribute-defined-outside-init 583 | 584 | # List of class names for which member attributes should not be checked (useful 585 | # for classes with dynamically set attributes). This supports the use of 586 | # qualified names. 587 | ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace 588 | 589 | # Show a hint with possible names when a member name was not found. The aspect 590 | # of finding the hint is based on edit distance. 591 | missing-member-hint=yes 592 | 593 | # The minimum edit distance a name should have in order to be considered a 594 | # similar match for a missing member name. 595 | missing-member-hint-distance=1 596 | 597 | # The total number of similar names that should be taken in consideration when 598 | # showing a hint for a missing member. 599 | missing-member-max-choices=1 600 | 601 | # Regex pattern to define which classes are considered mixins. 602 | mixin-class-rgx=.*[Mm]ixin 603 | 604 | # List of decorators that change the signature of a decorated function. 605 | signature-mutators= 606 | 607 | 608 | [VARIABLES] 609 | 610 | # List of additional names supposed to be defined in builtins. Remember that 611 | # you should avoid defining new builtins when possible. 612 | additional-builtins= 613 | 614 | # Tells whether unused global variables should be treated as a violation. 615 | allow-global-unused-variables=yes 616 | 617 | # List of names allowed to shadow builtins 618 | allowed-redefined-builtins= 619 | 620 | # List of strings which can identify a callback function by name. A callback 621 | # name must start or end with one of those strings. 622 | callbacks=cb_, 623 | _cb 624 | 625 | # A regular expression matching the name of dummy variables (i.e. expected to 626 | # not be used). 627 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 628 | 629 | # Argument names that match this expression will be ignored. 630 | ignored-argument-names=_.*|^ignored_|^unused_ 631 | 632 | # Tells whether we should check for unused import in __init__ files. 633 | init-import=no 634 | 635 | # List of qualified module names which can have objects that can redefine 636 | # builtins. 637 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io 638 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel"] 3 | build-backend = "setuptools.build_meta:__legacy__" -------------------------------------------------------------------------------- /serpapi/__init__.py: -------------------------------------------------------------------------------- 1 | from .__version__ import __version__ 2 | 3 | from .core import * 4 | from .exceptions import * 5 | -------------------------------------------------------------------------------- /serpapi/__version__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1.5" 2 | -------------------------------------------------------------------------------- /serpapi/core.py: -------------------------------------------------------------------------------- 1 | from .http import HTTPClient 2 | from .exceptions import SearchIDNotProvided 3 | from .models import SerpResults 4 | 5 | 6 | class Client(HTTPClient): 7 | """A class that handles API requests to SerpApi in a user–friendly manner. 8 | 9 | :param api_key: The API Key to use for SerpApi.com. 10 | 11 | Please provide ``api_key`` when instantiating this class. We recommend storing this in an environment variable, like so: 12 | 13 | .. code-block:: bash 14 | 15 | $ export SERPAPI_KEY=YOUR_API_KEY 16 | 17 | .. code-block:: python 18 | 19 | import os 20 | import serpapi 21 | 22 | serpapi = serpapi.Client(api_key=os.environ["SERPAPI_KEY"]) 23 | 24 | """ 25 | 26 | DASHBOARD_URL = "https://serpapi.com/dashboard" 27 | 28 | def __repr__(self): 29 | return "" 30 | 31 | def search(self, params: dict = None, **kwargs): 32 | """Fetch a page of results from SerpApi. Returns a :class:`SerpResults ` object, or unicode text (*e.g.* if ``'output': 'html'`` was passed). 33 | 34 | The following three calls are equivalent: 35 | 36 | .. code-block:: python 37 | 38 | >>> s = serpapi.search(q="Coffee", location="Austin, Texas, United States") 39 | 40 | .. code-block:: python 41 | 42 | >>> params = {"q": "Coffee", "location": "Austin, Texas, United States"} 43 | >>> s = serpapi.search(**params) 44 | 45 | .. code-block:: python 46 | 47 | >>> params = {"q": "Coffee", "location": "Austin, Texas, United States"} 48 | >>> s = serpapi.search(params) 49 | 50 | 51 | :param q: typically, this is the parameter for the search engine query. 52 | :param engine: the search engine to use. Defaults to ``google``. 53 | :param output: the output format desired (``html`` or ``json``). Defaults to ``json``. 54 | :param api_key: the API Key to use for SerpApi.com. 55 | :param **: any additional parameters to pass to the API. 56 | 57 | 58 | **Learn more**: https://serpapi.com/search-api 59 | """ 60 | if params is None: 61 | params = {} 62 | 63 | if kwargs: 64 | params.update(kwargs) 65 | 66 | r = self.request("GET", "/search", params=params) 67 | 68 | return SerpResults.from_http_response(r, client=self) 69 | 70 | def search_archive(self, params: dict = None, **kwargs): 71 | """Get a result from the SerpApi Search Archive API. 72 | 73 | :param search_id: the Search ID of the search to retrieve from the archive. 74 | :param api_key: the API Key to use for SerpApi.com. 75 | :param output: the output format desired (``html`` or ``json``). Defaults to ``json``. 76 | :param **: any additional parameters to pass to the API. 77 | 78 | **Learn more**: https://serpapi.com/search-archive-api 79 | """ 80 | if params is None: 81 | params = {} 82 | 83 | if kwargs: 84 | params.update(kwargs) 85 | 86 | try: 87 | search_id = params["search_id"] 88 | except KeyError: 89 | raise SearchIDNotProvided( 90 | f"Please provide 'search_id', found here: { self.DASHBOARD_URL }" 91 | ) 92 | 93 | r = self.request("GET", f"/searches/{ search_id }", params=params) 94 | return SerpResults.from_http_response(r, client=self) 95 | 96 | def locations(self, params: dict = None, **kwargs): 97 | """Get a list of supported Google locations. 98 | 99 | 100 | :param q: restricts your search to locations that contain the supplied string. 101 | :param limit: limits the number of locations returned. 102 | :param **: any additional parameters to pass to the API. 103 | 104 | **Learn more**: https://serpapi.com/locations-api 105 | """ 106 | if params is None: 107 | params = {} 108 | 109 | if kwargs: 110 | params.update(kwargs) 111 | 112 | r = self.request( 113 | "GET", 114 | "/locations.json", 115 | params=params, 116 | assert_200=True, 117 | ) 118 | return r.json() 119 | 120 | def account(self, params: dict = None, **kwargs): 121 | """Get SerpApi account information. 122 | 123 | :param api_key: the API Key to use for SerpApi.com. 124 | :param **: any additional parameters to pass to the API. 125 | 126 | **Learn more**: https://serpapi.com/account-api 127 | """ 128 | 129 | if params is None: 130 | params = {} 131 | 132 | if kwargs: 133 | params.update(kwargs) 134 | 135 | r = self.request("GET", "/account.json", params=params, assert_200=True) 136 | return r.json() 137 | 138 | 139 | # An un-authenticated client instance. 140 | _client = Client() 141 | search = _client.search 142 | search_archive = _client.search_archive 143 | locations = _client.locations 144 | account = _client.account 145 | -------------------------------------------------------------------------------- /serpapi/exceptions.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | class SerpApiError(Exception): 5 | """Base class for exceptions in this module.""" 6 | 7 | pass 8 | 9 | 10 | class APIKeyNotProvided(ValueError, SerpApiError): 11 | """API key is not provided.""" 12 | 13 | pass 14 | 15 | 16 | class SearchIDNotProvided(ValueError, SerpApiError): 17 | """Search ID is not provided.""" 18 | 19 | pass 20 | 21 | 22 | class HTTPError(requests.exceptions.HTTPError, SerpApiError): 23 | """HTTP Error.""" 24 | 25 | pass 26 | 27 | 28 | class HTTPConnectionError(HTTPError, requests.exceptions.ConnectionError, SerpApiError): 29 | """Connection Error.""" 30 | 31 | pass 32 | -------------------------------------------------------------------------------- /serpapi/http.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | from .exceptions import ( 4 | HTTPError, 5 | HTTPConnectionError, 6 | ) 7 | from .__version__ import __version__ 8 | 9 | 10 | class HTTPClient: 11 | """This class handles outgoing HTTP requests to SerpApi.com.""" 12 | 13 | BASE_DOMAIN = "https://serpapi.com" 14 | USER_AGENT = f"serpapi-python, v{__version__}" 15 | 16 | def __init__(self, *, api_key=None): 17 | # Used to authenticate requests. 18 | # TODO: do we want to support the environment variable? Seems like a security risk. 19 | self.api_key = api_key 20 | self.session = requests.Session() 21 | 22 | def request(self, method, path, params, *, assert_200=True, **kwargs): 23 | # Inject the API Key into the params. 24 | if "api_key" not in params: 25 | params["api_key"] = self.api_key 26 | 27 | # Build the URL, as needed. 28 | if not path.startswith("http"): 29 | url = self.BASE_DOMAIN + path 30 | else: 31 | url = path 32 | 33 | # Make the HTTP request. 34 | try: 35 | headers = {"User-Agent": self.USER_AGENT} 36 | 37 | r = self.session.request( 38 | method=method, url=url, params=params, headers=headers, **kwargs 39 | ) 40 | 41 | except requests.exceptions.ConnectionError as e: 42 | raise HTTPConnectionError(e) 43 | 44 | # Raise an exception if the status code is not 200. 45 | if assert_200: 46 | try: 47 | raise_for_status(r) 48 | except requests.exceptions.HTTPError as e: 49 | raise HTTPError(e) 50 | 51 | return r 52 | 53 | 54 | def raise_for_status(r): 55 | """Raise an exception if the status code is not 200.""" 56 | # TODO: put custom behavior in here for various status codes. 57 | 58 | try: 59 | r.raise_for_status() 60 | except requests.exceptions.HTTPError as e: 61 | raise HTTPError(e) 62 | -------------------------------------------------------------------------------- /serpapi/models.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from pprint import pformat 4 | from collections import UserDict 5 | 6 | from .textui import prettify_json 7 | from .exceptions import HTTPError 8 | 9 | 10 | class SerpResults(UserDict): 11 | """A dictionary-like object that represents the results of a SerpApi request. 12 | 13 | .. code-block:: python 14 | 15 | >>> search = serpapi.search(q="Coffee", location="Austin, Texas, United States") 16 | 17 | >>> print(search["search_metadata"].keys()) 18 | dict_keys(['id', 'status', 'json_endpoint', 'created_at', 'processed_at', 'google_url', 'raw_html_file', 'total_time_taken']) 19 | 20 | An instance of this class is returned if the response is a valid JSON object. 21 | It can be used like a dictionary, but also has some additional methods. 22 | """ 23 | 24 | def __init__(self, data, *, client): 25 | super().__init__(data) 26 | self.client = client 27 | 28 | def __getstate__(self): 29 | return self.data 30 | 31 | def __setstate__(self, state): 32 | self.data = state 33 | 34 | def __repr__(self): 35 | """The visual representation of the data, which is pretty printed, for 36 | ease of use. 37 | """ 38 | 39 | return prettify_json(json.dumps(self.data, indent=4)) 40 | 41 | def as_dict(self): 42 | """Returns the data as a standard Python dictionary. 43 | This can be useful when using ``json.dumps(search), for example.""" 44 | 45 | return self.data.copy() 46 | 47 | @property 48 | def next_page_url(self): 49 | """The URL of the next page of results, if any.""" 50 | 51 | serpapi_pagination = self.data.get("serpapi_pagination") 52 | 53 | if serpapi_pagination: 54 | return serpapi_pagination.get("next") 55 | 56 | def next_page(self): 57 | """Return the next page of results, if any.""" 58 | 59 | if self.next_page_url: 60 | # Include support for the API key, as it is not included in the next page URL. 61 | params = {"api_key": self.client.api_key} 62 | 63 | r = self.client.request("GET", path=self.next_page_url, params=params) 64 | return SerpResults.from_http_response(r, client=self.client) 65 | 66 | def yield_pages(self, max_pages=1_000): 67 | """A generator that ``yield`` s the next ``n`` pages of search results, if any. 68 | 69 | :param max_pages: limit the number of pages yielded to ``n``. 70 | """ 71 | 72 | current_page_count = 0 73 | 74 | current_page = self 75 | while current_page.next_page_url and current_page_count < max_pages: 76 | current_page = current_page.next_page() 77 | current_page_count += 1 78 | yield current_page 79 | 80 | @classmethod 81 | def from_http_response(cls, r, *, client=None): 82 | """Construct a SerpResults object from an HTTP response. 83 | 84 | :param assert_200: if ``True`` (default), raise an exception if the status code is not 200. 85 | :param client: the Client instance which was used to send this request. 86 | 87 | An instance of this class is returned if the response is a valid JSON object. 88 | Otherwise, the raw text (as a properly decoded unicode string) is returned. 89 | """ 90 | 91 | try: 92 | cls = cls(r.json(), client=client) 93 | 94 | return cls 95 | except ValueError: 96 | # If the response is not JSON, return the raw text. 97 | return r.text 98 | -------------------------------------------------------------------------------- /serpapi/textui.py: -------------------------------------------------------------------------------- 1 | try: 2 | import pygments 3 | from pygments import highlight, lexers, formatters 4 | except ImportError: 5 | pygments = None 6 | 7 | 8 | def prettify_json(s): 9 | if pygments: 10 | return highlight( 11 | s, 12 | lexers.JsonLexer(), 13 | formatters.TerminalFormatter(), 14 | ) 15 | else: 16 | return s 17 | -------------------------------------------------------------------------------- /serpapi/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def api_key_from_environment(): 5 | return os.getenv("SERP_API_KEY") 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Note: To use the 'upload' functionality of this file, you must: 5 | # $ pipenv install twine --dev 6 | 7 | import io 8 | import os 9 | import sys 10 | from shutil import rmtree 11 | 12 | from setuptools import find_packages, setup, Command 13 | 14 | # Package meta-data. 15 | NAME = "serpapi" 16 | DESCRIPTION = "The official Python client for SerpApi.com." 17 | URL = "https://github.com/serpapi/serpapi-python" 18 | EMAIL = "kenneth@serpapi.com" 19 | AUTHOR = "SerpApi.com" 20 | REQUIRES_PYTHON = ">=3.6.0" 21 | VERSION = None 22 | 23 | # What packages are required for this module to be executed? 24 | REQUIRED = ["requests"] 25 | 26 | # What packages are optional? 27 | EXTRAS = {"color": ["pygments"], "test": ["pytest"]} 28 | 29 | here = os.path.abspath(os.path.dirname(__file__)) 30 | 31 | # Import the README and use it as the long-description. 32 | # Note: this will only work if 'README.md' is present in your MANIFEST.in file! 33 | try: 34 | with io.open(os.path.join(here, "README.md"), encoding="utf-8") as f: 35 | long_description = "\n" + f.read() 36 | except FileNotFoundError: 37 | long_description = DESCRIPTION 38 | 39 | # Load the package's __version__.py module as a dictionary. 40 | about = {} 41 | if not VERSION: 42 | project_slug = NAME.lower().replace("-", "_").replace(" ", "_") 43 | with open(os.path.join(here, project_slug, "__version__.py")) as f: 44 | exec(f.read(), about) 45 | else: 46 | about["__version__"] = VERSION 47 | 48 | 49 | class TestCommand(Command): 50 | """Support setup.py test.""" 51 | 52 | description = "Test the package." 53 | user_options = [] 54 | 55 | @staticmethod 56 | def status(s): 57 | """Prints things in bold.""" 58 | print("\033[1m{0}\033[0m".format(s)) 59 | 60 | def initialize_options(self): 61 | pass 62 | 63 | def finalize_options(self): 64 | pass 65 | 66 | def run(self): 67 | os.system("{0} -m pytest".format(sys.executable)) 68 | sys.exit() 69 | 70 | class UploadCommand(Command): 71 | """Support setup.py upload.""" 72 | 73 | description = "Build and publish the package." 74 | user_options = [] 75 | 76 | @staticmethod 77 | def status(s): 78 | """Prints things in bold.""" 79 | print("\033[1m{0}\033[0m".format(s)) 80 | 81 | def initialize_options(self): 82 | pass 83 | 84 | def finalize_options(self): 85 | pass 86 | 87 | def run(self): 88 | try: 89 | self.status("Removing previous builds…") 90 | rmtree(os.path.join(here, "dist")) 91 | except OSError: 92 | pass 93 | 94 | self.status("Building Source and Wheel (universal) distribution…") 95 | os.system("{0} setup.py sdist bdist_wheel --universal".format(sys.executable)) 96 | 97 | self.status("Uploading the package to PyPI via Twine…") 98 | os.system("twine upload dist/*") 99 | 100 | self.status("Pushing git tags…") 101 | os.system("git tag v{0}".format(about["__version__"])) 102 | os.system("git push --tags") 103 | 104 | sys.exit() 105 | 106 | 107 | # Where the magic happens: 108 | setup( 109 | name=NAME, 110 | version=about["__version__"], 111 | description=DESCRIPTION, 112 | long_description=long_description, 113 | long_description_content_type="text/markdown", 114 | author=AUTHOR, 115 | author_email=EMAIL, 116 | python_requires=REQUIRES_PYTHON, 117 | url=URL, 118 | packages=find_packages(exclude=["tests", "*.tests", "*.tests.*", "tests.*"]), 119 | # If your package is a single module, use this instead of 'packages': 120 | # py_modules=['mypackage'], 121 | # entry_points={ 122 | # 'console_scripts': ['mycli=mymodule:cli'], 123 | # }, 124 | install_requires=REQUIRED, 125 | extras_require=EXTRAS, 126 | include_package_data=True, 127 | license="MIT", 128 | project_urls={"Documentation": "https://serpapi-python.readthedocs.io/en/latest/"}, 129 | keywords="scrape,serp,api,serpapi,scraping,json,search,localized,rank,google,bing,baidu,yandex,yahoo,ebay,scale,datamining,training,machine,ml,youtube,naver,walmart,apple,store,app,serpapi", 130 | classifiers=[ 131 | # Trove classifiers 132 | # Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers 133 | "License :: OSI Approved :: MIT License", 134 | "Programming Language :: Python", 135 | "Programming Language :: Python :: 3", 136 | "Programming Language :: Python :: 3.5", 137 | "Programming Language :: Python :: 3.6", 138 | "Programming Language :: Python :: 3.7", 139 | "Programming Language :: Python :: 3.8", 140 | "Programming Language :: Python :: 3.9", 141 | "Programming Language :: Python :: 3.10", 142 | "Programming Language :: Python :: 3.11", 143 | "Programming Language :: Python :: 3.12", 144 | "Natural Language :: English", 145 | "Topic :: Utilities", 146 | "Topic :: Internet :: WWW/HTTP", 147 | "Topic :: Internet :: WWW/HTTP :: Indexing/Search", 148 | "Topic :: Software Development :: Libraries :: Python Modules", 149 | "Programming Language :: Python :: Implementation :: CPython", 150 | ], 151 | # $ setup.py publish support. 152 | cmdclass={ 153 | "upload": UploadCommand, 154 | "test": TestCommand 155 | }, 156 | ) 157 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/serpapi/serpapi-python/cdcfb0ee8f7421f2fd5591519eac80cd64425751/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | import serpapi 6 | 7 | os.environ["CI"] = "1" 8 | 9 | 10 | @pytest.fixture 11 | def api_key(): 12 | return os.environ["API_KEY"] 13 | 14 | 15 | @pytest.fixture 16 | def client(api_key): 17 | return serpapi.Client(api_key=api_key) 18 | 19 | 20 | @pytest.fixture 21 | def invalid_key_client(api_key): 22 | return serpapi.Client(api_key="bunk-key") 23 | 24 | 25 | @pytest.fixture 26 | def coffee_params(): 27 | return {"q": "Coffee"} 28 | 29 | 30 | @pytest.fixture 31 | def coffee_search(client, coffee_params): 32 | return client.search(**coffee_params) 33 | 34 | 35 | @pytest.fixture 36 | def coffee_search_html(client, coffee_params): 37 | params = coffee_params.copy() 38 | params["output"] = "html" 39 | 40 | return client.search(**params) 41 | -------------------------------------------------------------------------------- /tests/example_search_apple_app_store_test.py: -------------------------------------------------------------------------------- 1 | # Example: apple_app_store search engine 2 | import pytest 3 | import os 4 | import serpapi 5 | 6 | def test_search_apple_app_store(client): 7 | data = client.search({ 8 | 'engine': 'apple_app_store', 9 | 'term': 'coffee', 10 | }) 11 | assert data.get('error') is None 12 | assert data['organic_results'] 13 | -------------------------------------------------------------------------------- /tests/example_search_baidu_test.py: -------------------------------------------------------------------------------- 1 | # Example: baidu search engine 2 | import pytest 3 | import os 4 | import serpapi 5 | 6 | 7 | def test_search_baidu(client): 8 | data = client.search({ 9 | 'engine': 'baidu', 10 | 'q': 'coffee', 11 | }) 12 | assert data.get('error') is None 13 | assert data['organic_results'] 14 | -------------------------------------------------------------------------------- /tests/example_search_bing_test.py: -------------------------------------------------------------------------------- 1 | # Example: bing search engine 2 | import pytest 3 | import os 4 | import serpapi 5 | 6 | def test_search_bing(client): 7 | data = client.search({ 8 | 'engine': 'bing', 9 | 'q': 'coffee', 10 | }) 11 | assert data.get('error') is None 12 | assert data['organic_results'] 13 | -------------------------------------------------------------------------------- /tests/example_search_duckduckgo_test.py: -------------------------------------------------------------------------------- 1 | # Example: duckduckgo search engine 2 | import pytest 3 | import os 4 | import serpapi 5 | 6 | def test_search_duckduckgo(client): 7 | data = client.search({ 8 | 'engine': 'duckduckgo', 9 | 'q': 'coffee', 10 | }) 11 | assert data.get('error') is None 12 | assert data['organic_results'] 13 | -------------------------------------------------------------------------------- /tests/example_search_ebay_test.py: -------------------------------------------------------------------------------- 1 | # Example: ebay search engine 2 | import pytest 3 | import os 4 | import serpapi 5 | 6 | def test_search_ebay(client): 7 | data = client.search({ 8 | 'engine': 'ebay', 9 | '_nkw': 'coffee', 10 | }) 11 | assert data.get('error') is None 12 | assert data['organic_results'] 13 | -------------------------------------------------------------------------------- /tests/example_search_google_autocomplete_test.py: -------------------------------------------------------------------------------- 1 | # Example: google_autocomplete search engine 2 | import pytest 3 | import os 4 | import serpapi 5 | 6 | def test_search_google_autocomplete(client): 7 | data = client.search({ 8 | 'engine': 'google_autocomplete', 9 | 'q': 'coffee', 10 | }) 11 | assert data.get('error') is None 12 | assert data['suggestions'] 13 | -------------------------------------------------------------------------------- /tests/example_search_google_events_test.py: -------------------------------------------------------------------------------- 1 | # Example: google_events search engine 2 | import pytest 3 | import os 4 | import serpapi 5 | 6 | def test_search_google_events(client): 7 | data = client.search({ 8 | 'engine': 'google_events', 9 | 'q': 'coffee', 10 | }) 11 | assert data.get('error') is None 12 | assert data['events_results'] 13 | -------------------------------------------------------------------------------- /tests/example_search_google_images_test.py: -------------------------------------------------------------------------------- 1 | # Example: google_images search engine 2 | import pytest 3 | import os 4 | import serpapi 5 | 6 | def test_search_google_images(client): 7 | data = client.search({ 8 | 'engine': 'google_images', 9 | 'engine': 'google_images', 10 | 'tbm': 'isch', 11 | 'q': 'coffee', 12 | }) 13 | assert data.get('error') is None 14 | assert data['images_results'] 15 | -------------------------------------------------------------------------------- /tests/example_search_google_jobs_test.py: -------------------------------------------------------------------------------- 1 | # Example: google_jobs search engine 2 | import pytest 3 | import os 4 | import serpapi 5 | 6 | def test_search_google_jobs(client): 7 | data = client.search({ 8 | 'engine': 'google_jobs', 9 | 'q': 'coffee', 10 | }) 11 | assert data.get('error') is None 12 | assert data['jobs_results'] 13 | -------------------------------------------------------------------------------- /tests/example_search_google_local_services_test.py: -------------------------------------------------------------------------------- 1 | # Example: google_local_services search engine 2 | import pytest 3 | import os 4 | import serpapi 5 | 6 | def test_search_google_local_services(client): 7 | 8 | data = client.search({ 9 | 'engine': 'google_local_services', 10 | 'q': 'electrician', 11 | 'data_cid': '6745062158417646970', 12 | }) 13 | assert data.get('error') is None 14 | assert data['local_ads'] 15 | -------------------------------------------------------------------------------- /tests/example_search_google_maps_test.py: -------------------------------------------------------------------------------- 1 | # Example: google_maps search engine 2 | import pytest 3 | import os 4 | import serpapi 5 | 6 | def test_search_google_maps(client): 7 | data = client.search({ 8 | 'engine': 'google_maps', 9 | 'q': 'pizza', 10 | 'll': '@40.7455096,-74.0083012,15.1z', 11 | 'type': 'search', 12 | }) 13 | assert data.get('error') is None 14 | assert data['local_results'] 15 | -------------------------------------------------------------------------------- /tests/example_search_google_play_test.py: -------------------------------------------------------------------------------- 1 | # Example: google_play search engine 2 | import pytest 3 | import os 4 | import serpapi 5 | 6 | def test_search_google_play(client): 7 | data = client.search({ 8 | 'engine': 'google_play', 9 | 'q': 'kite', 10 | 'store': 'apps', 11 | 'max_results': '2', 12 | }) 13 | assert data.get('error') is None 14 | assert data['organic_results'] 15 | -------------------------------------------------------------------------------- /tests/example_search_google_product_test.py: -------------------------------------------------------------------------------- 1 | # Example: google_product search engine 2 | import pytest 3 | import os 4 | import serpapi 5 | 6 | def test_search_google_product(client): 7 | data = client.search({ 8 | 'engine': 'google_product', 9 | 'q': 'coffee', 10 | 'product_id': '4887235756540435899', 11 | }) 12 | assert data.get('error') is None 13 | assert data['product_results'] 14 | -------------------------------------------------------------------------------- /tests/example_search_google_reverse_image_test.py: -------------------------------------------------------------------------------- 1 | # Example: google_reverse_image search engine 2 | import pytest 3 | import os 4 | import serpapi 5 | 6 | def test_search_google_reverse_image(client): 7 | data = client.search({ 8 | 'engine': 'google_reverse_image', 9 | 'image_url': 'https://i.imgur.com/5bGzZi7.jpg', 10 | 'max_results': '1', 11 | }) 12 | assert data.get('error') is None 13 | assert data['image_sizes'] 14 | -------------------------------------------------------------------------------- /tests/example_search_google_scholar_test.py: -------------------------------------------------------------------------------- 1 | # Example: google_scholar search engine 2 | import pytest 3 | import os 4 | import serpapi 5 | 6 | def test_search_google_scholar(client): 7 | 8 | data = client.search({ 9 | 'engine': 'google_scholar', 10 | 'q': 'coffee', 11 | }) 12 | assert data.get('error') is None 13 | assert data['organic_results'] 14 | -------------------------------------------------------------------------------- /tests/example_search_google_test.py: -------------------------------------------------------------------------------- 1 | # Example: google search engine 2 | import pytest 3 | import os 4 | import serpapi 5 | 6 | def test_search_google(client): 7 | 8 | data = client.search({ 9 | 'engine': 'google', 10 | 'q': 'coffee', 11 | 'engine': 'google', 12 | }) 13 | assert data.get('error') is None 14 | assert data['organic_results'] 15 | -------------------------------------------------------------------------------- /tests/example_search_home_depot_test.py: -------------------------------------------------------------------------------- 1 | # Example: home_depot search engine 2 | import pytest 3 | import os 4 | import serpapi 5 | 6 | def test_search_home_depot(client): 7 | 8 | data = client.search({ 9 | 'engine': 'home_depot', 10 | 'q': 'table', 11 | }) 12 | assert data.get('error') is None 13 | assert data['products'] 14 | -------------------------------------------------------------------------------- /tests/example_search_naver_test.py: -------------------------------------------------------------------------------- 1 | # Example: naver search engine 2 | import pytest 3 | import os 4 | import serpapi 5 | 6 | def test_search_naver(client): 7 | data = client.search({ 8 | 'engine': 'naver', 9 | 'query': 'coffee', 10 | }) 11 | assert data.get('error') is None 12 | assert data['ads_results'] 13 | -------------------------------------------------------------------------------- /tests/example_search_walmart_test.py: -------------------------------------------------------------------------------- 1 | # Example: walmart search engine 2 | import pytest 3 | import os 4 | import serpapi 5 | 6 | def test_search_walmart(client): 7 | data = client.search({ 8 | 'engine': 'walmart', 9 | 'query': 'coffee', 10 | }) 11 | assert data.get('error') is None 12 | assert data['organic_results'] 13 | 14 | -------------------------------------------------------------------------------- /tests/example_search_yahoo_test.py: -------------------------------------------------------------------------------- 1 | # Example: yahoo search engine 2 | import pytest 3 | import os 4 | import serpapi 5 | 6 | def test_search_yahoo(client): 7 | data = client.search({ 8 | 'engine': 'yahoo', 9 | 'p': 'coffee', 10 | }) 11 | assert data.get('error') is None 12 | assert data['organic_results'] 13 | -------------------------------------------------------------------------------- /tests/example_search_youtube_test.py: -------------------------------------------------------------------------------- 1 | # Example: youtube search engine 2 | import pytest 3 | import os 4 | import serpapi 5 | 6 | def test_search_youtube(client): 7 | 8 | data = client.search({ 9 | 'engine': 'youtube', 10 | 'search_query': 'coffee', 11 | }) 12 | assert data.get('error') is None 13 | assert data['video_results'] 14 | -------------------------------------------------------------------------------- /tests/test_integration.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import serpapi 4 | 5 | 6 | def test_basic_import(): 7 | """Test that basic import works as intended.""" 8 | import serpapi 9 | 10 | 11 | def test_entrypoints(client): 12 | """Test that pure references to the publicly accessible API surface introduces no errors.""" 13 | 14 | for api in [client, serpapi]: 15 | assert api.account 16 | assert api.search 17 | assert api.search_archive 18 | assert api.locations 19 | 20 | 21 | def test_account_without_credentials(): 22 | """Ensure that an HTTPError is raised when account is accessed without API Credentials.""" 23 | with pytest.raises(serpapi.HTTPError): 24 | serpapi.account() 25 | 26 | 27 | def test_account_with_bad_credentials(invalid_key_client): 28 | """Ensure that an HTTPError is raised when account is accessed with invalid API Credentials.""" 29 | with pytest.raises(serpapi.HTTPError): 30 | invalid_key_client.account() 31 | 32 | 33 | def test_account_with_credentials(client): 34 | """Ensure that account appears to be returning valid data if the API Key is correct.""" 35 | account = client.account() 36 | assert account 37 | assert account.keys() 38 | assert isinstance(account, dict) 39 | 40 | 41 | def test_coffee_search(coffee_search): 42 | assert isinstance(coffee_search, serpapi.SerpResults) 43 | assert hasattr(coffee_search, "__getitem__") 44 | 45 | 46 | def test_coffee_search_as_dict(coffee_search): 47 | d = coffee_search.as_dict() 48 | assert isinstance(d, dict) 49 | 50 | 51 | def test_coffee_search_html(coffee_search_html): 52 | assert isinstance(coffee_search_html, str) 53 | assert not hasattr(coffee_search_html, "next_page_url") 54 | 55 | 56 | def test_coffee_search_n_pages(coffee_search): 57 | page_count = 0 58 | max_pages = 3 59 | 60 | for page in coffee_search.yield_pages(max_pages=max_pages): 61 | page_count += 1 62 | 63 | assert page_count == max_pages 64 | 65 | 66 | def test_coffee_search_next_page(coffee_search): 67 | next_page = coffee_search.next_page() 68 | 69 | assert isinstance(next_page, serpapi.SerpResults) 70 | assert coffee_search["search_metadata"]["id"] != next_page["search_metadata"]["id"] 71 | 72 | 73 | def test_search_function_signature(coffee_params, client): 74 | s = client.search(coffee_params) 75 | assert s["search_metadata"]["id"] 76 | 77 | s = client.search(**coffee_params) 78 | assert s["search_metadata"]["id"] 79 | 80 | s = client.search(q='coffee') 81 | assert s["search_metadata"]["id"] 82 | --------------------------------------------------------------------------------