pytrends-modern 0.2.2__py3-none-any.whl → 0.2.4__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.
@@ -2,7 +2,7 @@
2
2
  pytrends-modern: Modern Google Trends API
3
3
  """
4
4
 
5
- __version__ = "0.2.2"
5
+ __version__ = "0.2.4"
6
6
  __author__ = "pytrends-modern contributors"
7
7
  __license__ = "MIT"
8
8
 
@@ -1,6 +1,6 @@
1
1
  """Browser configuration for Camoufox automation"""
2
2
 
3
- from typing import Optional, Union
3
+ from typing import Optional, Union, Dict, Any
4
4
  import os as os_module
5
5
 
6
6
 
@@ -12,7 +12,6 @@ class BrowserConfig:
12
12
 
13
13
  ⚠️ LIMITATIONS:
14
14
  - Only 1 keyword supported (no comparison)
15
- - Only 'today 1-m' timeframe supported
16
15
  - Only WORLDWIDE geo supported (no geo filtering)
17
16
  - Requires Google account login (first run)
18
17
 
@@ -29,6 +28,15 @@ class BrowserConfig:
29
28
  humanize: Enable human-like cursor movement (default: True)
30
29
  os: Operating system for fingerprint ('windows', 'macos', 'linux')
31
30
  geoip: Auto-detect geolocation from proxy IP (default: True if proxy)
31
+ rotate_fingerprint: Generate random fingerprint for each session (default: True)
32
+ Set to False to use persistent fingerprint
33
+ min_delay: Minimum delay between requests in seconds (default: 2)
34
+ max_delay: Maximum delay between requests in seconds (default: 5)
35
+ persistent_context: Keep browser profile between sessions (default: True)
36
+ Set to False to use fresh profile each time (helps avoid 429)
37
+ timeframe: Time range for trends data (default: 'today 1-m')
38
+ - 'today 1-m': Past month
39
+ - 'today 12-m': Past 12 months
32
40
 
33
41
  Example:
34
42
  >>> from pytrends_modern import TrendReq, BrowserConfig
@@ -57,6 +65,12 @@ class BrowserConfig:
57
65
  humanize: bool = True,
58
66
  os: str = 'linux',
59
67
  geoip: bool = True,
68
+ rotate_fingerprint: bool = True,
69
+ min_delay: float = 2.0,
70
+ max_delay: float = 5.0,
71
+ persistent_context: bool = True,
72
+ custom_config: Optional[Dict[str, Any]] = None,
73
+ timeframe: str = 'today 1-m',
60
74
  ):
61
75
  self.headless = headless
62
76
  self.proxy_server = proxy_server
@@ -66,4 +80,10 @@ class BrowserConfig:
66
80
  self.humanize = humanize
67
81
  self.os = os
68
82
  self.geoip = geoip if proxy_server else False
83
+ self.rotate_fingerprint = rotate_fingerprint
84
+ self.min_delay = min_delay
85
+ self.max_delay = max_delay
86
+ self.persistent_context = persistent_context
87
+ self.custom_config = custom_config or {}
88
+ self.timeframe = timeframe
69
89
 
@@ -92,7 +92,6 @@ class TrendReq:
92
92
 
93
93
  ⚠️ LIMITATIONS when using browser_config:
94
94
  - Only 1 keyword supported (no comparison)
95
- - Only 'today 1-m' timeframe supported
96
95
  - Only WORLDWIDE geo supported (no geo filtering)
97
96
  - Requires Chrome/Chromium browser installed
98
97
  """
