Сегодня я рассмотрю основы парсинга на Java, используя как пример Яндекс Карты. Есть множество способов работы с HTTP. Самым простым и давно доступным был класс HttpURLConnection, но с выходом Java 11 в стандартную библиотеку вошёл современный инструмент — HttpClient. Это мощный и удобный класс, который:

  • поддерживает синхронные и асинхронные запросы;

  • умеет работать с HTTP/1.1 и HTTP/2;

  • использует неблокирующую I/O-модель;

  • легко интегрируется с JSON, XML и другими форматами.

Что особенно важно — HttpClient входит в JDK и не требует сторонних зависимостей. Это делает его отличным выбором для написания утилит, микросервисов и интеграционных модулей без необходимости тащить тяжёлые фреймворки вроде Apache HttpClient или OkHttp.

В этой статье мы рассмотрим:

  • что такое HTTP и какие бывают методы запроса;

  • как работать с HttpClient в Java 11+;

  • как отправлять GET и POST-запросы;

  • как обрабатывать HTTP-ответы и извлекать данные;

  • как всё это можно использовать для парсинга данных с внешнего сервера на примере Яндекс Карт.

Мы не будем использовать сторонние библиотеки — только стандартный JDK. Такой подход не только уменьшает зависимость от внешнего кода, но и помогает лучше понять, как именно работают HTTP-запросы и сетевое взаимодействие в Java.

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

Что такое HTTP и как он работает

HTTP (HyperText Transfer Protocol) — это базовый сетевой протокол, лежащий в основе всего веба. Каждый раз, когда вы открываете веб-страницу, отправляете форму, кликаете по кнопке, ваше приложение или браузер отправляет HTTP-запрос на сервер. В ответ сервер формирует и возвращает HTTP-ответ, содержащий запрашиваемые данные или сообщение об ошибке.

Протокол работает по простой и интуитивно понятной модели:
Клиент делает запрос, а сервер отвечает.

Эта модель называется клиент-серверной архитектурой, и она подходит практически для любого взаимодействия: получение страницы, загрузка JSON с API, отправка формы и т. д.

Структура HTTP-запроса

Запрос включает в себя: метод, URL, заголовки и тело самого запроса.

Метод (HTTP verb) указывает, что именно хочет сделать клиент. Из основных методов для работы с парсингом можно выделить следующие:

  • GET — получить данные;

  • POST — отправить данные;

  • PUT — обновить ресурс;

  • DELETE — удалить ресурс и др.

URL — адрес ресурса, к которому мы обращаемся.

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

Тело запроса используется для передачи данных, например, в POST-запросах.

Рассмотрим на примере создания простого Get-запроса на сервер

Для примера возьмём бесплатный API JSONPlaceholder. Эндпоинт https://jsonplaceholder.typicode.com/posts/1 возвращает JSON вида:

{
        "userId": 1,
        "id": 1,
        "title": "…",
        "body": "…"
        }

Он удобен для демонстрации сетевого запроса и парсинга ответа.

Шаг 1. Создание HttpClient

HttpClient client = HttpClient.newBuilder()
        .version(HttpClient.Version.HTTP_2)
        .connectTimeout(Duration.ofSeconds(10))
        .build();

Этот код создает и настраивает HTTP-клиент для отправки запросов. Здесь:

HttpClient client = HttpClient.newBuilder() — создаёт новый объект HttpClient с помощью паттерна Builder.

newBuilder() — статический метод, который возвращает "строитель" для конфигурации клиента.

 .version(HttpClient.Version.HTTP_2) — устанавливает версию HTTP-протокола, которую будет использовать клиент.

 .connectTimeout(Duration.ofSeconds(10)) — задаёт максимальное время ожидания подключения к серверу. То есть, если сервер не ответит за 10 секунд, клиент прервёт соединение.

.build() — завершает настройку и возвращает готовый объект HttpClient.

Шаг 2. Формирование HttpRequest

Создадим HTTP-запрос для работы с API или парсинга данных:

Первым делом в создании запроса необходимо создать URI. Она представляет собой строку, которая идентифицирует ресурс в сети или локальной системе.

