shDima1 commited on
Commit
c57232a
·
verified ·
1 Parent(s): 0f70d94

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +180 -38
src/streamlit_app.py CHANGED
@@ -1,40 +1,182 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
  import streamlit as st
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
1
+ # app.py
2
+
 
3
  import streamlit as st
4
+ import random
5
+ from urllib.parse import urlparse
6
+ from concurrent.futures import ThreadPoolExecutor
7
+ from search_engine_parser.core.engines.google import Search as GoogleSearch
8
+ from search_engine_parser.core.engines.yandex import Search as YandexSearch
9
+ from search_engine_parser.core.engines.bing import Search as BingSearch
10
+ from search_engine_parser.core.engines.duckduckgo import Search as DuckDuckGoSearch
11
+
12
+ # --- Конфигурация страницы Streamlit ---
13
+ st.set_page_config(
14
+ page_title="MetaSearch | Поисковик-агрегатор",
15
+ page_icon="🌪️",
16
+ layout="wide",
17
+ initial_sidebar_state="collapsed"
18
+ )
19
+
20
+ # --- Кастомные стили для красоты ---
21
+ st.markdown("""
22
+ <style>
23
+ /* Стиль для контейнера с результатами */
24
+ .result-card {
25
+ border: 1px solid #e1e4e8;
26
+ border-radius: 12px;
27
+ padding: 1.5rem;
28
+ margin-bottom: 1.5rem;
29
+ box-shadow: 0 4px 6px rgba(0,0,0,0.05);
30
+ transition: box-shadow 0.3s ease-in-out;
31
+ background-color: #ffffff;
32
+ }
33
+ .result-card:hover {
34
+ box-shadow: 0 8px 12px rgba(0,0,0,0.1);
35
+ }
36
+ /* Стиль для заголовка-ссылки */
37
+ .result-card h3 a {
38
+ color: #0d6efd !important;
39
+ text-decoration: none !important;
40
+ font-weight: 600;
41
+ }
42
+ .result-card h3 a:hover {
43
+ text-decoration: underline !important;
44
+ }
45
+ /* Стиль для домена и источника */
46
+ .source-info {
47
+ display: flex;
48
+ align-items: center;
49
+ gap: 10px;
50
+ font-size: 0.9em;
51
+ color: #586069;
52
+ }
53
+ .domain {
54
+ color: #28a745; /* Зеленый цвет для домена */
55
+ }
56
+ </style>
57
+ """, unsafe_allow_html=True)
58
+
59
+
60
+ # --- Словарь с поисковиками и их иконками ---
61
+ ENGINES = {
62
+ "Google": {"engine": GoogleSearch(), "icon": "🇬"},
63
+ "Yandex": {"engine": YandexSearch(), "icon": "🇷🇺"},
64
+ "Bing": {"engine": BingSearch(), "icon": "🇧"},
65
+ "DuckDuckGo": {"engine": DuckDuckGoSearch(), "icon": "🦆"},
66
+ }
67
+
68
+ # --- Функции для поиска ---
69
+
70
+ def search_one_engine(engine_name: str, engine_details: dict, query: str, page: int = 1):
71
+ """
72
+ Выполняет поиск в одном поисковике и обрабатывает результаты.
73
+ """
74
+ try:
75
+ results = engine_details["engine"].search(query, page)
76
+ # Приводим результаты к единому формату
77
+ formatted_results = []
78
+ for res in results['results']: # Большинство парсеров возвращают ключ 'results'
79
+ formatted_results.append({
80
+ "engine": engine_name,
81
+ "icon": engine_details["icon"],
82
+ "title": res.get("titles", "Без заголовка"),
83
+ "link": res.get("links"),
84
+ "description": res.get("descriptions", "")
85
+ })
86
+ return formatted_results
87
+ except Exception as e:
88
+ # st.warning(f"Не удалось получить результаты из {engine_name}: {e}")
89
+ print(f"Ошибка при поиске в {engine_name}: {e}")
90
+ return []
91
+
92
+ def run_parallel_search(query: str):
93
+ """
94
+ Запускает поиск во всех движках параллельно.
95
+ """
96
+ all_results = []
97
+ with ThreadPoolExecutor(max_workers=len(ENGINES)) as executor:
98
+ # Создаем задачи для каждого поисковика
99
+ future_to_engine = {
100
+ executor.submit(search_one_engine, name, details, query): name
101
+ for name, details in ENGINES.items()
102
+ }
103
+ for future in future_to_engine:
104
+ try:
105
+ results = future.result()
106
+ if results:
107
+ all_results.extend(results)
108
+ except Exception as e:
109
+ engine_name = future_to_engine[future]
110
+ print(f"В задаче для {engine_name} произошла ошибка: {e}")
111
+
112
+ return all_results
113
+
114
+
115
+ # --- Основной интерфейс приложения ---
116
+
117
+ st.title("🌪️ MetaSearch")
118
+ st.markdown("##### Ваш умный агрегатор поиска. Получайте лучшее из Google, Yandex, Bing и DuckDuckGo.")
119
+
120
+ # Поле для ввода и кнопка
121
+ search_query = st.text_input(
122
+ "Что будем искать?",
123
+ placeholder="Например: лучшие практики Python...",
124
+ label_visibility="collapsed"
125
+ )
126
+
127
+ if st.button("Найти", type="primary", use_container_width=True) or search_query:
128
+ if search_query:
129
+ with st.spinner("Собираем информацию со всего интернета... 🕵️‍♂️"):
130
+ # Запускаем поиск
131
+ search_results = run_parallel_search(search_query)
132
+
133
+ # Обработка результатов: удаление дубликатов и перемешивание
134
+ unique_results = []
135
+ seen_links = set()
136
+ for result in search_results:
137
+ if result['link'] and result['link'] not in seen_links:
138
+ unique_results.append(result)
139
+ seen_links.add(result['link'])
140
+
141
+ if not unique_results:
142
+ st.error("К сожалению, ничего не найдено. Попробуйте другой запрос.")
143
+ else:
144
+ random.shuffle(unique_results) # Перемешиваем
145
+ st.success(f"Найдено {len(unique_results)} уникальных результатов. Вот лучшие из них:")
146
+ st.markdown("---")
147
+
148
+ # Отображение результатов
149
+ for result in unique_results:
150
+ # Извлекаем домен для красивого отображения
151
+ domain = "Неизвестный домен"
152
+ try:
153
+ parsed_uri = urlparse(result['link'])
154
+ domain = f'{parsed_uri.netloc}'
155
+ except:
156
+ pass
157
+
158
+ # Используем st.markdown с unsafe_allow_html для создания карточки
159
+ st.markdown(f"""
160
+ <div class="result-card">
161
+ <div class="source-info">
162
+ <span>{result['icon']} Найдено в <b>{result['engine']}</b></span>
163
+ </div>
164
+ <h3><a href="{result['link']}" target="_blank">{result['title']}</a></h3>
165
+ <p>{result.get('description', 'Описание недоступно.')}</p>
166
+ <div class="source-info">
167
+ <span class="domain">{domain}</span>
168
+ </div>
169
+ </div>
170
+ """, unsafe_allow_html=True)
171
+ else:
172
+ st.info("Пожалуйста, введите поисковый запрос.")
173
 
174
+ st.markdown(
175
+ """
176
+ ---
177
+ <p style='text-align: center; color: grey;'>
178
+ Создано с помощью Streamlit. <br>
179
+ <b>Внимание:</b> Это неофициальный инструмент. Поисковые системы могут временно блокировать запросы.
180
+ </p>
181
+ """, unsafe_allow_html=True
182
+ )