@@ -109,7 +108,7 @@ class TrendReq:
109
108
  warnings.warn(
110
109
  "⚠️ Camoufox browser mode is EXPERIMENTAL and has limitations:\n"
111
110
  " - Only 1 keyword supported (no keyword comparison)\n"
112
- " - Only 'today 1-m' timeframe supported\n"
111
+ " - Timeframe: 'today 1-m' (default) or 'today 12-m'\n"
113
112
  " - Only WORLDWIDE geo supported\n"
114
113
  " - Requires Google account login (first run)\n"
115
114
  " - Login session is saved for future runs",
@@ -170,6 +169,44 @@ class TrendReq:
170
169
  """Get a random user agent"""
171
170
  return random.choice(USER_AGENTS) if self.rotate_user_agent else USER_AGENTS[0]
172
171
 
172
+ def _browserforge_to_camoufox(self, fingerprint) -> Dict:
173
+ """Convert BrowserForge fingerprint to Camoufox config"""
174
+ config = {}
175
+
176
+ # Navigator properties
177
+ if hasattr(fingerprint, 'navigator'):
178
+ nav = fingerprint.navigator
179
+ if hasattr(nav, 'userAgent'):
180
+ config['navigator.userAgent'] = nav.userAgent
181
+ if hasattr(nav, 'language'):
182
+ config['navigator.language'] = nav.language
183
+ if hasattr(nav, 'hardwareConcurrency'):
184
+ config['navigator.hardwareConcurrency'] = nav.hardwareConcurrency
185
+ if hasattr(nav, 'maxTouchPoints'):
186
+ config['navigator.maxTouchPoints'] = nav.maxTouchPoints
187
+
188
+ # Screen properties
189
+ if hasattr(fingerprint, 'screen'):
190
+ scr = fingerprint.screen
191
+ if hasattr(scr, 'width'):
192
+ config['screen.width'] = scr.width
193
+ if hasattr(scr, 'height'):
194
+ config['screen.height'] = scr.height
195
+ if hasattr(scr, 'availWidth'):
196
+ config['screen.availWidth'] = scr.availWidth
197
+ if hasattr(scr, 'availHeight'):
198
+ config['screen.availHeight'] = scr.availHeight
199
+
200
+ return config
201
+
202
+ def _add_request_delay(self) -> None:
203
+ """Add random delay between requests to avoid rate limiting"""
204
+ if hasattr(self.browser_config, 'min_delay') and hasattr(self.browser_config, 'max_delay'):
205
+ import time
206
+ import random
207
+ delay = random.uniform(self.browser_config.min_delay, self.browser_config.max_delay)
208
+ time.sleep(delay)
209
+
173
210
  def _init_camoufox(self) -> None:
174
211
  """Initialize Camoufox browser with persistent context"""
175
212
  try:
@@ -212,15 +249,19 @@ class TrendReq:
212
249
 
213
250
  # Initialize Camoufox with persistent context
214
251
  try:
252
+ # Note: Camoufox automatically generates BrowserForge fingerprints
253
+ # based on the 'os' parameter. No need to manually pass fingerprints.
254
+
215
255
  # Camoufox() returns a context manager, we need to use __enter__() to get the context
216
256
  camoufox_manager = Camoufox(
217
- persistent_context=True,
218
- user_data_dir=user_data_dir,
257
+ persistent_context=self.browser_config.persistent_context,
258
+ user_data_dir=user_data_dir if self.browser_config.persistent_context else None,
219
259
  headless=self.browser_config.headless,
220
260
  humanize=self.browser_config.humanize if hasattr(self.browser_config, 'humanize') else True,
221
261
  os=self.browser_config.os if hasattr(self.browser_config, 'os') else 'linux',
222
262
  geoip=self.browser_config.geoip if hasattr(self.browser_config, 'geoip') else True,
223
- proxy=proxy_config
263
+ proxy=proxy_config,
264
+ config=self.browser_config.custom_config if self.browser_config.custom_config else None
224
265
  )
225
266
 
226
267
  # Enter the context manager to get the browser context
@@ -303,13 +344,27 @@ class TrendReq:
303
344
  if not self.browser_page:
304
345
  raise exceptions.BrowserError("Browser not initialized")
305
346
 
347
+ # Add random delay before request (anti-rate-limiting)
348
+ self._add_request_delay()
349
+
306
350
  # Clear cache
307
351
  self.browser_responses_cache.clear()
308
352
 
309
353
  # Build URL
310
354
  import urllib.parse
311
355
  encoded_keyword = urllib.parse.quote(keyword)
312
- url = f"https://trends.google.com/trends/explore?date=today%201-m&q={encoded_keyword}&hl=en-GB"
356
+
357
+ # Get timeframe from config (default: 'today 1-m')
358
+ timeframe = getattr(self.browser_config, 'timeframe', 'today 1-m')
359
+
360
+ # Build URL with or without date parameter
361
+ if timeframe == 'today 12-m':
362
+ # Past 12 months - no date parameter needed
363
+ url = f"https://trends.google.com/trends/explore?q={encoded_keyword}&hl=en-GB"
364
+ else:
365
+ # Default: today 1-m or custom timeframe
366
+ encoded_timeframe = urllib.parse.quote(timeframe)
367
+ url = f"https://trends.google.com/trends/explore?date={encoded_timeframe}&q={encoded_keyword}&hl=en-GB"
313
368
 
314
369
  try:
315
370
  # Navigate and wait for network idle
@@ -56,6 +56,20 @@ class AsyncTrendReq:
56
56
  """Async context manager exit"""
57
57
  await self._close_browser()
58
58
 
59
+ def _browserforge_to_camoufox(self, fingerprint) -> Dict:
60
+ """Convert BrowserForge fingerprint to Camoufox config"""
61
+ # Reuse the sync version's implementation
62
+ from pytrends_modern.request import TrendReq
63
+ return TrendReq._browserforge_to_camoufox(self, fingerprint)
64
+
65
+ async def _add_request_delay(self) -> None:
66
+ """Add random delay between requests to avoid rate limiting (async)"""
67
+ if hasattr(self.browser_config, 'min_delay') and hasattr(self.browser_config, 'max_delay'):
68
+ import asyncio
69
+ import random
70
+ delay = random.uniform(self.browser_config.min_delay, self.browser_config.max_delay)
71
+ await asyncio.sleep(delay)
72
+
59
73
  async def _init_camoufox(self) -> None:
60
74
  """Initialize Camoufox browser with persistent context (async)"""
61
75
  try:
@@ -98,15 +112,19 @@ class AsyncTrendReq:
98
112
 
99
113
  # Initialize AsyncCamoufox with persistent context
100
114
  try:
115
+ # Note: Camoufox automatically generates BrowserForge fingerprints
116
+ # based on the 'os' parameter. No need to manually pass fingerprints.
117
+
101
118
  # AsyncCamoufox() returns a context manager
102
119
  camoufox_manager = AsyncCamoufox(
103
- persistent_context=True,
104
- user_data_dir=user_data_dir,
120
+ persistent_context=self.browser_config.persistent_context,
121
+ user_data_dir=user_data_dir if self.browser_config.persistent_context else None,
105
122
  headless=self.browser_config.headless,
106
123
  humanize=self.browser_config.humanize if hasattr(self.browser_config, 'humanize') else True,
107
124
  os=self.browser_config.os if hasattr(self.browser_config, 'os') else 'linux',
108
125
  geoip=self.browser_config.geoip if hasattr(self.browser_config, 'geoip') else True,
109
- proxy=proxy_config
126
+ proxy=proxy_config,
127
+ config=self.browser_config.custom_config if self.browser_config.custom_config else None
110
128
  )
111
129
 
112
130
  # Enter the context manager to get the browser context
@@ -189,13 +207,27 @@ class AsyncTrendReq:
189
207
  if not self.browser_page:
190
208
  raise exceptions.BrowserError("Browser not initialized")
191
209
 
210
+ # Add random delay before request (anti-rate-limiting)
211
+ await self._add_request_delay()
212
+
192
213
  # Clear cache
193
214
  self.browser_responses_cache.clear()
194
215
 
195
216
  # Build URL
196
217
  import urllib.parse
197
218
  encoded_keyword = urllib.parse.quote(keyword)
198
- url = f"https://trends.google.com/trends/explore?date=today%201-m&q={encoded_keyword}&hl=en-GB"
219
+
220
+ # Get timeframe from config (default: 'today 1-m')
221
+ timeframe = getattr(self.browser_config, 'timeframe', 'today 1-m')
222
+
223
+ # Build URL with or without date parameter
224
+ if timeframe == 'today 12-m':
225
+ # Past 12 months - no date parameter needed
226
+ url = f"https://trends.google.com/trends/explore?q={encoded_keyword}&hl=en-GB"
227
+ else:
228
+ # Default: today 1-m or custom timeframe
229
+ encoded_timeframe = urllib.parse.quote(timeframe)
230
+ url = f"https://trends.google.com/trends/explore?date={encoded_timeframe}&q={encoded_keyword}&hl=en-GB"
199
231
 
200
232
  try:
201
233
  # Navigate and wait for network idle
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrends-modern
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Modern Google Trends API - Combining the best of pytrends, with RSS feeds, Selenium scraping, DrissionPage browser automation, and enhanced features
5
5
  Author: pytrends-modern contributors
6
6
  License: MIT
@@ -139,6 +139,36 @@ df = pytrends.interest_over_time()
139
139
  print(df.head())
140
140
  ```
141
141
 
142
+ **Avoiding 429 Rate Limits:**
143
+
144
+ If you're getting 429 errors even with browser mode, use these anti-rate-limit features:
145
+
146
+ ```python
147
+ import random
148
+ from pytrends_modern import TrendReq, BrowserConfig
149
+
150
+ # Add delays + rotate OS fingerprint
151
+ os_choice = random.choice(['windows', 'macos', 'linux'])
152
+ config = BrowserConfig(
153
+ headless=False,
154
+ min_delay=3.0, # Min delay between requests (seconds)
155
+ max_delay=7.0, # Max delay between requests
156
+ persistent_context=True, # Keep Google login
157
+ os=os_choice, # Rotate OS fingerprint
158
+ humanize=True
159
+ )
160
+
161
+ pytrends = TrendReq(browser_config=config)
162
+ # Delays are automatically added before each request
163
+ ```
164
+
165
+ **Anti-Rate-Limit Options:**
166
+ - `min_delay` / `max_delay` - Random delay between requests (default: 2-5s)
167
+ - `os` - Rotate between 'windows', 'macos', 'linux' for different fingerprints
168
+ - `persistent_context=False` - Fresh profile each time (no cookies)
169
+ - `proxy_server` - Use proxy to rotate IPs
170
+ - `humanize=True` - Human-like cursor movements (enabled by default)
171
+
142
172
  **Browser Mode Limitations:**
143
173
  - ⚠️ Only 1 keyword at a time (no comparisons)
144
174
  - ⚠️ Only 'today 1-m' timeframe
@@ -1,20 +1,20 @@
1
- pytrends_modern/__init__.py,sha256=lHYGzQ8olHW21bfEAYaxne-1Bm_WZ8NF8YCIIAr-KoE,844
1
+ pytrends_modern/__init__.py,sha256=ppZXkIWfYPLJqPsM1ybkhD403_3keQPAk6lGQxRTwhY,844
2
2
  pytrends_modern/browser_config.py,sha256=aEznKEaAiJ1vFPYMeNc2wiyehijnleNBFoiZIXWQcRQ,3513
3
- pytrends_modern/browser_config_camoufox.py,sha256=lJ_32V_BL3EuZLX1kDx4tM0ny7u9k86aHMOlwfn1seg,2720
3
+ pytrends_modern/browser_config_camoufox.py,sha256=vTxFPVFwjgFEGNSV5CFThgpnQY9N8gIl9IOHXeDaYcQ,3826
4
4
  pytrends_modern/camoufox_setup.py,sha256=WkytaNt64Gro8wJUPXaoUPhesDRvz460UeHGzETR6ds,10534
5
5
  pytrends_modern/cli.py,sha256=lwuXzFY5S7glSGL180dFkpnTcVsM7JfTzJ2vLKs8TLA,12784
6
6
  pytrends_modern/config.py,sha256=zPCMgdAFwcrQPMYO87dLAirkxnvpvsTx9DkWZGkl8zk,5198
7
7
  pytrends_modern/exceptions.py,sha256=3WWDiYha1Tj2Hn2TNQfAPUncdGHIJbqMLFRQjLLXm3k,1712
8
8
  pytrends_modern/proxy_extension.py,sha256=j-Cseb5G627q2G7dRdlkhqzGUG9dhmoTi1UK5wwF3u4,2214
9
9
  pytrends_modern/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- pytrends_modern/request.py,sha256=4_cBWmcMmzPiZE6OuCBM7me65RlHKLtOPCv6kRrQtas,46606
11
- pytrends_modern/request_async.py,sha256=9QUNDXPqAyUHxY_zoRMyt6gbXL41Fe69ZVmMun3-rfQ,13296
10
+ pytrends_modern/request.py,sha256=-qTDq8JVAlcNZla-URsUHHXM1bOC17O3v9oxf9H5K6A,49224
11
+ pytrends_modern/request_async.py,sha256=9JlNRG82F1D9-OAVLSpW7JHuBsyn1ahO8uGxBULQiNI,15028
12
12
  pytrends_modern/rss.py,sha256=6Qq8MsVJu9WtE5UcYlUze7EexT81Mq4af7pPFOMcDS0,11414
13
13
  pytrends_modern/scraper.py,sha256=x1xbFjUs7ULOBlIWLZ90G6WXJwB2EYuwUo0dWmHAjEQ,9706
14
14
  pytrends_modern/utils.py,sha256=xPf4nz4c8Mn-737PZTe6o3HChjZvIR8brIftNg0-IFE,6875
15
- pytrends_modern-0.2.2.dist-info/licenses/LICENSE,sha256=4K_FiN4IB1h5rffiOC8s5Tpxiv161v0eNIQJMDbvC0o,1469
16
- pytrends_modern-0.2.2.dist-info/METADATA,sha256=UL1kkTMnbNbLnL1xVdOscVGHL9CRpkD_g-W7m9Y7jK4,14585
17
- pytrends_modern-0.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
18
- pytrends_modern-0.2.2.dist-info/entry_points.txt,sha256=1ilOUXV2wt8NqQp2ViD-obi9k8iQANEa3eU-7S3jTgs,61
19
- pytrends_modern-0.2.2.dist-info/top_level.txt,sha256=bbuIEWVfkaA-sBTKf-Dzau5Ll2zlHs21o0zWtCmQG50,16
20
- pytrends_modern-0.2.2.dist-info/RECORD,,
15
+ pytrends_modern-0.2.4.dist-info/licenses/LICENSE,sha256=4K_FiN4IB1h5rffiOC8s5Tpxiv161v0eNIQJMDbvC0o,1469
16
+ pytrends_modern-0.2.4.dist-info/METADATA,sha256=dC5zcovunnqJHZAFMMeWgK61HWQWhOf8UTHBo-aE-tM,15645
17
+ pytrends_modern-0.2.4.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
18
+ pytrends_modern-0.2.4.dist-info/entry_points.txt,sha256=1ilOUXV2wt8NqQp2ViD-obi9k8iQANEa3eU-7S3jTgs,61
19
+ pytrends_modern-0.2.4.dist-info/top_level.txt,sha256=bbuIEWVfkaA-sBTKf-Dzau5Ll2zlHs21o0zWtCmQG50,16
20
+ pytrends_modern-0.2.4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5