├── .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 |

5 |
6 |

7 |
8 | [](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 |

16 |
17 |
19 | [](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 |
--------------------------------------------------------------------------------