如何在我的客户端应用程序中处理 third-party 站点上的重新验证

How to handle recaptcha on third-party site in my client application

我很好奇人们如何为 没有 public API 的网站构建 third-party 应用程序,但我真的找不到关于这个主题的任何教程.所以我决定试一试。我创建了一个简单的桌面应用程序,它使用 HttpClient 向我经常使用的站点发送 GET 请求,然后解析响应并在我的 WPF window 中显示数据。这种方法效果很好(可能是因为网站相当简单)。

但是,今天我尝试从不同的地方 运行 我的应用程序,并且我不断收到 403 错误以响应我的应用程序请求。事实证明,我使用的网络通过 VPN 服务器,而我尝试访问的站点使用 CloudFlare 作为保护层,这显然迫使 VPN 用户输入 reCaptcha 才能访问目标站点。

var baseAddress = new Uri("http://www.cloudflare.com");
using (var client = new HttpClient() { BaseAddress = baseAddress })
{
   var message = new HttpRequestMessage(HttpMethod.Get, "/");
   //this line returns CloudFlare home page if I use regualr network and reCaptcha page, when I use VPN
   var result = await client.SendAsync(message);
   //this line throws if I use VPN (403 Forbidden)
   result.EnsureSuccessStatusCode();
}

现在的问题是:在客户端应用程序中处理 CloudFlare 保护的正确方法是什么?我是否必须像网络浏览器一样在我的应用程序中显示 reCaptcha?我是否必须设置任何特定的 headers 才能获得正确的响应而不是 403?欢迎任何提示,因为这对我来说是一个全新的领域。

P.S。我用 C# 编写,因为这是我最熟悉的语言,但我不介意回答者使用任何其他语言,只要他们回答了问题。

我想,一种解决方法是在客户端应用程序之外的 Web 浏览器中处理验证码。

  1. 解析响应以查看它是否是验证码页面。
  2. 如果是 - 在浏览器中打开此页面。
  3. 让用户在那里解决验证码。
  4. 从浏览器的 cookie 存储中获取 CloudFlare cookie。您将需要 __cfduid(用户 ID)和 cf_clearance(解决验证码的证明)。
  5. 将这些 cookie 附加到客户端应用程序发送的请求中。
  6. 在接下来的 24 小时内正常使用应用程序(直到 CloudFlare cookie 过期)。

现在最难的部分是 (4)。手动复制粘贴 cookie 很容易使我问题中的代码片段与 VPN 一起工作:

var baseAddress = new Uri("http://www.cloudflare.com");
var cookieContainer = new CookieContainer();
using (var client = new HttpClient(new HttpClientHandler() { CookieContainer = cookieContainer } , true) { BaseAddress = baseAddress })
{
    var message = new HttpRequestMessage(HttpMethod.Get, "/");
    //I've also copy-pasted all the headers from browser
    //some of those might be optional
    message.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0");
    message.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
    message.Headers.Add("Accept-Encoding", "gzip, deflate" });
    message.Headers.Add("Accept-Language", "en-US;q=0.5,en;q=0.3");
    //adding CloudFlare cookies
    cookieContainer.Add(new Cookie("__cfduid", "copy-pasted-cookie-value", "/", "cloudflare.com"));
    cookieContainer.Add(new Cookie("cf_clearance", "copy-pasted-cookie-value", "/", "cloudflare.com"));
    var result = await client.SendAsync(message);
    result.EnsureSuccessStatusCode();
}

但我认为自动获取 cookie 的过程将是一项棘手的任务,因为不同的浏览器以不同的 and/or 格式存储 cookie。更不用说您需要使用外部浏览器才能使这种方法起作用,这真的很烦人。不过,还是要考虑一下。

对 "build third-party apps for sites with NO public APIs" 的回答是,即使某些软件供应商没有 public api,他们也有合作伙伴计划。

Netflix 就是一个很好的例子,他们曾经有一个 public api。一些在 Public Api 启用时开发的应用程序允许继续 api 使用。

在您的场景中,您的客户端应用充当网络爬虫(下载 html 内容并尝试解析信息)。您要做的是抓取 Cloudfare 数据,该数据不应由第三方应用程序(机器人)抓取。从 cloudfare 方面来看,他们做了正确的事情来拥有一个阻止自动请求的验证码。

此外,如果您尝试以高频率发送请求 (requests/sec),并且如果 Cloudfare 具有威胁检测机制,您的 IP 地址将被阻止。我假设他们已经确定了您尝试使用的 VPN 服务器 IP 地址并将其列入黑名单,这就是您收到 403 的原因。