URI uri = URI.create("https://jsonplaceholder.typicode.com/posts/1");

Основным отличием от URL является то, что URI идентифицирует ресурс, URL (Uniform Resource Locator) указывает ещё и способ его получения (протокол)

После этого настроим наш запрос через Builder

HttpRequest request = HttpRequest.newBuilder(uri)

Здесь HttpRequest.newBuilder() создаёт объект для конфигурации запроса. Он принимает URI (можно также передать строку напрямую: .newBuilder(URI.create("...")))

Далее указываем метод запроса

.GET()

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

После этого задаём таймаут запроса

.timeout(Duration.ofSeconds(12)) 

Максимальное время ожидания ответа от сервера — 12 секунд.

Если сервер не ответит за это время, выбросится HttpTimeoutException.

Далее добавляем HTTP-заголовок

.header("Accept", "application/json")
.header("User-Agent", "JavaHttpClient/1.0") 

Accept указывает серверу, что клиент ожидает ответ в формате JSON.

User-Agent идентифицирует клиента (некоторые сайты блокируют запросы без этого заголовка). 

Важно, что при компиляции запроса нужно использовать знак дефиса с ввода на клавиатуре, так как при использовании скопированного дефиса не всегда возможна корректная отправка запроса на сервер.

Можно также добавить другие заголовки. Например, Authorization для API с аутентификацией.

Следующим шагом идёт сборка запроса

.build();

Метод завершает настройку и возвращает готовый объект HttpRequest.

Шаг 3. Отправка запроса и получение ответа

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

Мы создаём блокирующий метод send, который ждёт ответа от сервера и возвращает HttpResponse<String> с телом в виде строки. 

Основным отличием блокирующего метода от неблокирующего, является поточностm запросов. Во время вызова блокирущего метода, основной поток ждёт, пока HTTP-запрос отправится и ответ (включая тело) полностью примется от сервера и будет готов к обработке. 

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

Неблокирующий (асинхронный) метод client.sendAsync(request, BodyHandlers.ofString()) немедленно возвращает запрос. Основная логика продолжается, не дожидаясь ответа сервера.

Когда ответ придёт, CompletableFuture перейдёт в «готовое» состояние, и вы сможете обработать его без блокировки вызывающего потока.

Шаг 4. Проверка HTTP-статуса

int statusCode = response.statusCode(); 
if (statusCode >= 200 && statusCode < 300) {
        System.out.println("Запрос успешен: " + statusCode); 
} else {
        System.err.println("Ошибка HTTP: код " + statusCode); 
   System.err.println("Тело ответа: " + response.body());
}

Это стандартный способ убедиться, что сервер вернул успешный ответ (коды 2xx). В противном случае можно обработать выведенную кодом 2хх ошибку, в соответствии с её значением.

Шаг 5. Получение и чтение тела ответа

String body = response.body(); 
System.out.println("Тело ответа JSON:\n" + body);

Здесь тело уже представляет собой строку, так как мы установили BodyHandlers.ofString(). Для того чтобы читать данные иначе, стримить их в файл или парсить по частям, можно использовать BodyHandlers.ofInputStream() или ofByteArray(). Такой пример мы рассмотрим в следующей статье.

Шаг 6. Простейший разбор JSON (без сторонних библиотек)

String title = extractValue(body, "\"title\":");
String postBody = extractValue(body, "\"body\":"); 

System.out.println("Заголовок: " + title); 
System.out.println("Содержимое: " + postBody);

body представляет собой исходную JSON-строку, из которой извлекаются данные

extractValue() — пользовательский метод (не из стандартной библиотеки), который ищет в строке body поле "title": и извлекает следующее за ним значение. Результат сохраняется в переменную title.

Аналогично первому этому, ищется поле "body" и сохраняется в переменную postBody.

После этого создадим метод для извлечения данных из JSON-файла.

Для этого создадим метод extractValue, который будет извлекать строковое значение, связанное с указанным ключом в JSON-строке.

