Compare commits

...

2 Commits

Author SHA1 Message Date
84f344084c merge branches 2024-10-13 19:05:30 +04:00
5ae300389c feature: wb parser 2024-10-13 18:34:11 +04:00
40 changed files with 379 additions and 136 deletions

View File

@ -14,4 +14,18 @@
<option name="Make" enabled="true" /> <option name="Make" enabled="true" />
</method> </method>
</configuration> </configuration>
<configuration default="false" name="ParsingService [local]" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot">
<option name="ACTIVE_PROFILES" value="dev" />
<envs>
<env name="JDBC_PASSWORD" value="postgres" />
<env name="JDBC_USERNAME" value="postgres" />
<env name="JDBC_URL" value="localhost:5432/parsed_data" />
<env name="SERVER_PORT" value="8080" />
</envs>
<module name="parsing-service.main" />
<option name="SPRING_BOOT_MAIN_CLASS" value="ru.pricepulse.parsingservice.ParsingServiceApplication" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component> </component>

View File

@ -48,6 +48,7 @@ dependencies {
testImplementation 'org.springframework.kafka:spring-kafka-test' testImplementation 'org.springframework.kafka:spring-kafka-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
} }
tasks.named('test') { tasks.named('test') {

View File

@ -1,10 +1,10 @@
package ru.pricepulse.parsingservice.config; package ru.pricepulse.parsingservice.config;
import java.time.format.DateTimeFormatter;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import java.time.format.DateTimeFormatter;
@Configuration @Configuration
public class DateTimeFormatterConfig { public class DateTimeFormatterConfig {

View File

@ -1,7 +1,6 @@
package ru.pricepulse.parsingservice.config; package ru.pricepulse.parsingservice.config;
import org.springframework.boot.autoconfigure.kafka.KafkaProperties; import org.springframework.boot.autoconfigure.kafka.KafkaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;

View File

@ -1,8 +1,5 @@
package ru.pricepulse.parsingservice.config; package ru.pricepulse.parsingservice.config;
import java.util.HashMap;
import java.util.Map;
import io.github.bonigarcia.wdm.WebDriverManager; import io.github.bonigarcia.wdm.WebDriverManager;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeDriver;
@ -13,6 +10,9 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import java.util.HashMap;
import java.util.Map;
@Configuration @Configuration
public class WebDriverConfig { public class WebDriverConfig {

View File

@ -1,11 +1,11 @@
package ru.pricepulse.parsingservice.config.properties; package ru.pricepulse.parsingservice.config.properties;
import java.util.List;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
@Getter @Getter
@Setter @Setter
@ConfigurationProperties(prefix = "marketplace.ozon") @ConfigurationProperties(prefix = "marketplace.ozon")

View File

@ -0,0 +1,5 @@
package ru.pricepulse.parsingservice.enumeration;
public enum Category {
LAPTOP
}

View File

@ -0,0 +1,8 @@
package ru.pricepulse.parsingservice.enumeration;
public enum Marketplace {
WILDBERRIES,
OZON,
DNS
}

View File

@ -1,15 +1,13 @@
package ru.pricepulse.parsingservice.pool; package ru.pricepulse.parsingservice.ozon_parser.pool;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.ObjectFactory;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
@Slf4j @Slf4j
@Component @Component
public class WebDriverPool { public class WebDriverPool {

View File

@ -1,4 +1,4 @@
package ru.pricepulse.parsingservice.service; package ru.pricepulse.parsingservice.ozon_parser.service;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -6,8 +6,8 @@ import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.pricepulse.parsingservice.service.dto.ParsedData; import ru.pricepulse.parsingservice.ozon_parser.service.dto.ParsedData;
import ru.pricepulse.parsingservice.service.messaging.ParsedDataProducer; import ru.pricepulse.parsingservice.ozon_parser.service.messaging.ParsedDataProducer;
@Slf4j @Slf4j
@Service @Service

View File

@ -1,4 +1,4 @@
package ru.pricepulse.parsingservice.service; package ru.pricepulse.parsingservice.ozon_parser.service;
public interface MarketplaceParsingService { public interface MarketplaceParsingService {

View File

@ -1,4 +1,4 @@
package ru.pricepulse.parsingservice.service; package ru.pricepulse.parsingservice.ozon_parser.service;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;

View File

@ -1,14 +1,14 @@
package ru.pricepulse.parsingservice.service.dto; package ru.pricepulse.parsingservice.ozon_parser.service.dto;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import ru.pricepulse.parsingservice.persistence.enums.MarketplaceEnum; import ru.pricepulse.parsingservice.enumeration.Marketplace;
@Getter @Getter
@Setter @Setter
public class ParsedData { public class ParsedData {
private MarketplaceEnum marketplace; private Marketplace marketplace;
private String category; private String category;

View File

@ -0,0 +1,7 @@
package ru.pricepulse.parsingservice.ozon_parser.service.marketplace.ozon;
public interface MarketplacePage {
boolean isLoaded();
}

View File

@ -1,12 +1,10 @@
package ru.pricepulse.parsingservice.service.marketplace.ozon.page; package ru.pricepulse.parsingservice.ozon_parser.service.marketplace.ozon.page;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.openqa.selenium.By; import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait; import org.openqa.selenium.support.ui.WebDriverWait;
import ru.pricepulse.parsingservice.service.marketplace.ozon.MarketplacePage; import ru.pricepulse.parsingservice.ozon_parser.service.marketplace.ozon.MarketplacePage;
@Slf4j @Slf4j
public class AccessDeniedPage implements MarketplacePage { public class AccessDeniedPage implements MarketplacePage {

View File

@ -1,24 +1,18 @@
package ru.pricepulse.parsingservice.service.marketplace.ozon.page; package ru.pricepulse.parsingservice.ozon_parser.service.marketplace.ozon.page;
import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOfAllElements; import lombok.extern.slf4j.Slf4j;
import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOfElementLocated; import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.WebDriverWait;
import ru.pricepulse.parsingservice.ozon_parser.service.marketplace.ozon.MarketplacePage;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j; import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOfAllElements;
import org.openqa.selenium.By; import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOfElementLocated;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import ru.pricepulse.parsingservice.service.marketplace.ozon.MarketplacePage;
@Slf4j @Slf4j
public class CategoryPage implements MarketplacePage { public class CategoryPage implements MarketplacePage {

View File

@ -1,28 +1,23 @@
package ru.pricepulse.parsingservice.service.marketplace.ozon.parsing; package ru.pricepulse.parsingservice.ozon_parser.service.marketplace.ozon.parsing;
import lombok.extern.slf4j.Slf4j;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.slf4j.MDC;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import ru.pricepulse.parsingservice.ozon_parser.pool.WebDriverPool;
import ru.pricepulse.parsingservice.ozon_parser.service.marketplace.ozon.page.AccessDeniedPage;
import ru.pricepulse.parsingservice.ozon_parser.service.marketplace.ozon.page.CategoryPage;
import java.time.Duration; import java.time.Duration;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.slf4j.MDC;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import ru.pricepulse.parsingservice.pool.WebDriverPool;
import ru.pricepulse.parsingservice.service.marketplace.ozon.page.AccessDeniedPage;
import ru.pricepulse.parsingservice.service.marketplace.ozon.page.CategoryPage;
@Slf4j @Slf4j
@Service @Service
public class CategoryPageParsingService { public class CategoryPageParsingService {

View File

@ -1,27 +1,14 @@
package ru.pricepulse.parsingservice.service.marketplace.ozon.parsing; package ru.pricepulse.parsingservice.ozon_parser.service.marketplace.ozon.parsing;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import ru.pricepulse.parsingservice.ozon_parser.service.MarketplaceParsingService;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Set;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import lombok.extern.slf4j.Slf4j;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.slf4j.MDC;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import ru.pricepulse.parsingservice.service.MarketplaceParsingService;
import ru.pricepulse.parsingservice.service.marketplace.ozon.page.AccessDeniedPage;
import ru.pricepulse.parsingservice.service.marketplace.ozon.page.CategoryPage;
@Slf4j @Slf4j
@Service @Service
public class ParsingService implements MarketplaceParsingService { public class ParsingService implements MarketplaceParsingService {

View File

@ -1,9 +1,9 @@
package ru.pricepulse.parsingservice.service.messaging; package ru.pricepulse.parsingservice.ozon_parser.service.messaging;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.kafka.core.KafkaTemplate; import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.pricepulse.parsingservice.service.dto.ParsedData; import ru.pricepulse.parsingservice.ozon_parser.service.dto.ParsedData;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor

View File

@ -1,4 +1,4 @@
package ru.pricepulse.parsingservice.service.request; package ru.pricepulse.parsingservice.ozon_parser.service.request;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;

View File

@ -1,10 +1,10 @@
package ru.pricepulse.parsingservice.service.scheduler; package ru.pricepulse.parsingservice.ozon_parser.service.scheduler;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.pricepulse.parsingservice.config.properties.OzonConfigProperties; import ru.pricepulse.parsingservice.config.properties.OzonConfigProperties;
import ru.pricepulse.parsingservice.service.marketplace.ozon.parsing.ParsingService; import ru.pricepulse.parsingservice.ozon_parser.service.marketplace.ozon.parsing.ParsingService;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor

View File

@ -1,14 +1,14 @@
package ru.pricepulse.parsingservice.service.scheduler; package ru.pricepulse.parsingservice.ozon_parser.service.scheduler;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.pricepulse.parsingservice.service.PartitionService; import ru.pricepulse.parsingservice.ozon_parser.service.PartitionService;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
@Slf4j @Slf4j
@Service @Service

View File

@ -1,20 +1,22 @@
package ru.pricepulse.parsingservice.persistence.entity; package ru.pricepulse.parsingservice.persistence.entity;
import java.math.BigDecimal;
import java.util.Objects;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.EmbeddedId; import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import lombok.Getter; import lombok.*;
import lombok.Setter;
import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.HibernateProxy;
import java.math.BigDecimal;
import java.util.Objects;
@Getter @Getter
@Setter @Setter
@Entity @Entity
@Table(name = "price_history") @Table(name = "price_history")
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PriceHistoryEntity { public class PriceHistoryEntity {
@EmbeddedId @EmbeddedId

View File

@ -1,22 +1,22 @@
package ru.pricepulse.parsingservice.persistence.entity; package ru.pricepulse.parsingservice.persistence.entity;
import java.io.Serializable; import jakarta.persistence.*;
import java.time.OffsetDateTime; import lombok.AllArgsConstructor;
import java.util.Objects;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction; import org.hibernate.annotations.OnDeleteAction;
import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.HibernateProxy;
import java.io.Serializable;
import java.time.OffsetDateTime;
import java.util.Objects;
@Getter @Getter
@Setter @Setter
@AllArgsConstructor
@NoArgsConstructor
@Embeddable @Embeddable
public class PriceHistoryId implements Serializable { public class PriceHistoryId implements Serializable {

View File

@ -1,39 +1,34 @@
package ru.pricepulse.parsingservice.persistence.entity; package ru.pricepulse.parsingservice.persistence.entity;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.proxy.HibernateProxy;
import ru.pricepulse.parsingservice.enumeration.Category;
import ru.pricepulse.parsingservice.enumeration.Marketplace;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Objects; import java.util.Objects;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.PrePersist;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.proxy.HibernateProxy;
import ru.pricepulse.parsingservice.persistence.enums.MarketplaceEnum;
@Getter @Getter
@Setter @Setter
@Entity @Entity
@Table(name = "product") @Table(name = "product")
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ProductEntity { public class ProductEntity {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false) @Column(name = "id", nullable = false)
private Long id; private Long id;
@Enumerated(EnumType.STRING)
@Column(name = "marketplace", nullable = false, length = Integer.MAX_VALUE) @Column(name = "marketplace", nullable = false, length = Integer.MAX_VALUE)
private MarketplaceEnum marketplace; @Enumerated(EnumType.STRING)
private Marketplace marketplace;
@Column(name = "category", nullable = false, length = Integer.MAX_VALUE) @Column(name = "category", nullable = false, length = Integer.MAX_VALUE)
private String category; @Enumerated(EnumType.STRING)
private Category category;
@Column(name = "brand", nullable = false, length = Integer.MAX_VALUE) @Column(name = "brand", nullable = false, length = Integer.MAX_VALUE)
private String brand; private String brand;

View File

@ -1,7 +0,0 @@
package ru.pricepulse.parsingservice.persistence.enums;
public enum MarketplaceEnum {
OZON
}

View File

@ -0,0 +1,7 @@
package ru.pricepulse.parsingservice.persistence.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import ru.pricepulse.parsingservice.persistence.entity.PriceHistoryEntity;
public interface ProductPriceRepository extends JpaRepository<PriceHistoryEntity, Long> {
}

View File

@ -0,0 +1,7 @@
package ru.pricepulse.parsingservice.persistence.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import ru.pricepulse.parsingservice.persistence.entity.ProductEntity;
public interface ProductRepository extends JpaRepository<ProductEntity, Long> {
}

View File

@ -1,7 +0,0 @@
package ru.pricepulse.parsingservice.service.marketplace.ozon;
public interface MarketplacePage {
boolean isLoaded();
}

View File

@ -1,7 +1,5 @@
package ru.pricepulse.parsingservice.web.handler; package ru.pricepulse.parsingservice.web.handler;
import java.net.URI;
import jakarta.persistence.EntityNotFoundException; import jakarta.persistence.EntityNotFoundException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@ -10,6 +8,8 @@ import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.ResponseStatus;
import java.net.URI;
@ControllerAdvice @ControllerAdvice
public class CommonExceptionHandler { public class CommonExceptionHandler {

View File

@ -1,9 +1,9 @@
package ru.pricepulse.parsingservice.web.handler; package ru.pricepulse.parsingservice.web.handler;
import java.net.URI;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import java.net.URI;
public record ErrorResponse ( public record ErrorResponse (
Integer statusCode, Integer statusCode,
HttpStatus status, HttpStatus status,

View File

@ -0,0 +1,19 @@
package ru.pricepulse.parsingservice.wildberries_parser;
import lombok.AllArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import ru.pricepulse.parsingservice.wildberries_parser.service.ParsingService;
@Component
@AllArgsConstructor
public class DebugRunner implements CommandLineRunner {
private final ParsingService parsingService;
@Override
public void run(String... args){
System.out.println("Начинаем отладку...");
parsingService.parse();
System.out.println("Заканчиваем отладку...");
}
}

View File

@ -0,0 +1,21 @@
package ru.pricepulse.parsingservice.wildberries_parser.configuration;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "marketplace.wildberries")
@Getter
@Setter
public class WbProperties {
private String baseUrl;
private String catalogUrl;
private String userAgent;
private String catalogWbUrl;
private int retryAttempts;
private long retryDelay;
private String laptopUrl;
private String shard;
}

View File

@ -0,0 +1,18 @@
package ru.pricepulse.parsingservice.wildberries_parser.configuration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient() {
return WebClient.builder()
.defaultHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0)")
.codecs(configurer -> configurer
.defaultCodecs()
.maxInMemorySize(10 * 1024 * 1024))
.build();
}
}

View File

@ -0,0 +1,25 @@
package ru.pricepulse.parsingservice.wildberries_parser.converter;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import ru.pricepulse.parsingservice.enumeration.Category;
import ru.pricepulse.parsingservice.enumeration.Marketplace;
import ru.pricepulse.parsingservice.persistence.entity.ProductEntity;
import ru.pricepulse.parsingservice.wildberries_parser.service.dto.ProductInfoDto;
import java.time.LocalDateTime;
@Component
public class ProductInfoDto2ProductEntity implements Converter<ProductInfoDto, ProductEntity> {
@Override
public ProductEntity convert(ProductInfoDto source) {
return ProductEntity.builder()
.marketplace(Marketplace.WILDBERRIES)
.category(Category.LAPTOP)
.brand(source.getBrand())
.productName(source.getName())
.createdAt(LocalDateTime.now())
.build();
}
}

View File

@ -0,0 +1,85 @@
package ru.pricepulse.parsingservice.wildberries_parser.service;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.transaction.Transactional;
import lombok.AllArgsConstructor;
import org.springframework.core.convert.ConversionService;
import org.springframework.stereotype.Service;
import ru.pricepulse.parsingservice.persistence.entity.PriceHistoryEntity;
import ru.pricepulse.parsingservice.persistence.entity.PriceHistoryId;
import ru.pricepulse.parsingservice.persistence.entity.ProductEntity;
import ru.pricepulse.parsingservice.persistence.repository.ProductPriceRepository;
import ru.pricepulse.parsingservice.persistence.repository.ProductRepository;
import ru.pricepulse.parsingservice.wildberries_parser.configuration.WbProperties;
import ru.pricepulse.parsingservice.wildberries_parser.service.client.Client;
import ru.pricepulse.parsingservice.wildberries_parser.service.dto.ProductInfoDto;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Service
@AllArgsConstructor
public class ParsingService {
private final Client client;
private final ObjectMapper objectMapper;
private final ConversionService conversionService;
private final ProductRepository productRepository;
private final WbProperties wbProperties;
private final ProductPriceRepository productPriceRepository;
@Transactional
public void parse() {
List<ProductEntity> productEntities = new ArrayList<>();
List<PriceHistoryEntity> priceHistories = new ArrayList<>();
final int elementsInPage = 100;
int page = 1;
Integer totalPages = null;
do {
var pageData = client.scrapPage(page, wbProperties.getShard(), wbProperties.getLaptopUrl());
if (totalPages == null) {
Map<String, Object> dataMap = (Map<String, Object>) pageData.get("data");
int totalElements = (int) dataMap.get("total");
totalPages = (int) Math.ceil((double) totalElements / elementsInPage);
}
List<ProductInfoDto> productInfoDtoList = convertMapObjectToListProductInfoDto(pageData);
productInfoDtoList.forEach(dto -> {
ProductEntity productEntity = conversionService.convert(dto, ProductEntity.class);
PriceHistoryEntity priceHistory = PriceHistoryEntity.builder()
.id(new PriceHistoryId(productEntity, OffsetDateTime.now()))
.price(BigDecimal.valueOf(dto.getSalePriceU() / 100.0))
.build();
productEntities.add(productEntity);
priceHistories.add(priceHistory);
});
page++;
} while (page <= totalPages);
productRepository.saveAll(productEntities);
productPriceRepository.saveAll(priceHistories);
}
private List<ProductInfoDto> convertMapObjectToListProductInfoDto(Map<String, Object> map) {
Map<String, ArrayList<Object>> dataMap = (Map<String, ArrayList<Object>>) map.get("data");
return getProductInfoDtos(dataMap);
}
private List<ProductInfoDto> getProductInfoDtos(Map<String, ArrayList<Object>> dataMap) {
return objectMapper.convertValue(
dataMap.get("products"),
new TypeReference<>() {}
);
}
}

View File

@ -0,0 +1,7 @@
package ru.pricepulse.parsingservice.wildberries_parser.service.client;
import java.util.Map;
public interface Client {
Map<String, Object> scrapPage(int page, String shard, String query);
}

View File

@ -0,0 +1,33 @@
package ru.pricepulse.parsingservice.wildberries_parser.service.client;
import lombok.AllArgsConstructor;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import ru.pricepulse.parsingservice.wildberries_parser.configuration.WbProperties;
import java.util.Map;
@AllArgsConstructor
@Service
public class ClientImpl implements Client {
private final WebClient webClient;
private final WbProperties wbProperties;
@Override
public Map<String, Object> scrapPage(int page, String shard, String query) {
String url = wbProperties.getCatalogWbUrl() +
shard +
query +
"?dest=-1257786&page=" + page + "&subject=2290";
return webClient.get()
.uri(url)
.retrieve()
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
})
.retry(50)
.block();
}
}

View File

@ -0,0 +1,20 @@
package ru.pricepulse.parsingservice.wildberries_parser.service.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ProductInfoDto {
private Long id;
private String brand;
private String name;
private String supplier;
private Double supplierRating;
private Integer salePriceU;
private Integer reviewRating;
}

View File

@ -21,6 +21,18 @@ marketplace:
ozon: ozon:
categories-urls: categories-urls:
- https://www.ozon.ru/category/noutbuki-15692 - https://www.ozon.ru/category/noutbuki-15692
wildberries:
base-url: "https://static-basket-01.wbbasket.ru"
catalog-url: "/vol0/data/main-menu-ru-ru-v3.json"
user-agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0)"
catalog-wb-url: "https://catalog.wb.ru/catalog/"
retry-attempts: 5
retry-delay: 1000
shard: "electronic15"
laptop-url: "/catalog"
logging: logging:
pattern: pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg %X%n" console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg %X%n"