Как решать капчу в code step Pipedream (Node.js)

pipedream captcha - How to Solve CAPTCHAs in a Pipedream Code Step (Node.js)

Решение капчи в Pipedream проще, чем та же задача в Zapier или Make.com, потому что code step это настоящая среда Node.js v20 с импортом npm-пакетов. Вы ничего не устанавливаете, пишете обычный JavaScript, и SDK CapSkip работает ровно так же, как на вашем ноутбуке. Отличий два, и оба про то, где выполняется код. Pipedream выполняет его в собственном облаке, поэтому решатель должен быть доступен из интернета. А у запуска сценария есть лимит времени, который короче решения reCAPTCHA, и именно он определяет, пишете вы один шаг или два.

Что понадобится

  • Сценарий Pipedream с code step. Ниже используется среда Node.js. Версия на Python приведена дальше.
  • CapSkip, запущенный в режиме Server на машине, доступной из интернета, со статическим публичным IP.
  • Sitekey и URL страницы сайта, который вы автоматизируете.
  • Две переменные окружения Pipedream с адресом решателя и его ключом.

Почему адрес loopback здесь не работает

Сценарии Pipedream выполняются на собственной инфраструктуре сервиса, в сети AWS us-east-1. Запрос к 127.0.0.1 изнутри code step ведёт в контейнер, где выполняется шаг, а не к вашему столу. Там никто не слушает, и SDK выбрасывает NetworkException.

У CapSkip два режима подключения, и ответ здесь второй. Local слушает 127.0.0.1 и обслуживает только это устройство, что правильно, когда автоматизация и решатель стоят на одной машине. Server слушает ваш сетевой адрес или публичный IP, поэтому облачная платформа может обратиться к той же машине Windows по API. Рекомендуется статический публичный IP, потому что домашний адрес, который меняется, сломает сценарий в три часа ночи и ничего вам не скажет. Оба режима настраиваются в разделе Настройки подключения.

Режим Server меняет только то, где работает решатель, и больше ничего. Это по-прежнему ваше железо и по-прежнему без счётчика, поэтому сценарий, который срабатывает десять тысяч раз в месяц, стоит столько же, сколько срабатывающий десять раз.

Шаг 1: положите адрес в переменную окружения

Не прописывайте публичный IP прямо в шаге. В Pipedream есть переменные окружения рабочего пространства, и code step читает их из обычного окружения процесса. Создайте две.

Имя переменнойЗначение
Переменная с именем CAPSKIP_HOSTПубличный IP машины с решателем, без схемы и без порта
Переменная с именем CAPSKIP_API_KEYКлюч, который вы сгенерировали в приложении CapSkip для этого сценария

Дайте этому сценарию отдельный ключ вместо общего. Отзыв ключа, который утёк в общее рабочее пространство, не должен утаскивать за собой остальную автоматизацию.

Шаг 2: всё решение в одном code step

Pipedream устанавливает npm-пакет в тот момент, когда вы его импортируете, поэтому отдельного шага установки и файла пакета нет. В строке импорта же задаётся и версия, а фиксировать её стоит на всём, что работает без присмотра.

// npm install capskip - Pipedream installs it from this import.
// The package is CommonJS, so take the default and destructure.
import capskip from "capskip";

const { CapSkip } = capskip;

export default defineComponent({
  async run({ steps, $ }) {
    const solver = new CapSkip({
      host: process.env.CAPSKIP_HOST,
      port: 8080,
      apiKey: process.env.CAPSKIP_API_KEY,
    });

    const result = await solver.recaptcha(
      "YOUR_SITEKEY",
      "https://example.com/page-with-recaptcha"
    );

    return result.code;   // the token, for the next step
  },
});

Вот и всё. Все варианты reCAPTCHA это один и тот же метод с объектом настроек: invisible со значением 1, enterprise со значением 1 или version со значением v3 вместе с именем действия. У Turnstile и GeeTest есть свои методы такой же формы, а полный список параметров приведён в документации CapSkip API.

Всё, что возвращает шаг, попадает в экспорты сценария, поэтому следующий шаг читает токен по имени самого шага. Если хотите дать значению метку, используйте хелпер экспорта.

// A named export reads better downstream than a bare return.
$.export("token", result.code);

// The next step then reads steps.solve_captcha.token

Таймаут выполнения и момент, когда одного шага перестаёт хватать

Вот ограничение, которое определяет всё остальное. У запуска в Pipedream лимит времени по умолчанию составляет 30 секунд для триггеров HTTP и email и 60 секунд для триггеров по расписанию. Его можно поднять в настройках сценария: до 300 секунд на бесплатных тарифах и до 750 секунд на платных.

Решение reCAPTCHA v2 обычно укладывается заметно быстрее 30 секунд, но обычно это не всегда, а собственный предел SDK задан в recaptchaTimeout и равен 300 секундам. Поэтому вариант из одного шага выше надёжен ровно настолько, насколько выставлен ваш таймаут. Если лимит сценария меньше времени решения, выполнение убивают на середине опроса, и вы получаете упавший запуск без всякой полезной информации.