private static String extractValue(String json, String key) {
    int idx = json.indexOf(key);
    if (idx < 0) return "";
    int start = json.indexOf("\"", idx + key.length()) + 1;
    int end = json.indexOf("\"", start);
    return json.substring(start, end);
}

Когда метод вызывается, он получает два параметра: строку json, содержащую JSON-данные, и строку key, представляющую искомый ключ в формате "ключ": (например, "title" или "body"). В первую очередь метод пытается найти позицию этого ключа в исходной JSON-строке с помощью вызова json.indexOf(key). Этот вызов возвращает индекс первого символа найденной подстроки ключа либо -1, если такой подстроки в JSON-строке не существует.

Если ключ не найден, метод немедленно завершает свою работу, возвращая пустую строку "". Этот механизм предотвращает дальнейшие операции с несуществующим ключом. 

В случае успешного нахождения ключа метод переходит к определению границ значения, связанного с этим ключом.

Для определения начала значения метод вычисляет позицию сразу после окончания найденного ключа idx + key.length() и ищет первую встречающуюся кавычку " начиная с этой позиции. К найденному индексу кавычки добавляется 1, чтобы пропустить саму кавычку и получить индекс первого символа значения. Это становится начальной границей извлекаемого значения.

Затем метод ищет следующую кавычку ", начиная поиск с только что определённой начальной позиции. Эта вторая кавычка отмечает конец извлекаемого значения. Между найденными границами содержится искомая строка, которую метод извлекает с помощью json.substring(start, end) и возвращает, как результат.

Важно отметить, что этот метод работает корректно только с простыми строковыми значениями в JSON. Он не обрабатывает экранированные кавычки внутри самих значений, не работает с булевыми значениями или null и зависит от конкретного форматирования JSON-строки. Для более сложных случаев парсинга JSON рекомендуется использовать специализированные библиотеки, такие как org.json, Gson или Jackson, которые обеспечивают более гибкий разбор JSON-структур. Пример работы с ними рассмотрим в дальнейшем, на примере парсинга Яндекс Карт.

