pytrends-modern 0.1.0__py3-none-any.whl
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.
- pytrends_modern/__init__.py +27 -0
- pytrends_modern/cli.py +352 -0
- pytrends_modern/config.py +196 -0
- pytrends_modern/exceptions.py +68 -0
- pytrends_modern/py.typed +0 -0
- pytrends_modern/request.py +849 -0
- pytrends_modern/rss.py +337 -0
- pytrends_modern/utils.py +267 -0
- pytrends_modern-0.1.0.dist-info/METADATA +394 -0
- pytrends_modern-0.1.0.dist-info/RECORD +14 -0
- pytrends_modern-0.1.0.dist-info/WHEEL +5 -0
- pytrends_modern-0.1.0.dist-info/entry_points.txt +2 -0
- pytrends_modern-0.1.0.dist-info/licenses/LICENSE +37 -0
- pytrends_modern-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pytrends-modern: Modern Google Trends API
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "1.0.0"
|
|
6
|
+
__author__ = "pytrends-modern contributors"
|
|
7
|
+
__license__ = "MIT"
|
|
8
|
+
|
|
9
|
+
from pytrends_modern.request import TrendReq
|
|
10
|
+
from pytrends_modern.rss import TrendsRSS
|
|
11
|
+
from pytrends_modern.exceptions import (
|
|
12
|
+
TooManyRequestsError,
|
|
13
|
+
ResponseError,
|
|
14
|
+
InvalidParameterError,
|
|
15
|
+
BrowserError,
|
|
16
|
+
DownloadError,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"TrendReq",
|
|
21
|
+
"TrendsRSS",
|
|
22
|
+
"TooManyRequestsError",
|
|
23
|
+
"ResponseError",
|
|
24
|
+
"InvalidParameterError",
|
|
25
|
+
"BrowserError",
|
|
26
|
+
"DownloadError",
|
|
27
|
+
]
|
pytrends_modern/cli.py
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command-line interface for pytrends-modern
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from typing import List, Optional
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
import click
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
from rich.progress import Progress
|
|
14
|
+
|
|
15
|
+
CLI_AVAILABLE = True
|
|
16
|
+
except ImportError:
|
|
17
|
+
CLI_AVAILABLE = False
|
|
18
|
+
|
|
19
|
+
from pytrends_modern import TrendReq, TrendsRSS
|
|
20
|
+
from pytrends_modern.exceptions import PyTrendsPlusError
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
if CLI_AVAILABLE:
|
|
24
|
+
console = Console()
|
|
25
|
+
|
|
26
|
+
def error(msg: str) -> None:
|
|
27
|
+
"""Print error message and exit"""
|
|
28
|
+
console.print(f"[bold red]Error:[/bold red] {msg}")
|
|
29
|
+
sys.exit(1)
|
|
30
|
+
|
|
31
|
+
def success(msg: str) -> None:
|
|
32
|
+
"""Print success message"""
|
|
33
|
+
console.print(f"[bold green]✓[/bold green] {msg}")
|
|
34
|
+
|
|
35
|
+
def info(msg: str) -> None:
|
|
36
|
+
"""Print info message"""
|
|
37
|
+
console.print(f"[bold blue]ℹ[/bold blue] {msg}")
|
|
38
|
+
|
|
39
|
+
@click.group()
|
|
40
|
+
@click.version_option(version="1.0.0", prog_name="pytrends-modern")
|
|
41
|
+
def cli() -> None:
|
|
42
|
+
"""
|
|
43
|
+
pytrends-modern: Modern Google Trends API
|
|
44
|
+
|
|
45
|
+
A next-generation library combining the best features from pytrends,
|
|
46
|
+
trendspyg, and more. Get interest over time, by region, related topics,
|
|
47
|
+
trending searches, and real-time RSS feeds.
|
|
48
|
+
"""
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
@cli.command()
|
|
52
|
+
@click.option("--keywords", "-k", required=True, help="Comma-separated keywords (max 5)")
|
|
53
|
+
@click.option("--timeframe", "-t", default="today 12-m", help='Time range (e.g., "today 12-m")')
|
|
54
|
+
@click.option("--geo", "-g", default="", help='Geographic location (e.g., "US", "GB")')
|
|
55
|
+
@click.option("--category", "-c", default=0, help="Category ID (0 for all)")
|
|
56
|
+
@click.option(
|
|
57
|
+
"--property", "-p", default="", help='Property: "", "images", "news", "youtube", "froogle"'
|
|
58
|
+
)
|
|
59
|
+
@click.option("--output", "-o", type=click.Path(), help="Output file path (CSV, JSON)")
|
|
60
|
+
@click.option(
|
|
61
|
+
"--format",
|
|
62
|
+
"-f",
|
|
63
|
+
type=click.Choice(["csv", "json", "table"]),
|
|
64
|
+
default="table",
|
|
65
|
+
help="Output format",
|
|
66
|
+
)
|
|
67
|
+
def interest(
|
|
68
|
+
keywords: str,
|
|
69
|
+
timeframe: str,
|
|
70
|
+
geo: str,
|
|
71
|
+
category: int,
|
|
72
|
+
property: str,
|
|
73
|
+
output: Optional[str],
|
|
74
|
+
format: str,
|
|
75
|
+
) -> None:
|
|
76
|
+
"""
|
|
77
|
+
Get interest over time for keywords
|
|
78
|
+
|
|
79
|
+
Example:
|
|
80
|
+
pytrends-modern interest -k "Python,JavaScript" -t "today 12-m"
|
|
81
|
+
"""
|
|
82
|
+
try:
|
|
83
|
+
# Parse keywords
|
|
84
|
+
kw_list = [k.strip() for k in keywords.split(",")]
|
|
85
|
+
|
|
86
|
+
if len(kw_list) > 5:
|
|
87
|
+
error("Maximum 5 keywords allowed")
|
|
88
|
+
|
|
89
|
+
# Initialize client
|
|
90
|
+
info(f"Fetching interest over time for: {', '.join(kw_list)}")
|
|
91
|
+
pytrends = TrendReq()
|
|
92
|
+
|
|
93
|
+
# Build payload
|
|
94
|
+
pytrends.build_payload(
|
|
95
|
+
kw_list=kw_list, timeframe=timeframe, geo=geo, cat=category, gprop=property
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# Get data
|
|
99
|
+
df = pytrends.interest_over_time()
|
|
100
|
+
|
|
101
|
+
if df.empty:
|
|
102
|
+
error("No data returned. Try different keywords or timeframe.")
|
|
103
|
+
|
|
104
|
+
# Remove isPartial column for display
|
|
105
|
+
if "isPartial" in df.columns:
|
|
106
|
+
df = df.drop("isPartial", axis=1)
|
|
107
|
+
|
|
108
|
+
# Output
|
|
109
|
+
if output:
|
|
110
|
+
if output.endswith(".json"):
|
|
111
|
+
df.to_json(output, orient="records", date_format="iso")
|
|
112
|
+
success(f"Data saved to {output}")
|
|
113
|
+
else:
|
|
114
|
+
df.to_csv(output)
|
|
115
|
+
success(f"Data saved to {output}")
|
|
116
|
+
else:
|
|
117
|
+
if format == "json":
|
|
118
|
+
console.print_json(df.to_json(orient="records", date_format="iso"))
|
|
119
|
+
elif format == "csv":
|
|
120
|
+
console.print(df.to_csv())
|
|
121
|
+
else:
|
|
122
|
+
# Display as table
|
|
123
|
+
table = Table(title=f"Interest Over Time: {', '.join(kw_list)}")
|
|
124
|
+
table.add_column("Date", style="cyan")
|
|
125
|
+
for kw in kw_list:
|
|
126
|
+
table.add_column(kw, style="magenta")
|
|
127
|
+
|
|
128
|
+
# Show last 10 rows
|
|
129
|
+
for idx, row in df.tail(10).iterrows():
|
|
130
|
+
table.add_row(str(idx.date()), *[str(int(row[kw])) for kw in kw_list])
|
|
131
|
+
|
|
132
|
+
console.print(table)
|
|
133
|
+
info(f"Showing last 10 of {len(df)} data points")
|
|
134
|
+
|
|
135
|
+
except PyTrendsPlusError as e:
|
|
136
|
+
error(str(e))
|
|
137
|
+
except Exception as e:
|
|
138
|
+
error(f"Unexpected error: {str(e)}")
|
|
139
|
+
|
|
140
|
+
@cli.command()
|
|
141
|
+
@click.option("--keywords", "-k", required=True, help="Comma-separated keywords")
|
|
142
|
+
@click.option("--geo", "-g", default="", help="Geographic location")
|
|
143
|
+
@click.option(
|
|
144
|
+
"--resolution", "-r", default="COUNTRY", help="Resolution: COUNTRY, REGION, CITY, DMA"
|
|
145
|
+
)
|
|
146
|
+
@click.option("--output", "-o", type=click.Path(), help="Output file path")
|
|
147
|
+
@click.option("--format", "-f", type=click.Choice(["csv", "json", "table"]), default="table")
|
|
148
|
+
def region(
|
|
149
|
+
keywords: str, geo: str, resolution: str, output: Optional[str], format: str
|
|
150
|
+
) -> None:
|
|
151
|
+
"""
|
|
152
|
+
Get interest by region for keywords
|
|
153
|
+
|
|
154
|
+
Example:
|
|
155
|
+
pytrends-modern region -k "Python" -g "US" -r "REGION"
|
|
156
|
+
"""
|
|
157
|
+
try:
|
|
158
|
+
kw_list = [k.strip() for k in keywords.split(",")]
|
|
159
|
+
|
|
160
|
+
info(f"Fetching interest by region for: {', '.join(kw_list)}")
|
|
161
|
+
pytrends = TrendReq()
|
|
162
|
+
|
|
163
|
+
pytrends.build_payload(kw_list=kw_list, geo=geo)
|
|
164
|
+
df = pytrends.interest_by_region(resolution=resolution)
|
|
165
|
+
|
|
166
|
+
if df.empty:
|
|
167
|
+
error("No data returned")
|
|
168
|
+
|
|
169
|
+
# Output
|
|
170
|
+
if output:
|
|
171
|
+
if output.endswith(".json"):
|
|
172
|
+
df.to_json(output, orient="index")
|
|
173
|
+
success(f"Data saved to {output}")
|
|
174
|
+
else:
|
|
175
|
+
df.to_csv(output)
|
|
176
|
+
success(f"Data saved to {output}")
|
|
177
|
+
else:
|
|
178
|
+
if format == "json":
|
|
179
|
+
console.print_json(df.to_json(orient="index"))
|
|
180
|
+
elif format == "csv":
|
|
181
|
+
console.print(df.to_csv())
|
|
182
|
+
else:
|
|
183
|
+
# Display as table
|
|
184
|
+
table = Table(title=f"Interest by Region: {', '.join(kw_list)}")
|
|
185
|
+
table.add_column("Region", style="cyan")
|
|
186
|
+
for kw in kw_list:
|
|
187
|
+
table.add_column(kw, style="magenta")
|
|
188
|
+
|
|
189
|
+
# Show top 10 regions
|
|
190
|
+
df_sorted = df.sort_values(by=kw_list[0], ascending=False)
|
|
191
|
+
for region, row in df_sorted.head(10).iterrows():
|
|
192
|
+
table.add_row(str(region), *[str(int(row[kw])) for kw in kw_list])
|
|
193
|
+
|
|
194
|
+
console.print(table)
|
|
195
|
+
info(f"Showing top 10 of {len(df)} regions")
|
|
196
|
+
|
|
197
|
+
except PyTrendsPlusError as e:
|
|
198
|
+
error(str(e))
|
|
199
|
+
except Exception as e:
|
|
200
|
+
error(f"Unexpected error: {str(e)}")
|
|
201
|
+
|
|
202
|
+
@cli.command()
|
|
203
|
+
@click.option("--geo", "-g", default="US", help="Country code (e.g., US, GB, CA)")
|
|
204
|
+
@click.option("--output", "-o", type=click.Path(), help="Output file path")
|
|
205
|
+
@click.option("--format", "-f", type=click.Choice(["csv", "json", "table"]), default="table")
|
|
206
|
+
@click.option("--images/--no-images", default=True, help="Include images")
|
|
207
|
+
@click.option("--articles/--no-articles", default=True, help="Include articles")
|
|
208
|
+
def rss(geo: str, output: Optional[str], format: str, images: bool, articles: bool) -> None:
|
|
209
|
+
"""
|
|
210
|
+
Get real-time trending searches from RSS feed (fast!)
|
|
211
|
+
|
|
212
|
+
Example:
|
|
213
|
+
pytrends-modern rss -g US
|
|
214
|
+
pytrends-modern rss -g GB --format json -o trends.json
|
|
215
|
+
"""
|
|
216
|
+
try:
|
|
217
|
+
info(f"Fetching RSS trends for {geo}...")
|
|
218
|
+
|
|
219
|
+
rss_client = TrendsRSS()
|
|
220
|
+
trends = rss_client.get_trends(
|
|
221
|
+
geo=geo, output_format="dict", include_images=images, include_articles=articles
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
if not trends:
|
|
225
|
+
error("No trends returned")
|
|
226
|
+
|
|
227
|
+
# Output
|
|
228
|
+
if output:
|
|
229
|
+
if output.endswith(".json"):
|
|
230
|
+
with open(output, "w") as f:
|
|
231
|
+
json.dump(trends, f, indent=2, default=str)
|
|
232
|
+
success(f"Data saved to {output}")
|
|
233
|
+
else:
|
|
234
|
+
# Save as CSV
|
|
235
|
+
df = rss_client.get_trends(geo=geo, output_format="dataframe")
|
|
236
|
+
df.to_csv(output, index=False)
|
|
237
|
+
success(f"Data saved to {output}")
|
|
238
|
+
else:
|
|
239
|
+
if format == "json":
|
|
240
|
+
console.print_json(json.dumps(trends, default=str))
|
|
241
|
+
elif format == "csv":
|
|
242
|
+
df = rss_client.get_trends(geo=geo, output_format="dataframe")
|
|
243
|
+
console.print(df.to_csv(index=False))
|
|
244
|
+
else:
|
|
245
|
+
# Display as table
|
|
246
|
+
table = Table(title=f"Trending Searches: {geo}")
|
|
247
|
+
table.add_column("Title", style="cyan", no_wrap=False)
|
|
248
|
+
table.add_column("Traffic", style="magenta")
|
|
249
|
+
if articles:
|
|
250
|
+
table.add_column("Articles", style="green")
|
|
251
|
+
|
|
252
|
+
for trend in trends[:15]: # Show top 15
|
|
253
|
+
row = [trend.get("title", "N/A")[:60], str(trend.get("traffic", "N/A"))]
|
|
254
|
+
if articles:
|
|
255
|
+
row.append(str(trend.get("article_count", 0)))
|
|
256
|
+
table.add_row(*row)
|
|
257
|
+
|
|
258
|
+
console.print(table)
|
|
259
|
+
info(f"Showing {min(15, len(trends))} of {len(trends)} trends")
|
|
260
|
+
|
|
261
|
+
except PyTrendsPlusError as e:
|
|
262
|
+
error(str(e))
|
|
263
|
+
except Exception as e:
|
|
264
|
+
error(f"Unexpected error: {str(e)}")
|
|
265
|
+
|
|
266
|
+
@cli.command()
|
|
267
|
+
@click.option("--keyword", "-k", required=True, help="Keyword to get suggestions for")
|
|
268
|
+
def suggest(keyword: str) -> None:
|
|
269
|
+
"""
|
|
270
|
+
Get keyword suggestions from Google Trends
|
|
271
|
+
|
|
272
|
+
Example:
|
|
273
|
+
pytrends-modern suggest -k "python"
|
|
274
|
+
"""
|
|
275
|
+
try:
|
|
276
|
+
info(f"Getting suggestions for: {keyword}")
|
|
277
|
+
pytrends = TrendReq()
|
|
278
|
+
suggestions = pytrends.suggestions(keyword)
|
|
279
|
+
|
|
280
|
+
if not suggestions:
|
|
281
|
+
error("No suggestions found")
|
|
282
|
+
|
|
283
|
+
table = Table(title=f"Suggestions for '{keyword}'")
|
|
284
|
+
table.add_column("Title", style="cyan")
|
|
285
|
+
table.add_column("Type", style="magenta")
|
|
286
|
+
|
|
287
|
+
for suggestion in suggestions[:10]:
|
|
288
|
+
table.add_row(suggestion.get("title", "N/A"), suggestion.get("type", "N/A"))
|
|
289
|
+
|
|
290
|
+
console.print(table)
|
|
291
|
+
|
|
292
|
+
except PyTrendsPlusError as e:
|
|
293
|
+
error(str(e))
|
|
294
|
+
except Exception as e:
|
|
295
|
+
error(f"Unexpected error: {str(e)}")
|
|
296
|
+
|
|
297
|
+
@cli.command()
|
|
298
|
+
@click.option("--country", "-c", default="united_states", help="Country name")
|
|
299
|
+
@click.option("--output", "-o", type=click.Path(), help="Output file path")
|
|
300
|
+
def trending(country: str, output: Optional[str]) -> None:
|
|
301
|
+
"""
|
|
302
|
+
Get trending searches for a country
|
|
303
|
+
|
|
304
|
+
Example:
|
|
305
|
+
pytrends-modern trending -c united_states
|
|
306
|
+
"""
|
|
307
|
+
try:
|
|
308
|
+
info(f"Getting trending searches for: {country}")
|
|
309
|
+
pytrends = TrendReq()
|
|
310
|
+
df = pytrends.trending_searches(pn=country)
|
|
311
|
+
|
|
312
|
+
if df.empty:
|
|
313
|
+
error("No trending searches found")
|
|
314
|
+
|
|
315
|
+
if output:
|
|
316
|
+
df.to_csv(output, index=False, header=False)
|
|
317
|
+
success(f"Data saved to {output}")
|
|
318
|
+
else:
|
|
319
|
+
table = Table(title=f"Trending Searches: {country}")
|
|
320
|
+
table.add_column("#", style="cyan")
|
|
321
|
+
table.add_column("Search Term", style="magenta")
|
|
322
|
+
|
|
323
|
+
for idx, term in enumerate(df.iloc[:, 0].head(20), 1):
|
|
324
|
+
table.add_row(str(idx), str(term))
|
|
325
|
+
|
|
326
|
+
console.print(table)
|
|
327
|
+
|
|
328
|
+
except PyTrendsPlusError as e:
|
|
329
|
+
error(str(e))
|
|
330
|
+
except Exception as e:
|
|
331
|
+
error(f"Unexpected error: {str(e)}")
|
|
332
|
+
|
|
333
|
+
def main() -> None:
|
|
334
|
+
"""Main entry point for CLI"""
|
|
335
|
+
if not CLI_AVAILABLE:
|
|
336
|
+
print("Error: CLI dependencies not installed.")
|
|
337
|
+
print("Install with: pip install pytrends-modern[cli]")
|
|
338
|
+
sys.exit(1)
|
|
339
|
+
|
|
340
|
+
cli()
|
|
341
|
+
|
|
342
|
+
else:
|
|
343
|
+
# CLI not available
|
|
344
|
+
def main() -> None:
|
|
345
|
+
"""Main entry point when CLI dependencies are missing"""
|
|
346
|
+
print("Error: CLI dependencies not installed.")
|
|
347
|
+
print("Install with: pip install pytrends-modern[cli]")
|
|
348
|
+
sys.exit(1)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
if __name__ == "__main__":
|
|
352
|
+
main()
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration and constants for pytrends-modern
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict
|
|
6
|
+
|
|
7
|
+
# Base URL for Google Trends
|
|
8
|
+
BASE_TRENDS_URL = "https://trends.google.com/trends"
|
|
9
|
+
|
|
10
|
+
# API Endpoints
|
|
11
|
+
GENERAL_URL = f"{BASE_TRENDS_URL}/api/explore"
|
|
12
|
+
INTEREST_OVER_TIME_URL = f"{BASE_TRENDS_URL}/api/widgetdata/multiline"
|
|
13
|
+
MULTIRANGE_INTEREST_OVER_TIME_URL = f"{BASE_TRENDS_URL}/api/widgetdata/multirange"
|
|
14
|
+
INTEREST_BY_REGION_URL = f"{BASE_TRENDS_URL}/api/widgetdata/comparedgeo"
|
|
15
|
+
RELATED_QUERIES_URL = f"{BASE_TRENDS_URL}/api/widgetdata/relatedsearches"
|
|
16
|
+
TRENDING_SEARCHES_URL = f"{BASE_TRENDS_URL}/hottrends/visualize/internal/data"
|
|
17
|
+
TOP_CHARTS_URL = f"{BASE_TRENDS_URL}/api/topcharts"
|
|
18
|
+
SUGGESTIONS_URL = f"{BASE_TRENDS_URL}/api/autocomplete/"
|
|
19
|
+
CATEGORIES_URL = f"{BASE_TRENDS_URL}/api/explore/pickers/category"
|
|
20
|
+
TODAY_SEARCHES_URL = f"{BASE_TRENDS_URL}/api/dailytrends"
|
|
21
|
+
REALTIME_TRENDING_SEARCHES_URL = f"{BASE_TRENDS_URL}/api/realtimetrends"
|
|
22
|
+
|
|
23
|
+
# HTTP Error codes to retry
|
|
24
|
+
ERROR_CODES = (500, 502, 504, 429)
|
|
25
|
+
|
|
26
|
+
# Default values
|
|
27
|
+
DEFAULT_TIMEOUT = (10, 25) # (connect, read) in seconds
|
|
28
|
+
DEFAULT_RETRIES = 3
|
|
29
|
+
DEFAULT_BACKOFF_FACTOR = 0.3
|
|
30
|
+
DEFAULT_HL = "en-US"
|
|
31
|
+
DEFAULT_TZ = 360
|
|
32
|
+
DEFAULT_GEO = ""
|
|
33
|
+
|
|
34
|
+
# Valid gprop values
|
|
35
|
+
VALID_GPROP = ["", "images", "news", "youtube", "froogle"]
|
|
36
|
+
|
|
37
|
+
# User agents for rotation
|
|
38
|
+
USER_AGENTS = [
|
|
39
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
40
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
41
|
+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
42
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
|
|
43
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14.1; rv:121.0) Gecko/20100101 Firefox/121.0",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
# Countries for RSS and geo filtering
|
|
47
|
+
COUNTRIES: Dict[str, str] = {
|
|
48
|
+
"US": "United States",
|
|
49
|
+
"GB": "United Kingdom",
|
|
50
|
+
"CA": "Canada",
|
|
51
|
+
"AU": "Australia",
|
|
52
|
+
"IN": "India",
|
|
53
|
+
"DE": "Germany",
|
|
54
|
+
"FR": "France",
|
|
55
|
+
"ES": "Spain",
|
|
56
|
+
"IT": "Italy",
|
|
57
|
+
"BR": "Brazil",
|
|
58
|
+
"MX": "Mexico",
|
|
59
|
+
"AR": "Argentina",
|
|
60
|
+
"JP": "Japan",
|
|
61
|
+
"KR": "South Korea",
|
|
62
|
+
"CN": "China",
|
|
63
|
+
"RU": "Russia",
|
|
64
|
+
"ZA": "South Africa",
|
|
65
|
+
"NL": "Netherlands",
|
|
66
|
+
"SE": "Sweden",
|
|
67
|
+
"NO": "Norway",
|
|
68
|
+
"DK": "Denmark",
|
|
69
|
+
"FI": "Finland",
|
|
70
|
+
"PL": "Poland",
|
|
71
|
+
"TR": "Turkey",
|
|
72
|
+
"SA": "Saudi Arabia",
|
|
73
|
+
"AE": "United Arab Emirates",
|
|
74
|
+
"SG": "Singapore",
|
|
75
|
+
"MY": "Malaysia",
|
|
76
|
+
"TH": "Thailand",
|
|
77
|
+
"ID": "Indonesia",
|
|
78
|
+
"PH": "Philippines",
|
|
79
|
+
"VN": "Vietnam",
|
|
80
|
+
"NZ": "New Zealand",
|
|
81
|
+
"IE": "Ireland",
|
|
82
|
+
"BE": "Belgium",
|
|
83
|
+
"CH": "Switzerland",
|
|
84
|
+
"AT": "Austria",
|
|
85
|
+
"PT": "Portugal",
|
|
86
|
+
"GR": "Greece",
|
|
87
|
+
"CZ": "Czech Republic",
|
|
88
|
+
"RO": "Romania",
|
|
89
|
+
"HU": "Hungary",
|
|
90
|
+
"IL": "Israel",
|
|
91
|
+
"EG": "Egypt",
|
|
92
|
+
"NG": "Nigeria",
|
|
93
|
+
"KE": "Kenya",
|
|
94
|
+
"CL": "Chile",
|
|
95
|
+
"CO": "Colombia",
|
|
96
|
+
"PE": "Peru",
|
|
97
|
+
"VE": "Venezuela",
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# US States
|
|
101
|
+
US_STATES: Dict[str, str] = {
|
|
102
|
+
"US-AL": "Alabama",
|
|
103
|
+
"US-AK": "Alaska",
|
|
104
|
+
"US-AZ": "Arizona",
|
|
105
|
+
"US-AR": "Arkansas",
|
|
106
|
+
"US-CA": "California",
|
|
107
|
+
"US-CO": "Colorado",
|
|
108
|
+
"US-CT": "Connecticut",
|
|
109
|
+
"US-DE": "Delaware",
|
|
110
|
+
"US-FL": "Florida",
|
|
111
|
+
"US-GA": "Georgia",
|
|
112
|
+
"US-HI": "Hawaii",
|
|
113
|
+
"US-ID": "Idaho",
|
|
114
|
+
"US-IL": "Illinois",
|
|
115
|
+
"US-IN": "Indiana",
|
|
116
|
+
"US-IA": "Iowa",
|
|
117
|
+
"US-KS": "Kansas",
|
|
118
|
+
"US-KY": "Kentucky",
|
|
119
|
+
"US-LA": "Louisiana",
|
|
120
|
+
"US-ME": "Maine",
|
|
121
|
+
"US-MD": "Maryland",
|
|
122
|
+
"US-MA": "Massachusetts",
|
|
123
|
+
"US-MI": "Michigan",
|
|
124
|
+
"US-MN": "Minnesota",
|
|
125
|
+
"US-MS": "Mississippi",
|
|
126
|
+
"US-MO": "Missouri",
|
|
127
|
+
"US-MT": "Montana",
|
|
128
|
+
"US-NE": "Nebraska",
|
|
129
|
+
"US-NV": "Nevada",
|
|
130
|
+
"US-NH": "New Hampshire",
|
|
131
|
+
"US-NJ": "New Jersey",
|
|
132
|
+
"US-NM": "New Mexico",
|
|
133
|
+
"US-NY": "New York",
|
|
134
|
+
"US-NC": "North Carolina",
|
|
135
|
+
"US-ND": "North Dakota",
|
|
136
|
+
"US-OH": "Ohio",
|
|
137
|
+
"US-OK": "Oklahoma",
|
|
138
|
+
"US-OR": "Oregon",
|
|
139
|
+
"US-PA": "Pennsylvania",
|
|
140
|
+
"US-RI": "Rhode Island",
|
|
141
|
+
"US-SC": "South Carolina",
|
|
142
|
+
"US-SD": "South Dakota",
|
|
143
|
+
"US-TN": "Tennessee",
|
|
144
|
+
"US-TX": "Texas",
|
|
145
|
+
"US-UT": "Utah",
|
|
146
|
+
"US-VT": "Vermont",
|
|
147
|
+
"US-VA": "Virginia",
|
|
148
|
+
"US-WA": "Washington",
|
|
149
|
+
"US-WV": "West Virginia",
|
|
150
|
+
"US-WI": "Wisconsin",
|
|
151
|
+
"US-WY": "Wyoming",
|
|
152
|
+
"US-DC": "District of Columbia",
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
# Categories mapping
|
|
156
|
+
CATEGORIES: Dict[str, int] = {
|
|
157
|
+
"all": 0,
|
|
158
|
+
"arts_entertainment": 3,
|
|
159
|
+
"autos_vehicles": 47,
|
|
160
|
+
"beauty_fitness": 44,
|
|
161
|
+
"books_literature": 22,
|
|
162
|
+
"business_industrial": 7,
|
|
163
|
+
"computers_electronics": 5,
|
|
164
|
+
"finance": 7,
|
|
165
|
+
"food_drink": 71,
|
|
166
|
+
"games": 8,
|
|
167
|
+
"health": 45,
|
|
168
|
+
"hobbies_leisure": 65,
|
|
169
|
+
"home_garden": 11,
|
|
170
|
+
"internet_telecom": 13,
|
|
171
|
+
"jobs_education": 958,
|
|
172
|
+
"law_government": 19,
|
|
173
|
+
"news": 16,
|
|
174
|
+
"online_communities": 299,
|
|
175
|
+
"people_society": 14,
|
|
176
|
+
"pets_animals": 66,
|
|
177
|
+
"real_estate": 29,
|
|
178
|
+
"reference": 533,
|
|
179
|
+
"science": 174,
|
|
180
|
+
"shopping": 18,
|
|
181
|
+
"sports": 20,
|
|
182
|
+
"travel": 67,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# Time periods for validation
|
|
186
|
+
VALID_TIME_PERIODS = [
|
|
187
|
+
"now 1-H",
|
|
188
|
+
"now 4-H",
|
|
189
|
+
"now 1-d",
|
|
190
|
+
"now 7-d",
|
|
191
|
+
"today 1-m",
|
|
192
|
+
"today 3-m",
|
|
193
|
+
"today 12-m",
|
|
194
|
+
"today 5-y",
|
|
195
|
+
"all",
|
|
196
|
+
]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Exceptions for pytrends-modern
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from requests import Response
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PyTrendsPlusError(Exception):
|
|
10
|
+
"""Base exception for pytrends-modern"""
|
|
11
|
+
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ResponseError(PyTrendsPlusError):
|
|
16
|
+
"""Exception for HTTP response errors"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, message: str, response: Optional[Response] = None):
|
|
19
|
+
super().__init__(message)
|
|
20
|
+
self.response = response
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def from_response(cls, response: Response) -> "ResponseError":
|
|
24
|
+
"""Create ResponseError from requests.Response object"""
|
|
25
|
+
message = (
|
|
26
|
+
f"The request failed: Google returned status code {response.status_code}. "
|
|
27
|
+
f"URL: {response.url}"
|
|
28
|
+
)
|
|
29
|
+
if response.text:
|
|
30
|
+
message += f"\nResponse: {response.text[:200]}"
|
|
31
|
+
return cls(message, response)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TooManyRequestsError(ResponseError):
|
|
35
|
+
"""Exception for rate limiting (HTTP 429)"""
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def from_response(cls, response: Response) -> "TooManyRequestsError":
|
|
39
|
+
"""Create TooManyRequestsError from requests.Response object"""
|
|
40
|
+
message = (
|
|
41
|
+
"You have reached your quota limit. Google is rate limiting your requests. "
|
|
42
|
+
"Please try again later or use proxies."
|
|
43
|
+
)
|
|
44
|
+
return cls(message, response)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class InvalidParameterError(PyTrendsPlusError):
|
|
48
|
+
"""Exception for invalid parameters"""
|
|
49
|
+
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class BrowserError(PyTrendsPlusError):
|
|
54
|
+
"""Exception for Selenium browser errors"""
|
|
55
|
+
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class DownloadError(PyTrendsPlusError):
|
|
60
|
+
"""Exception for download errors"""
|
|
61
|
+
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ConfigurationError(PyTrendsPlusError):
|
|
66
|
+
"""Exception for configuration errors"""
|
|
67
|
+
|
|
68
|
+
pass
|
pytrends_modern/py.typed
ADDED
|
File without changes
|