СправочникИнструменты › A-Parser и прокси

A-Parser и прокси: списки адресов, потоки и лимиты заданий

Баннер iprazon: приватный серверный пул IPv4 и SOCKS5 под многопоточные задания

A-Parser это многопоточный сборщик данных, который работает заданиями. Одно задание содержит парсер, список запросов, набор опций и шаблон вывода. Движок раздаёт запросы свободным потокам, каждый поток уходит наружу через прокси и возвращает ответ, а строки результата складываются в файл. Адреса подключаются отдельным слоем: внутри программы живут чекеры списка, они держат рабочий список в памяти и выдают потоку живую строку на время одного запроса. Дальше я разбираю, как устроены эти чекеры, чем общий список отличается от списка под конкретное задание, в каких форматах подаётся пул, как считать число потоков от целевого сайта, что даёт повтор запроса при неудаче, какие ставить таймауты, зачем разводить парсеры выдачи и парсеры сайтов по разным заданиям и что вписать в шаблон вывода, чтобы прогон читался по журналу.

Задание в A-Parser: из чего складывается нагрузка на пул

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

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

Схема прохождения одного запроса от очереди задания до внешнего адреса

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

Отсюда простое правило моего планирования: нагрузку я считаю на адрес, а число потоков вывожу уже из неё. Обратный порядок, когда сначала ставят красивую тысячу в поле потоков и потом смотрят, что получится, даёт кашу из таймаутов на первых же минутах прогона. Я так делал в самом начале работы с программой и потратил два вечера на разбор журнала, где половина строк оказалась пустой.

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

Проверялки списка адресов внутри программы

Чекер это отдельный объект в настройках прокси. У него есть имя, источник списка, формат строк, интервал перепроверки, число потоков проверки, таймаут и адрес, по которому проверяется работоспособность. Заданий может быть сорок, чекеров при этом три или четыре, и связь между ними задаётся выбором в самом задании.

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

Настройки -> Прокси -> Чекер "serp"
  Источник:            ссылка на файл списка
  Формат строк:        IP:PORT:LOGIN:PASS
  Интервал проверки:   5 минут
  Потоков проверки:    30
  Таймаут проверки:    6 сек
  Проверочный адрес:   http://www.google.com/robots.txt
  Ожидаемый код:       200

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

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

ЧекерИсточник спискаИнтервалПотоков проверкиПроверочный адресТаймаут
serpСсылка на файл в кабинете5 минут30Лёгкая страница поисковой системы6 сек
pagesСсылка на файл в кабинете15 минут20Своя страница на своём сервере8 сек
authЛокальный файл30 минут5Страница входа целевой площадки12 сек
probeЛокальный файл10 минут40Сервис отображения адреса выхода5 сек

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

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

Общий список и список под конкретное задание

Первые месяцы я работал с одним чекером на всё. Список один, обновляется по ссылке, все задания берут строки из общей кучи. Схема выглядит аккуратной ровно до того дня, когда рядом с парсером выдачи запускается сбор десяти тысяч страниц каталога.

Сравнение одного общего чекера и отдельных чекеров под каждый профиль нагрузки

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

Разделение чекеров снимает это целиком. Каждое задание в настройках получает свой набор: парсер выдачи работает через чекер serp, сбор страниц через pages, задания с авторизацией через auth. Списки при этом могут пересекаться по строкам, потому что пул общий, но рабочие списки и интервалы проверки у чекеров свои, и просадка остаётся внутри своего профиля.

ПризнакОдин чекер на все заданияЧекер под каждый профиль
Нагрузка на строкуСкладывается из всех прогоновОграничена одним профилем
Проверочный адресОбщий, усреднённыйСовпадает с целевым источником
Поиск причины просадкиПо всему журналу разомПо журналу одного чекера
Остановка одного заданияСписка не разгружаетСразу снимает нагрузку профиля
Настройка интервалаКомпромисс между задачамиСвоя цифра под каждый профиль
Что видно в отчётеОбщая доля отказовДоля отказов по каждому профилю

Есть и второй уровень разделения, про который забывают. Внутри задания набор сужается до одного конкретного чекера, и такая настройка перекрывает глобальную. Я пользуюсь этим, когда одно задание из десятка требует особого обращения: например, сбор с площадки, которая отвечает медленно и держит соединение по двадцать секунд. Такое задание получает собственный чекер с длинным таймаутом, остальные девять продолжают работать в обычном темпе.

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

Форматы подачи списка: ссылка на файл и строка с логином

Источник списка в чекере задаётся тремя способами: локальный файл, папка с файлами, ссылка. Ссылку я использую почти всегда. Кабинет отдаёт список по постоянному адресу, чекер забирает его сам на каждом круге, и обновление пула доезжает до программы без моего участия. Локальный файл я оставляю для служебных чекеров и для строк с авторизацией, которые меняю руками.

