В этой публикации мы постараемся разобраться в том, как дотянуться до заветных данных при помощи JavaScript и метода fetch(input, init)
Мы по шагам будем погружаться в Выборку(Fetch) и смотреть полученные итерации на каждом шаге, чтобы не запутаться и не пропустить важное.
Видео
Задача № 1
Мы хотим получить всю HTML-разметку страницы с сайта интернет магазина.
Немножко теории
Стандарт Fetch определяет запросы, ответы и процесс, который их связывает: выборка (fetching). Стандарт Fetch также определяет JavaScript API fetch(), который предоставляет большую часть сетевых функций на довольно низком уровне абстракции.
Заголовок запроса `Origin` указывает, откуда происходит выборка. Заголовок `Origin` — это версия заголовка`Referer` [sic], которая не показывает путь. Он используется для всех HTTP-выборок, чей ответ на запрос поврежден «cors», а также для тех, у которых метод запроса не является ни GET, ни HEAD. Из-за ограничений совместимости он не входит во все выборки.
Протокол CORS существует, чтобы разрешить обмен ответами из разных источников и более гибкую выборку, чем это возможно с помощью элемента формы HTML <form>. Он расположен поверх HTTP и позволяет в ответах объявлять, что они могут быть переданы другим источникам. Протокол CORS состоит из набора заголовков, которые указывают, может ли ответ быть общим для разных источников.
Блокировка чтения из разных источников (Cross-origin read blocking), более известная как CORB, представляет собой алгоритм, который выявляет сомнительные выборки ресурсов из разных источников (например, выборки, которые в любом случае не удались бы, как попытки отрисовки JSON внутри элемента img) и блокирует их до того, как они достигнут веб-страницы. CORB снижает риск утечки конфиденциальных данных, удерживая их подальше от веб-страниц с разными источниками.
Метод fetch() — это относительно низкоуровневый API для выборки ресурсов. Он охватывает немного больше вопросов, чем XMLHttpRequest, хотя в настоящее время его не хватает, когда дело доходит до последовательности запросов (а не ответов).
Вывод из теории
Если по-простому, то мы не можем получить данные, если отправляем запросы с другого источника. Нам нужно «прикинуться» браузером, получить документ и уже из этого документа отправить запрос на тот же домен.
Решение
Мы потренируемся получать данные на примере интернет-магазина. Откроем страницу в браузере:
https://www.ozon.ru/category/besprovodnye-pylesosy-10657/
Откроем «Инструменты разработчика» (CTRL+SHIFT+i) и перейдём на вкладку «Console«.
И вот мы сразу видим блокировку чтения из разных источников. То есть сами разработчики этого сайта не могут получить данные со своего другого поддомена. Из источника «www.ozon.ru» нельзя получить данные другого источника «xapi.ozon.ru«. Мы не ввели ни одного запроса, но уже столкнулись с работой CORB.
«cross-origin» или по-русски «перекрестное происхождение» мешает нам получать данные из других источников.
В нашем случае мы будем работать в зоне «www.ozon.ru» и посылать запросы из браузера в эту же зону при помощи метода fetch(). Например, будем слать запрос на:
https://www.ozon.ru/context/detail/id/148279648/
Первый вид команды будет выглядеть так:
fetch("https://www.ozon.ru/context/detail/id/148279648/")
Мы вызвали функцию fetch() и передали в качестве одного аргумента текстовое значение нужной нам страницы (её адрес). Той страницы, с которой мы хотим получить разметку, не загружая её в новой вкладке.
Функция вернула нам объект Обещания Promise со статусом fulfilled. У Обещаний может быть всего три статуса:
- fulfilled — (выполнено)
- rejected — (отклонено)
- pending — (ожидает выполнения)
То есть объект Promise всегда находится в каком-либо из этих состояний.
В нашем случае мы успешно выполнили запрос на нужный адрес (статус fulfilled). Результат работы Обещания вернул нам экземпляр объекта Response. Стандарт Fetch имеет специальный класс Response(интерфейс Response). Давайте посмотрим, как выглядит экземпляр объекта Response.
Мы получили «успешное» Обещание и теперь нам нужно сделать следующий шаг, который символизирует «успех». В объектах Promise мы должны вызвать метод then() и передать в него результат «успеха» (экземпляр объекта Response на первой итерации then). В качестве обратного вызова мы укажем console.log(), чтобы увидеть результат в консоли браузера. Мы выведем условную выдуманную переменную resp
fetch("https://www.ozon.ru/context/detail/id/148279648/").then(resp => {console.log(resp)})
Сейчас мы видим сам экземпляр объекта Response, его ключи и их значения. Так где же данные? Где же лежит наш заветный код страницы?
Теперь, когда мы получили экземпляр объекта Response со статусом 200 мы можем получить ТЕЛО ОТВЕТА. В стандарте Fetch за тело ответа отвечает интерфейс «mixin Body«.
У него есть 5 вариантов «распознавания» тела ответа, которые прилетают с экземпляром объекта Response:
- arrayBuffer()
- blob()
- formData()
- json()
- text()
Нас будет интересовать пятый вариант — text(), который просто вернёт нам строковый тип данных из тела ответа. Это будет «успех» и теперь тело ответа мы вернём в следующую итерацию then(). Во втором then мы выведем в консоль тело ответа. (уже НЕ экземпляр объекта Response). Переменную resBody мы также выдумаем из воздуха. Можно называть их как угодно, лишь бы вам было понятно, что они означают.
fetch("https://www.ozon.ru/context/detail/id/148279648/") .then(resp => {return resp.text()}) .then(resBody => {console.log(resBody)})
И мы видим в консоли разметку запрашиваемой страницы — тело ответа. Отлично. Мы уже близко.
Чтобы начать взаимодействовать с разметкой, нужно заранее создать переменную до вызова функции fetch(). То есть сначала у нас будет переменная, в которую мы будем складывать разметку и только потом мы вызываем fetch(), внутри которого меняем значение этой переменной.
Для примера создадим переменную razmetka:
var razmetka = ""
Это будет пустая строка.
Вызовем fetch():
fetch("https://www.ozon.ru/context/detail/id/148279648/") .then(resp => {return resp.text()}) .then(resBody => {razmetka = resBody})
Важно учитывать, что Fetch является асинхронной операцией т. к. мы не можем знать когда ответит сервер или ответит вообще. Нужно заранее объявлять видимые переменные (глобальные), чтобы они создались при синхронной загрузке кода интерпретатором.
После выборки переменная razmetka получает новое значение. Теперь в переменной лежит разметка другой страницы сайта, а значит мы получили данные при при помощи функции fetch()
Задача № 2 (альтернативный пример)
Нужно получить простую HTML-разметку из файла text.html. Нужно поместить разметку в текущий документ, открытый в браузере пользователя. Получаемая разметка такая:
<h1>Заголовок публикации</h1> <p>Текст под заголовком</p>
Документ пользователя содержит только один заголовок на странице:
<h1>Fetch | Шаг 1</h1>
Шаг 1 — Смотрим, что возвращает метод fetch() с одним аргументом
var a = fetch("text.html")
В нашей простой задаче мы передаём в качестве аргумента строку, которая указывает на местоположение документа на сервере.
Результатом выполнения выборки Fetch является обещание Promise.
Шаг 2 — Достаём из обещания ответ
Теперь мы можем работать с обещанием и создавать цепочку последовательных операций. Давайте поглядим о каком [[PromiseResult]] идёт речь. В метод then() я передам созвучное название «promiseResult«.
var a = fetch("text.html") .then(promiseResult => {console.log(promiseResult)})
Стрелочной функцией мы выводим в консоль успешный результат обещания. На этот раз мы получаем объект Response.
У этого объекта есть свои интересные свойства. Но мы идём дальше т. к. пока того, что нам нужно, не видно.
Шаг 3 — Достаём из ответа данные
Отобразим содержимое объекта Response в консоли браузера, как и делали ранее. Воспользуемся методом ответов Response, который именуется text()
var a = fetch("text.html") .then(promiseResult => { console.log(promiseResult) return promiseResult.text() }) .then(responseResult => {console.log(responseResult)})
Мы получили содержимое файла text.html и вывели его в консоль браузера.
Шаг 4 — Публикация содержимого из файла на страницу
Теперь логичнее всего завершить начатое — это опубликовать ответ на страницу. Воспользуемся простой конструкцией «document.write(responseResult)«:
var a = fetch("text.html") .then(promiseResult => { console.log(promiseResult) return promiseResult.text() }) .then(responseResult => { console.log(responseResult) document.write(responseResult) })
Готово.
Итог. Что мы делали?
Fetch возвращает объект Promise, из Promise получаем объект Response, а из Response достаём нужное нам содержимое файла — выбираем нужный метод.
Ссылки для информации
Стандарт Fetch на русском языке — https://efim360.ru/fetch/
Оригинальная версия стандарта Fetch на английском языке — https://fetch.spec.whatwg.org/