Files
tavily-key-generator/firecrawl_browser_solver.py
T

496 lines
17 KiB
Python

"""
使用 Camoufox 完成 Firecrawl 注册
思路:从真实注册页开始,自动处理邮箱验证和 API Key 提取
"""
import os
import re
import threading
import time
import requests as std_requests
from camoufox.sync_api import Camoufox
from config import (
API_KEY_TIMEOUT,
EMAIL_CODE_TIMEOUT,
FIRECRAWL_REGISTER_HEADLESS,
)
from mail_provider import get_verification_link
_HERE = os.path.dirname(os.path.abspath(__file__))
_SAVE_FILE = os.path.join(_HERE, "firecrawl_accounts.txt")
_SAVE_LOCK = threading.Lock()
_FIRECRAWL_SIGNUP_RESULT_TIMEOUT = 15
def attach_signup_feedback_tracker(page):
"""记录注册请求的关键反馈,方便识别风控或表单错误。"""
events = []
def handle_response(response):
url = response.url.lower()
if not any(token in url for token in ("signin", "signup", "auth", "clerk")):
return
try:
body = response.text()
except Exception:
body = ""
events.append(
{
"url": response.url,
"status": response.status,
"body": body[:1500],
}
)
page.on("response", handle_response)
return events
def detect_signup_result(page, signup_events):
"""根据页面内容和网络响应判断注册提交是否真的成功。"""
snapshots = []
current_url = page.url.lower()
if "confirm-email" in current_url or "confirm_email" in current_url:
return ("sent", "")
try:
snapshots.append(page.locator("body").inner_text())
except Exception:
pass
try:
snapshots.append(page.content())
except Exception:
pass
snapshots.extend(event.get("body", "") for event in signup_events[-6:])
combined = "\n".join(snapshots).lower()
if "security check failed" in combined or "suspicious activity" in combined:
return (
"blocked",
"Firecrawl 返回了 Security check failed / suspicious activity,当前浏览器指纹或网络被风控拦截。",
)
if "already exists" in combined or "account already exists" in combined:
return ("exists", "这个邮箱看起来已经注册过了。")
if "invalid email" in combined or "email address is invalid" in combined:
return ("invalid_email", "Firecrawl 认为这个邮箱地址无效。")
if "password is not strong enough" in combined or "at least 12 characters" in combined:
return (
"weak_password",
"Firecrawl 拒绝了当前密码强度,至少需要 12 位,并同时包含大小写、数字和特殊字符。",
)
success_markers = (
"check your email",
"confirm email",
"confirmation link",
"verify your email",
"verification email",
"email has been sent",
"we sent you an email",
"did not receive the email",
"once confirmed, you may sign in",
)
if any(marker in combined for marker in success_markers):
return ("sent", "")
return ("", "")
def wait_for_signup_result(page, signup_events, timeout=_FIRECRAWL_SIGNUP_RESULT_TIMEOUT):
"""等待注册提交后的明确结果,避免被风控时继续盲等验证邮件。"""
deadline = time.time() + timeout
while time.time() < deadline:
status, message = detect_signup_result(page, signup_events)
if status:
return status, message
time.sleep(1)
current_url = page.url.lower()
if "confirm-email" in current_url or "confirm_email" in current_url:
return ("sent", "")
if "view=signup" in current_url or current_url.rstrip("/").endswith("/signin"):
return (
"stalled",
"提交后页面仍停留在注册页,Firecrawl 没有明确确认已发送验证邮件。",
)
return ("", "")
def fill_first_input(page, selectors, value):
"""填充第一个存在的输入框"""
for selector in selectors:
if page.query_selector(selector):
page.fill(selector, value)
return selector
return None
def extract_api_key_from_page(page):
"""从 API Keys 页面提取 API Key"""
try:
# 等待 API Keys 页面加载
time.sleep(3)
# 尝试多种选择器查找 API Key
selectors = [
'code:has-text("fc-")',
'[data-testid="api-key"]',
'.api-key',
'input[value^="fc-"]',
'span:has-text("fc-")',
]
for selector in selectors:
elements = page.query_selector_all(selector)
for element in elements:
text = element.inner_text() or element.get_attribute('value') or ''
match = re.search(r'fc-[a-zA-Z0-9_-]{20,}', text)
if match:
return match.group(0)
# 从页面 HTML 中提取
html = page.content()
matches = re.findall(r'fc-[a-zA-Z0-9_-]{20,}', html)
if matches:
return matches[0]
return None
except Exception as e:
print(f"⚠️ 提取 API Key 失败: {e}")
return None
def create_api_key(page):
"""在 Dashboard 中创建新的 API Key"""
try:
# 查找并点击创建 API Key 按钮
create_selectors = [
'button:has-text("Create")',
'button:has-text("New API Key")',
'button:has-text("Generate")',
'[data-testid="create-api-key"]',
]
for selector in create_selectors:
if page.query_selector(selector):
page.click(selector)
time.sleep(2)
break
# 如果有名称输入框,填写名称
name_input = page.query_selector('input[name="name"], input[placeholder*="name" i]')
if name_input:
page.fill('input[name="name"], input[placeholder*="name" i]', 'auto-generated-key')
time.sleep(1)
# 点击确认按钮
confirm_selectors = [
'button:has-text("Create")',
'button:has-text("Generate")',
'button:has-text("Confirm")',
'button[type="submit"]',
]
for selector in confirm_selectors:
if page.query_selector(selector):
page.click(selector)
time.sleep(3)
break
return True
except Exception as e:
print(f"⚠️ 创建 API Key 失败: {e}")
return False
def save_account(email, password, api_key):
"""并发注册时串行写入 firecrawl_accounts.txt"""
with _SAVE_LOCK:
with open(_SAVE_FILE, 'a', encoding='utf-8') as f:
f.write(f"{email},{password},{api_key}\n")
def verify_api_key(api_key, timeout=30):
"""真实调用 Firecrawl API,验证新 key 可用"""
transient_errors = (
std_requests.exceptions.SSLError,
std_requests.exceptions.ConnectionError,
std_requests.exceptions.Timeout,
)
last_error = None
for attempt in range(1, 4):
try:
response = std_requests.post(
"https://api.firecrawl.dev/v2/scrape",
json={
"url": "https://example.com",
},
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
timeout=timeout,
)
break
except transient_errors as exc:
last_error = exc
if attempt < 3:
print(f"⚠️ API Key 调用测试遇到网络/TLS 异常,正在重试 ({attempt}/3): {exc}")
time.sleep(attempt)
continue
print(f"⚠️ API Key 调用测试遇到网络/TLS 异常,暂时无法确认 Key 是否可用: {exc}")
print(" 这通常是本地代理 / TUN / DNS 劫持链路导致的瞬时握手失败,不一定代表 Key 无效。")
return None
except Exception as exc:
print(f"❌ API Key 调用测试失败: {exc}")
return False
else:
print(f"⚠️ API Key 调用测试未获得有效响应: {last_error}")
return None
if response.status_code == 200:
print("✅ API Key 调用测试通过")
return True
preview = response.text.strip().replace("\n", " ")[:160]
print(f"❌ API Key 调用测试失败: HTTP {response.status_code}")
if preview:
print(f" 响应: {preview}")
return False
def submit_form(page, input_selector=None):
"""提交表单"""
button_selectors = [
'button[type="submit"]',
'button:has-text("Sign up")',
'button:has-text("Continue")',
'button:has-text("Register")',
]
for selector in button_selectors:
if page.query_selector(selector):
try:
page.click(selector, timeout=3000)
return True
except Exception:
continue
if input_selector and page.query_selector(input_selector):
try:
page.press(input_selector, 'Enter')
return True
except Exception:
return False
return False
def register_with_browser(email, password):
"""使用浏览器自动化注册 Firecrawl"""
print(f"🌐 使用浏览器模式注册 Firecrawl: {email}")
try:
with Camoufox(headless=FIRECRAWL_REGISTER_HEADLESS) as browser:
page = browser.new_page()
signup_events = attach_signup_feedback_tracker(page)
# 1. 访问注册页
print("🧭 进入注册页...")
page.goto("https://firecrawl.dev/", wait_until="networkidle", timeout=30000)
time.sleep(2)
# 查找并点击 Sign Up 按钮
signup_selectors = [
'a:has-text("Sign up")',
'a:has-text("Sign Up")',
'button:has-text("Sign up")',
'a[href*="signup"]',
'a[href*="register"]',
]
for selector in signup_selectors:
if page.query_selector(selector):
page.click(selector)
time.sleep(3)
break
# 2. 填写注册表单
print("📝 填写注册信息...")
# 填写邮箱
email_selector = fill_first_input(
page,
['input[name="email"]', 'input[type="email"]', 'input[placeholder*="email" i]'],
email
)
if not email_selector:
print("❌ 未找到邮箱输入框")
return None
time.sleep(1)
# 填写密码
password_selector = fill_first_input(
page,
['input[name="password"]', 'input[type="password"]'],
password
)
if not password_selector:
print("❌ 未找到密码输入框")
return None
time.sleep(1)
# 3. 提交注册表单
print("📤 提交注册...")
submit_form(page, email_selector)
status, message = wait_for_signup_result(page, signup_events)
if status != "sent":
if message:
print(f"❌ {message}")
if status in {"blocked", "stalled"}:
if FIRECRAWL_REGISTER_HEADLESS:
print("💡 建议:把 FIRECRAWL_REGISTER_HEADLESS=false,再用可见浏览器重试。")
else:
print("💡 建议:切换更干净的网络/代理后重试,或在可见浏览器里手动完成注册动作。")
return None
# 4. 等待邮箱验证链接
print(f"📧 等待邮箱验证链接(最多 {EMAIL_CODE_TIMEOUT} 秒)...")
verify_url = get_verification_link(email, timeout=EMAIL_CODE_TIMEOUT)
if not verify_url:
print("❌ 未收到验证邮件")
return None
print(f"✅ 收到验证链接: {verify_url[:50]}...")
# 5. 访问验证链接
print("🔗 访问验证链接...")
page.goto(verify_url, wait_until="networkidle", timeout=60000)
time.sleep(5)
# 6. 检查是否需要登录
current_url = page.url.lower()
if 'login' in current_url or 'signin' in current_url:
print("🔐 需要登录...")
# 填写邮箱
fill_first_input(
page,
['input[name="email"]', 'input[type="email"]'],
email
)
time.sleep(1)
# 填写密码
fill_first_input(
page,
['input[name="password"]', 'input[type="password"]'],
password
)
time.sleep(1)
# 提交登录
submit_form(page)
time.sleep(5)
# 7. Firecrawl 新版会先进入 onboarding,页面 HTML 里通常已经带有 apiKey。
print("🔍 先从当前页面提取 API Key...")
api_key = extract_api_key_from_page(page)
if api_key:
print(f"✅ 当前页面已拿到 API Key: {api_key[:20]}...")
else:
print("ℹ️ 当前页面未直接拿到 API Key,继续尝试后台入口...")
# 8. 当前页面拿不到时,再尝试后台入口。
if not api_key:
print("🔑 导航到 API Keys 页面...")
# 尝试多种方式找到 API Keys 页面
api_key_nav_selectors = [
'a:has-text("API Keys")',
'a[href*="api-key"]',
'a[href*="apikey"]',
'a[href*="keys"]',
'button:has-text("API Keys")',
]
found_nav = False
for selector in api_key_nav_selectors:
if page.query_selector(selector):
page.click(selector)
time.sleep(3)
found_nav = True
break
if not found_nav:
# 尝试直接访问常见的 API Keys URL
possible_urls = [
"https://www.firecrawl.dev/app/api-keys",
"https://www.firecrawl.dev/app/settings",
"https://www.firecrawl.dev/app",
"https://firecrawl.dev/dashboard/api-keys",
"https://firecrawl.dev/api-keys",
"https://app.firecrawl.dev/api-keys",
]
for url in possible_urls:
try:
page.goto(url, wait_until="networkidle", timeout=15000)
time.sleep(3)
if 'api' in page.url.lower() and 'key' in page.url.lower():
found_nav = True
break
except:
continue
if not found_nav:
print("⚠️ 未找到 API Keys 页面,尝试从当前页面提取...")
# 9. 尝试提取现有的 API Key
print("🔍 查找 API Key...")
api_key = extract_api_key_from_page(page)
# 10. 如果没有找到,尝试创建新的 API Key
if not api_key:
print("💡 未找到现有 API Key,尝试创建新的...")
if create_api_key(page):
api_key = extract_api_key_from_page(page)
if not api_key:
print("❌ 无法获取 API Key")
return "SUCCESS_NO_KEY"
print(f"✅ 获取到 API Key: {api_key[:20]}...")
# 10. 验证 API Key
print("🧪 验证 API Key 可用性...")
verify_result = verify_api_key(api_key)
if verify_result is False:
print("⚠️ API Key 验证失败,但仍然保存")
elif verify_result is None:
print("⚠️ API Key 可用性暂时无法确认,但更像是网络/TLS 问题,仍然保存")
save_account(email, password, api_key)
print(f"🎉 注册成功")
print(f" 邮箱: {email}")
print(f" 密码: {password}")
print(f" Key : {api_key}")
return api_key
except Exception as e:
print(f"❌ 注册失败: {e}")
import traceback
traceback.print_exc()
return None
if __name__ == "__main__":
from tavily_core import create_email
email, password = create_email(service="firecrawl")
register_with_browser(email, password)