complete task INDX-0002. parse one site from list
This commit is contained in:
		
							
								
								
									
										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> | ||||||
|   | |||||||
							
								
								
									
										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 | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,14 +1,27 @@ | |||||||
| package searchengine.services; | package searchengine.services; | ||||||
|  |  | ||||||
| import lombok.RequiredArgsConstructor; | import lombok.RequiredArgsConstructor; | ||||||
|  | import org.jsoup.Jsoup; | ||||||
|  | import org.jsoup.nodes.Document; | ||||||
| import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||||
| import searchengine.config.SitesList; | 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.Random; | ||||||
|  | import java.util.Set; | ||||||
|  | import java.util.concurrent.ConcurrentHashMap; | ||||||
|  | import java.util.regex.Pattern; | ||||||
|  |  | ||||||
| @Service | @Service | ||||||
| @RequiredArgsConstructor | @RequiredArgsConstructor | ||||||
| public class IndexingServiceImpl implements IndexingService { | public class IndexingServiceImpl implements IndexingService { | ||||||
|     boolean indexingIsRunning = false; |     private boolean indexingIsRunning = false; | ||||||
|     private final SitesList sitesList; |     private final SitesList sitesList; | ||||||
|  |     private final Random random = new Random(); | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void startIndexing() { |     public void startIndexing() { | ||||||
| @@ -16,12 +29,137 @@ public class IndexingServiceImpl implements IndexingService { | |||||||
|             System.out.println("Индексация уже запущена"); |             System.out.println("Индексация уже запущена"); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         System.out.println("Список сайтов для индексации:"); |  | ||||||
|         sitesList.getSites().forEach(site -> System.out.println("URL: " + site.getUrl() + ", Название: " + site.getName())); |  | ||||||
|  |  | ||||||
|         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 уже был обработан, выходим | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Задержка для соблюдения правил robots.txt | ||||||
|  |         Thread.sleep(50 + random.nextInt(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 | ||||||
		Reference in New Issue
	
	Block a user
	 Eduard Kuksa
					Eduard Kuksa