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.
- pytrends_modern/__init__.py +7 -1
- pytrends_modern/browser_config.py +94 -0
- pytrends_modern/browser_config_camoufox.py +67 -0
- pytrends_modern/camoufox_setup.py +300 -0
- pytrends_modern/proxy_extension.py +78 -0
- pytrends_modern/request.py +454 -4
- pytrends_modern/request_async.py +342 -0
- {pytrends_modern-0.1.2.dist-info → pytrends_modern-0.2.1.dist-info}/METADATA +73 -2
- pytrends_modern-0.2.1.dist-info/RECORD +20 -0
- pytrends_modern-0.1.2.dist-info/RECORD +0 -15
- {pytrends_modern-0.1.2.dist-info → pytrends_modern-0.2.1.dist-info}/WHEEL +0 -0
- {pytrends_modern-0.1.2.dist-info → pytrends_modern-0.2.1.dist-info}/entry_points.txt +0 -0
- {pytrends_modern-0.1.2.dist-info → pytrends_modern-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {pytrends_modern-0.1.2.dist-info → pytrends_modern-0.2.1.dist-info}/top_level.txt +0 -0
pytrends_modern/__init__.py
CHANGED
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
pytrends-modern: Modern Google Trends API
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
__version__ = "
|
|
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
|