基本上,您完全依赖于您尝试通过客户端应用程序访问的 Cloudfare 页面中的安全漏洞。这是我不推荐的黑客 Cloudfare(做一些 cloudfare 限制的事情)。

如果您有一个很棒的想法,最好联系他们的开发团队并进行讨论。

如果您仍然需要它,我在 2 年前遇到了同样的问题并提出了以下解决方案。

它使用 C# WebBrowser class 打开受 Cloudflare 保护的网页,等待大约 6 秒,以便 CloudFlare 保存清除 cookie,然后程序将 cookie 保存到磁盘。

您需要一个支持 javascript 的浏览器,例如 C# WebBrowser class,因为 Cloudflare 验证码页面需要 javascript 才能运行并倒计时以保存 cookie,任何其他尝试将失败。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Net;
using System.Threading;

namespace kek
{
    public partial class Form1 : Form
    {
        [DllImport("wininet.dll", SetLastError = true)]
        public static extern bool InternetGetCookieEx(string url, string cookieName, StringBuilder cookieData, ref int size, Int32 dwFlags, IntPtr lpReserved);

        private Uri Uri = new Uri("http://www.my-cloudflare-protected-website.com");
        private const Int32 InternetCookieHttponly = 0x2000;
        private const Int32 ERROR_INSUFFICIENT_BUFFER = 0x7A;

        public Form1()
        {
            InitializeComponent();

            webBrowser1.DocumentCompleted += new System.Windows.Forms.WebBrowserDocumentCompletedEventHandler(this.webBrowser1_DocumentCompleted);

            webBrowser1.Navigate(Uri, null, null, "User-Agent: kappaxdkappa\r\n"); //user-agent needs to be set another way if that doesnt work
        }

        private void webBrowser1_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            int waitTime = 0;

            if(webBrowser1.DocumentTitle.Contains("We are under attack")) //check what string identifies the unique cloudflare captcha page and put it here
            {
                waitTime = 6000;
            }

            Task.Run(async () =>
            {
                await Task.Delay(waitTime); //cookie can be saved right away, but the waiting period might not have passed yet

                String cloudflareCookie = GetCookie(Uri, "cf_clearance");

                if (!String.IsNullOrEmpty(cloudflareCookie))
                {
                    System.IO.StreamWriter file = new System.IO.StreamWriter("c:\CFcookie.blob"); //save to %appdata%\MyProgram\Cookies\clearence.blob
                    file.Write(cloudflareCookie);
                    file.Close();
                }
            });
        }

        String GetCookie(Uri uri, String cookieName)
        {
            int datasize = 0;
            StringBuilder cookieData = new StringBuilder(datasize);

            InternetGetCookieEx(uri.ToString(), cookieName, cookieData, ref datasize, InternetCookieHttponly, IntPtr.Zero);

            if (Marshal.GetLastWin32Error() == ERROR_INSUFFICIENT_BUFFER && datasize > 0)
            {
                cookieData = new StringBuilder(datasize);
                if (InternetGetCookieEx(uri.ToString(), cookieName, cookieData, ref datasize, InternetCookieHttponly, IntPtr.Zero))
                {
                    if (cookieData.Length > 0)
                    {
                        CookieContainer container = new CookieContainer();
                        container.SetCookies(uri, cookieData.ToString());

                        return container.GetCookieHeader(uri);
                    }
                }
            }

            return String.Empty;
        }
    }
}

一些注意事项:

  • 使用更好的用户代理
  • cookie 也被保存到磁盘,因为我需要它做某事 别的。不确定内置浏览器是否为下一个保存了 cookie 时间,但如果没有,您可以通过这种方式重新加载它。
  • 将 "We are under attack" 短语更改为标识的短语 您试图绕过的 CF 验证码页面。
  • __cfduid cookie 不需要 afaik

编辑:抱歉,在阅读了此处的其他答案后,我非常专注于 Cloudflare 本身,以至于我没有注意到您需要绕过有时在 Cloudflare 页面上找到的 Recaptcha。我的代码可以在浏览器和 cookie 部分为您提供一些帮助,但您将很难解决 Recaptcha,至少现在是这样。几周前,他们变得更加困难。我可以推荐编译你自己的 Firefox 版本,然后通过点击复选框自动解决验证码。如果你没有得到那个简单的验证码,那么你需要为用户显示它。请注意,您还需要随机化浏览器的行为以及点击复选框的方式,否则它会将您检测为机器人。