Ваша ситуацияЧто делать
Небольшие объёмы, и вы можете поднять лимит выполнения до 300 секундОставьте вариант из одного шага. Поднимите таймаут в настройках сценария
Большие объёмы или вы платите за время выполненияРазделите шаг и используйте хелпер rerun, описанный ниже
Страницы проверки Turnstile или GeeTest, которые занимают больше времениРазделите шаг. Именно эти типы чаще всего выходят за короткий лимит

Шаг 3: разделите его с помощью хелпера rerun

В Pipedream есть примитив опроса, которого нет у большинства платформ автоматизации. Хелпер flow.rerun завершает текущий шаг, ждёт и запускает тот же шаг снова с переданным вами состоянием. Пока идёт ожидание, сценарий не выполняется, поэтому медленное решение ничего вам не стоит и не может упереться в лимит.

Работает это благодаря трём вещам. Счётчик запусков начинается с 1 и увеличивается при каждом перезапуске. Объект контекста, который вы передаёте, доступен для чтения на следующем проходе. А превышение предела повторов переводит сценарий к следующему шагу, а не роняет его, поэтому бросайте исключение, если вам нужно другое поведение.

// No SDK here. The raw endpoints suit a step that exits
// between polls, because nothing has to stay in memory.
const MAX_RETRIES = 20;
const DELAY = 15000;   // 15s, the recommended first wait for v2

export default defineComponent({
  async run({ steps, $ }) {
    const { run } = $.context;
    const base = `http://${process.env.CAPSKIP_HOST}:8080`;
    const key = process.env.CAPSKIP_API_KEY;

    if (run.runs === 1) {
      const params = new URLSearchParams({
        key,
        method: "userrecaptcha",
        googlekey: "YOUR_SITEKEY",
        pageurl: "https://example.com/page-with-recaptcha",
        json: "1",
      });
      const submitted = await fetch(`${base}/in.php?${params}`);
      const { request: id } = await submitted.json();

      // The id survives into the next run through the context.
      return $.flow.rerun(DELAY, { id }, MAX_RETRIES);
    }

    const { id } = $.context.run.context;
    const polled = await fetch(
      `${base}/res.php?key=${key}&action=get&id=${id}&json=1`
    );
    const data = await polled.json();

    if (data.request !== "CAPCHA_NOT_READY") {
      return data.request;   // the token
    }
    if (run.runs === MAX_RETRIES + 1) {
      throw new Error("Solve did not finish in time");
    }
    return $.flow.rerun(DELAY, { id }, MAX_RETRIES);
  },
});

Написание этого ответа об ожидании многих сбивает с толку. Это CAPCHA_NOT_READY, без буквы T, и это не ошибка: он означает, что ответ ещё не готов и нужно опросить снова. Считать его сбоем это самая частая ошибка в самописном цикле опроса, и на эту тему есть подробный разбор ответа CAPCHA_NOT_READY.

Ещё одна деталь про этот эндпоинт. Результат можно прочитать ровно один раз. Если вы залогируете тело ответа, а затем прочитаете его снова на более позднем шаге, второе чтение вернёт пустоту и будет выглядеть как неудачное решение.

Отправляйте токен сразу

Токен reCAPTCHA живёт около двух минут. В сценарии потерять его проще, чем кажется, потому что шаг задержки, медленный HTTP-запрос или слишком долгое ожидание перед перезапуском съедают один и тот же бюджет. Поставьте шаг, который отправляет токен, сразу после шага, который его получил, и не накапливайте токены на потом. Полная картина в руководстве по сроку действия токена reCAPTCHA.

Версия на Python

Pipedream также выполняет code step на Python 3.12 и точно так же ставит пакеты pip по вашим импортам. SDK сам не читает переменные окружения, поэтому шаг ниже читает CAPSKIP_HOST, CAPSKIP_PORT и CAPSKIP_API_KEY и передаёт их в конструктор.

# pip install capskip - Pipedream installs it from this import
import os
from capskip import CapSkip

def handler(pd: "pipedream"):
    # Your Pipedream environment variables. The SDK does not read
    # them by itself, so pass them to the constructor.
    solver = CapSkip(
        host=os.environ["CAPSKIP_HOST"],
        port=int(os.environ["CAPSKIP_PORT"]),
        apiKey=os.environ["CAPSKIP_API_KEY"],
    )

    page_url = pd.steps["trigger"]["event"]["body"]["url"]
    result = solver.recaptcha(sitekey="YOUR_SITEKEY", url=page_url)

    # Downstream steps read pd.steps["solve"]["token"]
    return {"token": result["code"]}

Если пойдёте этим путём, задайте CAPSKIP_PORT со значением 8080 вместе с двумя остальными. Одно ограничение, о котором стоит знать заранее: хелперы rerun и delay описаны в документации для Node.js, поэтому шаг на Python подходит только для варианта в один проход. Если вам нужна схема с разделённым опросом, напишите этот шаг на Node.

