Yêu cầu môi trường và công nghệ nền tảng
Để xây dựng công cụ tự động hóa, cần thiết lập môi trường phát triển ổn định và lựa chọn đúng bộ thư viện hỗ trợ. Các thành phần chính bao gồm:
- Môi trường phát triển: Visual Studio 2022 (hoặc IDE tương thích với C#), .NET 6.0 trở lên, trình duyệt Chrome phiên bản ổn định.
- Công nghệ cốt lõi:
C#: Ngôn ngữ lập trình mạnh về kiểm soát kiểu dữ liệu và tích hợp tốt với hệ sinh thái Windows.Playwright: Thư viện tự động hóa đa trình duyệt do Microsoft phát triển, hỗ trợ điều khiển trình duyệt ở chế độ có giao diện hoặc không đầu (headless), đặc biệt phù hợp với các trang web sử dụng JavaScript nặng.Newtonsoft.Json: Thư viện xử lý dữ liệu JSON, dùng để phân tích phản hồi từ API và chuẩn bị payload yêu cầu.File cấu hình dạng INI: Lưu trữ thông tin nhạy cảm như tài khoản, mật khẩu, đường dẫn thực thi trình duyệt — tránh hardcode và đảm bảo tính linh hoạt khi triển khai.
Thiết kế chức năng chính
Công cụ được thiết kế theo luồng làm việc tuần tự: đăng nhập tài khoản chính → lấy danh sách tài khoản phụ → đăng nhập từng tài khoản phụ → trích xuất và lưu trữ Cookie. Các mô-đun chức năng bao gồm:
- Quản lý cấu hình: Đọc/ghi thông tin đăng nhập và đường dẫn Chrome từ tập tin
Config.ini. - Xử lý đăng nhập tài khoản chính: Mở trình duyệt, điền thông tin, xác thực thủ công (nếu có CAPTCHA), sau đó gọi API để lấy danh sách cửa hàng con.
- Mã hóa tham số yêu cầu: Thực hiện mã hóa một số trường nhạy cảm (ví dụ:
subject_id,member_id) theo thuật toán XOR với giá trị cố định trước khi gửi yêu cầu tới backend. - Đăng nhập tài khoản phụ: Sử dụng Cookie của tài khoản chính để khởi tạo ngữ cảnh trình duyệt mới, sau đó gọi API đăng nhập riêng lẻ cho từng cửa hàng con và thu thập Cookie kết quả.
- Ghi nhật ký hoạt động: Hiển thị thời gian, tên cửa hàng, trạng thái lưu Cookie trong giao diện người dùng để tiện kiểm tra và gỡ lỗi.
Cài đặt chi tiết các thành phần then chốt
1. Đọc/ghi cấu hình từ tập tin INI
Thay vì lưu trực tiếp vào mã nguồn, toàn bộ thông số được quản lý qua tập tin cấu hình:
private const string ConfigPath = "Config.ini";
private void LoadConfiguration()
{
if (File.Exists(ConfigPath))
{
txtUsername.Text = IniReader.GetValue("Credentials", "Username", ConfigPath);
txtPassword.Text = IniReader.GetValue("Credentials", "Password", ConfigPath);
txtBrowserPath.Text = IniReader.GetValue("Paths", "ChromeExecutable", ConfigPath);
}
DetectChromeInstallation();
}
private void DetectChromeInstallation()
{
var candidates = new[]
{
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"Google", "Chrome", "Application", "chrome.exe"),
@"C:\Program Files\Google\Chrome\Application\chrome.exe",
@"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
};
foreach (var path in candidates)
{
if (File.Exists(path) && string.IsNullOrEmpty(txtBrowserPath.Text))
{
txtBrowserPath.Text = path;
break;
}
}
}
private void SaveConfiguration(string username, string password, string browserPath)
{
IniWriter.SetValue("Credentials", "Username", username, ConfigPath);
IniWriter.SetValue("Credentials", "Password", password, ConfigPath);
IniWriter.SetValue("Paths", "ChromeExecutable", browserPath, ConfigPath);
}
2. Mã hóa tham số theo thuật toán XOR
Dựa trên logic JavaScript gốc từ frontend Douyin Shop, hàm mã hóa được tái hiện trong C# như sau:
public static class ParameterEncoder
{
public static string EncodeUtf8Xor5(string input)
{
if (string.IsNullOrWhiteSpace(input)) return string.Empty;
var bytes = Encoding.UTF8.GetBytes(input);
var hexParts = new List<string>();
foreach (byte b in bytes)
{
byte encoded = (byte)(b ^ 5);
hexParts.Add(encoded.ToString("x2"));
}
return string.Concat(hexParts);
}
}
Ví dụ sử dụng: ParameterEncoder.EncodeUtf8Xor5("123456789") → chuỗi hex tương ứng với từng byte đã XOR với 5.
3. Tự động hóa đăng nhập bằng Playwright
Chức năng đăng nhập được chia làm hai giai đoạn rõ ràng:
Đăng nhập tài khoản chính (chế độ có giao diện)
public async Task<(List<Cookie>, ApiResponse<ShopListResponse>)> LoginAsPrimary(string chromePath, string user, string pass)
{
using var playwright = await Playwright.CreateAsync();
var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
Headless = false,
ExecutablePath = chromePath,
Args = new[] { "--start-maximized" }
});
var context = await browser.NewContextAsync(new BrowserNewContextOptions { ViewportSize = new ViewportSize { Width = 1280, Height = 720 } });
var page = await context.NewPageAsync();
await page.GotoAsync("https://fxg.jinritemai.com/login/common");
await page.WaitForTimeoutAsync(2500);
// Chọn phương thức đăng nhập bằng email
await page.Locator("text=Email").ClickAsync();
await page.Locator("[placeholder=\"Nhập email\"]").FillAsync(user);
await page.Locator("[placeholder=\"Mật khẩu\"]").FillAsync(pass);
await page.Locator("input[type='checkbox']").CheckAsync();
await page.Locator("button:has-text('Đăng nhập')").ClickAsync();
await WaitForNavigationOrTimeout(page, 15_000); // Chờ chuyển hướng hoặc timeout
// Gọi API lấy danh sách tài khoản phụ
var apiResponse = await page.APIRequest.GetAsync(
"https://fxg.jinritemai.com/ecomauth/loginv1/get_pc_login_shop_infos?" +
"bus_child_type=0&bus_type=1&login_source=feige_pc_client&subject_aid=4966&sys_id=1");
var body = await apiResponse.TextAsync();
var shopList = JsonConvert.DeserializeObject<ApiResponse<ShopListResponse>>(body);
var cookies = await context.CookiesAsync();
return (cookies, shopList);
}
Đăng nhập tài khoản phụ (chế độ headless)
public async Task ExtractSubAccountCookies(string chromePath, List<Cookie> parentCookies, ShopInfo shop)
{
using var playwright = await Playwright.CreateAsync();
var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
Headless = true,
ExecutablePath = chromePath
});
var context = await browser.NewContextAsync();
await context.AddCookiesAsync(parentCookies);
var page = await context.NewPageAsync();
var formData = page.APIRequest.CreateFormData();
formData.Set("fp", "");
formData.Set("aid", "4272");
formData.Set("login_subject_uid", ParameterEncoder.EncodeUtf8Xor5(shop.SubjectId));
formData.Set("user_identity_id", ParameterEncoder.EncodeUtf8Xor5(shop.MemberId));
formData.Set("timestamp", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString());
var loginResponse = await page.APIRequest.PostAsync(
"https://doudian-sso.jinritemai.com/aff/subject/login/?subject_aid=4966",
new APIRequestContextOptions { Form = formData });
var responseText = await loginResponse.TextAsync();
var redirectObj = JsonConvert.DeserializeObject<RedirectResponse>(responseText);
var callbackUrl = redirectObj.RedirectUrl.Replace(
"https://fxg.jinritemai.com/index/login",
"https://fxg.jinritemai.com/passport/sso/aff/login/callback/"
);
await page.APIRequest.GetAsync($"{callbackUrl}&aid=4272&subject_aid=4966");
var finalCookies = await context.CookiesAsync();
await SaveToStorage(shop.ShopName, finalCookies);
}
4. Nhật ký hoạt động thời gian thực
Sử dụng ListBox để hiển thị dòng log theo thời gian thực:
private void AppendLog(string message)
{
var timestamp = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss");
lbxLogs.Items.Add($"[{timestamp}] {message}");
lbxLogs.TopIndex = lbxLogs.Items.Count - 1;
}
Kết luận
Công cụ này minh họa cách kết hợp C# với Playwright để xây dựng giải pháp tự động hóa đăng nhập và thu thập Cookie cho nhiều tài khoản phụ trên nền tảng Douyin Shop. Việc tách biệt rõ ràng giữa các lớp chức năng — cấu hình, mã hóa, điều khiển trình duyệt và xử lý API — giúp dễ dàng bảo trì và mở rộng. Các kỹ thuật như đọc file cấu hình dạng INI, tái hiện thuật toán mã hóa JS sang C#, và sử dụng cả chế độ headless và non-headless trong cùng một quy trình là những điểm đáng chú ý trong kiến trúc thiết kế.