netseecxld commited on
Commit
a5adbb4
·
verified ·
1 Parent(s): a4fede3

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -263
app.py DELETED
@@ -1,263 +0,0 @@
1
- import sqlite3
2
- import asyncio
3
- import aiohttp
4
- import json
5
- import os
6
- import time
7
- import threading
8
- from bs4 import BeautifulSoup
9
- from fastapi import FastAPI
10
- from huggingface_hub import HfApi, hf_hub_download
11
- import uvicorn
12
-
13
- # ─── НАСТРОЙКИ ────────────────────────────────────────────────
14
- DB_PATH = '/app/linkm_posts.db'
15
- PROGRESS_FILE = '/app/linkm_progress.json'
16
- BASE_URL = 'https://linkm.me/posts/{}'
17
- START_ID = 0
18
- END_ID = 212915997
19
- WORKERS = 150
20
- BATCH_SIZE = 500
21
- REQUEST_TIMEOUT = 10
22
- HF_TOKEN = os.environ.get('HF_TOKEN')
23
- HF_DATASET = os.environ.get('HF_DATASET')
24
- PUSH_EVERY = 10000 # пушить каждые 10k найденных
25
-
26
- HEADERS = {
27
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
28
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
29
- 'Accept-Language': 'en-US,en;q=0.5',
30
- }
31
-
32
- # ─── СОСТОЯНИЕ ────────────────────────────────────────────────
33
- state = {
34
- 'status': 'starting',
35
- 'current_id': START_ID,
36
- 'found': 0,
37
- 'processed': 0,
38
- 'speed': 0,
39
- 'last_push': 0,
40
- 'started_at': time.time(),
41
- }
42
-
43
- # ─── PULL С HF ПРИ СТАРТЕ ─────────────────────────────────────
44
- def pull_from_hf():
45
- if not HF_TOKEN or not HF_DATASET:
46
- return
47
- try:
48
- hf_hub_download(
49
- repo_id=HF_DATASET,
50
- filename='linkm_posts.db',
51
- repo_type='dataset',
52
- local_dir='/app',
53
- token=HF_TOKEN,
54
- )
55
- print('✅ База скачана с HF')
56
- except Exception as e:
57
- print(f'⚠️ Не удалось скачать базу: {e}')
58
-
59
- try:
60
- hf_hub_download(
61
- repo_id=HF_DATASET,
62
- filename='linkm_progress.json',
63
- repo_type='dataset',
64
- local_dir='/app',
65
- token=HF_TOKEN,
66
- )
67
- print('✅ Прогресс скачан с HF')
68
- except Exception as e:
69
- print(f'⚠️ Прогресс не найден на HF: {e}')
70
-
71
- # ─── БАЗА ДАННЫХ ──────────────────────────────────────────────
72
- def init_db():
73
- conn = sqlite3.connect(DB_PATH)
74
- c = conn.cursor()
75
- c.execute('''
76
- CREATE TABLE IF NOT EXISTS posts (
77
- post_id INTEGER PRIMARY KEY,
78
- text TEXT NOT NULL,
79
- parsed_at TEXT DEFAULT (datetime('now'))
80
- )
81
- ''')
82
- conn.commit()
83
- conn.close()
84
-
85
- # ─── ПРОГРЕСС ─────────────────────────────────────────────────
86
- def load_progress():
87
- if os.path.exists(PROGRESS_FILE):
88
- try:
89
- with open(PROGRESS_FILE, 'r') as f:
90
- data = json.load(f)
91
- print(f'▶️ Продолжаем с ID: {data["next_id"]} (найдено: {data["found"]:,})')
92
- return data
93
- except Exception:
94
- pass
95
- return {'next_id': START_ID, 'processed': 0, 'found': 0}
96
-
97
- def save_progress(data):
98
- with open(PROGRESS_FILE, 'w') as f:
99
- json.dump(data, f)
100
-
101
- # ─── ПАРСИНГ ──────────────────────────────────────────────────
102
- def extract_text(html: str):
103
- soup = BeautifulSoup(html, 'html.parser')
104
-
105
- main = soup.find('main', class_='deeplink_main')
106
- if main:
107
- p = main.find('p')
108
- if p:
109
- parts = [node.strip() for node in p.descendants if isinstance(node, str) and node.strip()]
110
- result = ' '.join(parts).strip()
111
- if ' on Link. ' in result:
112
- result = result.split(' on Link. ', 1)[1].strip()
113
- elif result.endswith(' on Link.'):
114
- return None
115
- if result:
116
- return result
117
-
118
- og = soup.find('meta', property='og:description')
119
- if og:
120
- content = og.get('content', '').strip()
121
- if ' on Link. ' in content:
122
- content = content.split(' on Link. ', 1)[1].strip()
123
- elif content.endswith(' on Link.'):
124
- return None
125
- if content:
126
- return content
127
-
128
- return None
129
-
130
- async def fetch_post(session, post_id):
131
- try:
132
- async with session.get(
133
- BASE_URL.format(post_id),
134
- timeout=aiohttp.ClientTimeout(total=REQUEST_TIMEOUT)
135
- ) as resp:
136
- if resp.status != 200:
137
- return post_id, None
138
- html = await resp.text(encoding='utf-8', errors='replace')
139
- return post_id, extract_text(html)
140
- except Exception:
141
- return post_id, None
142
-
143
- def save_batch(rows):
144
- if not rows:
145
- return
146
- conn = sqlite3.connect(DB_PATH)
147
- conn.executemany('INSERT OR IGNORE INTO posts (post_id, text) VALUES (?, ?)', rows)
148
- conn.commit()
149
- conn.close()
150
-
151
- # ─── PUSH В HUGGINGFACE ───────────────────────────────────────
152
- def push_to_hf(progress):
153
- if not HF_TOKEN or not HF_DATASET:
154
- return
155
- try:
156
- api = HfApi(token=HF_TOKEN)
157
- api.upload_file(
158
- path_or_fileobj=DB_PATH,
159
- path_in_repo='linkm_posts.db',
160
- repo_id=HF_DATASET,
161
- repo_type='dataset',
162
- )
163
- api.upload_file(
164
- path_or_fileobj=PROGRESS_FILE,
165
- path_in_repo='linkm_progress.json',
166
- repo_id=HF_DATASET,
167
- repo_type='dataset',
168
- )
169
- print(f'✅ Запушено в HF (найдено: {progress["found"]:,})')
170
- state['last_push'] = progress['found']
171
- except Exception as e:
172
- print(f'❌ Ошибка пуша: {e}')
173
-
174
- # ─── ГЛАВНЫЙ ЦИКЛ ─────────────────────────────────────────────
175
- async def run_parser():
176
- pull_from_hf()
177
- init_db()
178
-
179
- progress = load_progress()
180
- current_id = progress['next_id']
181
- start_time = time.time()
182
-
183
- state['status'] = 'running'
184
- state['current_id'] = current_id
185
- state['found'] = progress['found']
186
- state['processed'] = progress['processed']
187
- state['last_push'] = progress['found']
188
-
189
- connector = aiohttp.TCPConnector(limit=WORKERS, ssl=False)
190
-
191
- async with aiohttp.ClientSession(connector=connector, headers=HEADERS) as session:
192
- while current_id < END_ID:
193
- batch_ids = list(range(current_id, min(current_id + BATCH_SIZE, END_ID)))
194
- results = await asyncio.gather(*[fetch_post(session, pid) for pid in batch_ids])
195
-
196
- found_rows = [(pid, txt) for pid, txt in results if txt]
197
- save_batch(found_rows)
198
-
199
- progress['processed'] += len(batch_ids)
200
- progress['found'] += len(found_rows)
201
- progress['next_id'] = current_id + BATCH_SIZE
202
- save_progress(progress)
203
-
204
- elapsed = time.time() - start_time
205
- speed = progress['processed'] / max(elapsed, 1)
206
-
207
- state['current_id'] = current_id
208
- state['found'] = progress['found']
209
- state['processed'] = progress['processed']
210
- state['speed'] = round(speed)
211
-
212
- print(f'ID: {current_id:,} | Найдено: {progress["found"]:,} | {speed:.0f} ID/сек')
213
-
214
- if progress['found'] - state['last_push'] >= PUSH_EVERY:
215
- push_to_hf(progress)
216
-
217
- current_id += BATCH_SIZE
218
-
219
- state['status'] = 'done'
220
- push_to_hf(progress)
221
-
222
- def start_parser():
223
- asyncio.run(run_parser())
224
-
225
- # ─── FASTAPI ──────────────────────────────────────────────────
226
- app = FastAPI()
227
-
228
- @app.get('/status')
229
- def get_status():
230
- elapsed = time.time() - state['started_at']
231
- remaining_ids = END_ID - state['current_id']
232
- remaining_sec = remaining_ids / max(state['speed'], 1)
233
- return {
234
- 'status': state['status'],
235
- 'current_id': state['current_id'],
236
- 'end_id': END_ID,
237
- 'percent': round(state['current_id'] / END_ID * 100, 2),
238
- 'found': state['found'],
239
- 'processed': state['processed'],
240
- 'speed_per_sec': state['speed'],
241
- 'elapsed_min': round(elapsed / 60, 1),
242
- 'remaining_min': round(remaining_sec / 60, 1),
243
- 'last_push_at': state['last_push'],
244
- }
245
-
246
- @app.get('/search')
247
- def search(q: str, limit: int = 50):
248
- conn = sqlite3.connect(DB_PATH)
249
- c = conn.cursor()
250
- c.execute('SELECT post_id, text FROM posts WHERE text LIKE ? LIMIT ?', (f'%{q}%', limit))
251
- rows = c.fetchall()
252
- conn.close()
253
- return {
254
- 'query': q,
255
- 'count': len(rows),
256
- 'results': [{'post_id': r[0], 'text': r[1]} for r in rows]
257
- }
258
-
259
- # ─── СТАРТ ────────────────────────────────────────────────────
260
- if __name__ == '__main__':
261
- t = threading.Thread(target=start_parser, daemon=True)
262
- t.start()
263
- uvicorn.run(app, host='0.0.0.0', port=7860)