Merhaba! Bir süredir kullanıcı adı ve şifre ile giriş yapılan bazı sitelerden fiyat ve stok durumu çekme üzerine çalışıyorum. Çoğu site için her şey sorunsuz ilerledi, ancak bir tanesi girişte CAPTCHA doğrulaması eklemiş ve bu noktada takıldım. CAPTCHA’yı çözmek için ücretli servisler mevcut, ama hem maliyetten kaçınmak hem de başka bir sisteme bağımlı olmak istemediğim için kendi çözümümü geliştirmeye karar verdim.
ASP.NET Core MVC uygulamamda Playwright ve SignalR kullanarak bir manuel CAPTCHA çözümü geliştirdim.
Kurgum şu şekilde: Uygulamamda, giriş gerektiren siteler için bir “Manuel Login” butonu ekledim. Eğer kullanıcı bir arama yaptığında herhangi bir siteye giriş yapılamadı uyarısı alırsa, bu buton sayesinde manuel olarak bağlanabiliyor. İşleyiş şu şekilde:
- İlgili siteye gidiliyor.
- Kullanıcı adı ve şifre alanları otomatik dolduruluyor.
- Eğer site CAPTCHA doğrulaması istiyorsa, ekran görüntüsü alınıyor ve kullanıcıya gösteriliyor.
- Kullanıcı, CAPTCHA’yı çözmek için bir metin kutucuğuna gördüğü değerleri yazıyor.
- “Devam Et” butonuna basıldığında giriş tamamlanıyor.
Not: Giriş tamamlandıktan sonra, sitelerin arama bölümlerinde genellikle tekrar CAPTCHA istenmiyor. En azından benim deneyimimde bu durumla karşılaşmadım.
SignalR ile Kullanıcı Etkileşimi
Kullanıcıdan CAPTCHA çözümünü almak ve işlemi devam ettirmek için SignalR kütüphanesini kullandım. SignalR, istemci-sunucu arasında gerçek zamanlı iletişim sağlayarak bu tür senaryolar için oldukça kullanışlı.
Kod Yapısı
Aşağıda, çözümümün temel parçalarını oluşturan kodlardan CaptchaHub.cs dosyası şu şekilde:
public class CaptchaHub : Hub
{
private readonly PlaywrightServis _scraper;
public CaptchaHub(PlaywrightServis scraper)
{
_scraper = scraper;
}
public override async Task OnConnectedAsync()
{
Console.WriteLine($"[CaptchaHub] Yeni client bağlandı: {Context.ConnectionId}");
await base.OnConnectedAsync();
}
public async Task RequestManualLogin(string siteId, string connectionId)
{
try
{
Console.WriteLine($"[CaptchaHub] RequestManualLogin çağrıldı. siteId={siteId}, connectionId={connectionId}");
await _scraper.StartManualLoginAsync(siteId, connectionId); // connectionId'yi ilet.
}
catch (Exception ex)
{
Console.WriteLine($"[CaptchaHub] Hata: {ex.Message}");
await Clients.Caller.SendAsync("ManualLoginError", siteId, connectionId, ex.Message);
}
}
public async Task SendCaptchaSolution(string siteId, string jobId, string solution)
{
Console.WriteLine($"[CaptchaHub] SendCaptchaSolution çağrıldı. jobId={jobId}");
await _scraper.HandleCaptchaSolutionAsync(siteId, jobId, solution);
}
}
Bu hub, istemci bağlantılarını yönetiyor ve CAPTCHA çözüm sürecini koordine ediyor. RequestManualLogin metodu, manuel giriş işlemini başlatırken, SendCaptchaSolution metodu kullanıcıdan gelen CAPTCHA çözümünü işliyor.
İstemci Tarafı: JavaScript ve SignalR
İstemci tarafında, SignalR bağlantısını kuran ve kullanıcı arayüzünü yöneten JavaScript kodunu index-v1.0.js dosyasında topladım. Aşağıda, bu dosyanın içeriği yer alıyor:
let connection = null;
function startSignalRConnection() {
// Bağlantı zaten varsa, yeni bir tane oluşturmaya gerek yok.
if (connection && connection.state === signalR.HubConnectionState.Connected) {
console.log("SignalR bağlantısı zaten mevcut.");
return Promise.resolve(connection);
}
// Yeni bir bağlantı nesnesi oluştur.
connection = new signalR.HubConnectionBuilder()
.withUrl("/CaptchaHub")
.withAutomaticReconnect()
.build();
// Sunucudan gelen mesajları dinle.
connection.on("ReceiveCaptchaPrompt", (jobId, siteName, base64Image, siteId) => {
Swal.close();
setTimeout(() => {
Swal.fire({
title: `${siteName} için CAPTCHA`,
html: `
<img src="data:image/png;base64,${base64Image}" style="max-width:100%; max-height:300px;" />
<input type="text" id="captchaSolution" class="swal2-input" placeholder="CAPTCHA metnini girin" />
`,
showCancelButton: true,
confirmButtonText: 'Gönder',
preConfirm: () => {
const solution = document.getElementById('captchaSolution').value;
if (!solution) {
Swal.showValidationMessage("Lütfen CAPTCHA metnini girin.");
}
return { solution };
}
}).then((result) => {
if (result.isConfirmed) {
// CAPTCHA çözümünü sunucuya gönder.
connection.invoke("SendCaptchaSolution", siteId, jobId, result.value.solution)
.catch(err => Swal.fire('Hata', 'CAPTCHA gönderilemedi: ' + err, 'error'));
}
});
}, 1500);
});
connection.on("LoginComplete", (jobId, success, siteName, error) => {
if (success) {
Swal.fire('Başarılı', `${siteName} için login tamamlandı!`, 'success');
} else {
Swal.fire('Hata', `${siteName} login hatası: ${error}`, 'error');
}
});
connection.on("ManualLoginError", (siteId, jobId, error) => {
Swal.fire('Hata', 'Manuel login başlatılırken hata oluştu: ' + error, 'error');
});
// Bağlantıyı başlat ve Promise ile geri dön.
return connection.start()
.then(() => {
console.log("SignalR bağlantısı başarıyla kuruldu.");
return connection; // Bağlantı nesnesini döndür.
})
.catch(err => {
console.error("SignalR bağlantı hatası: " + err);
return Promise.reject(err);
});
}
// "Manuel Login" butonuna tıklama olayını dinle.
$("#jqxListBox").on('click', '.jqx-listbox-button', function (event) {
event.stopImmediatePropagation();
const siteId = String($(event.target).data('site-id'));
if (siteId) {
startSignalRConnection().then(conn => {
const connectionId = conn.connectionId; // Tarayıcının benzersiz bağlantı kimliğini al.
console.log("Manuel login isteği gönderiliyor. SiteId:", siteId, "ConnectionId:", connectionId);
// Invoke metodu ile bağlantı kimliğini sunucuya gönder.
conn.invoke("RequestManualLogin", siteId, connectionId)
.then(() => {
Swal.fire({
toast: true,
position: 'top-end',
icon: 'info',
title: 'Manuel login isteği gönderildi',
showConfirmButton: false,
timer: 2000
});
})
.catch(err => {
console.error("Manuel login hatası:", err);
Swal.fire('Hata', 'Manuel login başlatılamadı: ' + err, 'error');
});
}).catch(err => {
console.error("SignalR bağlantı hatası:", err);
Swal.fire('Hata', 'SignalR bağlantısı kurulamadı: ' + err, 'error');
});
} else {
console.warn("Site ID alınamadı.");
Swal.fire({
icon: 'warning',
title: 'Uyarı',
text: 'Site ID alınamadı, lütfen tekrar deneyin.',
confirmButtonText: 'Tamam'
});
}
});
Bu çözümle, CAPTCHA doğrulamasını manuel olarak aşmış oldum. Eğer siz de benzer bir sorunla karşılaşıyorsanız, bu yöntemi deneyebilir ve kendi ihtiyaçlarınıza göre özelleştirebilirsiniz.
Selamlar!