Формат строки программа распознаёт по числу полей. Короткая запись из адреса и порта работает при доступе по привязке машины: сервер узнаёт клиента по входящему адресу и пропускает соединение молча. Длинная запись добавляет пару логина в конец строки и работает откуда угодно, включая случайную машину коллеги. Протокол пишется префиксом, и без него строка читается как обычный HTTP.

198.51.100.24:8080
198.51.100.25:8080:ap7k:Rm4xQz9t
socks5://198.51.100.26:1080
socks5://198.51.100.27:1080:ap7k:Rm4xQz9t

Кабинет отдаёт список в обоих видах, коротком и с парой логина, отдельными ссылками, поэтому подстановка занимает пару минут: копирую ссылку нужного формата в поле источника и жму проверку. Когда мне нужен именно транспорт SOCKS5 под задания с нестандартными портами, я беру купить SOCKS5 списком под задания и указываю префикс протокола прямо в строках файла.

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

Перед тем как отдать новый список чекеру, я прогоняю его коротким скриптом. Программа умеет проверять строки сама, её проверка отвечает на вопрос о доступности порта. Мне же нужно увидеть адрес выхода и время ответа по каждой строке, чтобы понять, чем список отличается от прошлого.

while read -r line; do
  start=$(date +%s%N)
  out=$(curl -s -m 10 --proxy "http://$line" https://api.ipify.org)
  ms=$(( ($(date +%s%N) - start) / 1000000 ))
  printf '%-24s %6s мс  %s\n' "${line%%:*}" "$ms" "${out:-нет ответа}"
done < pool.txt | sort -k2 -n

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

Отдельная мелочь про кодировку и переносы строк. Файл, скачанный из кабинета и открытый в редакторе под Windows, иногда сохраняется с возвратом каретки, и чекер читает такую строку вместе с невидимым символом на конце. Порт при этом разбирается неправильно, и весь список показывает нули живых строк. Проверяется это одной командой и лечится пересохранением файла в юниксовом переносе.

Расчёт потоков: от целевого сайта к цифре в поле

Число потоков в задании я вывожу из двух величин: сколько строк в рабочем списке чекера и сколько одновременных соединений принимает целевой сайт с одной строки. Второе число подбирается опытом, первое известно заранее. Произведение и есть цифра, которую я вписываю в поле.

Множитель на строку у разных задач отличается в двадцать раз, и в этом основная причина, по которой одна универсальная настройка потоков не работает. Поисковая система принимает одно соединение и сразу показывает проверку на втором. Обычный сайт с каталогом товаров спокойно держит четыре и начинает подтормаживать на шести. Проверка кодов ответа по большому списку ссылок укладывается в десять соединений на строку, потому что каждый запрос коротким методом HEAD весит считанные байты.

Столбцы с числом потоков под разные типы заданий на пуле из полутора сотен адресов

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

Тип заданияСоединений на строкуСтрок в спискеПотоков в заданииЧто ограничивает
Парсер поисковой выдачи1150150Порог источника по темпу
Сбор страниц каталога4150600Скорость ответа сайта
Проверка кодов ответа101501500Пропускная способность канала
Сбор с авторизацией0,515075Длительность сессии
Разбор карточек товара3150450Вес страницы в килобайтах
Съём заголовков по списку доменов81501200Число открытых сокетов на машине

Подбор множителя я делаю коротким тестом до основного прогона. Беру двадцать строк списка, ставлю сорок потоков, гоняю тысячу запросов и смотрю долю отказов. Ноль отказов означает, что можно поднимать. Три процента означают, что цифра рабочая. Десять процентов и выше означают перебор, и я снимаю потоки на четверть. Тест занимает три минуты и экономит ночь.

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

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

Лимит потоков пакета и две привязки

Со стороны пула лимит стоит на числе одновременных соединений. Потолок обычного пакета это 1000 потоков, у корпоративного он поднят до 3000. Цифра относится ко всему, что уходит через пул с моей стороны, включая потоки проверки чекеров.

Складывать пакеты между собой по потокам нельзя. Покупка второго обычного пакета оставляет потолок там же, где он стоял, на тысяче, и это первое, что я объясняю коллегам при планировании расширения. Дорога к трём тысячам одна, через корпоративный пакет.

Вторая цифра касается привязок. Пакет включает два адреса подключения со свободной заменой, и когда заняты обе привязки, потолок делится поровну: по 500 потоков на сторону у обычного пакета, по 1500 у корпоративного. Схема с двумя привязками у меня стандартная: рабочая машина с интерфейсом программы и сервер, где крутятся ночные задания. Половина лимита на каждую сторону закрывает и то и другое с запасом.

Дальше простая арифметика по моим цифрам из прошлой таблицы. Парсер выдачи на 150 потоков, сбор каталога на 600 и служебные чекеры на 95 дают 845 потоков одновременно. При одной привязке это укладывается в обычный пакет. При двух привязках, когда на сервере остаётся 500, тот же набор уже перебирает, и я разношу задания по времени: каталог идёт ночью, выдача днём. Проверка кодов ответа на 1500 потоков живёт только на корпоративном пакете при одной привязке.

Задание "serp-msk"       150 потоков   чекер serp
Задание "catalog-pages"  600 потоков   чекер pages
Задание "auth-cards"      75 потоков   чекер auth
Чекеры (проверка)         95 потоков   serp 30, pages 20, auth 5, probe 40
                        --------------
Одновременно             920 потоков   при лимите 1000 на пакет

Такой расчёт я держу в текстовом файле рядом с пресетами и правлю его каждый раз, когда добавляю задание. Восемьдесят потоков запаса выглядят скромно, и запас там намеренно небольшой: программа сама снижает фактическое число активных соединений, когда очередь пустеет к концу прогона. Реальная загрузка редко достигает расчётной цифры, но планировать я предпочитаю по верхней границе. Адреса под такую схему я беру там, где список выдаётся сразу под многопоточную работу: прокси для A-Parser с выдачей списком подставляются в чекер ссылкой и обновляются без ручной пересборки файла.

Превышение лимита выглядит в журнале узнаваемо. Часть соединений начинает отваливаться на этапе установки, код ответа при этом пустой, а время до отказа стабильно короткое, доли секунды. Отличить это от проблем целевого сайта просто: отказы приходят разом по всем заданиям, включая те, что ходят на совершенно другие площадки.

Повторы запроса при неудаче

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

Я держу три попытки на заданиях по сбору выдачи и пять на сборе произвольных страниц. Разница объясняется тем, что случайный отказ на большом сайте встречается чаще, а порог источника выдачи после трёх подряд неудач обычно означает системную проблему с настройкой темпа. Больше пяти попыток я не ставил никогда: очередь начинает вращаться вокруг десятка проблемных запросов, а остальные ждут свободных потоков.

Что считать неудачей, настраивается отдельно, и это самая полезная опция во всём блоке. По умолчанию неудачей считается отсутствие ответа. Реальный сбор требует более широкой проверки: страница проверки приходит с кодом 200 и внешне выглядит успешным ответом, а внутри пусто. Я задаю проверку по содержимому и по коду одновременно.

ОтветЧто за ним стоитРеакция задания
Нет ответа за таймаутСтрока списка перегружена или медленнаПовтор с другой строкой, счётчик попыток растёт
Код 403Источник ограничил темп с этой точки выходаПовтор с другой строкой, пауза перед следующим
Код 429Превышена частота обращений с точки выходаПовтор через увеличенный интервал
Код 407Пара логина в списке разошлась с настройкой пулаОбновляю список из кабинета, задание на паузу
Код 200, страница проверкиСработала защита источникаПовтор с другой строкой по правилу содержимого
Код 200, пустое телоСоединение оборвалось на середине ответаПовтор, при серии снижаю потоки
Код 503Целевой сайт перегружен моим же темпомСнижаю потоки задания на треть

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

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

Таймауты соединения и ответа

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

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

Тип заданияВес ответаОжидаемое время запросаТаймаут заданияПопыток
Парсер поисковой выдачи300-600 КБ1,5-2,5 сек10 сек3
Сбор страниц каталога150-400 КБ1-2 сек8 сек5
Проверка кодов методом HEADЗаголовки0,3-0,6 сек4 сек2
Разбор карточек товара200-800 КБ1-3 сек12 сек4
Сбор с авторизацией100-300 КБ2-5 сек25 сек2
Выгрузка файлов и картинок1-8 МБ4-12 сек40 сек2

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

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

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

Отдельная настройка касается паузы между запросами внутри потока. В заданиях по сбору выдачи я ставлю задержку в две-три секунды, и работает она в связке с числом потоков: при той же задержке рост потоков уплотняет суммарный поток обращений с пула. Сто пятьдесят потоков с паузой в три секунды дают пятьдесят обращений в секунду с пула, по одному обращению в три секунды с каждой строки. Поисковая система принимает такую плотность спокойно, проверки появляются раз в несколько тысяч запросов.

Парсеры выдачи и парсеры сайтов по разным заданиям

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

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

Разнесение по заданиям снимает компромисс. Первое задание собирает выдачу и пишет ссылки в файл, работает через чекер serp на ста пятидесяти потоках с паузой в три секунды. Второе задание читает этот файл как источник запросов, работает через чекер pages на шестистах потоках без пауз. Запускаются они одно за другим или параллельно с задержкой в несколько минут, чтобы второму было что читать.

Задание 1  serp-collect
  Парсер:    поисковая выдача
  Запросы:   файл ключей, 12 000 строк
  Потоки:    150       Таймаут: 10 сек    Попыток: 3
  Чекер:     serp      Пауза: 3 сек
  Вывод:     links.txt (по одной ссылке в строку)

Задание 2  pages-fetch
  Парсер:    загрузка страницы
  Запросы:   links.txt
  Потоки:    600       Таймаут: 8 сек     Попыток: 5
  Чекер:     pages     Пауза: нет
  Вывод:     pages.tsv (запрос, код, время, адрес выхода)

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

Третий плюс касается повторного прогона. Файл ссылок остаётся на диске, и повторный обход не требует нового съёма выдачи. За месяц у меня накапливается несколько таких файлов, и часть задач закрывается перебором того, что уже собрано. Пул под первую половину этой связки я держу отдельным списком: адреса под съём выдачи в Key Collector работают в щадящем темпе и не смешиваются с плотным обходом страниц.

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

Шаблон вывода для диагностики

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

Набор полей у меня одинаковый на всех заданиях: исходный запрос, код ответа, адрес выхода, номер попытки, длительность запроса в миллисекундах, размер тела ответа. Названия переменных подставляются из подсказки под полем шаблона, у каждого парсера свой набор, и общими остаются только запрос и код.

$query\t$code\t$proxy\t$try\t$time\t$size\t$p1.title

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

cut -f2 pages.tsv | sort | uniq -c | sort -rn

awk -F'\t' '$2!=200 {print $3}' pages.tsv | sort | uniq -c | sort -rn | head -20

cut -f5 pages.tsv | sort -n | awk '{a[NR]=$1} END {print a[int(NR*0.95)]}'

Первая команда собирает распределение кодов ответа и показывает профиль прогона одной строкой. Вторая отвечает на вопрос, распределены ли отказы по всему списку ровно или собраны на десятке строк. Ровное распределение означает, что перебран темп на задании. Собранные отказы означают, что часть списка сегодня медленнее остальных, и я поднимаю интервал проверки чекера. Третья команда даёт цифру для следующей настройки таймаута.

Поле номера попытки полезно тем, что показывает скрытую нагрузку. Файл на сто тысяч строк результата может стоить ста восьмидесяти тысяч обращений, если треть запросов прошла со второй попытки. Без этого поля в отчёте видно только сто тысяч успешных строк, и расчёт нагрузки на пул получается заниженным в полтора раза. Я сравниваю сумму попыток с числом строк после каждого крупного прогона.

Журнал задания я держу на среднем уровне подробности. Полный уровень пишет каждое обращение и на шестистах потоках раздувает файл до гигабайтов за ночь. Средний оставляет ошибки, смены строк и старты попыток, чего достаточно для разбора. Файл журнала я переношу вместе с файлом результата в папку с датой прогона в имени.

Суточный прогон в итоге выглядит так. Вечером я обновляю ссылки на списки в чекерах и запускаю probe, чтобы посмотреть время ответа по строкам. Дальше стартует serp-collect ступенями до ста пятидесяти потоков, через десять минут pages-fetch до шестисот. Первые полчаса я смотрю в отчёт: доля успешных ответов, число активных соединений, средняя длительность запроса. Ночью работает автоматика, утром я разбираю два файла результата тремя командами выше.

Числа за нормальный прогон у меня такие: доля успешных ответов выше девяноста пяти процентов, среднее число попыток на запрос ниже 1,2, медиана времени ответа около полутора секунд на выдаче и около секунды на страницах. Отклонение любого из трёх показателей я разбираю в тот же день, потому что назавтра причина размывается новыми прогонами. Под новую связку заданий я подставляю в чекеры адреса под многопоточный сбор данных, копирую файл с расчётом потоков от похожей задачи и правлю в нём две цифры.

По соседним инструментам раздела арифметика потоков раскладывается иначе. Страница про профили Dolphin Anty описывает браузерную схему, где одному профилю соответствует одна точка выхода. Материал о настройке Key Collector объясняет паузы под источники статистики запросов. Разбор шаблонов ZennoPoster показывает, как строятся длинные сценарии действий поверх собранных данных. В практическом разделе стоит открыть страницу про расчёт нагрузки по лимиту потоков: там подробно расписано распределение потолка между привязками и параллельными задачами.