pytrends-modern 0.1.2__py3-none-any.whl → 0.2.1__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,13 +2,16 @@
2
2
  pytrends-modern: Modern Google Trends API
3
3
  """
4
4
 
5
- __version__ = "1.0.0"
5
+ __version__ = "0.2.1"
6
6
  __author__ = "pytrends-modern contributors"
7
7
  __license__ = "MIT"
8
8
 
9
9
  from pytrends_modern.request import TrendReq
10
+ from pytrends_modern.request_async import AsyncTrendReq
10
11
  from pytrends_modern.rss import TrendsRSS
11
12
  from pytrends_modern.scraper import TrendsScraper
13
+ from pytrends_modern.browser_config_camoufox import BrowserConfig
14
+ from pytrends_modern import camoufox_setup
12
15
  from pytrends_modern.exceptions import (
13
16
  TooManyRequestsError,
14
17
  ResponseError,
@@ -19,8 +22,11 @@ from pytrends_modern.exceptions import (
19
22
 
20
23
  __all__ = [
21
24
  "TrendReq",
25
+ "AsyncTrendReq",
22
26
  "TrendsRSS",
23
27
  "TrendsScraper",
28
+ "BrowserConfig",
29
+ "camoufox_setup",
24
30
  "TooManyRequestsError",
25
31
  "ResponseError",
26
32
  "InvalidParameterError",
@@ -0,0 +1,94 @@
1
+ """Browser configuration for DrissionPage automation."""
2
+
3
+ from typing import Optional
4
+
5
+
6
+ class BrowserConfig:
7
+ """Configuration for DrissionPage browser automation.
8
+
9
+ When enabled, TrendReq will use DrissionPage to capture network traffic
10
+ from trends.google.com instead of making direct API calls.
11
+
12
+ ⚠️ LIMITATIONS when using BrowserConfig:
13
+ - Only 1 keyword supported (no comparison)
14
+ - Only 'today 1-m' timeframe supported
15
+ - Only WORLDWIDE geo supported (no geo filtering)
16
+ - Requires Chrome/Chromium browser installed
17
+
18
+ Args:
19
+ browser_path: Path to Chrome/Chromium executable.
20
+ Defaults: '/usr/bin/chromium' or '/usr/bin/chrome'
21
+ port: Browser remote debugging port (default: 9222)
22
+ headless: Run browser in headless mode (default: True)
23
+ proxy: Proxy server URL (e.g., 'http://proxy.com:8080')
24
+ proxy_username: Proxy username (for authenticated proxies)
25
+ proxy_password: Proxy password (for authenticated proxies)
26
+ user_data_dir: Browser profile directory to persist login session.
27
+ If not provided, creates temp directory (won't persist)
28
+
29
+ Example:
30
+ >>> from pytrends_modern import TrendReq, BrowserConfig
31
+ >>> # Without auth - manual login required once
32
+ >>> config = BrowserConfig(
33
+ ... browser_path='/usr/bin/chromium',
34
+ ... user_data_dir='~/.config/chromium-pytrends'
35
+ ... )
36
+ >>> # With proxy auth
37
+ >>> config = BrowserConfig(
38
+ ... browser_path='/usr/bin/chromium',
39
+ ... proxy='153.80.44.3:64804',
40
+ ... proxy_username='user',
41
+ ... proxy_password='pass',
42
+ ... user_data_dir='~/.config/chromium-pytrends'
43
+ ... )
44
+ >>> pytrends = TrendReq(browser_config=config)
45
+ >>> pytrends.build_payload(['Python']) # Only 1 keyword!
46
+ >>> df = pytrends.interest_over_time()
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ browser_path: Optional[str] = None,
52
+ port: int = 9222,
53
+ headless: bool = True,
54
+ proxy: Optional[str] = None,
55
+ proxy_username: Optional[str] = None,
56
+ proxy_password: Optional[str] = None,
57
+ user_data_dir: Optional[str] = None
58
+ ):
59
+ self.browser_path = browser_path or self._get_default_browser_path()
60
+ self.port = port
61
+ self.headless = headless
62
+ self.proxy = proxy
63
+ self.proxy_username = proxy_username
64
+ self.proxy_password = proxy_password
65
+ self.user_data_dir = user_data_dir
66
+
67
+ @staticmethod
68
+ def _get_default_browser_path() -> str:
69
+ """Get default browser path based on common locations."""
70
+ import os
71
+
72
+ # Common Chrome/Chromium paths
73
+ paths = [
74
+ '/usr/bin/chromium',
75
+ '/usr/bin/chromium-browser',
76
+ '/usr/bin/chrome',
77
+ '/usr/bin/google-chrome',
78
+ '/snap/bin/chromium',
79
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
80
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
81
+ ]
82
+
83
+ for path in paths:
84
+ if os.path.exists(path):
85
+ return path
86
+
87
+ # Default fallback
88
+ return '/usr/bin/chromium'
89
+
90
+ def __repr__(self) -> str:
91
+ return (
92
+ f"BrowserConfig(browser_path='{self.browser_path}', "
93
+ f"port={self.port}, headless={self.headless}, proxy={self.proxy})"
94
+ )
@@ -0,0 +1,67 @@
1
+ """Browser configuration for Camoufox automation"""
2
+
3
+ from typing import Optional
4
+ import os as os_module
5
+
6
+
7
+ class BrowserConfig:
8
+ """Configuration for Camoufox browser automation.
9
+
10
+ Uses Camoufox (Playwright Firefox) with advanced fingerprinting and
11
+ anti-detection to bypass Google's bot detection.
12
+
13
+ ⚠️ LIMITATIONS:
14
+ - Only 1 keyword supported (no comparison)
15
+ - Only 'today 1-m' timeframe supported
16
+ - Only WORLDWIDE geo supported (no geo filtering)
17
+ - Requires Google account login (first run)
18
+
19
+ Args:
20
+ headless: Run browser in headless mode (default: False)
21
+ Set to 'virtual' on Linux to use Xvfb
22
+ proxy_server: Proxy server URL (e.g., 'http://proxy.com:8080')
23
+ proxy_username: Proxy username (for authenticated proxies)
24
+ proxy_password: Proxy password (for authenticated proxies)
25
+ user_data_dir: Browser profile directory to persist login session.
26
+ Default: ~/.config/camoufox-pytrends
27
+ humanize: Enable human-like cursor movement (default: True)
28
+ os: Operating system for fingerprint ('windows', 'macos', 'linux')
29
+ geoip: Auto-detect geolocation from proxy IP (default: True if proxy)
30
+
31
+ Example:
32
+ >>> from pytrends_modern import TrendReq, BrowserConfig
33
+ >>> # Simple usage (logs in once, saves session)
34
+ >>> config = BrowserConfig()
35
+ >>> pytrends = TrendReq(browser_config=config)
36
+ >>> pytrends.build_payload(['Python'])
37
+ >>> df = pytrends.interest_over_time()
38
+ >>>
39
+ >>> # With proxy
40
+ >>> config = BrowserConfig(
41
+ ... proxy_server='http://proxy.com:8080',
42
+ ... proxy_username='user',
43
+ ... proxy_password='pass',
44
+ ... geoip=True
45
+ ... )
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ headless: bool = False,
51
+ proxy_server: Optional[str] = None,
52
+ proxy_username: Optional[str] = None,
53
+ proxy_password: Optional[str] = None,
54
+ user_data_dir: Optional[str] = None,
55
+ humanize: bool = True,
56
+ os: str = 'linux',
57
+ geoip: bool = True,
58
+ ):
59
+ self.headless = headless
60
+ self.proxy_server = proxy_server
61
+ self.proxy_username = proxy_username
62
+ self.proxy_password = proxy_password
63
+ self.user_data_dir = user_data_dir or os_module.path.expanduser('~/.config/camoufox-pytrends-profile')
64
+ self.humanize = humanize
65
+ self.os = os
66
+ self.geoip = geoip if proxy_server else False
67
+
@@ -0,0 +1,300 @@
1
+ """
2
+ Camoufox setup and configuration helper for pytrends-modern
3
+
4
+ This module helps users set up their Google account login for browser mode.
5
+ """
6
+
7
+ import os
8
+ from typing import Optional
9
+
10
+
11
+ def get_default_profile_dir() -> str:
12
+ """Get the default Camoufox profile directory"""
13
+ return os.path.expanduser('~/.config/camoufox-pytrends-profile')
14
+
15
+
16
+ def is_profile_configured(profile_dir: Optional[str] = None) -> bool:
17
+ """
18
+ Check if Camoufox profile is configured (has Google login)
19
+
20
+ Args:
21
+ profile_dir: Custom profile directory, or None for default
22
+
23
+ Returns:
24
+ True if profile exists and appears configured
25
+ """
26
+ if profile_dir is None:
27
+ profile_dir = get_default_profile_dir()
28
+ else:
29
+ profile_dir = os.path.expanduser(profile_dir)
30
+
31
+ # Check if profile directory exists and has content
32
+ if not os.path.exists(profile_dir):
33
+ return False
34
+
35
+ # Check if it has Firefox profile structure (indicates browser has been used)
36
+ # Camoufox uses Firefox, so we check for common Firefox profile files
37
+ profile_indicators = [
38
+ 'prefs.js',
39
+ 'cookies.sqlite',
40
+ 'storage',
41
+ ]
42
+
43
+ for indicator in profile_indicators:
44
+ if os.path.exists(os.path.join(profile_dir, indicator)):
45
+ return True
46
+
47
+ return False
48
+
49
+
50
+ def setup_profile(profile_dir: Optional[str] = None, headless: bool = False) -> bool:
51
+ """
52
+ Interactive setup: Open browser for user to log in to Google
53
+
54
+ Args:
55
+ profile_dir: Custom profile directory, or None for default
56
+ headless: Run in headless mode (not recommended for first setup)
57
+
58
+ Returns:
59
+ True if setup completed successfully
60
+
61
+ Raises:
62
+ ImportError: If Camoufox is not installed
63
+ """
64
+ try:
65
+ from camoufox.sync_api import Camoufox
66
+ except ImportError:
67
+ raise ImportError(
68
+ "Camoufox is required for browser mode. "
69
+ "Install with: pip install pytrends-modern[browser]"
70
+ )
71
+
72
+ if profile_dir is None:
73
+ profile_dir = get_default_profile_dir()
74
+ else:
75
+ profile_dir = os.path.expanduser(profile_dir)
76
+
77
+ print("=" * 70)
78
+ print("🔧 Camoufox Profile Setup for pytrends-modern")
79
+ print("=" * 70)
80
+ print(f"\n📁 Profile directory: {profile_dir}")
81
+
82
+ if is_profile_configured(profile_dir):
83
+ print("✓ Profile already exists")
84
+ response = input("\nReconfigure profile? This will open the browser again (y/N): ")
85
+ if response.lower() != 'y':
86
+ print("Setup cancelled.")
87
+ return False
88
+
89
+ print("\n📖 Instructions:")
90
+ print("1. Browser will open to Google Trends")
91
+ print("2. Log in to your Google account")
92
+ print("3. Once logged in and page loads, press Enter here")
93
+ print("4. Your login will be saved for future use")
94
+ print("\n⚠️ IMPORTANT: Browser mode has limitations:")
95
+ print(" - Only 1 keyword at a time (no comparisons)")
96
+ print(" - Only 'today 1-m' timeframe")
97
+ print(" - Only WORLDWIDE region")
98
+ print()
99
+
100
+ input("Press Enter to open browser...")
101
+
102
+ try:
103
+ with Camoufox(
104
+ persistent_context=True,
105
+ user_data_dir=profile_dir,
106
+ headless=headless,
107
+ humanize=True,
108
+ os='linux',
109
+ geoip=True,
110
+ ) as context:
111
+ page = context.pages[0] if context.pages else context.new_page()
112
+
113
+ print("\n🌐 Opening Google Trends...")
114
+ print(" Please log in to your Google account")
115
+
116
+ # Navigate to Google Trends
117
+ page.goto(
118
+ "https://trends.google.com/trends/explore?q=Python&hl=en-GB",
119
+ wait_until='networkidle',
120
+ timeout=60000
121
+ )
122
+
123
+ title = page.title()
124
+ if "429" in title or "error" in title.lower():
125
+ print(f"\n⚠️ Page title: {title}")
126
+ print(" You may need to log in or solve a CAPTCHA")
127
+ else:
128
+ print(f"✓ Page loaded: {title}")
129
+
130
+ print("\n📋 Please:")
131
+ print(" 1. Log in to Google if not already logged in")
132
+ print(" 2. Make sure the page loads correctly")
133
+ print(" 3. Then come back here and press Enter")
134
+
135
+ input("\nPress Enter when done (browser will close)...")
136
+
137
+ # Verify profile was created
138
+ if is_profile_configured(profile_dir):
139
+ print("\n✅ SUCCESS! Profile configured successfully")
140
+ print(f"📁 Profile saved to: {profile_dir}")
141
+ print("\n💡 You can now use pytrends-modern with browser mode:")
142
+ print(" from pytrends_modern import TrendReq, BrowserConfig")
143
+ print(" config = BrowserConfig()")
144
+ print(" pytrends = TrendReq(browser_config=config)")
145
+ return True
146
+ else:
147
+ print("\n⚠️ Warning: Profile directory exists but may not be fully configured")
148
+ print(" Try running setup again or check if browser saved data")
149
+ return False
150
+
151
+ except Exception as e:
152
+ print(f"\n❌ Error during setup: {e}")
153
+ return False
154
+
155
+
156
+ def export_profile(source_dir: Optional[str] = None, dest_path: str = "./camoufox-profile.tar.gz") -> bool:
157
+ """
158
+ Export profile to a tar.gz file for portability (Docker, other machines, etc.)
159
+
160
+ Args:
161
+ source_dir: Source profile directory, or None for default
162
+ dest_path: Destination file path for the exported profile
163
+
164
+ Returns:
165
+ True if export successful
166
+ """
167
+ import tarfile
168
+
169
+ if source_dir is None:
170
+ source_dir = get_default_profile_dir()
171
+ else:
172
+ source_dir = os.path.expanduser(source_dir)
173
+
174
+ if not is_profile_configured(source_dir):
175
+ print(f"❌ Profile not configured at: {source_dir}")
176
+ return False
177
+
178
+ try:
179
+ print(f"📦 Exporting profile from: {source_dir}")
180
+ print(f"📁 To: {dest_path}")
181
+
182
+ with tarfile.open(dest_path, "w:gz") as tar:
183
+ tar.add(source_dir, arcname=os.path.basename(source_dir))
184
+
185
+ print(f"✅ Profile exported successfully!")
186
+ print(f"📊 File size: {os.path.getsize(dest_path) / 1024 / 1024:.2f} MB")
187
+ return True
188
+
189
+ except Exception as e:
190
+ print(f"❌ Export failed: {e}")
191
+ return False
192
+
193
+
194
+ def import_profile(source_path: str, dest_dir: Optional[str] = None) -> bool:
195
+ """
196
+ Import profile from a tar.gz file (for Docker, other machines, etc.)
197
+
198
+ Args:
199
+ source_path: Source tar.gz file path
200
+ dest_dir: Destination profile directory, or None for default
201
+
202
+ Returns:
203
+ True if import successful
204
+ """
205
+ import tarfile
206
+
207
+ if dest_dir is None:
208
+ dest_dir = get_default_profile_dir()
209
+ else:
210
+ dest_dir = os.path.expanduser(dest_dir)
211
+
212
+ if not os.path.exists(source_path):
213
+ print(f"❌ Source file not found: {source_path}")
214
+ return False
215
+
216
+ try:
217
+ print(f"📦 Importing profile from: {source_path}")
218
+ print(f"📁 To: {dest_dir}")
219
+
220
+ # Create parent directory if needed
221
+ os.makedirs(os.path.dirname(dest_dir), exist_ok=True)
222
+
223
+ with tarfile.open(source_path, "r:gz") as tar:
224
+ tar.extractall(path=os.path.dirname(dest_dir))
225
+
226
+ if is_profile_configured(dest_dir):
227
+ print(f"✅ Profile imported successfully!")
228
+ return True
229
+ else:
230
+ print(f"⚠️ Profile imported but may not be fully configured")
231
+ return False
232
+
233
+ except Exception as e:
234
+ print(f"❌ Import failed: {e}")
235
+ return False
236
+
237
+
238
+ def print_profile_status(profile_dir: Optional[str] = None):
239
+ """Print current profile configuration status"""
240
+ if profile_dir is None:
241
+ profile_dir = get_default_profile_dir()
242
+ else:
243
+ profile_dir = os.path.expanduser(profile_dir)
244
+
245
+ print("=" * 70)
246
+ print("🔍 Camoufox Profile Status")
247
+ print("=" * 70)
248
+ print(f"\n📁 Profile directory: {profile_dir}")
249
+
250
+ if is_profile_configured(profile_dir):
251
+ print("✅ Status: Configured")
252
+ print("\n💡 Profile is ready to use with browser mode")
253
+ print("\n📦 To use in Docker/other machines:")
254
+ print(" 1. Export: from pytrends_modern.camoufox_setup import export_profile")
255
+ print(" export_profile(dest_path='profile.tar.gz')")
256
+ print(" 2. Copy profile.tar.gz to target machine/container")
257
+ print(" 3. Import: from pytrends_modern.camoufox_setup import import_profile")
258
+ print(" import_profile('profile.tar.gz')")
259
+ else:
260
+ print("❌ Status: Not configured")
261
+ print("\n⚠️ You need to run setup before using browser mode:")
262
+ print(" from pytrends_modern.camoufox_setup import setup_profile")
263
+ print(" setup_profile()")
264
+ print("\n Or use the CLI:")
265
+ print(" python -m pytrends_modern.camoufox_setup")
266
+
267
+ print("=" * 70)
268
+
269
+
270
+ if __name__ == "__main__":
271
+ """Run setup when called as a module"""
272
+ import sys
273
+
274
+ if len(sys.argv) > 1:
275
+ command = sys.argv[1]
276
+
277
+ if command == "status":
278
+ print_profile_status()
279
+ elif command == "export":
280
+ dest = sys.argv[2] if len(sys.argv) > 2 else "./camoufox-profile.tar.gz"
281
+ success = export_profile(dest_path=dest)
282
+ sys.exit(0 if success else 1)
283
+ elif command == "import":
284
+ if len(sys.argv) < 3:
285
+ print("❌ Usage: python -m pytrends_modern.camoufox_setup import <source.tar.gz>")
286
+ sys.exit(1)
287
+ source = sys.argv[2]
288
+ success = import_profile(source)
289
+ sys.exit(0 if success else 1)
290
+ else:
291
+ print(f"❌ Unknown command: {command}")
292
+ print("\nUsage:")
293
+ print(" python -m pytrends_modern.camoufox_setup # Run setup")
294
+ print(" python -m pytrends_modern.camoufox_setup status # Check status")
295
+ print(" python -m pytrends_modern.camoufox_setup export [path] # Export profile")
296
+ print(" python -m pytrends_modern.camoufox_setup import <path> # Import profile")
297
+ sys.exit(1)
298
+ else:
299
+ success = setup_profile()
300
+ sys.exit(0 if success else 1)
@@ -0,0 +1,78 @@
1
+ """
2
+ Proxy extension generator for Chrome/Chromium with automatic authentication
3
+ Creates a simple extension that handles proxy auth without any UI
4
+ """
5
+
6
+ import os
7
+ import tempfile
8
+ import json
9
+
10
+
11
+ def create_proxy_extension(username: str, password: str, host: str, port: int) -> str:
12
+ """
13
+ Create a Chrome extension that automatically handles proxy authentication
14
+ No UI, no dialogs - completely automatic
15
+
16
+ Args:
17
+ username: Proxy username
18
+ password: Proxy password
19
+ host: Proxy host/IP
20
+ port: Proxy port
21
+
22
+ Returns:
23
+ Path to the extension folder (DrissionPage needs folder not ZIP)
24
+ """
25
+
26
+ # Create temp directory for extension
27
+ extension_dir = tempfile.mkdtemp(prefix='proxy_auth_')
28
+
29
+ # Manifest v3 - simple and clean
30
+ manifest = {
31
+ "manifest_version": 3,
32
+ "name": "Auto Proxy Auth",
33
+ "version": "1.0",
34
+ "description": "Automatic proxy authentication",
35
+ "permissions": [
36
+ "webRequest",
37
+ "webRequestAuthProvider"
38
+ ],
39
+ "host_permissions": [
40
+ "<all_urls>"
41
+ ],
42
+ "background": {
43
+ "service_worker": "background.js"
44
+ }
45
+ }
46
+
47
+ # Background script - handles auth automatically
48
+ background_js = f"""
49
+ // Automatic proxy authentication - no UI
50
+ chrome.webRequest.onAuthRequired.addListener(
51
+ function(details) {{
52
+ console.log('[Proxy Auth] Providing credentials for:', details.url);
53
+ return {{
54
+ authCredentials: {{
55
+ username: "{username}",
56
+ password: "{password}"
57
+ }}
58
+ }};
59
+ }},
60
+ {{urls: ["<all_urls>"]}},
61
+ ["blocking"]
62
+ );
63
+
64
+ console.log('[Proxy Auth] Extension loaded - auth will be automatic');
65
+ """
66
+
67
+ # Write manifest
68
+ with open(os.path.join(extension_dir, 'manifest.json'), 'w') as f:
69
+ json.dump(manifest, f, indent=2)
70
+
71
+ # Write background script
72
+ with open(os.path.join(extension_dir, 'background.js'), 'w') as f:
73
+ f.write(background_js)
74
+
75
+ print(f"[Proxy Extension] Created at: {extension_dir}")
76
+ print(f"[Proxy Extension] Username: {username}, Host: {host}:{port}")
77
+
78
+ return extension_dir