Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f7f14d7114 | |||
| ef4d26f11b | |||
| fc43e6e806 | |||
| 5783feb60d | |||
| 98ef180878 |
189
app.py
189
app.py
@@ -20,8 +20,17 @@ from datetime import datetime
|
||||
try:
|
||||
from playwright.async_api import async_playwright
|
||||
PLAYWRIGHT_AVAILABLE = True
|
||||
# 尝试导入 stealth 反检测
|
||||
try:
|
||||
from playwright_stealth import stealth_async
|
||||
STEALTH_AVAILABLE = True
|
||||
except ImportError:
|
||||
STEALTH_AVAILABLE = False
|
||||
print("⚠️ playwright-stealth 未安装,反爬虫能力降低")
|
||||
except ImportError:
|
||||
PLAYWRIGHT_AVAILABLE = False
|
||||
STEALTH_AVAILABLE = False
|
||||
print("⚠️ Playwright 未安装,请运行: pip install playwright && playwright install chromium")
|
||||
|
||||
# 检查 agent-browser 是否可用
|
||||
def check_agent_browser():
|
||||
@@ -194,36 +203,140 @@ async def capture_with_playwright(
|
||||
):
|
||||
"""
|
||||
使用 Playwright 捕获网页
|
||||
Playwright 有更强的反爬虫能力
|
||||
"""
|
||||
if not PLAYWRIGHT_AVAILABLE:
|
||||
return {"success": False, "error": "Playwright not installed"}
|
||||
return {"success": False, "error": "Playwright not installed. Run: pip install playwright && playwright install chromium"}
|
||||
|
||||
session_id = str(uuid.uuid4())[:8]
|
||||
|
||||
try:
|
||||
async with async_playwright() as p:
|
||||
browser = await p.chromium.launch(headless=True)
|
||||
# 启动浏览器,添加反检测配置
|
||||
# 尝试使用真实 Chrome(如果安装了)
|
||||
try:
|
||||
# 首先尝试使用真实 Chrome 浏览器
|
||||
browser = await p.chromium.launch(
|
||||
headless=True,
|
||||
channel='chrome', # 使用真实 Chrome 而不是 Chromium
|
||||
args=[
|
||||
'--disable-blink-features=AutomationControlled',
|
||||
'--disable-dev-shm-usage',
|
||||
'--no-sandbox',
|
||||
'--ignore-certificate-errors',
|
||||
]
|
||||
)
|
||||
except:
|
||||
# 如果没有 Chrome,使用 Chromium
|
||||
browser = await p.chromium.launch(
|
||||
headless=True,
|
||||
args=[
|
||||
'--disable-blink-features=AutomationControlled',
|
||||
'--disable-dev-shm-usage',
|
||||
'--no-sandbox',
|
||||
'--ignore-certificate-errors',
|
||||
]
|
||||
)
|
||||
|
||||
viewport_settings = viewport or {"width": 1920, "height": 1080}
|
||||
|
||||
# 创建上下文,模拟真实浏览器
|
||||
context = await browser.new_context(
|
||||
viewport={
|
||||
"width": viewport_settings.get("width", 1920),
|
||||
"height": viewport_settings.get("height", 1080)
|
||||
},
|
||||
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
||||
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.6099.109 Safari/537.36',
|
||||
# 添加更多浏览器特征
|
||||
locale='en-US',
|
||||
timezone_id='America/New_York',
|
||||
color_scheme='light',
|
||||
has_touch=False,
|
||||
is_mobile=False,
|
||||
java_script_enabled=True,
|
||||
accept_downloads=True,
|
||||
)
|
||||
|
||||
# 注入反检测脚本
|
||||
await context.add_init_script("""
|
||||
// 移除 webdriver 标记
|
||||
Object.defineProperty(navigator, 'webdriver', {
|
||||
get: () => undefined
|
||||
});
|
||||
|
||||
// 添加插件
|
||||
Object.defineProperty(navigator, 'plugins', {
|
||||
get: () => {
|
||||
return [
|
||||
{name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format'},
|
||||
{name: 'Chrome PDF Viewer', filename: 'mhjfbmdgncjokjjpdkfnmlglggk', description: ''},
|
||||
{name: 'Native Client', filename: 'internal-nacl-plugin', description: ''}
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
// 语言列表
|
||||
Object.defineProperty(navigator, 'languages', {
|
||||
get: () => ['en-US', 'en', 'zh-CN']
|
||||
});
|
||||
|
||||
// 平台
|
||||
Object.defineProperty(navigator, 'platform', {
|
||||
get: () => 'Win32'
|
||||
});
|
||||
|
||||
// 硬件并发
|
||||
Object.defineProperty(navigator, 'hardwareConcurrency', {
|
||||
get: () => 8
|
||||
});
|
||||
|
||||
// 设备内存
|
||||
Object.defineProperty(navigator, 'deviceMemory', {
|
||||
get: () => 8
|
||||
});
|
||||
|
||||
// Chrome 对象
|
||||
window.chrome = {
|
||||
runtime: {
|
||||
PlatformOs: {MAC: 'mac', WIN: 'win', ANDROID: 'android', CROS: 'cros', LINUX: 'linux', OPENBSD: 'openbsd'},
|
||||
PlatformArch: {ARM: 'arm', X86_32: 'x86-32', X86_64: 'x86-64'},
|
||||
PlatformNaclArch: {ARM: 'arm', X86_32: 'x86-32', X86_64: 'x86-64'},
|
||||
Request: function() {},
|
||||
connect: function() {},
|
||||
sendMessage: function() {}
|
||||
},
|
||||
loadTimes: function() {},
|
||||
csi: function() {},
|
||||
app: {}
|
||||
};
|
||||
""")
|
||||
|
||||
page = await context.new_page()
|
||||
|
||||
try:
|
||||
await page.goto(url, wait_until="networkidle", timeout=30000)
|
||||
except:
|
||||
await page.goto(url, wait_until="domcontentloaded", timeout=30000)
|
||||
# 应用 stealth 反检测(如果可用)
|
||||
if STEALTH_AVAILABLE:
|
||||
await stealth_async(page)
|
||||
|
||||
try:
|
||||
await page.goto(url, wait_until="domcontentloaded", timeout=30000)
|
||||
except Exception as e:
|
||||
# 如果 domcontentloaded 超时,尝试 commit
|
||||
try:
|
||||
await page.goto(url, wait_until="commit", timeout=30000)
|
||||
except:
|
||||
pass
|
||||
|
||||
# 固定等待时间(用于验证、加载等过程)
|
||||
if wait_time > 0:
|
||||
await page.wait_for_timeout(wait_time)
|
||||
|
||||
# 等待页面基本稳定
|
||||
try:
|
||||
await page.wait_for_load_state("networkidle", timeout=5000)
|
||||
except:
|
||||
# networkidle 超时没关系,继续执行
|
||||
pass
|
||||
|
||||
if scroll_times > 0:
|
||||
for i in range(scroll_times):
|
||||
await page.evaluate("window.scrollBy(0, 800)")
|
||||
@@ -251,6 +364,59 @@ async def capture_with_playwright(
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
|
||||
async def capture_with_cdp(
|
||||
url_hint: str = "",
|
||||
action: str = "screenshot",
|
||||
cdp_port: int = 9222
|
||||
):
|
||||
"""
|
||||
使用 CDP 连接到已打开的 Chrome 浏览器
|
||||
用于方案三:手动验证后自动截图
|
||||
"""
|
||||
if not PLAYWRIGHT_AVAILABLE:
|
||||
return {"success": False, "error": "Playwright not installed"}
|
||||
|
||||
try:
|
||||
async with async_playwright() as p:
|
||||
# 连接到已运行的 Chrome
|
||||
browser = await p.chromium.connect_over_cdp(f"http://localhost:{cdp_port}")
|
||||
|
||||
# 获取所有页面
|
||||
contexts = browser.contexts
|
||||
result_data = []
|
||||
|
||||
for context in contexts:
|
||||
for page in context.pages:
|
||||
current_url = page.url
|
||||
title = await page.title()
|
||||
|
||||
# 如果 URL 包含提示词或者是唯一页面
|
||||
if url_hint in current_url or not url_hint:
|
||||
if action == "screenshot":
|
||||
temp_file = CAPTURE_DIR / f"cdp_capture_{uuid.uuid4()[:8]}.png"
|
||||
await page.screenshot(path=str(temp_file), full_page=True)
|
||||
result_data.append({
|
||||
"url": current_url,
|
||||
"title": title,
|
||||
"file_path": str(temp_file)
|
||||
})
|
||||
elif action == "html":
|
||||
html = await page.content()
|
||||
result_data.append({
|
||||
"url": current_url,
|
||||
"title": title,
|
||||
"html": html
|
||||
})
|
||||
|
||||
if result_data:
|
||||
return {"success": True, "pages": result_data}
|
||||
else:
|
||||
return {"success": False, "error": "No matching page found"}
|
||||
|
||||
except Exception as e:
|
||||
return {"success": False, "error": f"CDP connection failed: {str(e)}"}
|
||||
|
||||
|
||||
def capture_webpage(
|
||||
url: str,
|
||||
action: str = "screenshot",
|
||||
@@ -354,7 +520,8 @@ def capture():
|
||||
"full_page": false,
|
||||
"viewport": {"width": 1920, "height": 1080},
|
||||
"wait_time": 2000,
|
||||
"backend": "auto" | "agent-browser" | "playwright"
|
||||
"backend": "auto" | "agent-browser" | "playwright" | "chrome-cdp",
|
||||
"cdp_port": 9222 // 用于 chrome-cdp 后端,连接到已打开的 Chrome
|
||||
}
|
||||
"""
|
||||
data = request.get_json()
|
||||
@@ -377,6 +544,8 @@ def capture():
|
||||
viewport = data.get("viewport")
|
||||
wait_time = int(data.get("wait_time", 2000))
|
||||
backend = data.get("backend", "auto")
|
||||
cdp_port = int(data.get("cdp_port", 9222))
|
||||
url_hint = data.get("url_hint", "")
|
||||
|
||||
result = capture_webpage(
|
||||
url=url,
|
||||
@@ -386,7 +555,9 @@ def capture():
|
||||
full_page=full_page,
|
||||
viewport=viewport,
|
||||
wait_time=wait_time,
|
||||
backend=backend
|
||||
backend=backend,
|
||||
|
||||
|
||||
)
|
||||
|
||||
if not result["success"]:
|
||||
|
||||
69
manual_capture_guide.md
Normal file
69
manual_capture_guide.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# 手动 Chrome 捕获指南
|
||||
|
||||
## 方案三完整流程
|
||||
|
||||
### 1️⃣ 启动真实 Chrome(带调试端口)
|
||||
|
||||
在您的本地机器或服务器上运行:
|
||||
|
||||
```bash
|
||||
# Linux
|
||||
google-chrome --remote-debugging-port=9222 https://www.techpowerup.com/gpu-specs/rtx-pro-6000-blackwell.c4272
|
||||
|
||||
# 或者使用已有的 Chrome 进程(需要先关闭所有 Chrome)
|
||||
google-chrome-stable --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug
|
||||
```
|
||||
|
||||
### 2️⃣ 手动验证(如 Cloudflare)
|
||||
|
||||
- 在浏览器中手动完成验证过程
|
||||
- 等待页面完全加载
|
||||
- 确认可以看到正常内容
|
||||
|
||||
### 3️⃣ 连接到 Chrome 并截图
|
||||
|
||||
验证完成后,运行脚本:
|
||||
|
||||
```bash
|
||||
# 连接到 Chrome 并截图
|
||||
python3.12 /tmp/connect_chrome.py techpowerup
|
||||
|
||||
# 或者捕获当前所有页面
|
||||
python3.12 /tmp/connect_chrome.py
|
||||
```
|
||||
|
||||
### 4️⃣ 查看结果
|
||||
|
||||
```bash
|
||||
# 查看截图
|
||||
ls -lh /tmp/chrome_capture_*.png
|
||||
|
||||
# 查看HTML
|
||||
head -100 /tmp/chrome_html_0.html
|
||||
```
|
||||
|
||||
## API 集成方案
|
||||
|
||||
如果需要通过 API 获取内容,可以:
|
||||
|
||||
1. 在服务代码中添加 CDP 连接选项
|
||||
2. 用户先手动验证
|
||||
3. API 连接到已验证的浏览器实例
|
||||
|
||||
## 注意事项
|
||||
|
||||
⚠️ **重要**:
|
||||
- Chrome 必须带 `--remote-debugging-port=9222` 启动
|
||||
- 所有 Chrome 进程必须先关闭(或使用新的 user-data-dir)
|
||||
- 验证完成后才能运行脚本
|
||||
- 截图后浏览器保持打开,可以继续使用
|
||||
|
||||
## 一键脚本
|
||||
|
||||
```bash
|
||||
# 启动 Chrome 并等待用户验证
|
||||
google-chrome --remote-debugging-port=9222 "https://www.techpowerup.com/gpu-specs/rtx-pro-6000-blackwell.c4272" &
|
||||
echo "请在浏览器中完成验证,然后按 Enter 继续截图..."
|
||||
read
|
||||
python3.12 /tmp/connect_chrome.py techpowerup
|
||||
```
|
||||
@@ -295,20 +295,21 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>滚动设置(用于加载动态内容)</label>
|
||||
<label>等待设置(重要!)</label>
|
||||
<div class="row">
|
||||
<div>
|
||||
<label for="scrollTimes">滚动次数</label>
|
||||
<label for="waitTime">页面加载等待(ms) ⏱️</label>
|
||||
<input type="number" id="waitTime" value="2000" min="0" max="60000" step="500">
|
||||
<small style="color: #666; display: block; margin-top: 4px;">某些网站需要较长验证过程,可设置为 10000-30000</small>
|
||||
</div>
|
||||
<div>
|
||||
<label for="scrollTimes">滚动次数(可选)</label>
|
||||
<input type="number" id="scrollTimes" value="0" min="0" max="20">
|
||||
</div>
|
||||
<div>
|
||||
<label for="scrollDelay">滚动间隔(ms)</label>
|
||||
<input type="number" id="scrollDelay" value="1000" min="500" max="5000" step="100">
|
||||
</div>
|
||||
<div>
|
||||
<label for="waitTime">页面加载等待(ms)</label>
|
||||
<input type="number" id="waitTime" value="2000" min="0" max="10000" step="500">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -350,16 +351,23 @@
|
||||
</div>
|
||||
|
||||
<div class="card api-docs">
|
||||
<h3>📚 API 文档</h3>
|
||||
<p>POST /api/capture</p>
|
||||
<pre><code>{
|
||||
<h3>📚 使用说明</h3>
|
||||
<p><strong>重要提示:</strong></p>
|
||||
<ul style="margin: 10px 0; line-height: 1.8;">
|
||||
<li>⏱️ <strong>页面加载等待</strong>:某些网站需要验证过程(如 Cloudflare),请设置较长时间(10000-30000ms)</li>
|
||||
<li>🔄 <strong>滚动次数</strong>:用于加载动态内容(如微博、推特等),建议 3-5 次</li>
|
||||
<li>🎯 <strong>全页截图</strong>:滚动加载后建议开启此选项</li>
|
||||
</ul>
|
||||
<p style="margin-top: 15px;"><strong>API 调用:</strong></p>
|
||||
<pre><code>POST /api/capture
|
||||
{
|
||||
"url": "https://example.com",
|
||||
"action": "screenshot", // 或 "html"
|
||||
"scroll_times": 3,
|
||||
"scroll_delay": 1000,
|
||||
"full_page": true,
|
||||
"viewport": {"width": 1920, "height": 1080},
|
||||
"wait_time": 2000
|
||||
"wait_time": 15000, // 重要!验证网站需设置较长等待
|
||||
"scroll_times": 3, // 可选:加载动态内容
|
||||
"scroll_delay": 1000, // 可选:滚动间隔
|
||||
"full_page": true, // 可选:全页截图
|
||||
"backend": "playwright" // 可选:playwright 或 agent-browser
|
||||
}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user