Полный код должен выглядеть следующим образом

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class GetRequestExample {
    public static void main(String[] args) {
        try {
            HttpClient client = HttpClient.newBuilder()
                    .version(HttpClient.Version.HTTP_2)
                    .connectTimeout(Duration.ofSeconds(10))
                    .build();

            URI uri = URI.create("https://jsonplaceholder.typicode.com/posts/1");
            HttpRequest request = HttpRequest.newBuilder(uri)
                    .GET()
                    .timeout(Duration.ofSeconds(12))
                    .header("Accept", "application/json")
                    .header("User-Agent", "JavaHttpClient/1.0")
                    .build();

            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

            int status = response.statusCode();
            if (status >= 200 && status < 300) {
                System.out.println("Статус: " + status);

                String body = response.body();
                System.out.println("Raw JSON:\n" + body);
                System.out.println("Заголовок: " + extract(body, "\"title\":"));
                System.out.println("Тело: " + extract(body, "\"body\":"));
            } else {
                System.err.println("Ошибка: " + status + ", body: " + response.body());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static String extract(String json, String key) {
        int i = json.indexOf(key);
        if (i < 0) return "<not found>";
        int s = json.indexOf('\"', i + key.length()) + 1;
        int e = json.indexOf('\"', s);
        return json.substring(s, e);
    }
}

Создание Post-запроса

Первым делом, по аналогии с созданием get-запроса, создаём HttpClient.

HttpClient client = HttpClient.newBuilder()
        .version(HttpClient.Version.HTTP_2)
        .connectTimeout(Duration.ofSeconds(10))
        .build();

После этого переходим к подготовке тела запроса

Java не знает структуру тела — вы можете отправлять JSON, URL‑encoded формы, файлы:

String json = """ 
 { 
   "title": "foo", 
   "body": "bar", 
   "userId": 1 
 } 
""";

или собрать форму:

String form = "name=" + URLEncoder.encode("Alice", UTF_8)
        + "&age=" + URLEncoder.encode("30", UTF_8);

Важно выбрать подходящий тип отправляемого контента.

Далее переходим к созданию HttpReqest

HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://jsonplaceholder.typicode.com/posts"))
        .timeout(Duration.ofSeconds(5))
        .header("Accept", "application/json")
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString(json))
        .build();

Здесь:

  • uri — адрес API.

  • Заголовки Accept/Content-Type говорят API, что мы ожидаем JSON и отправляем JSON соответственно.

  • BodyPublishers.ofString(...) отправляет тело как строку.

Если тело не нужно — .POST(BodyPublishers.noBody()) вполне допустимо.

Переходим к отправке запроса (синхронно)

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

Этот метод блокирует текущий поток до получения полного ответа (код + заголовки + тело)

Для асинхронной отправки запроса можно использовать следующий код:

CompletableFuture<HttpResponse<String>> future = client.sendAsync(
        request,
        HttpResponse.BodyHandlers.ofString()
);

future.thenApply(HttpResponse::body)
      .thenAccept(body -> System.out.println("Ответ: " + body))
        .exceptionally(e -> {
        System.err.println("Ошибка: " + e);
          return null;
                  });

sendAsync(...) возвращает CompletableFuture, который завершится, как только HTTP-ответ будет получен (заголовки и тело).

thenApply(HttpResponse::body) извлекает тело ответа, thenAccept(...) работает с ним, а exceptionally(...) позволяет отловить исключения, например, таймаут или IOException Programming

Код после отправки запроса выполняется без задержки — весь сетевой I/O идёт в пуле потоков.

Для первичной обработки ответа от сервера можете заменить код на следующий:

client.sendAsync(postReq, HttpResponse.BodyHandlers.ofString())
        .thenApply(resp -> {
        if (resp.statusCode() >= 200 && resp.statusCode() < 300) {
        return resp.body();
          } else {
                  throw new RuntimeException("HTTP Error: " + resp.statusCode());
        }
        })
        .thenAccept(body -> System.out.println("Ответ при создании: " + body))
        .exceptionally(e -> {
        System.err.println("Не удалось создать ресурс: " + e);
          return null;
                  });

Так, мы отправляем запрос, получаем CompletableFuture<HttpResponse<String>>, проверяем statusCode(), и только при статусе 2xx парсим тело — иначе вызываем ошибку через throw.

exceptionally(...) универсально обрабатывает любые возникшие исключения — сетевые, JSON-ошибки, тайм-ауты внутри thenApply(…).

Пример кода — от начала до конца

ublic class PostJsonExample {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_2)
                .connectTimeout(Duration.ofSeconds(10))
                .build();

        String json = """
          {
            "title": "foo",
            "body": "bar",
            "userId": 1
          }
        """;

        HttpRequest req = HttpRequest.newBuilder()
                .uri(URI.create("https://jsonplaceholder.typicode.com/posts"))
                .timeout(Duration.ofSeconds(5))
                .header("Accept", "application/json")
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(json))
                .build();

        HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());
        int code = resp.statusCode();
        if (code >= 200 && code < 300) {
            System.out.println("Created, Location: " + resp.headers().firstValue("Location").orElse("<none>"));
            System.out.println("Response:\n" + resp.body());
        } else {
            System.err.println("Failed with HTTP " + code + ":\n" + resp.body());
        }
    }
}

Парсинг Яндекс Карт

Yandex предоставляют публичный Geocoder HTTP API, который позволяет по адресу получить геокоординаты в формате JSON.

Вот пошаговая инструкция, как получить API-ключ для Yandex Geocoder API:

1. Регистрация в Яндекс

Создайте аккаунт в Яндекс, если его у вас нет и авторизуйтесь в Кабинете разработчика.

2. Создание API-ключа

Нажмите кнопку «Подключить API».

Выберите «JavaScript API и HTTP Геокодер» и нажмите «Продолжить».

3. Заполнение анкеты

В форме укажите ваше имя, email, телефон, название компании или же выберите частное лицо.

Отметьте, что ваш сервис соответствует условиям бесплатного использования.

Нажмите «Продолжить».

4. Получение ключа

После заполнения формы вам будет выдан API-ключ.

