Compare commits
4 Commits
ee4bb2175f
...
a60533aa1a
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a60533aa1a | ||
![]() |
8425b6a500 | ||
![]() |
1209b34ceb | ||
![]() |
443ea20d55 |
5
pom.xml
5
pom.xml
@ -50,6 +50,11 @@
|
|||||||
<artifactId>spring-dotenv</artifactId>
|
<artifactId>spring-dotenv</artifactId>
|
||||||
<version>4.0.0</version>
|
<version>4.0.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jsoup</groupId>
|
||||||
|
<artifactId>jsoup</artifactId>
|
||||||
|
<version>1.18.3</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@ -4,8 +4,8 @@ import org.springframework.http.ResponseEntity;
|
|||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import searchengine.config.SitesList;
|
|
||||||
import searchengine.dto.statistics.StatisticsResponse;
|
import searchengine.dto.statistics.StatisticsResponse;
|
||||||
|
import searchengine.services.IndexingService;
|
||||||
import searchengine.services.StatisticsService;
|
import searchengine.services.StatisticsService;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@ -13,9 +13,11 @@ import searchengine.services.StatisticsService;
|
|||||||
public class ApiController {
|
public class ApiController {
|
||||||
|
|
||||||
private final StatisticsService statisticsService;
|
private final StatisticsService statisticsService;
|
||||||
|
private final IndexingService indexingService;
|
||||||
|
|
||||||
public ApiController(StatisticsService statisticsService) {
|
public ApiController(StatisticsService statisticsService, IndexingService indexingService) {
|
||||||
this.statisticsService = statisticsService;
|
this.statisticsService = statisticsService;
|
||||||
|
this.indexingService = indexingService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/statistics")
|
@GetMapping("/statistics")
|
||||||
@ -25,7 +27,7 @@ public class ApiController {
|
|||||||
|
|
||||||
@GetMapping("/startIndexing")
|
@GetMapping("/startIndexing")
|
||||||
public ResponseEntity<StatisticsResponse> startIndexing() {
|
public ResponseEntity<StatisticsResponse> startIndexing() {
|
||||||
System.out.println(new SitesList().getSites());
|
indexingService.startIndexing();
|
||||||
return ResponseEntity.ok(statisticsService.getStatistics());
|
return ResponseEntity.ok(statisticsService.getStatistics());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
42
src/main/java/searchengine/model/Link.java
Normal file
42
src/main/java/searchengine/model/Link.java
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package searchengine.model;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public record Link(URI uri, int depth, Set<Link> children, Link parent) {
|
||||||
|
public Link {
|
||||||
|
children = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Link(URI uri, Link parent) {
|
||||||
|
this(uri, (parent == null) ? 0 : parent.depth() + 1, new HashSet<>(), parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addChild(URI childUri) {
|
||||||
|
children.add(new Link(childUri, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Переопределяем equals и hashCode, исключая поля children и parent
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Link link = (Link) o;
|
||||||
|
return depth == link.depth && Objects.equals(uri, link.uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(uri, depth); // Исключаем children и parent
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() { // А он нужен?
|
||||||
|
return "Link{" +
|
||||||
|
"uri=" + uri +
|
||||||
|
", depth=" + depth +
|
||||||
|
'}'; // Исключаем children и parent
|
||||||
|
}
|
||||||
|
}
|
@ -18,8 +18,7 @@ public class PageEntity {
|
|||||||
@JoinColumn(name = "site_id", nullable = false)
|
@JoinColumn(name = "site_id", nullable = false)
|
||||||
private SiteEntity site;
|
private SiteEntity site;
|
||||||
|
|
||||||
@Column(columnDefinition = "TEXT NOT NULL, UNIQUE KEY INDEX_PATH (path(255))") // Unique index version
|
@Column(columnDefinition = "VARCHAR(255) NOT NULL", unique = true)
|
||||||
// @Column(columnDefinition = "TEXT NOT NULL, INDEX PATH_INDEX USING BTREE(path(255))") // Non-unique index version
|
|
||||||
private String path;
|
private String path;
|
||||||
|
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
package searchengine.services;
|
package searchengine.services;
|
||||||
|
|
||||||
public interface IndexingService {
|
public interface IndexingService {
|
||||||
|
void startIndexing();
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,163 @@
|
|||||||
package searchengine.services;
|
package searchengine.services;
|
||||||
|
|
||||||
public class IndexingServiceImpl implements IndexingService {
|
import lombok.RequiredArgsConstructor;
|
||||||
boolean indexingIsRunning = false;
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import searchengine.config.SitesList;
|
||||||
|
import searchengine.model.Link;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class IndexingServiceImpl implements IndexingService {
|
||||||
|
private boolean indexingIsRunning = false;
|
||||||
|
private final SitesList sitesList;
|
||||||
|
|
||||||
|
@Override
|
||||||
public void startIndexing() {
|
public void startIndexing() {
|
||||||
if (indexingIsRunning) {
|
if (indexingIsRunning) {
|
||||||
System.out.println("Индексация уже запущена");
|
System.out.println("Индексация уже запущена");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Логика для запуска индексации или перенидексации сайтов
|
|
||||||
System.out.println("Запуск индексации");
|
|
||||||
indexingIsRunning = true;
|
indexingIsRunning = true;
|
||||||
System.out.println("Индексация запущена");
|
|
||||||
|
// Выводим список сайтов для индексации
|
||||||
|
System.out.println("Список сайтов для индексации:");
|
||||||
|
sitesList.getSites().forEach(site ->
|
||||||
|
System.out.println("URL: " + site.getUrl() + ", Название: " + site.getName())
|
||||||
|
);
|
||||||
|
|
||||||
|
// Начинаем парсинг первого сайта
|
||||||
|
String startUrl = sitesList.getSites().get(0).getUrl();
|
||||||
|
String siteName = sitesList.getSites().get(0).getName();
|
||||||
|
|
||||||
|
System.out.println("Начинаем парсинг сайта " + siteName + " (" + startUrl + ")");
|
||||||
|
|
||||||
|
Set<URI> visitedLinks = ConcurrentHashMap.newKeySet(); // Набор для отслеживания посещенных URL
|
||||||
|
|
||||||
|
try {
|
||||||
|
URI startUri = new URI(startUrl);
|
||||||
|
System.out.printf("\n=== Начало индексации %s (%s) ===\n", siteName, startUri);
|
||||||
|
Link rootLink = new Link(startUri, null); // Создаем корневой Link
|
||||||
|
parse(rootLink, visitedLinks); // Запуск парсинга
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
System.out.println("Некорректный URL: " + startUrl);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
System.out.println("Индексация была прервана: " + e.getMessage());
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
} finally {
|
||||||
|
indexingIsRunning = false;
|
||||||
|
System.out.println("\n=== Индексация завершена ===");
|
||||||
|
System.out.println("Всего страниц проиндексировано: " + visitedLinks.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parse(Link link, Set<URI> visitedLinks) throws InterruptedException {
|
||||||
|
// Добавляем текущий URL в visitedLinks до начала обработки
|
||||||
|
if (!visitedLinks.add(link.uri())) {
|
||||||
|
return; // Если URL уже был обработан, выходим
|
||||||
|
}
|
||||||
|
|
||||||
|
// Задержка для защиты от блокировки
|
||||||
|
Thread.sleep((long) (50 + (Math.random() * 150)));
|
||||||
|
System.out.println("Парсим страницу: " + link.uri());
|
||||||
|
|
||||||
|
// Добавляем дочерние ссылки
|
||||||
|
addChildLinks(link, visitedLinks);
|
||||||
|
|
||||||
|
// Рекурсивно обрабатываем дочерние ссылки
|
||||||
|
link.children().forEach(child -> {
|
||||||
|
try {
|
||||||
|
parse(child, visitedLinks);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new RuntimeException("Парсинг был прерван: " + e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addChildLinks(Link link, Set<URI> visitedLinks) {
|
||||||
|
try {
|
||||||
|
Document document = Jsoup.connect(link.uri().toString())
|
||||||
|
.userAgent("Mozilla/5.0")
|
||||||
|
.referrer("https://www.google.com")
|
||||||
|
.get();
|
||||||
|
|
||||||
|
document.select("a[href]").forEach(element -> {
|
||||||
|
String hrefStr = element.attr("href").strip();
|
||||||
|
try {
|
||||||
|
// Кодируем URL перед созданием объекта URI
|
||||||
|
String encodedHrefStr = URLEncoder.encode(hrefStr, StandardCharsets.UTF_8)
|
||||||
|
.replaceAll("%3A", ":")
|
||||||
|
.replaceAll("%2F", "/");
|
||||||
|
URI href = URI.create(encodedHrefStr);
|
||||||
|
|
||||||
|
if (!href.isAbsolute()) {
|
||||||
|
href = link.uri().resolve(href);
|
||||||
|
}
|
||||||
|
href = normalizeUri(href);
|
||||||
|
|
||||||
|
// Проверяем, что ссылка корректна
|
||||||
|
if (isCorrectUrl(link.uri(), href, visitedLinks)) {
|
||||||
|
link.addChild(href); // Используем метод addChild из класса Link
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Игнорируем некорректные URL без вывода сообщений
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Игнорируем ошибки без вывода сообщений
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private URI normalizeUri(URI href) {
|
||||||
|
try {
|
||||||
|
String path = href.getPath(); // Получаем путь
|
||||||
|
if (path == null) {
|
||||||
|
path = ""; // Если путь равен null, заменяем на пустую строку
|
||||||
|
} else {
|
||||||
|
path = path.replaceAll("/+$", ""); // Убираем завершающие слэши
|
||||||
|
}
|
||||||
|
return new URI(
|
||||||
|
href.getScheme(),
|
||||||
|
href.getAuthority(),
|
||||||
|
path, // Используем обработанный путь
|
||||||
|
null,
|
||||||
|
href.getFragment()
|
||||||
|
);
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
return href; // В случае ошибки возвращаем исходный URI
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isCorrectUrl(URI baseUri, URI href, Set<URI> visitedLinks) {
|
||||||
|
// Проверка на некорректные протоколы
|
||||||
|
String hrefStr = href.toString();
|
||||||
|
if (hrefStr.startsWith("javascript:") || hrefStr.startsWith("mailto:") || hrefStr.startsWith("tel:")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка на принадлежность тому же домену
|
||||||
|
if (href.getHost() == null || !href.getHost().equals(baseUri.getHost())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка на файлы и якоря
|
||||||
|
Pattern patternNotFile = Pattern.compile("(\\S+(\\.(?i)(jpg|png|gif|bmp|pdf|php|doc|docx|rar))$)");
|
||||||
|
Pattern patternNotAnchor = Pattern.compile("#([\\w\\-]+)?$");
|
||||||
|
if (href.isOpaque() || patternNotFile.matcher(hrefStr).find() || patternNotAnchor.matcher(hrefStr).find()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка на уже посещенные ссылки
|
||||||
|
return !visitedLinks.contains(href);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -16,9 +16,11 @@ spring:
|
|||||||
|
|
||||||
indexing-settings:
|
indexing-settings:
|
||||||
sites:
|
sites:
|
||||||
- url: https://www.lenta.ru
|
- url: https://wiki.kuksa.dev
|
||||||
name: Лента.ру
|
name: Wiki Kuksa.Dev
|
||||||
|
- url: https://www.autelrobotics.com
|
||||||
|
name: Autel Robotics
|
||||||
- url: https://www.skillbox.ru
|
- url: https://www.skillbox.ru
|
||||||
name: Skillbox
|
name: Skillbox
|
||||||
- url: https://www.playback.ru
|
# - url: https://www.playback.ru
|
||||||
name: PlayBack.Ru
|
# name: PlayBack.Ru
|
Loading…
x
Reference in New Issue
Block a user