Compare commits

..

No commits in common. "a60533aa1a575f3f2e8cd2faee4f96fdb6ce3979" and "ee4bb2175f043195830240e539d5cbe7ab622aa6" have entirely different histories.

7 changed files with 14 additions and 212 deletions

View File

@ -50,11 +50,6 @@
<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>

View File

@ -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,11 +13,9 @@ 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, IndexingService indexingService) { public ApiController(StatisticsService statisticsService) {
this.statisticsService = statisticsService; this.statisticsService = statisticsService;
this.indexingService = indexingService;
} }
@GetMapping("/statistics") @GetMapping("/statistics")
@ -27,7 +25,7 @@ public class ApiController {
@GetMapping("/startIndexing") @GetMapping("/startIndexing")
public ResponseEntity<StatisticsResponse> startIndexing() { public ResponseEntity<StatisticsResponse> startIndexing() {
indexingService.startIndexing(); System.out.println(new SitesList().getSites());
return ResponseEntity.ok(statisticsService.getStatistics()); return ResponseEntity.ok(statisticsService.getStatistics());
} }
} }

View File

@ -1,42 +0,0 @@
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
}
}

View File

@ -18,7 +18,8 @@ public class PageEntity {
@JoinColumn(name = "site_id", nullable = false) @JoinColumn(name = "site_id", nullable = false)
private SiteEntity site; private SiteEntity site;
@Column(columnDefinition = "VARCHAR(255) NOT NULL", unique = true) @Column(columnDefinition = "TEXT NOT NULL, UNIQUE KEY INDEX_PATH (path(255))") // Unique index version
// @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)

View File

@ -1,5 +1,4 @@
package searchengine.services; package searchengine.services;
public interface IndexingService { public interface IndexingService {
void startIndexing();
} }

View File

@ -1,163 +1,16 @@
package searchengine.services; package searchengine.services;
import lombok.RequiredArgsConstructor;
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 { public class IndexingServiceImpl implements IndexingService {
private boolean indexingIsRunning = false; 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);
} }
} }

View File

@ -16,11 +16,9 @@ spring:
indexing-settings: indexing-settings:
sites: sites:
- url: https://wiki.kuksa.dev - url: https://www.lenta.ru
name: Wiki Kuksa.Dev name: Лента.ру
- 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