Скопируйте его. Именно он будет использоваться в создании нашего HttpClient для дальнейшего парсинга.

Новый ключ может начать работать не сразу, а через 15–60 минут (иногда до суток).

Если при использовании возникает ошибка 403 Forbidden, подождите некоторое время и повторите запрос.

Создание парсера

Создаём HttpClient по примеру, рассмотренному выше.

HttpClient client = HttpClient.newBuilder()
        .version(HttpClient.Version.HTTP_2)
        .connectTimeout(Duration.ofSeconds(10))
        .build();

После этого переходим к формированию URL и созданию Http-запроса

Создайте переменные типа String, которые будут хранить ваш API-ключ, адрес, координаты которого мы будем принимать и непосредственно URL

Для этого важно кодировать адрес через URLEncoder.encode() — иначе символы кириллицы и пробелы приведут к ошибке. Также важно указать заголовки Accept: application/json. Их указывать обязательно, так как без этих заголовков Яндекс может блокировать запросы.

Блок кода с формированием URL должен выглядеть следующим образом:

String apiKey = "ваш API-ключ";
String address = URLEncoder.encode("Москва, Кремль", StandardCharsets.UTF_8);
String url = "https://geocode-maps.yandex.ru/1.x?geocode=" + address + "&format=json&apikey=" + apiKey;

Создаём и отправляем запрос по примеру, рассмотренному в нашей статье ранее.

HttpResponse<String> response = client.send(request, BodyHandlers.ofString());


HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create(url))
        .header("Accept", "application/json")
        .GET()
        .build();

После этого делаем проверку статуса ответа от сервера.

Также делаем по примеру из нашей статьи.

if (response.statusCode() != 200) {
    System.err.println("Ошибка: " + response.body());
    return;
}

Если код ошибки сервера отличается от 200, печатаем полностью тело ответа и завершаем поток.

Переходим к десериализации JSON

Десериализация — это процесс преобразования строки формата JSON в Java-объект (POJO). Проще говоря: мы получаем JSON-текст (например, из REST API), а затем оборачиваем его в Java-класс, с доступом к нужным полям как к свойствам объекта. Это значительно удобнее и безопаснее, чем работать напрямую со строковым body().

Реализовывать её мы будем с помощью библиотеки Jackson. Это позволит нам качественно обрабатывать JSON-объекты. 

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

Для конфигурации на maven откройте файл в корне проекта и добавьте в секцию <dependencies> следующий код:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version> <!-- или актуальная версия -->
</dependency>

Если проект использует Gradle (файл build.gradle):

Добавьте в секцию dependencies:

implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2'

Если проект без системы сборки (только JAR-файлы):

Скачайте вручную:

  • jackson-databind

  • jackson-core

  • jackson-annotations

Добавьте JAR-файлы в classpath вашего проекта:

В IntelliJ IDEA: File → Project Structure → Libraries → "+" → Java

В командной строке: javac -cp ".;jackson-*.jar" GetRequestExample.java

После добавления зависимости обновите зависимости в вашей IDE:

Maven: нажмите кнопку Reload в панели Maven

Gradle: выполните Refresh Gradle Project

Убедитесь, что импорты в коде работают:

import com.fasterxml.jackson.databind.ObjectMapper; 
import java.io.IOException; 

После этого переходим к коду:

Код будет работать по алгоритму: создадим объект ObjectMapper из Jackson, который будет преобразовывать JSON-строку во вложенные POJO согласно структуре, после чего метод readValue() преобразует JSON-строку в объект класса YandexGeocodeResponse, автоматически сопоставляя поля JSON со свойствами класса (с учётом аннотаций вроде @JsonIgnoreProperties). В результате получается структурированный Java-объект, который содержащит все данные из JSON-ответа и с которым можно работать напрямую.

String json = response.body();
ObjectMapper mapper = new ObjectMapper();
YandexGeocodeResponse resp = mapper.readValue(json, YandexGeocodeResponse.class);

После этого реализуем навигацию по вложенным объектам в файле.