Защита порта, который вы только что открыли

Режим Server выставляет слушателя в публичный интернет, поэтому относитесь к нему как к любому другому открытому сервису. Три вещи стоит сделать в первый же день.

  • Включите проверку API-ключа в приложении CapSkip и дайте этому сценарию отдельный ключ. Без неё в качестве ключа принимается любая строка.
  • Закройте решатель правилом фаервола вместо того, чтобы держать 8080 открытым для всех.
  • Решите, как вы сформулируете это правило. Обычный исходящий трафик Pipedream идёт из стандартных диапазонов AWS us-east-1, которые слишком широки, чтобы их имело смысл вносить в белый список. Если нужно узкое правило, Pipedream предлагает VPC с выделенным статическим исходящим IP на рабочее пространство, и в белый список вы вносите именно этот адрес.

Частые ошибки и что они означают

Что вы видитеПричинаИсправить
NetworkException или отказ в соединении на порту 8080Решатель в режиме Local или переменная с хостом задана неверноПереключитесь в режим Server и укажите в переменной с хостом публичный IP
Выполнение убивают на середине решенияЛимит времени сценария меньше, чем заняло решениеПоднимите его в настройках сценария или перейдите к варианту с rerun
Шаг возвращает как результат буквальный ответ об ожиданииВетка опроса приняла CAPCHA_NOT_READY за ответПроверяйте это значение явно и перезапускайте шаг, а не возвращайте его
ERROR_WRONG_USER_KEY от сырого эндпоинтаПеременная с ключом пуста, поэтому была отправлена пустая строкаПроверьте имя переменной окружения, включая регистр
При втором чтении токен пустРезультат можно прочитать только один разПрочитайте его один раз, сохраните в переменную и передавайте дальше её
Целевой сайт отклоняет действительный токенОн истёк между решением и отправкойПереставьте шаг отправки сразу после шага решения
Cannot use import statement outside a moduleШаг смешивает import и requireВыберите один стиль на шаг. Code step является ES-модулем

FAQ

Можно ли использовать CapSkip из Pipedream, ничего не открывая наружу?

Напрямую нет, потому что вычисления Pipedream принадлежат Pipedream. Кто-то должен принимать входящее соединение. Простой ответ это режим Server с проверкой ключа и правилом фаервола. Если ваша политика полностью запрещает открытый порт, альтернатива в том, чтобы держать работу с капчей на подконтрольной вам машине и заставить Pipedream обращаться к её собственному эндпоинту, но это перемещает открытую точку, а не убирает её.

Считается ли перезапуск отдельным выполнением?

Шаг запускается снова, поэтому код выполняется больше одного раза, именно для этого и существует счётчик. Смысл хелпера в том, что сценарий не держится открытым во время ожидания, поэтому медленное решение не выталкивает одно выполнение за лимит времени. Если для вас важен биллинг, сверьтесь с правилами учёта на вашем тарифе, но проблема таймаута решается в любом случае.

Что использовать: SDK или сырые эндпоинты?

SDK, когда всю работу делает один шаг, потому что он берёт на себя опрос, наращивает паузу начиная с 250 миллисекунд вместо сна с постоянным интервалом и выдаёт типизированные ошибки. Сырые эндпоинты, когда вы разделяете работу на перезапуски, потому что между опросами шаг завершается и в памяти не остаётся клиента, который держал бы задачу. Оба варианта обращаются к одному и тому же сервису на одном порту.

Чем это отличается от того же в n8n или Zapier?

В основном средой выполнения. Pipedream даёт настоящий Node.js с импортом npm-пакетов, поэтому SDK встаёт сразу, а хелпер rerun аккуратно справляется с медленными решениями. Code step в Zapier не умеет ставить пакеты, поэтому руководство по капче в Zapier построено вокруг его таймаута. У Make.com вообще нет code step, и именно поэтому пошаговое руководство по Make.com собрано из HTTP-модулей. n8n можно развернуть у себя рядом с решателем, поэтому руководство по n8n часто может оставить режим Local.

Коротко

Переведите CapSkip в режим Server, храните его адрес и ключ в переменных окружения Pipedream и импортируйте SDK прямо в code step на Node.js. Если лимит времени сценария с запасом больше вашего самого медленного решения, вся интеграция это один шаг. Если нет, разделите шаг: отправьте задачу на сырой эндпоинт, передайте её id хелперу rerun и опрашивайте результат при следующем заходе. Отправляйте токен сразу после получения, потому что он истекает примерно через две минуты.

Сторона Node.js разобрана на странице сервиса распознавания капчи для Node.js, капча с флажком на странице сервиса распознавания reCAPTCHA v2, а эквивалентные вызовы в Python, PHP и C# на на странице SDK для распознавания капчи.

Стоит знать, прежде чем встраивать это в то, что работает целый день: CapSkip выполняет обход капчи на железе, которым вы уже владеете, поэтому сценарий, который срабатывает постоянно, и сценарий, который срабатывает изредка, стоят совершенно одинаково.