الخلاصة أولاً: حين تحتفظ الجلسة بملفات تعريف الارتباط والحالة المحلية بين عمليات التشغيل، تعيد بيئة QA إنتاج الحالة الوظيفية نفسها فيقلّ ظهور اختبارات CAPTCHA غير المتوقعة. أما الجلسة الجديدة تمامًا فتبدأ بلا حالة محفوظة، ما يجعل سلوك الصفحة متفاوتًا بين تشغيل وآخر. يعرض هذا الدليل أنماطًا عملية بلغتي Python وJavaScript لحفظ الجلسة وملف Chrome الشخصي واستعادتهما بثبات داخل سير عملك.
لماذا يقلّل الاحتفاظ بالجلسة من ظهور اختبارات CAPTCHA
| حالة الجلسة | تكرار الكابتشا | السبب |
|---|---|---|
| جلسة جديدة بلا ملفات تعريف الارتباط | مرتفع | لا توجد حالة تطبيق محفوظة بعد |
| جلسة سابقة داخل التطبيق نفسه | متوسط | تُعاد بيانات الاعتماد والحالة الوظيفية |
| جلسة مهيأة داخل صفحات تملكها | منخفض | اكتملت عناصر التطبيق والخطوات المسبقة |
| ملف شخصي ثابت بعمر اختباري معروف | منخفض نسبيًا | يسهل تكرار الحالة نفسها بين التشغيلات |
مثال تطبيقي: فريق QA في الرياض يختبر بوابة دفع على بيئة staging قبل كل إصدار. مع جلسة فارغة يختلف ظهور CAPTCHA بين دورة وأخرى، فيصعب فصل الأخطاء الحقيقية عن تفاوت البيئة. وبملف شخصي ثابت لكل سيناريو تصبح الحالة قابلة للتكرار، ويقتصر استدعاء خدمة الحل على ما يظهر فعلاً. ولفريق صغير تكفي خطة BASIC من CaptchaAI بسعر 15 دولارًا شهريًا و5 threads، فالفوترة بالـ threads المتزامنة لا بعدد عمليات الحل.
حفظ ملفات تعريف الارتباط واستعادتها في Selenium وPython
دالة لحفظ الجلسة وإعادة تحميلها
import json
import os
import time
from selenium import webdriver
class PersistentSession:
def __init__(self, profile_name="default", cookie_dir="./sessions"):
self.profile_name = profile_name
self.cookie_dir = cookie_dir
self.cookie_file = os.path.join(cookie_dir, f"{profile_name}_cookies.json")
os.makedirs(cookie_dir, exist_ok=True)
def create_driver(self):
options = webdriver.ChromeOptions()
options.add_argument("--window-size=1920,1080")
return webdriver.Chrome(options=options)
def save_cookies(self, driver):
"""Save all cookies to disk."""
cookies = driver.get_cookies()
with open(self.cookie_file, "w") as f:
json.dump(cookies, f, indent=2)
print(f"Saved {len(cookies)} cookies to {self.cookie_file}")
def load_cookies(self, driver, domain=None):
"""Restore cookies from disk."""
if not os.path.exists(self.cookie_file):
print("No saved cookies found")
return False
with open(self.cookie_file) as f:
cookies = json.load(f)
loaded = 0
for cookie in cookies:
# Filter by domain if specified
if domain and domain not in cookie.get("domain", ""):
continue
# Remove problematic fields
cookie.pop("sameSite", None)
cookie.pop("storeId", None)
try:
driver.add_cookie(cookie)
loaded += 1
except Exception as e:
print(f"Skip cookie {cookie.get('name')}: {e}")
print(f"Loaded {loaded}/{len(cookies)} cookies")
return loaded > 0
def run_with_session(self, url, callback):
"""Run a task with persistent session."""
driver = self.create_driver()
try:
# Navigate to domain first (required for cookie loading)
driver.get(url)
time.sleep(1)
# Load saved cookies
self.load_cookies(driver)
# Refresh to apply cookies
driver.get(url)
time.sleep(2)
# Execute task
result = callback(driver)
# Save updated cookies
self.save_cookies(driver)
return result
finally:
driver.quit()
# Usage
session = PersistentSession("target-site")
def my_task(driver):
# Check if already logged in
if "dashboard" in driver.current_url:
print("Session restored — no login needed")
return driver.page_source
else:
print("Need to login + solve CAPTCHA")
# Solve CAPTCHA with CaptchaAI...
return None
result = session.run_with_session("https://example.com", my_task)
دليل بيانات مستخدم Chrome: الاحتفاظ بالملف الشخصي كاملاً
الخيار الأشمل للثبات: يحفظ ملفات تعريف الارتباط والتخزين المحلي والسجل وحالة المتصفح دفعة واحدة عبر --user-data-dir:
import os
from selenium import webdriver
PROFILE_DIR = os.path.abspath("./chrome-profiles/profile-1")
def create_persistent_driver():
options = webdriver.ChromeOptions()
options.add_argument(f"--user-data-dir={PROFILE_DIR}")
options.add_argument("--profile-directory=Default")
options.add_argument("--no-sandbox")
return webdriver.Chrome(options=options)
# First run: builds fresh profile
driver = create_persistent_driver()
driver.get("https://example.com")
# ... solve CAPTCHA, login, etc.
driver.quit()
# Second run: same profile, cookies and state preserved
driver = create_persistent_driver()
driver.get("https://example.com")
# Often skips CAPTCHA because session is recognized
driver.quit()
ما الذي يحفظه دليل بيانات المستخدم
| العنصر المحفوظ | أثره على اختبارات CAPTCHA |
|---|---|
| ملفات تعريف الارتباط | رموز الجلسة والتفضيلات الصادرة من تطبيقك |
| localStorage | رموز الحالة الخاصة بالموقع |
| IndexedDB | الحالة الداخلية لعنصر reCAPTCHA |
| ذاكرة التخزين المؤقت | تحميل أسرع للصفحة |
| السجل | إشارات مسار التصفح داخل التطبيق |
| عمال الخدمة (Service workers) | الفحوصات الخلفية لاختبارات CAPTCHA |
سياق Puppeteer المستمر عبر userDataDir
يوفّر Puppeteer الثبات نفسه بتمرير userDataDir عند الإطلاق، فيحفظ ملف المتصفح كاملاً دون منطق منفصل لملفات تعريف الارتباط:
const puppeteer = require("puppeteer");
const path = require("path");
const USER_DATA_DIR = path.resolve("./chrome-profiles/profile-1");
async function runWithPersistentProfile() {
const browser = await puppeteer.launch({
headless: false,
userDataDir: USER_DATA_DIR,
args: [
"--no-sandbox",
"--window-size=1920,1080",
],
});
const page = await browser.newPage();
await page.goto("https://example.com", { waitUntil: "networkidle0" });
// Check if session is active
const isLoggedIn = await page.evaluate(() =>
document.querySelector(".user-menu") !== null
);
if (isLoggedIn) {
console.log("Session active — no CAPTCHA needed");
} else {
console.log("Session expired — solving CAPTCHA");
// Solve with CaptchaAI...
}
await browser.close();
}
حفظ localStorage وsessionStorage واستعادتهما
بعض التطبيقات تخزّن الحالة في localStorage وsessionStorage. احفظهما لإكمال صورة الجلسة:
def save_storage(driver, filepath):
"""Save localStorage and sessionStorage."""
storage = driver.execute_script("""
return {
localStorage: Object.fromEntries(
Object.entries(localStorage)
),
sessionStorage: Object.fromEntries(
Object.entries(sessionStorage)
),
};
""")
with open(filepath, "w") as f:
json.dump(storage, f, indent=2)
def restore_storage(driver, filepath):
"""Restore localStorage and sessionStorage."""
if not os.path.exists(filepath):
return
with open(filepath) as f:
storage = json.load(f)
for key, value in storage.get("localStorage", {}).items():
driver.execute_script(
f"localStorage.setItem('{key}', '{value}')"
)
for key, value in storage.get("sessionStorage", {}).items():
driver.execute_script(
f"sessionStorage.setItem('{key}', '{value}')"
)
تهيئة جلسة اختبار معروفة داخل تطبيقك
بدل الاعتماد على صفحات خارجية، جهّز الجلسة عبر خطوات تملكها أنت:
- صفحة الصحة.
- صفحة تسجيل الدخول الاختبارية.
- صفحة المساعدة الداخلية.
بهذا يبدأ كل تشغيل من حالة معروفة قابلة للتكرار، فتصبح نتائج QA قابلة للمقارنة بين الدورات.
import time
def prime_session(driver, owned_urls=None):
"""Load owned application pages to establish a known QA session state."""
default_urls = [
"https://staging.example.com/health",
"https://staging.example.com/help",
"https://staging.example.com/login",
]
urls = owned_urls or default_urls
for url in urls:
driver.get(url)
time.sleep(2)
print(f"Prepared QA session with {len(urls)} owned pages")
driver = create_persistent_driver()
prime_session(driver)
driver.get("https://staging.example.com/form")
إدارة عدة ملفات شخصية للمتصفح
لتشغيل سيناريوهات QA متوازية، يوزّع هذا المدير الاستخدام على ملفات منفصلة ويسجّل إحصاءاتها:
import os
import json
import time
from datetime import datetime
class SessionManager:
"""Manage multiple persistent browser profiles."""
def __init__(self, base_dir="./sessions"):
self.base_dir = base_dir
self.meta_file = os.path.join(base_dir, "profiles.json")
os.makedirs(base_dir, exist_ok=True)
if os.path.exists(self.meta_file):
with open(self.meta_file) as f:
self.profiles = json.load(f)
else:
self.profiles = {}
def _save_meta(self):
with open(self.meta_file, "w") as f:
json.dump(self.profiles, f, indent=2)
def get_profile_dir(self, name):
return os.path.join(self.base_dir, f"profile-{name}")
def create_profile(self, name, network_label=None):
"""Create a new browser profile."""
profile_dir = self.get_profile_dir(name)
os.makedirs(profile_dir, exist_ok=True)
self.profiles[name] = {
"created": datetime.now().isoformat(),
"last_used": None,
"use_count": 0,
"network_label": network_label,
"captcha_solves": 0,
}
self._save_meta()
return profile_dir
def get_least_used_profile(self):
"""Get the profile used least recently."""
if not self.profiles:
return None
return min(
self.profiles.items(),
key=lambda x: x[1].get("last_used") or ""
)[0]
def record_use(self, name, solved_captcha=False):
"""Record profile usage."""
if name in self.profiles:
self.profiles[name]["last_used"] = datetime.now().isoformat()
self.profiles[name]["use_count"] += 1
if solved_captcha:
self.profiles[name]["captcha_solves"] += 1
self._save_meta()
def get_stats(self):
"""Print profile statistics."""
for name, meta in self.profiles.items():
print(f"Profile: {name}")
print(f" Uses: {meta['use_count']}")
print(f" CAPTCHAs: {meta['captcha_solves']}")
print(f" Last used: {meta.get('last_used', 'never')}")
print()
# Usage
manager = SessionManager()
# Create 5 rotating profiles
for i in range(5):
manager.create_profile(f"worker-{i}")
# Get next profile to use
profile_name = manager.get_least_used_profile()
profile_dir = manager.get_profile_dir(profile_name)
# Use with Selenium
options = webdriver.ChromeOptions()
options.add_argument(f"--user-data-dir={os.path.abspath(profile_dir)}")
driver = webdriver.Chrome(options=options)
# After task
manager.record_use(profile_name, solved_captcha=True)
manager.get_stats()
تنظيف ملفات تعريف الارتباط وتدوير صلاحيتها
تنتهي صلاحية ملفات تعريف الارتباط مع الوقت، فنظّف المنتهية ودمج الجديدة دوريًا:
from datetime import datetime, timezone
def clean_expired_cookies(cookie_file):
"""Remove expired cookies from saved file."""
if not os.path.exists(cookie_file):
return
with open(cookie_file) as f:
cookies = json.load(f)
now = datetime.now(timezone.utc).timestamp()
valid = [c for c in cookies if c.get("expiry", float("inf")) > now]
removed = len(cookies) - len(valid)
if removed > 0:
with open(cookie_file, "w") as f:
json.dump(valid, f, indent=2)
print(f"Removed {removed} expired cookies")
def merge_cookies(existing_file, new_cookies):
"""Merge new cookies with existing, preferring newer values."""
existing = []
if os.path.exists(existing_file):
with open(existing_file) as f:
existing = json.load(f)
# Index by (name, domain)
cookie_map = {}
for c in existing:
key = (c["name"], c.get("domain", ""))
cookie_map[key] = c
for c in new_cookies:
key = (c["name"], c.get("domain", ""))
cookie_map[key] = c # Newer overwrites
merged = list(cookie_map.values())
with open(existing_file, "w") as f:
json.dump(merged, f, indent=2)
return len(merged)
حلّ المشكلات الشائعة
| المشكلة | السبب | الحل |
|---|---|---|
| ملفات تعريف الارتباط لا تُحمَّل | لم تنتقل إلى المجال أولاً | استدعِ driver.get(url) قبل add_cookie |
| خطأ قفل الملف الشخصي | جلسة Chrome سابقة لم تُغلق | أنهِ عمليات Chrome واحذف SingletonLock |
| الجلسة ما زالت منتهية | عدم تطابق sameSite في ملف تعريف الارتباط |
احذف sameSite قبل التحميل |
| حُظر التخزين | سياق CORS أو أمان الأصل | حمّل التخزين بعد الانتقال إلى الأصل الصحيح |
| ارتفاع تكرار CAPTCHA مع الوقت | تغيّر في بيئة الشبكة أو الجلسة | افصل الشبكة بين سيناريوهات QA وسجّل تغييرات البيئة |
الأسئلة الشائعة
ما الفرق بين حفظ ملفات تعريف الارتباط فقط واستخدام دليل بيانات مستخدم Chrome كاملاً؟
حفظ ملفات تعريف الارتباط يخزّن رموز الجلسة فقط، وهو خفيف وسهل النقل. أما دليل بيانات المستخدم فيحفظ معها localStorage وIndexedDB وذاكرة التخزين المؤقت، فيعيد حالة أقرب للاكتمال لكنه أثقل في المشاركة. اختر الأخف ما لم تحتج الحالة الكاملة.
أين أخزّن ملفات الجلسة بأمان داخل مستودع الفريق؟
عامل ملفات الجلسة كأسرار لأنها تمنح دخولاً فعليًا:
- أبقها خارج نظام التحكم بالإصدارات.
- احفظها في مخزن مشفّر بصلاحيات محدودة.
- لا تُدرجها في سجلات أو نسخ مكشوفة.
متى أنظّف ملفات تعريف الارتباط أو أعيد ضبط الملف الشخصي؟
نظّف المنتهية قبل كل دورة تشغيل حتى لا يتضخم الملف بقيم غير صالحة. أما إعادة الضبط الكاملة فمنطقية عند تغيّر بيئة الاختبار أو بدء سيناريو QA جديد يحتاج حالة نظيفة.
هل يعمل الاحتفاظ بالجلسة في وضع Chrome مقطوع الرأس وأنظمة CI؟
نعم. تعمل أدلة بيانات المستخدم وملفات تعريف الارتباط بالطريقة نفسها في الوضع مقطوع الرأس وداخل خطوط CI، لكن اختبره في بيئتك إذ قد يتغيّر سلوك الصفحة حسب البيئة.
كيف يقلّل الاحتفاظ بالجلسة من تكلفة CaptchaAI؟
كل اختبار CAPTCHA لا يظهر هو استدعاء أقل لخدمة الحل. فبتقليل تكرار التحديات عبر جلسات ثابتة ينخفض عدد مرات اللجوء إلى CaptchaAI، وحين تظهر فعلاً تحلّها الخدمة كالمعتاد.
أدلة ذات صلة
جلسات المتصفح الثابتة تقلّل من ظهور اختبارات CAPTCHA — احصل على مفتاح CaptchaAI لحلّ ما يظهر منها رغم ذلك.