Современные проекты на JavaScript чаще всего работают на удалённых серверах, в среде Node.js. При обмене данными между скриптами (обычно по API-интерфейсу), а также при сборе информации с внешних сайтов (скрапинге) разработчикам нужен простой и надёжный способ отправлять HTTP-запросы. И хотя на рынке существует много библиотек для этих целей, встроенная node-fetch стала одной из самых популярных благодаря своей простоте и схожести с браузерным Fetch API.
В этой статье мы разберём, что такое node-fetch, чем удобна эта библиотека и какие ограничения у неё есть, а также покажем примеры использования node-fetch с прокси.
Что такое node-fetch и почему разработчики его используют
Node-fetch – это встроенная библиотека фреймворка Node.js, которая в синтаксисе подражает дефолтному API-интерфейсу браузеров для JavaScript-кода (Fetch API). Ранее компонент node-fetch поставлялся отдельно и требовал ручной установки, но с 2022 года включён в стандартный установочный пакет Node.js (18 версия и выше).
Разработчики используют node-fetch как наиболее простую и лёгкую альтернативу сторонних HTTP-клиентов при работе с HTTP-запросами и ответами. Например, с её помощью можно организовать обмен данными с удалённым веб-сервисом или создать API для своей программы (при обмене информацией и файлами с внешними клиентами). Через node-fetch можно взаимодействовать с CLI-утилитами.
Особо стоит отметить то, что node-fetch из коробки поддерживает асинхронность и промисы. Но давайте обо всё по порядку.
Основы node-fetch
Node-fetch – это, если упростить, HTTP-клиент, который позволяет отправлять запросы и получать ответ сервера, например, HTML-код, JSON, файл и т.д. Никакие дополнительные программы и библиотеки для этого не нужны.
Когда вы вызываете node-fetch из своих JS-скриптов, то библиотека:
- Отправляет HTTP-запрос.
- Получает «сырой» HTML (как если бы вы открыли исходный код страницы в блокноте).
- Возвращает этот HTML как текст.
Без дополнительных параметров node-fetch по умолчанию отправляет GET-запросы. Но тип запроса можно сменить на любой другой (POST, PUT, DELETE).
Node-fetch не загружает никакие скрипты (имеются ввиду те, что задействуются при рендеринге страниц в классическом браузере), не выполняет их, не строит DOM-структуру документа и не отображает страниц. Она никаким образом не взаимодействует ни с какими браузерами (в том числе с headless-браузерами).
Собственно, функция fetch() в браузерах реализует ту же функцию, ведь это тоже штатный HTTP-клиент.
Удобнее всего с помощью node-fetch обмениваться форматированными данными с удалёнными серверами и сайтами, например, JSON или XML. В этом случае ответ легко будет разобрать на мелкие составляющие.
Простейший пример асинхронного использования node-fetch
Если вы используете старую версию Node.js (меньше 18 версии), то сначала нужно установить библиотеку:
npm install node-fetch
Создайте простой текстовый файл и смените ему расширение на .js. Пусть будет «node-fetch.js. Наполните файл содержимым:
// Здесь мы импортируем библиотеку node-fetch в свой проект
import fetch from 'node-fetch';
// Отправляем GET-запрос через функцию fetch (), тип запроса указывать не нужно, он используется по дефолту
const response = await fetch('https://my-target.site.com/posts/1');
// Преобразуем ответ сервера в текст
const body = await response.text();
// Выводим ответ в консоль
console.log (body);
Запустите свой скрипт (команда для консоли):
node node-fetch.js
Примерно так выглядит POST-запрос (например, для отправки данных для публикации в блоге):
// Здесь мы импортируем библиотеку node-fetch в свой проект
import fetch from 'node-fetch';
// Явно указываем тип запроса POST и его содержимое
const response = await fetch('https://my-target.site.com/post', {method: 'POST', body: 'My first post from node-fetch'});
// На всякий случай забираем ответ сервера в JSON-формате…
const data = await response.json();
// И выводим его в консоль
console.log(data);
Библиотека node-fetch отлично работает с ошибками (для отладки) и HTTP-заголовками.
Node-fetch для веб-скрапинга: плюсы и ограничения
Node-fetch вполне подходит для простейших задач веб-скрапинга – в роли лёгкого и удобного инструмента для отправки HTTP-запросов и получения исходного HTML-кода страниц.
Преимущества node-fetch для парсинга можно обозначить так:
- Это штатная библиотека Node.js, ничего дополнительно устанавливать не нужно.
- Синтаксис максимально соответствует Fetch-API браузеров. Не нужно изучать специфические команды, операторы и их параметры.
- Node-fetch потребляет минимум ресурсов.
- С помощью библиотеки можно быстро получить исходный HTML-код целевых страниц, а также обменяться форматированными данными (JSON, XML и т.п.).
- Node-fetch поддерживает отладку и работу не только с HTTP-заголовками, но и с конкретными метаданными (статусы, куки, редиректы, кодировка, User-Agent и т.п.).
- Работа с промисами и асинхронность из коробки.
- Наличие расширений и плагинов, например, для работы со сжатием, счётчиками и т.п.).
К недостаткам node-fetch можно отнести:
- Из-за особенностей архитектуры Node.js, библиотека node-fetch не умеет работать с кешем и некоторыми свойствами подключений (keepalive, destination, integrity, mode, type и т.п.).
- В качестве целевых URL нельзя передавать относительные адреса, только абсолютные.
- Node-fetch не умеет рендерить страницы, то есть она всегда работает только с исходным кодом. Для парсинга современных сайтов и веб-приложений это может быть критичной проблемой. А через headless-браузер node-fetch не перенаправить. Для понимания контраста изучите материал по парсингу с Puppeteer.
- В node-fetch нет функций для поиска отдельного контента. Для этого ответ сервера придётся разбирать сторонними синтаксическими анализаторами.
- Из коробки в node-fetch нет поддержки прокси.
Почему скрапинг часто требует прокси
Прокси для скрапинга – это не просто рекомендация, а критическая необходимость. Дело в том, что многие современные сайты быстро выявляют автоматический трафик и блокируют его, чтобы снизить нагрузку и исключить доступ к чувствительному контенту. Первое, что анализируется – это IP-адрес пользователя и количество его подключений. Большое число обращений с одного и того же IP легко выявляется, в результате адрес попадает в бан (временный или постоянный, в зависимости от типа адреса).
Эффективно бороться с такими блокировками позволяют ротируемые прокси (резидентные, а ещё лучше мобильные). Если не использовать прокси, то процесс парсинга может существенно растянуться по времени или полностью остановиться в любой момент. Прокси не только помогают с обходом блокировок, но и обеспечивают параллельное выполнение большого числа запросов, ускоряя процесс. А ещё за счёт смены расположения IP (подбора прокси в нужных локациях) можно обходить географические ограничения и получать доступ к региональным библиотекам контента.
Конечно, одних только прокси недостаточно, но они являются базовым условием для выстраивания всех остальных средств обхода механизмов защиты целевых сайтов.
Резидентные прокси
Лучшие прокси-серверы для доступа к ценным данным со всего мира.
Node-fetch не поддерживает прокси изначально – вот почему
Библиотека node-fetch специально спроектирована как минималистичный инструмент: она не содержит встроенного функционала для работы через прокси, чтобы оставаться лёгкой и простой в обиходе.
При отправке запросов node-fetch полагается на системные настройки сетевого подключения хоста. То есть чисто технически, если вам нужно запустить коннект через прокси-сервер, вы можете это сделать на уровне настроек операционной системы.
По аналогии: в браузерном API-fetch тоже нет поддержки прокси, fetch() полагается на настройки браузера или ОС.
В рамках 100% соответствия синтаксиса действия разработчиков node-fetch можно считать более чем разумными.
Но неудобными…
К слову, в Node.js тоже нет никаких встроенных механизмов для работы через прокси. Как исправить эту несправедливость, опишем ниже.
Использование node-fetch с HTTP-прокси
Чтобы подружить node-fetch с прокси, вам по факту нужно будет изменить агент подключения для каждого типа запросов: http, https, socks, – для всей среды Node.js.
Это делается через параметр agent, вам потребуются дополнительные пакеты-агенты, например:
- https-proxy-agent
- http-proxy-agent
- socks-proxy-agent (если нужен SOCKS)
Пример: Отправка запроса через HTTP-прокси с https-proxy-agent
// Импортируем библиотеку node-fetch и https-proxy-agent
import fetch from 'node-fetch';
import HttpsProxyAgent from 'https-proxy-agent';
// Прописываем актуальный адрес вашего прокси
const proxyAgent = new HttpsProxyAgent('http://YOURPROXYLOGIN:PSSWRD@YOUR-PROXYHOST:PORT');
// Задействуем асинхронность
(async () => {
// Отправляем запрос к целевому сайту
const response = await fetch('https://target-site.com', {
// В качестве агента выбираем наш прокси
agent: proxyAgent
});
// Получаем ответ в виде текста
const text = await response.text();
// Выводим этот текст в лог консоли
console.log(text);
})();
Обратите внимание: node fetch proxy работают через «агента», раздувать код не нужно (одна библиотека – одна задача), а «агента» легко заменить на любого другого: http, https, socks, что-то своё.
Пример: Ротация HTTP-прокси в скрипте скрапера
// Импортируем библиотеку node-fetch и https-proxy-agent
import fetch from 'node-fetch';
import HttpsProxyAgent from 'https-proxy-agent';
// Список прокси (укажите здесь свои адреса)
let proxyList = [
'http://user1:pass1@proxy1.example.com:8080',
'http://user2:pass2@proxy2.example.com:8080',
'http://user3:pass3@proxy3.example.com:8080'
];
// Реализация функции случайного выбора прокси
function getRandomProxy() {
const index = Math.floor(Math.random() * proxyList.length);
return proxyList[index];
}
// Функция для удаления плохого (нерабочего) прокси
function removeBadProxy(badProxy) {
proxyList = proxyList.filter(proxy => proxy !== badProxy);
// Дополнительно выводим данные о плохом прокси в консоль
console.log(`Proxy removed: ${badProxy}`);
}
// Функция отправки запроса с ротацией прокси
async function scrapeWithProxy(url) {
// Если в списке нет прокси, останавливаем парсинг
if (proxyList.length === 0) {
// А в консоль пишем, что прокси закончились
console.log('No available proxies left');
return;
}
// Тут обращаемся к функции выбора случайного прокси
const proxy = getRandomProxy();
// Отправляем запрос через агента
const agent = new HttpsProxyAgent(proxy);
try {
// На всякий случай добавляем таймаут, чтобы не ждать «вечную» загрузку
const res = await fetch(url, { agent, timeout: 10000 }); // 10 секунд
// Если ответ сервера содержит ошибку
if (!res.ok) {
// Идентифицируем её
throw new Error(`HTTP error: ${res.status}`);
}
// Сохраняем ответ в виде текста
const text = await res.text();
// Выводим в консоль информацию об успешном коннекте, и в качестве подтверждения считаем число символов в ответе
console.log(`Response from proxy ${proxy.substring(0, 30)}...: received ${text.length} characters`);
// Ловим ошибку
} catch (err) {
// Выводим в консоль данные о проблемном подключении и дополняем его содержимым ошибки
console.error(`Error with proxy ${proxy.substring(0, 30)}...:`, err.message);
// Удаляем из списка плохой прокси
removeBadProxy(proxy);
}
}
// Пример списка целевыхURL, замените на свои
const urls = [
'https://httpbin.org/ip',
'https://httpbin.org/headers',
'https://httpbin.org/user-agent'
];
// Запуск скрапинга по списку URL
(async () => {
for (const url of urls) {
// Если список прокси пустой, то останавливаем процесс и пишем об этом в консоли
if (proxyList.length === 0) {
console.log('Stopped: all proxies are unavailable');
break;
}
// Если ошибок нет, выполняем парсинг
await scrapeWithProxy(url);
}
})();
При каждой ошибке, например, при ошибке соединения и др., node-fetch http proxy автоматически удаляется из списка. Если список прокси становится пустым, скрипт останавливается. В консоль выводятся данные о том, какой прокси был удалён и с какой ошибкой. Таймаут исключает ситуацию зависания с неотвечающим прокси.
Пример: Использование socks-proxy-agent для доступа к заблокированным ресурсам
В отличие от высокоуровневых HTTP/HTTPS-прокси, SOCKS-прокси работают на транспортном уровне OSI и не вмешиваются в HTTP-заголовки. Что в свою очередь обеспечивает максимальную конфиденциальность и отсутствие каких-либо технических признаков работы через сторонние узлы.
Пример скрипта node-fetch с прокси на основе SOCKS5-протокола (если профильная библиотека отсутствует, предварительно установите её командой «npm install socks-proxy-agent»):
// Импортируем библиотеку node-fetch и https-proxy-agent
import fetch from 'node-fetch';
import { SocksProxyAgent } from 'socks-proxy-agent';
// Пример SOCKS-прокси (можно использовать свой)
const socksProxy = 'socks5h://127.0.0.1:9050'; // здесь мы показали вариант локального Tor-прокси
// Создаём «агента» SOCKS
const agent = new SocksProxyAgent(socksProxy);
// Целевой URL, к которому нужно обратиться (может быть заблокирован без прокси)
const targetUrl = 'https://httpbin.org/ip';
// Здесь описывается логика асинхронной функции запроса
async function fetchThroughSocks() {
try {
// Тут отправляем запрос через 'node-fetch' с использованием агента и таймаута
const res = await fetch(targetUrl, { agent, timeout: 10000 }); // Таймаут 10 секунд, можно заменить на своё значение
// Если ресурс недоступен…
if (!res.ok) {
// Забираем описание ошибки
throw new Error(`HTTP error: ${res.status}`);
}
// Ответ сохраняем в виде простого текста
const data = await res.text();
// В консоль выводим полученные данные
console.log(`Response received: ${data}`);
// Если «поймали» ошибку
} catch (err) {
// Выводим сообщение об ошибке
console.error(`Error: ${err.message}`);
}
}
// Запуск нашей функции
fetchThroughSocks();
Заключение и рекомендации
Работу библиотеки node-fetch с прокси легче всего настроить через агента. Он в свою очередь может работать по протоколу HTTP, HTTPS или SOCKS. Примеры скриптов для каждого из вариантов мы показали выше.
Сама библиотека node-fetch во многом заменяет простейший HTTP-клиент, благодаря которому можно обмениваться запросами и данными между своими скриптами или с внешними web-приложениями без задействования «тяжёлых» комбинаций, построенных на основе полноценных или headless-браузеров в связке с API. Но node-fetch не умеет рендерить код страницы как браузер, поэтому подходит только для простейших задач.
Основная проблема – это даже не настройка агента, а сами прокси. От их качества, места расположения и надёжности будет зависеть эффективность работы вашего скрипта. Выбирайте Froxy на роль своего прокси-провайдера и не пожалеете: более 10 млн. IP в сети, 200+ стран присутствия, серверные, резидентные и мобильные прокси с автоматической ротацией. Для типовых бизнес-задач есть готовые облачные парсеры.