Основной смысл навигации заключается в последовательной и безопасной проверке наличия всех вложенных объектов в структуре JSON‑ответа от API Яндекс Геокодера. 

Программа последовательно проверяет resp, resp.response, resp.response.GeoObjectCollection, featureMember и длину всего массива данных. Таким образом, исключается возможность пустого ответа при обращении к вложенным полям.

if (resp != null &&
        resp.response != null &&
        resp.response.GeoObjectCollection != null &&
        resp.response.GeoObjectCollection.featureMember != null &&
        resp.response.GeoObjectCollection.featureMember.length > 0) {

Затем берётся первый элемент массива featureMember и проверяется наличие внутри нашего искомого GeoObject, страны его местонахождения и строки, содержащей координаты искомого места. После чего строка координат разделяется по пробелу на долготу и широту, и если получено ровно два значения, они выводятся в консоль в формате "широта, долгота". Если на любом этапе проверки возникает ошибка (null-значение или неверный формат), вывод координат пропускается.

FeatureMember member = resp.response.GeoObjectCollection.featureMember[0];
    if (member.GeoObject != null && member.GeoObject.Point != null && member.GeoObject.Point.pos != null) {
        String[] coords = member.GeoObject.Point.pos.split(" ");
        if (coords.length == 2) {
            System.out.println("Координаты: " + coords[1] + ", " + coords[0]);
            return;
        }
    }
}

После этого выведем в консоль полный JSON для отладки.

System.out.println("Не удалось извлечь координаты из ответа");
System.out.println("Полный ответ: " + json);

Также сделаем обработку исключений в catch после закрытия метода try

catch (Exception e) {
    e.printStackTrace();
}

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

Для правильного маппинга ответа пропишем аннотации к классам

Они позволяют правильно десереализировать данные из JSON и пропускать поля из JSON, которых нет в Java-классе. Без аннотации парсер выдаст ошибку, если в JSON появится неизвестное поле.

Прописываются они следующим образом:

@JsonIgnoreProperties(ignoreUnknown = true)
static class YandexGeocodeResponse {
    public Response response;
}

Пропишите подобным образом аннотации для классов Response со значением GeoObjectCollection, GeoObjectCollection со значением featureMember, FeatureMember со значением GeoObject, GeoObject со значением Point и Point со значением pos.

Полный код должен выглядеть следующим образом:

import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.charset.StandardCharsets;
import java.time.Duration;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {

    public static void main(String[] args) {
        try {
            HttpClient client = HttpClient.newBuilder()
                    .version(HttpClient.Version.HTTP_2)
                    .connectTimeout(Duration.ofSeconds(10))
                    .build();

            String apiKey = "Ваш API";
            String address = URLEncoder.encode("Москва, Красная площадь", StandardCharsets.UTF_8);
            String url = "https://geocode-maps.yandex.ru/1.x?geocode=" + address + "&format=json&apikey=" + apiKey;

            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create(url))
                    .header("Accept", "application/json")
                    .GET()
                    .build();

            HttpResponse<String> response = client.send(request, BodyHandlers.ofString());

            if (response.statusCode() != 200) {
                System.err.println("Ошибка: " + response.body());
                return;
            }
            String json = response.body();
            ObjectMapper mapper = new ObjectMapper();
            YandexGeocodeResponse resp = mapper.readValue(json, YandexGeocodeResponse.class);

            if (resp != null &&
                    resp.response != null &&
                    resp.response.GeoObjectCollection != null &&
                    resp.response.GeoObjectCollection.featureMember != null &&
                    resp.response.GeoObjectCollection.featureMember.length > 0) {

                FeatureMember member = resp.response.GeoObjectCollection.featureMember[0];
                if (member.GeoObject != null && member.GeoObject.Point != null && member.GeoObject.Point.pos != null) {
                    String[] coords = member.GeoObject.Point.pos.split(" ");
                    if (coords.length == 2) {
                        System.out.println("Координаты: " + coords[1] + ", " + coords[0]);
                        return;
                    }
                }
            }
            System.out.println("Не удалось извлечь координаты из ответа");
            System.out.println("Полный ответ: " + json);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    static class YandexGeocodeResponse {
        public Response response;
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    static class Response {
        public GeoObjectCollection GeoObjectCollection;
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    static class GeoObjectCollection {
        public FeatureMember[] featureMember;
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    static class FeatureMember {
        public GeoObject GeoObject;
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    static class GeoObject {
        public Point Point;
    }

    @JsonIgnoreProperties(ignoreUnknown = true)
    static class Point {
        public String pos;
    }
}

Этот код представляет собой Java-приложение, которое отправляет POST-запрос с JSON-данными на удалённый сервер и обрабатывает полученный ответ. 

Скрипт работает на основе Yandex Geocoder API, у которого есть ограничение в 25000 запросов. Для интеграции представленного кода и полноценной автоматизации процесса парсинга в больших масштабах, развернём проект в Amvera Cloud. За счет ротируемых IP и разных проектов, в которых можно указать разные API-ключи, стандартный лимит можно превзойти.

Помимо этого, данный способ даст нам встроенный CI/CD (доставка обновлений через Git), бэкапы, логирование и много других полезных встроенных функций.

Деплой проекта в Amvera Cloud

Создадим новый проект в сервисе Amvera.

Для этого предварительно зарегистрируемся по ссылке. Стартового баланса в 111 р. хватит на эксперименты и первые недели работы проекта.

Находясь на главной странице, нажимаем «Создать».

Вводим название проекта и выбираем тариф «Начальный Плюс». Далее можно уменьшить тариф, но для запуска лучше повысить производительность.

Нажимаем «Далее» и переходим к загрузке данных проекта.

Существует два варианта загрузить проект: командами git или через интерфейс сервиса. Мы воспользуемся Git, так как это упрощает доставку обновлений. Но можно использовать и загрузку/удаление файлов через личный кабинет, тем более можно комбинировать способы.


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

Для этого пропишем:

Mvn package

Учтите, что для этого требуется ранее предустановленный Maven.

Нужный нам JAR появится в корне папки /target. Его потребуется вписать далее в конфигурационный файл amvera.yaml

На этом этапе нам предлагается склонировать репозиторий созданного проекта. Но так как приложение уже создано, мы пойдём иным путём.

1. Вызовем терминал в IDE, где открыто приложение, или откроем папку проекта в терминале

2. Инициализируем локальный гит-репозиторий командой

git init

3. Добавим удаленный репозиторий нашего проекта (url вашего репозитория будет отличаться. Во избежание синтаксических ошибок скопируйте ссылку на втором шаге создания проекта)

git remote add amvera https://git.amvera.ru/имя_пользователя/имя_проекта

4. Добавим файлы и сделаем первый коммит

git add .

git commit -m "init"

5. Запушим наш код в репозиторий проекта

git push amvera master

6.  Нажимаем «Далее» и наблюдаем интерфейс создания конфигурационного файла.

Среди всех полей нам требуется вписать в jarName имя нашего jar-файла, в нашем случае это PARSEBINANCE-1.0-SNAPSHOT.jar , но в вашем случае это будут совершенно другие названия.

Пример:

Далее начнется сборка проекта, после – запуск самого приложения.

Следить за ходом сборки и запуска можно в соответствующих логах.

Получение данных парсинга Яндекс Карт

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

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

Вывод координат в лог
Вывод координат в лог

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

Заключение

В этой статье мы рассмотрели общие концепции парсинга данных на Java. Мы разработали базовый парсер координат. Проект можно использовать как основу для более сложных задач. В следующей статье мы рассмотрим пример использования парсинга и отправки запросов на конкретном, более практическом сценарии. А именно, я покажу как организовать парсинг отзывов с 2ГИС и Яндекс Карт.

Комментарии (2)


  1. cry_san
    18.08.2025 05:12

    Вы еще на ассемблере научите парсить.
    Используйте обертку https://jsoup.org


  1. kmatveev
    18.08.2025 05:12

    Статья сойдёт, но название чудное, тут к парсингу относится только самодельный extractValue(), в котором из техник парсинга наблюдаем indexOf() и substring().