stealth-google-search 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stealth_google_search-0.1.0/LICENSE +21 -0
- stealth_google_search-0.1.0/PKG-INFO +320 -0
- stealth_google_search-0.1.0/README.md +294 -0
- stealth_google_search-0.1.0/google_search/__init__.py +4 -0
- stealth_google_search-0.1.0/google_search/google_search.py +149 -0
- stealth_google_search-0.1.0/google_search/utils/__init__.py +1 -0
- stealth_google_search-0.1.0/google_search/utils/helpers.py +165 -0
- stealth_google_search-0.1.0/google_search/utils/models.py +18 -0
- stealth_google_search-0.1.0/google_search/utils/parser.py +497 -0
- stealth_google_search-0.1.0/pyproject.toml +39 -0
- stealth_google_search-0.1.0/setup.cfg +4 -0
- stealth_google_search-0.1.0/stealth_google_search.egg-info/PKG-INFO +320 -0
- stealth_google_search-0.1.0/stealth_google_search.egg-info/SOURCES.txt +14 -0
- stealth_google_search-0.1.0/stealth_google_search.egg-info/dependency_links.txt +1 -0
- stealth_google_search-0.1.0/stealth_google_search.egg-info/requires.txt +5 -0
- stealth_google_search-0.1.0/stealth_google_search.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
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.
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: stealth-google-search
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Stealthy Google Search and AI Overview scraper with anti-bot evasion and automatic redirect resolution.
|
|
5
|
+
Author-email: Your Name <your.email@example.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/SeMoazamAli/google-search
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/SeMoazamAli/google-search/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: undetected-chromedriver>=3.5.5
|
|
21
|
+
Requires-Dist: selenium>=4.20.0
|
|
22
|
+
Requires-Dist: parsel>=1.9.0
|
|
23
|
+
Requires-Dist: requests>=2.31.0
|
|
24
|
+
Requires-Dist: setuptools>=65.0.0
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# Google Search & AI Overview Scraper
|
|
28
|
+
|
|
29
|
+
A robust, stealthy Python scraper for Google Search results. Built using `undetected-chromedriver` and `parsel`, this tool automatically bypasses Google's bot detection, extracts **Google AI Overviews**, scrapes clean organic search results (titles, genuine snippets, provider domains, dates), and automatically resolves Google's `/goto?url=...` encrypted redirect links into their **original destination URLs**.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Table of Contents
|
|
34
|
+
|
|
35
|
+
- [Features](#features)
|
|
36
|
+
- [Prerequisites](#prerequisites)
|
|
37
|
+
- [Installation](#installation)
|
|
38
|
+
- [Quick Start](#quick-start)
|
|
39
|
+
- [Detailed Parameter Reference](#detailed-parameter-reference)
|
|
40
|
+
- [Response Structure & Schema](#response-structure--schema)
|
|
41
|
+
- [Advanced Usage Examples](#advanced-usage-examples)
|
|
42
|
+
- [1. Scraping Multiple Pages](#1-scraping-multiple-pages)
|
|
43
|
+
- [2. Extracting AI Overview Answers](#2-extracting-ai-overview-answers)
|
|
44
|
+
- [3. Running in Visible (Non-Headless) Mode](#3-running-in-visible-non-headless-mode)
|
|
45
|
+
- [4. Ultra-Fast Scraping (Disabling URL Resolution)](#4-ultra-fast-scraping-disabling-url-resolution)
|
|
46
|
+
- [5. Pinning a Specific Chrome Version](#5-pinning-a-specific-chrome-version)
|
|
47
|
+
- [Troubleshooting & FAQs](#troubleshooting--faqs)
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Features
|
|
52
|
+
|
|
53
|
+
- ๐ก๏ธ **Anti-Bot Evasion**: Employs `undetected-chromedriver` with native browser fingerprinting to evade Google CAPTCHA blocks.
|
|
54
|
+
- ๐ค **Google AI Overview Extraction**: Captures the generated AI summary answer and cited sources whenever present.
|
|
55
|
+
- ๐ **Automatic Redirect Unmasking**: Uses concurrent thread pooling to automatically resolve Google's `/goto?url=CAES...` redirects into full, direct landing page URLs (e.g. `https://en.wikipedia.org/...`).
|
|
56
|
+
- ๐งน **Clean Snippet Parsing**: Specifically targets Google's snippet elements while stripping inline scripts, image loaders, and feedback modals.
|
|
57
|
+
- ๐ช **Stealth Headless Mode**: Rather than using detectable `--headless=new` flags that trigger bot detection, runs off-screen (`--window-position=-52000,-52000`) so it doesn't disturb your desktop.
|
|
58
|
+
- ๐ **Multi-Page Pagination**: Supports scraping across multiple pages (`start=0`, `start=10`, etc.) with URL deduplication.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Prerequisites
|
|
63
|
+
|
|
64
|
+
1. **Python 3.10+** (Python 3.10, 3.11, or 3.12+).
|
|
65
|
+
2. **Google Chrome Browser**: A standard desktop installation of Google Chrome must be installed on your machine.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
### 1. Clone the Repository
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
git clone https://github.com/your-username/google-search.git
|
|
75
|
+
cd google-search
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 2. Create and Activate a Virtual Environment
|
|
79
|
+
|
|
80
|
+
#### On Windows (PowerShell):
|
|
81
|
+
```powershell
|
|
82
|
+
python -m venv .venv
|
|
83
|
+
.venv\Scripts\Activate.ps1
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
#### On Linux / macOS:
|
|
87
|
+
```bash
|
|
88
|
+
python3 -m venv .venv
|
|
89
|
+
source .venv/bin/activate
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 3. Install Dependencies
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pip install -r requirements.txt
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
> **Note for Python 3.12+ users:** `setuptools` is included in `requirements.txt`. It is required because Python 3.12 removed `distutils`, which `undetected-chromedriver` relies on for version resolution.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Quick Start
|
|
103
|
+
|
|
104
|
+
Create a script (or run directly from `google_search/google_search.py`):
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from google_search.google_search import get_search
|
|
108
|
+
|
|
109
|
+
# Run a simple search
|
|
110
|
+
response = get_search("Who won the 2022 FIFA World Cup?", pages=1)
|
|
111
|
+
|
|
112
|
+
# Check if Google provided an AI Overview
|
|
113
|
+
if response.get("ai_response"):
|
|
114
|
+
print("๐ค AI Overview:")
|
|
115
|
+
print(response["ai_response"]["text"])
|
|
116
|
+
print("-" * 50)
|
|
117
|
+
|
|
118
|
+
# Print organic search results
|
|
119
|
+
print(f"Found {response['meta']['result_count']} results:\n")
|
|
120
|
+
for item in response["results"]:
|
|
121
|
+
print(f"Title: {item['title']}")
|
|
122
|
+
print(f"URL: {item['url']}")
|
|
123
|
+
print(f"Provider: {item.get('provider')}")
|
|
124
|
+
print(f"Description: {item.get('description')}")
|
|
125
|
+
print(f"Date: {item.get('date')}")
|
|
126
|
+
print("-" * 50)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Detailed Parameter Reference
|
|
132
|
+
|
|
133
|
+
The primary entry point is the function:
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
def get_search(
|
|
137
|
+
query: str,
|
|
138
|
+
pages: int = 1,
|
|
139
|
+
version: int | None = None,
|
|
140
|
+
headless: bool = True,
|
|
141
|
+
resolve_urls: bool = True
|
|
142
|
+
) -> dict:
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Parameter Details
|
|
146
|
+
|
|
147
|
+
| Parameter | Type | Default | Description |
|
|
148
|
+
| :--- | :--- | :--- | :--- |
|
|
149
|
+
| `query` | `str` | *(Required)* | The search query string (e.g., `"Python web scraping"`, `"World Cup 2022 winner"`). Special characters and spaces are safely encoded. |
|
|
150
|
+
| `pages` | `int` | `1` | Number of search result pages to scrape. <br>โข Set to `1` for the first 10 results.<br>โข Set to `2` or higher to paginate (`&start=10`, `&start=20`, etc.).<br>โข If set to `0` or `None`, it scrapes until no more results are found. |
|
|
151
|
+
| `version` | `int \| None` | `None` | The major version of Google Chrome installed on your machine (e.g., `128`, `130`, `155`). <br>โข If `None` (recommended), `undetected-chromedriver` automatically detects your installed Chrome version and downloads the matching driver.<br>โข Pin this only if auto-detection fails or you have multiple browser versions. |
|
|
152
|
+
| `headless` | `bool` | `True` | Browser visibility mode:<br>โข `True` *(default)*: Spawns the browser pushed off-screen (`--window-position=-52000,-52000`). It operates invisibly without popping up windows or triggering Google's headless detection CAPTCHAs.<br>โข `False`: Launches Chrome in normal visible mode so you can see navigation, scrolling, and debugging in real time. |
|
|
153
|
+
| `resolve_urls` | `bool` | `True` | Redirect resolution setting:<br>โข `True` *(default)*: Concurrent background threads send HTTP requests to Google's `/goto?url=...` redirect tokens to extract and return the real landing page URL (e.g., `https://en.wikipedia.org/...`).<br>โข `False`: Skips redirect resolution and leaves the raw Google redirect URLs. Useful if you want the absolute fastest scraping speed and do not need direct external URLs. |
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Response Structure & Schema
|
|
158
|
+
|
|
159
|
+
The function returns a Python dictionary with the following schema:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
{
|
|
163
|
+
"query": "Who won the 2022 FIFA World Cup?",
|
|
164
|
+
"ai_response": {
|
|
165
|
+
"text": "The Argentina national football team won the 2022 FIFA World Cup...",
|
|
166
|
+
"sources": [
|
|
167
|
+
{
|
|
168
|
+
"title": "Learn more",
|
|
169
|
+
"url": "https://support.google.com/websearch?p=ai_overviews",
|
|
170
|
+
"provider": "support.google.com",
|
|
171
|
+
"description": "AI responses may include mistakes. Learn more",
|
|
172
|
+
"type": "ai_source"
|
|
173
|
+
}
|
|
174
|
+
]
|
|
175
|
+
},
|
|
176
|
+
"results": [
|
|
177
|
+
{
|
|
178
|
+
"title": "2022 FIFA World Cup final",
|
|
179
|
+
"url": "https://en.wikipedia.org/wiki/2022_FIFA_World_Cup_final",
|
|
180
|
+
"provider": "en.wikipedia.org",
|
|
181
|
+
"description": "They defeated Poland 3โ1 in the round of 16, England 2โ1 in the quarter-final, and Morocco 2โ0 in the semi-final. The final took place in front of 88,966 ...",
|
|
182
|
+
"date": "18 December 2022",
|
|
183
|
+
"type": "organic"
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"title": "FIFA World Cup Qatar 2022โข",
|
|
187
|
+
"url": "https://www.fifa.com/en/tournaments/mens/worldcup/qatar2022",
|
|
188
|
+
"provider": "fifa.com",
|
|
189
|
+
"description": "The FIFA World Cup Qatar 2022 was played from 20 November to 18 December 2022. 32 teams competed across 64 matches in the 22nd edition of the tournament.",
|
|
190
|
+
"date": "18 December 2022",
|
|
191
|
+
"type": "organic"
|
|
192
|
+
}
|
|
193
|
+
],
|
|
194
|
+
"urls": [
|
|
195
|
+
"https://en.wikipedia.org/wiki/2022_FIFA_World_Cup_final",
|
|
196
|
+
"https://www.fifa.com/en/tournaments/mens/worldcup/qatar2022"
|
|
197
|
+
],
|
|
198
|
+
"meta": {
|
|
199
|
+
"result_count": 7,
|
|
200
|
+
"url_count": 7,
|
|
201
|
+
"has_ai_response": True,
|
|
202
|
+
"pages_scraped": 1
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Field Descriptions
|
|
208
|
+
|
|
209
|
+
- **`query`** (`str`): The query string executed.
|
|
210
|
+
- **`ai_response`** (`dict | None`): Contains Google's AI Overview answer. If Google did not generate an AI Overview for the query, this is `None`.
|
|
211
|
+
- **`text`** (`str`): Clean text of the AI Overview answer.
|
|
212
|
+
- **`sources`** (`list[dict]`): List of sources referenced by the AI answer.
|
|
213
|
+
- **`results`** (`list[dict]`): Deduplicated list of organic search results:
|
|
214
|
+
- **`title`** (`str`): Title of the web page.
|
|
215
|
+
- **`url`** (`str`): Original destination URL (unmasked from Google redirect).
|
|
216
|
+
- **`provider`** (`str`): Domain or provider name (e.g., `en.wikipedia.org`, `fifa.com`).
|
|
217
|
+
- **`description`** (`str`): Clean search snippet/summary from Google (with scripts and UI noise filtered).
|
|
218
|
+
- **`date`** (`str | None`): Publish or update date if Google displayed one.
|
|
219
|
+
- **`type`** (`str`): Type of result (`"organic"`).
|
|
220
|
+
- **`urls`** (`list[str]`): Deduplicated list of all external URLs found across the scraped pages.
|
|
221
|
+
- **`meta`** (`dict`):
|
|
222
|
+
- **`result_count`** (`int`): Total number of organic results returned.
|
|
223
|
+
- **`url_count`** (`int`): Total count of unique links.
|
|
224
|
+
- **`has_ai_response`** (`bool`): `True` if an AI overview was detected and parsed.
|
|
225
|
+
- **`pages_scraped`** (`int`): Number of Google result pages successfully processed.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Advanced Usage Examples
|
|
230
|
+
|
|
231
|
+
### 1. Scraping Multiple Pages
|
|
232
|
+
|
|
233
|
+
To scrape 3 pages of results (up to ~30 results):
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
from google_search.google_search import get_search
|
|
237
|
+
|
|
238
|
+
results = get_search("machine learning tutorials", pages=3)
|
|
239
|
+
print(f"Scraped {results['meta']['pages_scraped']} pages.")
|
|
240
|
+
print(f"Total results: {results['meta']['result_count']}")
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### 2. Extracting AI Overview Answers
|
|
244
|
+
|
|
245
|
+
Google often provides generative AI summaries for informative or question queries:
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
from google_search.google_search import get_search
|
|
249
|
+
|
|
250
|
+
data = get_search("How does photosynthesis work?", pages=1)
|
|
251
|
+
|
|
252
|
+
if data["meta"]["has_ai_response"]:
|
|
253
|
+
ai = data["ai_response"]
|
|
254
|
+
print("Answer:\n", ai["text"])
|
|
255
|
+
print("\nCited Sources:")
|
|
256
|
+
for s in ai["sources"]:
|
|
257
|
+
print(f"- {s['title']} ({s['url']})")
|
|
258
|
+
else:
|
|
259
|
+
print("No AI Overview generated for this query.")
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### 3. Running in Visible (Non-Headless) Mode
|
|
263
|
+
|
|
264
|
+
If you are debugging or want to observe Chrome typing and scrolling:
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
from google_search.google_search import get_search
|
|
268
|
+
|
|
269
|
+
data = get_search("latest tech news", pages=1, headless=False)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### 4. Ultra-Fast Scraping (Disabling URL Resolution)
|
|
273
|
+
|
|
274
|
+
If you only need titles and snippet text and do not require resolved destination URLs, pass `resolve_urls=False`:
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
from google_search.google_search import get_search
|
|
278
|
+
|
|
279
|
+
# Saves ~1 second of network time by skipping HTTP 302 redirect checks
|
|
280
|
+
data = get_search("fast query", pages=1, resolve_urls=False)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### 5. Pinning a Specific Chrome Version
|
|
284
|
+
|
|
285
|
+
If auto-detection fails or you have a standalone Chrome version installed:
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
from google_search.google_search import get_search
|
|
289
|
+
|
|
290
|
+
# Forces undetected-chromedriver to download and match Chrome 130 driver
|
|
291
|
+
data = get_search("Python tutorials", version=130)
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## Troubleshooting & FAQs
|
|
297
|
+
|
|
298
|
+
### Q1: `ModuleNotFoundError: No module named 'distutils'`
|
|
299
|
+
* **Cause**: You are running Python 3.12+ where `distutils` was removed.
|
|
300
|
+
* **Fix**: Ensure `setuptools` is installed in your virtual environment:
|
|
301
|
+
```bash
|
|
302
|
+
pip install setuptools
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Q2: `SessionNotCreatedException: This version of ChromeDriver only supports Chrome version X`
|
|
306
|
+
* **Cause**: Your installed Chrome version was updated, but the driver cache is stale or a version was hardcoded.
|
|
307
|
+
* **Fix**: Leave `version=None` in `get_search(...)` so it matches automatically. You can also delete the driver cache directory located in `%APPDATA%\undetected_chromedriver` (Windows) or `~/.local/share/undetected_chromedriver` (Linux/Mac).
|
|
308
|
+
|
|
309
|
+
### Q3: `OSError: [WinError 6] The handle is invalid` on script exit
|
|
310
|
+
* **Cause**: On Windows, when Chrome closes its background communication pipes, Python's garbage collection occasionally warns that the process handle closed early.
|
|
311
|
+
* **Status**: This is harmless and does not affect the scraped data. The script includes a safe deletion hook (`_safe_del`) to suppress this warning.
|
|
312
|
+
|
|
313
|
+
### Q4: Does this work on cloud servers (AWS / DigitalOcean / Heroku)?
|
|
314
|
+
* **Note**: Google flags datacenter IP ranges very aggressively. If running on a cloud VM, install standard Chrome (`google-chrome-stable`), `xvfb` for display emulation, and route traffic through residential proxies to prevent CAPTCHAs.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## License
|
|
319
|
+
|
|
320
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# Google Search & AI Overview Scraper
|
|
2
|
+
|
|
3
|
+
A robust, stealthy Python scraper for Google Search results. Built using `undetected-chromedriver` and `parsel`, this tool automatically bypasses Google's bot detection, extracts **Google AI Overviews**, scrapes clean organic search results (titles, genuine snippets, provider domains, dates), and automatically resolves Google's `/goto?url=...` encrypted redirect links into their **original destination URLs**.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Features](#features)
|
|
10
|
+
- [Prerequisites](#prerequisites)
|
|
11
|
+
- [Installation](#installation)
|
|
12
|
+
- [Quick Start](#quick-start)
|
|
13
|
+
- [Detailed Parameter Reference](#detailed-parameter-reference)
|
|
14
|
+
- [Response Structure & Schema](#response-structure--schema)
|
|
15
|
+
- [Advanced Usage Examples](#advanced-usage-examples)
|
|
16
|
+
- [1. Scraping Multiple Pages](#1-scraping-multiple-pages)
|
|
17
|
+
- [2. Extracting AI Overview Answers](#2-extracting-ai-overview-answers)
|
|
18
|
+
- [3. Running in Visible (Non-Headless) Mode](#3-running-in-visible-non-headless-mode)
|
|
19
|
+
- [4. Ultra-Fast Scraping (Disabling URL Resolution)](#4-ultra-fast-scraping-disabling-url-resolution)
|
|
20
|
+
- [5. Pinning a Specific Chrome Version](#5-pinning-a-specific-chrome-version)
|
|
21
|
+
- [Troubleshooting & FAQs](#troubleshooting--faqs)
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- ๐ก๏ธ **Anti-Bot Evasion**: Employs `undetected-chromedriver` with native browser fingerprinting to evade Google CAPTCHA blocks.
|
|
28
|
+
- ๐ค **Google AI Overview Extraction**: Captures the generated AI summary answer and cited sources whenever present.
|
|
29
|
+
- ๐ **Automatic Redirect Unmasking**: Uses concurrent thread pooling to automatically resolve Google's `/goto?url=CAES...` redirects into full, direct landing page URLs (e.g. `https://en.wikipedia.org/...`).
|
|
30
|
+
- ๐งน **Clean Snippet Parsing**: Specifically targets Google's snippet elements while stripping inline scripts, image loaders, and feedback modals.
|
|
31
|
+
- ๐ช **Stealth Headless Mode**: Rather than using detectable `--headless=new` flags that trigger bot detection, runs off-screen (`--window-position=-52000,-52000`) so it doesn't disturb your desktop.
|
|
32
|
+
- ๐ **Multi-Page Pagination**: Supports scraping across multiple pages (`start=0`, `start=10`, etc.) with URL deduplication.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Prerequisites
|
|
37
|
+
|
|
38
|
+
1. **Python 3.10+** (Python 3.10, 3.11, or 3.12+).
|
|
39
|
+
2. **Google Chrome Browser**: A standard desktop installation of Google Chrome must be installed on your machine.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
### 1. Clone the Repository
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
git clone https://github.com/your-username/google-search.git
|
|
49
|
+
cd google-search
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 2. Create and Activate a Virtual Environment
|
|
53
|
+
|
|
54
|
+
#### On Windows (PowerShell):
|
|
55
|
+
```powershell
|
|
56
|
+
python -m venv .venv
|
|
57
|
+
.venv\Scripts\Activate.ps1
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
#### On Linux / macOS:
|
|
61
|
+
```bash
|
|
62
|
+
python3 -m venv .venv
|
|
63
|
+
source .venv/bin/activate
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 3. Install Dependencies
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install -r requirements.txt
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
> **Note for Python 3.12+ users:** `setuptools` is included in `requirements.txt`. It is required because Python 3.12 removed `distutils`, which `undetected-chromedriver` relies on for version resolution.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Quick Start
|
|
77
|
+
|
|
78
|
+
Create a script (or run directly from `google_search/google_search.py`):
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from google_search.google_search import get_search
|
|
82
|
+
|
|
83
|
+
# Run a simple search
|
|
84
|
+
response = get_search("Who won the 2022 FIFA World Cup?", pages=1)
|
|
85
|
+
|
|
86
|
+
# Check if Google provided an AI Overview
|
|
87
|
+
if response.get("ai_response"):
|
|
88
|
+
print("๐ค AI Overview:")
|
|
89
|
+
print(response["ai_response"]["text"])
|
|
90
|
+
print("-" * 50)
|
|
91
|
+
|
|
92
|
+
# Print organic search results
|
|
93
|
+
print(f"Found {response['meta']['result_count']} results:\n")
|
|
94
|
+
for item in response["results"]:
|
|
95
|
+
print(f"Title: {item['title']}")
|
|
96
|
+
print(f"URL: {item['url']}")
|
|
97
|
+
print(f"Provider: {item.get('provider')}")
|
|
98
|
+
print(f"Description: {item.get('description')}")
|
|
99
|
+
print(f"Date: {item.get('date')}")
|
|
100
|
+
print("-" * 50)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Detailed Parameter Reference
|
|
106
|
+
|
|
107
|
+
The primary entry point is the function:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
def get_search(
|
|
111
|
+
query: str,
|
|
112
|
+
pages: int = 1,
|
|
113
|
+
version: int | None = None,
|
|
114
|
+
headless: bool = True,
|
|
115
|
+
resolve_urls: bool = True
|
|
116
|
+
) -> dict:
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Parameter Details
|
|
120
|
+
|
|
121
|
+
| Parameter | Type | Default | Description |
|
|
122
|
+
| :--- | :--- | :--- | :--- |
|
|
123
|
+
| `query` | `str` | *(Required)* | The search query string (e.g., `"Python web scraping"`, `"World Cup 2022 winner"`). Special characters and spaces are safely encoded. |
|
|
124
|
+
| `pages` | `int` | `1` | Number of search result pages to scrape. <br>โข Set to `1` for the first 10 results.<br>โข Set to `2` or higher to paginate (`&start=10`, `&start=20`, etc.).<br>โข If set to `0` or `None`, it scrapes until no more results are found. |
|
|
125
|
+
| `version` | `int \| None` | `None` | The major version of Google Chrome installed on your machine (e.g., `128`, `130`, `155`). <br>โข If `None` (recommended), `undetected-chromedriver` automatically detects your installed Chrome version and downloads the matching driver.<br>โข Pin this only if auto-detection fails or you have multiple browser versions. |
|
|
126
|
+
| `headless` | `bool` | `True` | Browser visibility mode:<br>โข `True` *(default)*: Spawns the browser pushed off-screen (`--window-position=-52000,-52000`). It operates invisibly without popping up windows or triggering Google's headless detection CAPTCHAs.<br>โข `False`: Launches Chrome in normal visible mode so you can see navigation, scrolling, and debugging in real time. |
|
|
127
|
+
| `resolve_urls` | `bool` | `True` | Redirect resolution setting:<br>โข `True` *(default)*: Concurrent background threads send HTTP requests to Google's `/goto?url=...` redirect tokens to extract and return the real landing page URL (e.g., `https://en.wikipedia.org/...`).<br>โข `False`: Skips redirect resolution and leaves the raw Google redirect URLs. Useful if you want the absolute fastest scraping speed and do not need direct external URLs. |
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Response Structure & Schema
|
|
132
|
+
|
|
133
|
+
The function returns a Python dictionary with the following schema:
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
{
|
|
137
|
+
"query": "Who won the 2022 FIFA World Cup?",
|
|
138
|
+
"ai_response": {
|
|
139
|
+
"text": "The Argentina national football team won the 2022 FIFA World Cup...",
|
|
140
|
+
"sources": [
|
|
141
|
+
{
|
|
142
|
+
"title": "Learn more",
|
|
143
|
+
"url": "https://support.google.com/websearch?p=ai_overviews",
|
|
144
|
+
"provider": "support.google.com",
|
|
145
|
+
"description": "AI responses may include mistakes. Learn more",
|
|
146
|
+
"type": "ai_source"
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
},
|
|
150
|
+
"results": [
|
|
151
|
+
{
|
|
152
|
+
"title": "2022 FIFA World Cup final",
|
|
153
|
+
"url": "https://en.wikipedia.org/wiki/2022_FIFA_World_Cup_final",
|
|
154
|
+
"provider": "en.wikipedia.org",
|
|
155
|
+
"description": "They defeated Poland 3โ1 in the round of 16, England 2โ1 in the quarter-final, and Morocco 2โ0 in the semi-final. The final took place in front of 88,966 ...",
|
|
156
|
+
"date": "18 December 2022",
|
|
157
|
+
"type": "organic"
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"title": "FIFA World Cup Qatar 2022โข",
|
|
161
|
+
"url": "https://www.fifa.com/en/tournaments/mens/worldcup/qatar2022",
|
|
162
|
+
"provider": "fifa.com",
|
|
163
|
+
"description": "The FIFA World Cup Qatar 2022 was played from 20 November to 18 December 2022. 32 teams competed across 64 matches in the 22nd edition of the tournament.",
|
|
164
|
+
"date": "18 December 2022",
|
|
165
|
+
"type": "organic"
|
|
166
|
+
}
|
|
167
|
+
],
|
|
168
|
+
"urls": [
|
|
169
|
+
"https://en.wikipedia.org/wiki/2022_FIFA_World_Cup_final",
|
|
170
|
+
"https://www.fifa.com/en/tournaments/mens/worldcup/qatar2022"
|
|
171
|
+
],
|
|
172
|
+
"meta": {
|
|
173
|
+
"result_count": 7,
|
|
174
|
+
"url_count": 7,
|
|
175
|
+
"has_ai_response": True,
|
|
176
|
+
"pages_scraped": 1
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Field Descriptions
|
|
182
|
+
|
|
183
|
+
- **`query`** (`str`): The query string executed.
|
|
184
|
+
- **`ai_response`** (`dict | None`): Contains Google's AI Overview answer. If Google did not generate an AI Overview for the query, this is `None`.
|
|
185
|
+
- **`text`** (`str`): Clean text of the AI Overview answer.
|
|
186
|
+
- **`sources`** (`list[dict]`): List of sources referenced by the AI answer.
|
|
187
|
+
- **`results`** (`list[dict]`): Deduplicated list of organic search results:
|
|
188
|
+
- **`title`** (`str`): Title of the web page.
|
|
189
|
+
- **`url`** (`str`): Original destination URL (unmasked from Google redirect).
|
|
190
|
+
- **`provider`** (`str`): Domain or provider name (e.g., `en.wikipedia.org`, `fifa.com`).
|
|
191
|
+
- **`description`** (`str`): Clean search snippet/summary from Google (with scripts and UI noise filtered).
|
|
192
|
+
- **`date`** (`str | None`): Publish or update date if Google displayed one.
|
|
193
|
+
- **`type`** (`str`): Type of result (`"organic"`).
|
|
194
|
+
- **`urls`** (`list[str]`): Deduplicated list of all external URLs found across the scraped pages.
|
|
195
|
+
- **`meta`** (`dict`):
|
|
196
|
+
- **`result_count`** (`int`): Total number of organic results returned.
|
|
197
|
+
- **`url_count`** (`int`): Total count of unique links.
|
|
198
|
+
- **`has_ai_response`** (`bool`): `True` if an AI overview was detected and parsed.
|
|
199
|
+
- **`pages_scraped`** (`int`): Number of Google result pages successfully processed.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Advanced Usage Examples
|
|
204
|
+
|
|
205
|
+
### 1. Scraping Multiple Pages
|
|
206
|
+
|
|
207
|
+
To scrape 3 pages of results (up to ~30 results):
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from google_search.google_search import get_search
|
|
211
|
+
|
|
212
|
+
results = get_search("machine learning tutorials", pages=3)
|
|
213
|
+
print(f"Scraped {results['meta']['pages_scraped']} pages.")
|
|
214
|
+
print(f"Total results: {results['meta']['result_count']}")
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### 2. Extracting AI Overview Answers
|
|
218
|
+
|
|
219
|
+
Google often provides generative AI summaries for informative or question queries:
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
from google_search.google_search import get_search
|
|
223
|
+
|
|
224
|
+
data = get_search("How does photosynthesis work?", pages=1)
|
|
225
|
+
|
|
226
|
+
if data["meta"]["has_ai_response"]:
|
|
227
|
+
ai = data["ai_response"]
|
|
228
|
+
print("Answer:\n", ai["text"])
|
|
229
|
+
print("\nCited Sources:")
|
|
230
|
+
for s in ai["sources"]:
|
|
231
|
+
print(f"- {s['title']} ({s['url']})")
|
|
232
|
+
else:
|
|
233
|
+
print("No AI Overview generated for this query.")
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### 3. Running in Visible (Non-Headless) Mode
|
|
237
|
+
|
|
238
|
+
If you are debugging or want to observe Chrome typing and scrolling:
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
from google_search.google_search import get_search
|
|
242
|
+
|
|
243
|
+
data = get_search("latest tech news", pages=1, headless=False)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### 4. Ultra-Fast Scraping (Disabling URL Resolution)
|
|
247
|
+
|
|
248
|
+
If you only need titles and snippet text and do not require resolved destination URLs, pass `resolve_urls=False`:
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
from google_search.google_search import get_search
|
|
252
|
+
|
|
253
|
+
# Saves ~1 second of network time by skipping HTTP 302 redirect checks
|
|
254
|
+
data = get_search("fast query", pages=1, resolve_urls=False)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### 5. Pinning a Specific Chrome Version
|
|
258
|
+
|
|
259
|
+
If auto-detection fails or you have a standalone Chrome version installed:
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
from google_search.google_search import get_search
|
|
263
|
+
|
|
264
|
+
# Forces undetected-chromedriver to download and match Chrome 130 driver
|
|
265
|
+
data = get_search("Python tutorials", version=130)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Troubleshooting & FAQs
|
|
271
|
+
|
|
272
|
+
### Q1: `ModuleNotFoundError: No module named 'distutils'`
|
|
273
|
+
* **Cause**: You are running Python 3.12+ where `distutils` was removed.
|
|
274
|
+
* **Fix**: Ensure `setuptools` is installed in your virtual environment:
|
|
275
|
+
```bash
|
|
276
|
+
pip install setuptools
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Q2: `SessionNotCreatedException: This version of ChromeDriver only supports Chrome version X`
|
|
280
|
+
* **Cause**: Your installed Chrome version was updated, but the driver cache is stale or a version was hardcoded.
|
|
281
|
+
* **Fix**: Leave `version=None` in `get_search(...)` so it matches automatically. You can also delete the driver cache directory located in `%APPDATA%\undetected_chromedriver` (Windows) or `~/.local/share/undetected_chromedriver` (Linux/Mac).
|
|
282
|
+
|
|
283
|
+
### Q3: `OSError: [WinError 6] The handle is invalid` on script exit
|
|
284
|
+
* **Cause**: On Windows, when Chrome closes its background communication pipes, Python's garbage collection occasionally warns that the process handle closed early.
|
|
285
|
+
* **Status**: This is harmless and does not affect the scraped data. The script includes a safe deletion hook (`_safe_del`) to suppress this warning.
|
|
286
|
+
|
|
287
|
+
### Q4: Does this work on cloud servers (AWS / DigitalOcean / Heroku)?
|
|
288
|
+
* **Note**: Google flags datacenter IP ranges very aggressively. If running on a cloud VM, install standard Chrome (`google-chrome-stable`), `xvfb` for display emulation, and route traffic through residential proxies to prevent CAPTCHAs.
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## License
|
|
293
|
+
|
|
294
|
+
This project is licensed under the [MIT License](LICENSE).
|