Really-amin commited on
Commit
ed86cd3
·
verified ·
1 Parent(s): 3d30bed

Upload app.py

Browse files
Files changed (1) hide show
  1. app/app.py +305 -507
app/app.py CHANGED
@@ -1,11 +1,9 @@
1
- ```python
2
  import gradio as gr
3
  import logging
4
- import requests
5
  import os
6
  from datetime import datetime
7
  from typing import Dict, List, Optional, Tuple
8
- from enhanced_legal_scraper import EnhancedLegalScraper, LegalDocument, IRANIAN_LEGAL_SOURCES
9
 
10
  # Create log directory
11
  log_dir = '/app/logs'
@@ -24,571 +22,371 @@ logger = logging.getLogger(__name__)
24
 
25
  class LegalScraperInterface:
26
  def __init__(self):
27
- self.scraper = EnhancedLegalScraper(delay=1.5)
28
  self.is_scraping = False
29
- self.api_base_url = os.getenv("API_BASE_URL", "http://localhost:8000")
30
 
31
- def scrape_real_sources(self, urls_text: str, max_docs: int, doc_type: str) -> Tuple[str, str, str]:
 
32
  if self.is_scraping:
33
  return "❌ اسکراپینگ در حال انجام است", "", ""
 
34
  try:
35
  self.is_scraping = True
36
- urls = [url.strip() for url in urls_text.split('\n') if url.strip()] or IRANIAN_LEGAL_SOURCES
37
- status_msg = f"🔄 شروع اسکراپینگ {len(urls)} منبع..."
38
- max_docs = min(max_docs, 20)
39
- response = requests.post(
40
- f"{self.api_base_url}/api/scrape",
41
- json={"max_docs": max_docs}
42
- )
43
- response.raise_for_status()
44
- result = response.json()
45
- documents = [LegalDocument(**doc) for doc in result["documents"]]
 
 
 
46
 
47
- status = f"✅ {len(documents)} سند با موفقیت جمع‌آوری شد"
48
  summary_lines = [
49
- "📊 **خلاصه نتایج:**",
50
- f"- تعداد اسناد: {len(documents)}",
51
  f"- منابع پردازش شده: {len(urls)}",
52
- f"- زمان: {datetime.now().strftime('%H:%M:%S')}",
53
- ""
 
54
  ]
55
- if documents:
56
- doc_types = {}
57
- for doc in documents:
58
- doc_types[doc.document_type] = doc_types.get(doc.document_type, 0) + 1
59
- summary_lines.append("📈 **توزیع بر اساس نوع:**")
60
- type_names = {
61
- 'law': 'قوانین', 'news': 'اخبار',
62
- 'ruling': 'آرا', 'regulation': 'آیین‌نامه', 'general': 'عمومی'
63
- }
64
- for doc_type, count in doc_types.items():
65
- summary_lines.append(f"- {type_names.get(doc_type, doc_type)}: {count}")
66
- top_docs = sorted(documents, key=lambda x: x.importance_score, reverse=True)[:3]
67
- summary_lines.extend(["", "⭐ **مهم‌ترین اسناد:**"])
68
- for doc in top_docs:
69
- summary_lines.append(f"- {doc.title[:50]}... (امتیاز: {doc.importance_score:.2f})")
70
  summary = "\n".join(summary_lines)
71
 
 
72
  preview_lines = []
73
- for i, doc in enumerate(documents[:3], 1):
74
  preview_lines.extend([
75
- f"**{i}. {doc.title}**",
76
- f"🏷️ نوع: {type_names.get(doc.document_type, doc.document_type)} | ⭐ امتیاز: {doc.importance_score:.2f}",
77
- f"🔗 منبع: {doc.source_url}",
78
- f"📝 خلاصه: {doc.summary or 'خلاصه در دسترس نیست'}",
79
- f"📌 کلیدواژه‌ها: {', '.join(doc.keywords) if doc.keywords else 'ندارد'}",
80
- ""
81
  ])
82
- preview = "\n".join(preview_lines)
 
 
83
  return status, summary, preview
 
84
  except Exception as e:
85
- logger.error(f"Scrape failed: {e}")
86
- return f"❌ خطا در اسکراپینگ: {str(e)}", "", ""
 
 
87
  finally:
88
  self.is_scraping = False
89
 
90
- def search_documents(self, query: str, search_type: str, doc_filter: str) -> str:
 
91
  try:
92
- if not query.strip():
93
- return "❌ لطفاً عبارت جستجو را وارد کنید"
94
- filter_map = {
95
- 'همه': None,
96
- 'قوانین': 'law',
97
- 'اخبار': 'news',
98
- 'آرا': 'ruling',
99
- 'آیین‌نامه': 'regulation',
100
- 'عمومی': 'general'
101
- }
102
- response = requests.post(
103
- f"{self.api_base_url}/api/search",
104
- json={"query": query, "search_type": search_type, "doc_filter": doc_filter}
105
- )
106
- response.raise_for_status()
107
- results = response.json()
108
- if not results:
109
- return "📭 هیچ نتیجه‌ای یافت نشد"
110
- output_lines = ["📋 **نتایج جستجو:**"]
111
- for i, result in enumerate(results[:10], 1):
112
- output_lines.extend([
113
- f"**{i}. {result['title']}**",
114
- f"🏷️ نوع: {filter_map.get(result['document_type'], result['document_type'])}",
115
- f"⭐ امتیاز اهمیت: {result['importance_score']:.2f}",
116
- f"🔍 امتیاز شباهت: {result.get('similarity_score', 0.0):.2f}",
117
- f"🔗 منبع: {result['source_url']}",
118
- f"📝 خلاصه: {result['summary'] or 'خلاصه در دسترس نیست'}",
119
- f"📄 محتوا: {result['content'][:200]}...",
120
- ""
121
- ])
122
- return "\n".join(output_lines)
123
- except Exception as e:
124
- logger.error(f"Search failed: {e}")
125
- return f"❌ خطا در جستجو: {str(e)}"
126
-
127
- def get_analytics(self) -> Tuple[str, str]:
128
- try:
129
- response = requests.get(f"{self.api_base_url}/api/statistics")
130
- response.raise_for_status()
131
- stats = response.json()
132
- text_lines = [
133
- "📊 **تحلیل آماری اسناد حقوقی:**",
134
- f"- تعداد کل اسناد: {stats['total_documents']}",
135
  "",
136
- "🏷️ **توزیع بر اساس نوع:**"
137
  ]
 
138
  type_names = {
139
- 'law': 'قوانین', 'news': 'اخبار',
140
- 'ruling': 'آرا', 'regulation': 'آیین‌نامه', 'general': 'عمومی'
 
 
 
141
  }
142
- for doc_type, count in stats['by_type'].items():
143
- text_lines.append(f"- {type_names.get(doc_type, doc_type)}: {count}")
144
- text_lines.extend(["", "📈 **توزيع بر اساس دسته‌بندی:**"])
145
- for category, count in stats['by_category'].items():
146
- text_lines.append(f"- {category}: {count}")
147
- text_lines.extend([
148
- "",
149
- "⭐ **توزيع اهمیت:**",
150
- f"- بالا (>0.7): {stats['importance_distribution']['high']}",
151
- f"- متوسط (0.3-0.7): {stats['importance_distribution']['medium']}",
152
- f"- پایین (<0.3): {stats['importance_distribution']['low']}"
153
- ])
154
- text_lines.extend(["", "🔑 **کلیدواژه‌های برتر:**"])
155
- for kw, count in list(stats['top_keywords'].items())[:10]:
156
- text_lines.append(f"- {kw}: {count}")
157
- text_lines.extend(["", "📅 **فعالیت اخیر (هفت روز گذشته):**"])
158
- for day, count in stats['recent_activity'].items():
159
- text_lines.append(f"- {day}: {count} سند")
160
- text_summary = "\n".join(text_lines)
161
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  import plotly.express as px
163
  import pandas as pd
164
- type_df = pd.DataFrame([
165
- {'نوع': type_names.get(k, k), 'تعداد': v}
166
- for k, v in stats['by_type'].items()
167
- ])
168
- type_fig = px.pie(
169
- type_df,
170
- names='نوع',
171
- values='تعداد',
172
- title='توزيع اسناد بر اساس نوع',
173
- color_discrete_sequence=px.colors.qualitative.Plotly
174
- )
175
- type_fig.update_layout(
176
- title_x=0.5,
177
- margin=dict(l=20, r=20, t=50, b=20),
178
- height=400
179
- )
180
- importance_df = pd.DataFrame([
181
- {'سطح': 'بالا (>0.7)', 'تعداد': stats['importance_distribution']['high']},
182
- {'سطح': 'متوسط (0.3-0.7)', 'تعداد': stats['importance_distribution']['medium']},
183
- {'سطح': 'پایین (<0.3)', 'تعداد': stats['importance_distribution']['low']}
184
- ])
185
- importance_fig = px.bar(
186
- importance_df,
187
- x='سطح',
188
- y='تعداد',
189
- title='توزيع اسناد بر اساس اهمیت',
190
- color='سطح',
191
- color_discrete_sequence=px.colors.qualitative.Plotly
192
- )
193
- importance_fig.update_layout(
194
- title_x=0.5,
195
- margin=dict(l=20, r=20, t=50, b=20),
196
- height=400
197
- )
198
- viz_html = f"""
199
- <div style='display: flex; justify-content: space-around;'>
200
- <div style='width: 45%;'>{type_fig.to_html(full_html=False, include_plotlyjs='cdn')}</div>
201
- <div style='width: 45%;'>{importance_fig.to_html(full_html=False, include_plotlyjs='cdn')}</div>
202
- </div>
203
- """
204
- return text_summary, viz_html
205
  except Exception as e:
206
- logger.error(f"Analytics failed: {e}")
207
- return f"❌ خطا در تحلیل: {str(e)}", "<div style='text-align: center; padding: 50px;'><h3>خطا در تولید نمودارها</h3></div>"
208
 
209
- def export_data(self, export_format: str) -> Tuple[str, Optional[gr.File]]:
 
 
 
 
210
  try:
211
- timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
212
- filename = f"/app/data/legal_documents_{timestamp}.{export_format.lower()}"
213
- if export_format == "CSV":
214
- result = self.scraper.export_to_csv(filename)
215
- if result:
216
- return f" فایل CSV با موفقیت تولید شد: {filename}", gr.File(value=filename, visible=True)
217
- else:
218
- return "❌ خطا در تولید فایل CSV", None
219
- else:
220
- import sqlite3
221
- import pandas as pd
222
- import json
223
- conn = sqlite3.connect(self.scraper.db_path)
224
- query = '''
225
- SELECT * FROM legal_documents
226
- ORDER BY importance_score DESC, date_scraped DESC
227
- '''
228
- df = pd.read_sql_query(query, conn)
229
- conn.close()
230
- for col in ['tags', 'keywords', 'legal_entities', 'embedding']:
231
- if col in df.columns:
232
- df[col] = df[col].apply(lambda x: json.loads(x) if x else [])
233
- df.to_json(filename, orient='records', lines=True, force_ascii=False)
234
- return f"✅ فایل JSON با موفقیت تولید شد: {filename}", gr.File(value=filename, visible=True)
235
  except Exception as e:
236
- logger.error(f"Export failed: {e}")
237
- return f"❌ خطا در تولید فایل: {str(e)}", None
 
238
 
239
- def process_ocr(self, file: Optional[gr.File]) -> str:
240
- if not file:
241
- return "❌ لطفاً یک فایل PDF یا تصویر آپلود کنید"
242
  try:
243
- file_extension = os.path.splitext(file.name)[1].lower()
244
- endpoint = "/api/ocr/extract-pdf" if file_extension == '.pdf' else "/api/ocr/extract-image"
245
- with open(file.name, "rb") as f:
246
- files = {"file": (os.path.basename(file.name), f, "multipart/form-data")}
247
- response = requests.post(f"{self.api_base_url}{endpoint}", files=files)
248
- response.raise_for_status()
249
- result = response.json()
250
- if result["success"]:
251
- return f"""
252
- ✅ **نتایج OCR:**
253
- - **روش**: {result['method']}
254
- - **متن استخراج شده**: {result['text'][:500]}{'...' if len(result['text']) > 500 else ''}
255
- - **متادیتا**: {json.dumps(result['metadata'], ensure_ascii=False, indent=2)}
256
- """
257
  else:
258
- return f"❌ خطا در OCR: {result['metadata']['error']}"
 
259
  except Exception as e:
260
- logger.error(f"OCR failed: {e}")
261
- return f"❌ خطا در پردازش OCR: {str(e)}"
 
262
 
263
  def create_interface():
264
  interface = LegalScraperInterface()
 
265
  css = """
266
- .tab-content { padding: 20px; background: #f8f9fa; border-radius: 10px; }
267
- .gradio-container { direction: rtl; font-family: 'Vazir', Arial, sans-serif; }
268
- h1, h2, h3 { text-align: center; color: #2c3e50; }
269
- .btn-primary { background-color: #3498db !important; }
270
- .btn-secondary { background-color: #7f8c8d !important; }
 
 
 
 
 
 
 
 
271
  """
272
 
273
- with gr.Blocks(css=css, theme=gr.themes.Soft()) as app:
274
- gr.Markdown("""
275
- # 🕷️ سامانه هوشمند جمع‌آوری و تحلیل اسناد حقوقی ایران
276
- **نسخه پیشرفته با هوش مصنوعی، OCR و پردازش زبان پارسی** | 🚀 مناسب برای حقوقدانان و پژوهشگران
 
 
 
277
  """)
278
 
279
- with gr.Tabs():
280
- with gr.Tab("📥 جمع‌آوری اسناد"):
281
- gr.HTML('<div class="tab-content">')
282
- gr.Markdown("### 🌐 جمع‌آوری هوشمند اسناد حقوقی")
283
- with gr.Row():
284
  urls_input = gr.Textbox(
285
- label="🔗 آدرس‌های منابع (اختیاری)",
286
- placeholder="هر URL در یک خط\nمثال:\nhttps://www.irna.ir\nhttps://www.tasnimnews.com",
287
- lines=5
 
288
  )
 
289
  max_docs = gr.Slider(
290
- label="📚 حداکثر تعداد اسناد",
291
- minimum=1,
292
- maximum=20,
293
- value=5,
294
- step=1,
295
- info="برای عملکرد بهتر، حداکثر ۲۰ سند"
296
  )
297
- doc_type = gr.Dropdown(
298
- label="🏷️ نوع محتوا",
299
- choices=["همه", "قوانین", "اخبار", "آرا", "آیین‌نامه", "عمومی"],
300
- value="همه"
301
- )
302
- scrape_btn = gr.Button("🚀 شروع جمع‌آوری واقعی", variant="primary")
303
- with gr.Row():
304
  status_output = gr.Textbox(
305
- label="📡 وضعیت لحظه‌ای",
306
- placeholder="آماده برای شروع جمع‌آوری...",
307
- lines=5, interactive=False
308
- )
309
- with gr.Row():
310
- summary_output = gr.Textbox(
311
- label="📋 خلاصه تفصیلی نتایج",
312
- lines=15, interactive=False,
313
- show_copy_button=True
314
- )
315
- preview_output = gr.Textbox(
316
- label="👁️ پیش‌نمایش اسناد پردازش شده",
317
- lines=15, interactive=False,
318
- show_copy_button=True
319
- )
320
- scrape_btn.click(
321
- fn=interface.scrape_real_sources,
322
- inputs=[urls_input, max_docs, doc_type],
323
- outputs=[status_output, summary_output, preview_output]
324
- )
325
- gr.HTML('</div>')
326
-
327
- with gr.Tab("🔍 جستجوی پیشرفته"):
328
- gr.HTML('<div class="tab-content">')
329
- gr.Markdown("### 🧠 جستجوی هوشمند با پردازش زبان طبیعی")
330
- with gr.Row():
331
- search_input = gr.Textbox(
332
- label="🔍 عبارت جستجو",
333
- placeholder="مثال: قانون اساسی، حقوق شهروندی، آیین دادرسی مدنی، مقررات کار",
334
- scale=3,
335
- info="جستجوی هوشمند معنا و مفهوم را درنظر می‌گیرد"
336
- )
337
- search_type = gr.Dropdown(
338
- label="🎯 نوع جستجو",
339
- choices=["هوشمند", "متنی"],
340
- value="هوشمند",
341
- info="هوشمند: بر اساس معنا | متنی: تطبیق کلمات",
342
- scale=1
343
- )
344
- doc_filter = gr.Dropdown(
345
- label="🏷️ فیلتر نوع سند",
346
- choices=["همه", "قوانین", "اخبار", "آرا", "آیین‌نامه", "عمومی"],
347
- value="همه",
348
- scale=1
349
- )
350
- search_btn = gr.Button("🔍 جستجوی پیشرفته", variant="primary")
351
- search_results = gr.Textbox(
352
- label="📋 نتایج جستجوی هوشمند",
353
- lines=20, interactive=False,
354
- show_copy_button=True,
355
- placeholder="نتایج جستجو با امتیاز شباهت و اهمیت اینجا نمایش داده می‌شود..."
356
- )
357
- search_btn.click(
358
- fn=interface.search_documents,
359
- inputs=[search_input, search_type, doc_filter],
360
- outputs=[search_results]
361
- )
362
- gr.HTML('</div>')
363
-
364
- with gr.Tab("📊 آمار و تحلیل هوشمند"):
365
- gr.HTML('<div class="tab-content">')
366
- gr.Markdown("### 📈 تحلیل جامع داده‌های جمع‌آوری شده")
367
- analytics_btn = gr.Button("🔄 بروزرسانی تحلیل‌های هوشمند", variant="secondary")
368
- with gr.Row():
369
- stats_text = gr.Textbox(
370
- label="📈 گزارش آماری تفصیلی",
371
- lines=25, interactive=False,
372
- show_copy_button=True
373
- )
374
- stats_viz = gr.HTML(
375
- label="📊 نمودارهای تعاملی پیشرفته",
376
- value="<div style='text-align: center; padding: 50px; background: #f8f9fa; border-radius: 10px;'><h3>📊 برای مشاهده نمودارهای تعاملی، دکمه بروزرسانی را بزنید</h3></div>"
377
- )
378
- analytics_btn.click(
379
- fn=interface.get_analytics,
380
- outputs=[stats_text, stats_viz]
381
- )
382
- app.load(
383
- fn=interface.get_analytics,
384
- outputs=[stats_text, stats_viz]
385
- )
386
- gr.HTML('</div>')
387
-
388
- with gr.Tab("💾 خروجی داده‌ها"):
389
- gr.HTML('<div class="tab-content">')
390
- gr.Markdown("### 📁 دانلود و خروجی داده‌های پردازش شده")
391
- with gr.Row():
392
- export_format = gr.Dropdown(
393
- label="📋 فرمت خروجی",
394
- choices=["CSV", "JSON"],
395
- value="JSON",
396
- info="JSON شامل اطلاعات کامل NLP است"
397
  )
398
- export_btn = gr.Button("💾 تولید فایل خروجی", variant="primary")
399
- export_status = gr.Textbox(
400
- label="📄 وضعیت خروجی",
401
- placeholder="وضعیت تولید فایل اینجا نمایش داده می‌شود...",
402
  interactive=False,
403
- lines=3
404
- )
405
- export_file = gr.File(
406
- label="📁 دانلود فایل",
407
- visible=True
408
- )
409
- export_btn.click(
410
- fn=interface.export_data,
411
- inputs=[export_format],
412
- outputs=[export_status, export_file]
413
  )
414
- gr.HTML('</div>')
415
-
416
- with gr.Tab("📄 پردازش OCR"):
417
- gr.HTML('<div class="tab-content">')
418
- gr.Markdown("### 📄 استخراج متن از PDF و تصاویر")
419
- file_input = gr.File(
420
- label="📤 آپلود فایل (PDF یا تصویر)",
421
- file_types=[".pdf", ".jpg", ".jpeg", ".png", ".bmp", ".tiff"]
422
  )
423
- ocr_btn = gr.Button("🚀 پردازش فایل", variant="primary")
424
- ocr_output = gr.Textbox(
425
- label="📋 نتایج OCR",
426
- lines=15,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
427
  interactive=False,
428
- show_copy_button=True
429
  )
430
- ocr_btn.click(
431
- fn=interface.process_ocr,
432
- inputs=[file_input],
433
- outputs=[ocr_output]
434
  )
435
- gr.HTML('</div>')
436
 
437
- with gr.Tab("📚 راهنما و اطلاعات"):
438
- gr.HTML('<div class="tab-content">')
439
- gr.Markdown("""
440
- # 🕷️ راهنمای کاربری سیستم هوشمند اسناد حقوقی ایران
441
-
442
- ## 🌟 ویژگی‌های منحصر به فرد
443
-
444
- ### 🤖 هوش مصنوعی پیشرفته
445
- - **مدل ParsBERT**: پردازش حرفه‌ای متن‌های فارسی
446
- - **امتیازدهی هوشمند**: ارزیابی خودکار اهمیت اسناد (0-1)
447
- - **دسته‌بندی خودکار**: تشخیص نوع سند (قانون، رای، اخبار، آیین‌نامه)
448
- - **استخراج کلیدواژه**: شناسایی مفاهیم و اصطلاحات کلیدی
449
- - **تولید خلاصه**: خلاصه‌سازی هوشمند محتوا
450
- - **تحلیل نهادهای حقوقی**: شناسایی قوانین، مواد، دادگاه‌ها
451
- - **OCR پیشرفته**: استخراج متن از PDF و تصاویر
452
- - **رعایت robots.txt**: اسکراپینگ اخلاقی و قانونی
453
-
454
- ### 🌐 منابع واقعی ایرانی
455
- - **مرکز پژوهش‌های مجلس شورای اسلامی** (rc.majlis.ir)
456
- - **پورتال ملی دولت الکترونیک** (dolat.ir)
457
- - **خبرگزاری صدا و سیما** (iribnews.ir)
458
- - **خبرگزاری جمهوری اسلامی** (irna.ir)
459
- - **خبرگزاری تسنیم** (tasnimnews.com)
460
- - **خبرگزاری مهر** (mehrnews.com)
461
- - **خبرگزاری فارس** (farsnews.ir)
462
-
463
- ### 🔍 جستجوی معنایی
464
- - **درک معنا**: جستجو بر اساس مفهوم، نه فقط کلمات
465
- - **امتیاز شباهت**: نمایش میزان مرتبط بودن نتایج (0-1)
466
- - **رتبه‌بندی هوشمند**: ترکیب امتیاز شباهت و اهمیت
467
- - **فیلترهای پیشرفته**: جستجو بر اساس نوع، تاریخ، منبع
468
-
469
- ## 🚀 نحوه استفاده
470
-
471
- ### مرحله ۱: جمع‌آوری اسناد 📥
472
- 1. **انتخاب منابع**: منابع سفارشی وارد کنید یا از پیش‌فرض استفاده کنید
473
- 2. **تنظیم پارامترها**: نوع محتوا و تعداد مطلوب را مشخص کنید
474
- 3. **شروع فرآیند**: دکمه "شروع جمع‌آوری واقعی" را بزنید
475
- 4. **پیگیری پیشرفت**: وضعیت لحظه‌ای را دنبال کنید
476
-
477
- ### مرحله ۲: جستجوی هوشمند 🔍
478
- 1. **وارد کردن کلیدواژه**: عبارت مورد نظر را تایپ کنید
479
- 2. **انتخاب نوع جستجو**: هوشمند (پیشنهادی) یا متنی
480
- 3. **اعمال فیلتر**: نوع سند مورد نظر را انتخاب کنید
481
- 4. **بررسی نتایج**: امتیازها و خلاصه‌ها را مطالعه کنید
482
-
483
- ### مرحله ۳: تحلیل داده‌ها 📊
484
- 1. **مشاهده آمار**: اطلاعات کلی مجم��عه داده‌ها
485
- 2. **بررسی نمودارها**: توزیع‌ها و روندهای تعاملی
486
- 3. **تحلیل کلیدواژه‌ها**: مفاهیم پرتکرار و مهم
487
- 4. **پیگیری فعالیت**: روند جمع‌آوری در زمان
488
-
489
- ### مرحله ۴: خروجی داده‌ها 💾
490
- 1. **انتخاب فرمت**: CSV (ساده) یا JSON (کامل با NLP)
491
- 2. **تولید فایل**: شامل تمام اطلاعات پردازش شده
492
- 3. **دانلود**: فایل آماده برای استفاده خارجی
493
-
494
- ### مرحله ۵: پردازش OCR 📄
495
- 1. **آپلود فایل**: PDF یا تصویر (JPG, PNG, BMP, TIFF)
496
- 2. **پردازش**: دکمه "پردازش فایل" را بزنید
497
- 3. **مشاهده نتایج**: متن استخراج شده و متادیتا
498
-
499
- ## 📊 شاخص‌های کیفی
500
-
501
- ### امتیاز اهمیت (Importance Score)
502
- - **0.8-1.0**: 🔥 اهمیت بسیار بالا (قوانین اساسی، آرای مهم)
503
- - **0.6-0.8**: ⭐ اهمیت بالا (قوانین عادی، اخبار مهم)
504
- - **0.4-0.6**: 🟡 اهمیت متوسط (آیین‌نامه‌ها، اخبار عادی)
505
- - **0.2-0.4**: ⚪ اهمیت پایین (مطالب عمومی)
506
- - **0.0-0.2**: 🔸 اهمیت خیلی پایین
507
-
508
- ### امتیاز شباهت (Similarity Score)
509
- - **0.9-1.0**: تطابق کامل با جستجو
510
- - **0.7-0.9**: شباهت بسیار بالا
511
- - **0.5-0.7**: شباهت قابل قبول
512
- - **0.3-0.5**: شباهت ضعیف
513
- - **0.0-0.3**: عدم مرتبط بودن
514
-
515
- ## ⚙️ تنظیمات پیشرفته
516
-
517
- ### بهینه‌سازی عملکرد
518
- - **حداکثر ۲۰ سند**: برای عملکرد بهتر در هاگینگ فیس
519
- - **تأخیر ۱.۵ ثانیه**: بین درخواست‌ها برای احترام به سرورها
520
- - **رعایت robots.txt**: جلوگیری از نقض قوانین سایت‌ها
521
- - **CPU Mode**: پردازش بر روی CPU برای سازگاری
522
- - **Memory Management**: مدیریت بهینه حافظه
523
-
524
- ### امنیت و اخلاق
525
- - **Rate Limiting**: محدودیت سرعت درخواست‌ها
526
- - **منابع عمومی**: فقط از محتوای عمومی استفاده
527
- - **حفظ حریم خصوصی**: عدم ذخیره اطلاعات شخصی
528
-
529
- ## 🔧 عیب‌یابی
530
-
531
- ### مشکلات رایج
532
-
533
- **❌ "سیستم آماده نیست"**
534
- - صفحه را بروزرسانی کنید
535
- - چند دقیقه صبر کنید (بارگذاری مدل)
536
-
537
- **❌ "هیچ سندی یافت نشد"**
538
- - منابع دیگری امتحان کنید
539
- - کلیدواژه‌های ساده‌تر استفاده کنید
540
- - نوع محتوای مختلف انتخاب کنید
541
-
542
- **⚠️ "خطا در اسکراپینگ یا OCR"**
543
- - اتصال اینترنت را بررسی کنید
544
- - منابع معتبر وارد کنید
545
- - تعداد کمتری درخواست کنید
546
-
547
- **🐌 "عملکرد آهسته"**
548
- - طبیعی است (پردازش هوش مصنوعی)
549
- - تعداد اسناد را کم کنید
550
- - از جستجوی متنی استفاده کنید
551
-
552
- ## 🆘 پشتیبانی
553
-
554
- ### راه‌های ارتباط
555
- - **GitHub Issues**: گزارش باگ و درخواست ویژگی
556
- - **Documentation**: راهنمای کامل فنی
557
- - **Community**: انجمن کاربران
558
-
559
- ### منابع یادگیری
560
- - **Tutorial Videos**: آموزش تصویری
561
- - **Best Practices**: بهترین روش‌های استفاده
562
- - **API Documentation**: راهنمای برنامه‌نویسی
563
-
564
- ---
565
-
566
- ## 📜 اطلاعات قانونی
567
-
568
- **⚖️ تذکر مهم**: این ابزار صرفاً برای مقاصد آموزشی، پژوهشی و اطلاع‌رسانی ��راحی شده است.
569
-
570
- **📋 مسئولیت**: کاربران مسئول رعایت قوانین کپی‌رایت و حریم خصوصی هستند.
571
-
572
- **🔒 حریم خصوصی**: هیچ اطلاعات شخصی ذخیره یا منتقل نمی‌شود.
573
-
574
- ---
575
-
576
- **💡 نسخه**: 2.1 Enhanced | **📅 تاریخ**: مهر ۱۴۰۴ | **🏛️ Made for Iranian Legal Community**
577
- """)
578
- gr.HTML('</div>')
579
 
580
  return app
581
 
582
- if __name__ == "__main__":
583
- print("🚀 Starting Enhanced Iranian Legal Scraper...")
584
- print("🌟 Features: Persian NLP, Real Web Scraping, OCR, Smart Analytics")
585
- app = create_interface()
586
- app.launch(
 
 
 
 
 
 
 
587
  server_name="0.0.0.0",
588
  server_port=7860,
589
- share=True,
590
  show_error=True,
591
- show_tips=True,
592
- enable_queue=True
593
  )
594
- ```
 
 
 
 
1
  import gradio as gr
2
  import logging
 
3
  import os
4
  from datetime import datetime
5
  from typing import Dict, List, Optional, Tuple
6
+ from enhanced_legal_scraper import EnhancedLegalScraper, LegalDocument
7
 
8
  # Create log directory
9
  log_dir = '/app/logs'
 
22
 
23
  class LegalScraperInterface:
24
  def __init__(self):
25
+ self.scraper = EnhancedLegalScraper(delay=2.0)
26
  self.is_scraping = False
 
27
 
28
+ def scrape_real_sources(self, urls_text: str, max_docs: int) -> Tuple[str, str, str]:
29
+ """Scrape websites from provided URLs"""
30
  if self.is_scraping:
31
  return "❌ اسکراپینگ در حال انجام است", "", ""
32
+
33
  try:
34
  self.is_scraping = True
35
+ urls = [url.strip() for url in urls_text.split('\n') if url.strip()]
36
+
37
+ if not urls:
38
+ # Use default sources if no URLs provided
39
+ urls = [
40
+ "https://rc.majlis.ir",
41
+ "https://dolat.ir",
42
+ "https://iribnews.ir"
43
+ ]
44
+
45
+ documents = self.scraper.scrape_real_sources(urls, max_docs)
46
+
47
+ status = f"✅ اسکراپینگ کامل شد - {len(documents)} سند جمع‌آوری شد"
48
 
49
+ # Create summary
50
  summary_lines = [
51
+ f"📊 **خلاصه نتایج:**",
52
+ f"- تعداد کل اسناد: {len(documents)}",
53
  f"- منابع پردازش شده: {len(urls)}",
54
+ f"- زمان اسکراپینگ: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
55
+ "",
56
+ "📋 **جزئیات:**"
57
  ]
58
+
59
+ for i, url in enumerate(urls):
60
+ docs_from_url = [doc for doc in documents if url in doc.source_url]
61
+ summary_lines.append(f"- {url}: {len(docs_from_url)} سند")
62
+
 
 
 
 
 
 
 
 
 
 
63
  summary = "\n".join(summary_lines)
64
 
65
+ # Create preview
66
  preview_lines = []
67
+ for doc in documents[:3]:
68
  preview_lines.extend([
69
+ f"**{doc.title}**",
70
+ f"نوع: {doc.document_type}",
71
+ f"منبع: {doc.source_url}",
72
+ f"امتیاز اهمیت: {doc.importance_score:.2f}",
73
+ f"خلاصه: {doc.summary[:100]}..." if doc.summary else "بدون خلاصه",
74
+ "---"
75
  ])
76
+
77
+ preview = "\n".join(preview_lines) if preview_lines else "هیچ سندی یافت نشد"
78
+
79
  return status, summary, preview
80
+
81
  except Exception as e:
82
+ error_msg = f" خطا در اسکراپینگ: {str(e)}"
83
+ logger.error(error_msg)
84
+ return error_msg, "", ""
85
+
86
  finally:
87
  self.is_scraping = False
88
 
89
+ def get_database_stats(self) -> Tuple[str, str]:
90
+ """Get database statistics and visualizations"""
91
  try:
92
+ stats = self.scraper.get_enhanced_statistics()
93
+
94
+ stats_lines = [
95
+ "📊 **آمار پایگاه داده:**",
96
+ f"- کل اسناد: {stats.get('total_documents', 0)}",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  "",
98
+ "📈 **بر اساس نوع:**"
99
  ]
100
+
101
  type_names = {
102
+ 'law': 'قوانین',
103
+ 'news': 'اخبار',
104
+ 'ruling': 'آرا',
105
+ 'regulation': 'آیین‌نامه',
106
+ 'general': 'عمومی'
107
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
109
+ for doc_type, count in stats.get('by_type', {}).items():
110
+ stats_lines.append(f"- {type_names.get(doc_type, doc_type)}: {count}")
111
+
112
+ stats_text = "\n".join(stats_lines)
113
+
114
+ # Create visualization
115
+ viz_html = self._create_stats_visualization(stats)
116
+
117
+ return stats_text, viz_html
118
+
119
+ except Exception as e:
120
+ error_msg = f"خطا در دریافت آمار: {str(e)}"
121
+ logger.error(error_msg)
122
+ return error_msg, ""
123
+
124
+ def _create_stats_visualization(self, stats: Dict) -> str:
125
+ """Create visualization for statistics"""
126
+ try:
127
  import plotly.express as px
128
  import pandas as pd
129
+
130
+ by_type = stats.get('by_type', {})
131
+ if by_type and stats.get('total_documents', 0) > 0:
132
+ type_names = {
133
+ 'law': 'قوانین',
134
+ 'news': 'اخبار',
135
+ 'ruling': 'آرا',
136
+ 'regulation': 'آیین‌نامه',
137
+ 'general': 'عمومی'
138
+ }
139
+
140
+ labels = [type_names.get(k, k) for k in by_type.keys()]
141
+ values = list(by_type.values())
142
+
143
+ fig = px.pie(
144
+ values=values,
145
+ names=labels,
146
+ title="توزیع اسناد بر اساس نوع"
147
+ )
148
+ fig.update_traces(textposition='inside', textinfo='percent+label')
149
+
150
+ return fig.to_html()
151
+ else:
152
+ return "<p>داده‌ای برای نمایش یافت نشد</p>"
153
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  except Exception as e:
155
+ return f"<p>خطا در ایجاد نمودار: {str(e)}</p>"
 
156
 
157
+ def search_documents(self, query: str) -> str:
158
+ """Search in collected documents"""
159
+ if not query.strip():
160
+ return "لطفاً کلیدواژه‌ای برای جستجو وارد کنید"
161
+
162
  try:
163
+ results = self.scraper.search_with_similarity(query, limit=10)
164
+
165
+ if not results:
166
+ return f"هیچ سندی با کلیدواژه '{query}' یافت نشد"
167
+
168
+ result_lines = [f"🔍 **نتایج جستجو برای '{query}':** ({len(results)} مورد یافت شد)\n"]
169
+
170
+ for i, result in enumerate(results, 1):
171
+ result_lines.extend([
172
+ f"**{i}. {result['title']}**",
173
+ f" نوع: {result['document_type']}",
174
+ f" امتیاز شباهت: {result.get('similarity_score', 0):.3f}",
175
+ f" منبع: {result['source_url']}",
176
+ f" خلاصه: {result.get('summary', 'ندارد')[:100]}...",
177
+ "---"
178
+ ])
179
+
180
+ return "\n".join(result_lines)
181
+
 
 
 
 
 
182
  except Exception as e:
183
+ error_msg = f"خطا در جستجو: {str(e)}"
184
+ logger.error(error_msg)
185
+ return error_msg
186
 
187
+ def export_data(self) -> Tuple[str, str]:
188
+ """Export collected data"""
 
189
  try:
190
+ filename = f"/app/data/legal_documents_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
191
+ success = self.scraper.export_to_csv(filename)
192
+
193
+ if success:
194
+ return f"✅ فایل با موفقیت تولید شد: {filename}", filename
 
 
 
 
 
 
 
 
 
195
  else:
196
+ return "❌ خطا در تولید فایل", ""
197
+
198
  except Exception as e:
199
+ error_msg = f" خطا در export: {str(e)}"
200
+ logger.error(error_msg)
201
+ return error_msg, ""
202
 
203
  def create_interface():
204
  interface = LegalScraperInterface()
205
+
206
  css = """
207
+ .gradio-container {
208
+ max-width: 1200px !important;
209
+ margin: auto;
210
+ font-family: 'Tahoma', sans-serif;
211
+ }
212
+ .header {
213
+ background: linear-gradient(135deg, #2c3e50, #3498db);
214
+ color: white;
215
+ padding: 20px;
216
+ border-radius: 10px;
217
+ text-align: center;
218
+ margin-bottom: 20px;
219
+ }
220
  """
221
 
222
+ with gr.Blocks(css=css, title="اسکراپر اسناد حقوقی", theme=gr.themes.Soft()) as app:
223
+
224
+ gr.HTML("""
225
+ <div class="header">
226
+ <h1>🕷️ اسکراپر پیشرفته اسناد حقوقی</h1>
227
+ <p>سیستم هوشمند جمع‌آوری و تحلیل اسناد حقوقی</p>
228
+ </div>
229
  """)
230
 
231
+ with gr.Tab("🕷️ اسکراپینگ"):
232
+ gr.Markdown("## جمع‌آوری اسناد از منابع وب")
233
+
234
+ with gr.Row():
235
+ with gr.Column(scale=2):
236
  urls_input = gr.Textbox(
237
+ label="📝 URL های منابع",
238
+ placeholder="هر URL را در یک خط وارد کنید (اختیاری):\nhttps://example.com\nhttps://example.org",
239
+ lines=5,
240
+ value="https://rc.majlis.ir\nhttps://dolat.ir\nhttps://iribnews.ir"
241
  )
242
+
243
  max_docs = gr.Slider(
244
+ label="حداکثر اسناد",
245
+ minimum=5,
246
+ maximum=50,
247
+ value=10,
248
+ step=5
 
249
  )
250
+
251
+ scrape_btn = gr.Button("🚀 شروع اسکراپینگ", variant="primary")
252
+
253
+ with gr.Column(scale=1):
 
 
 
254
  status_output = gr.Textbox(
255
+ label=" وضعیت",
256
+ interactive=False,
257
+ lines=3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  )
259
+
260
+ with gr.Row():
261
+ summary_output = gr.Textbox(
262
+ label="📊 خلاصه نتایج",
263
  interactive=False,
264
+ lines=8
 
 
 
 
 
 
 
 
 
265
  )
266
+
267
+ preview_output = gr.Textbox(
268
+ label="👁️ پیش‌نمایش",
269
+ interactive=False,
270
+ lines=8
 
 
 
271
  )
272
+
273
+ scrape_btn.click(
274
+ fn=interface.scrape_real_sources,
275
+ inputs=[urls_input, max_docs],
276
+ outputs=[status_output, summary_output, preview_output]
277
+ )
278
+
279
+ with gr.Tab("🔍 جستجو"):
280
+ gr.Markdown("## جستجو در اسناد جمع‌آوری شده")
281
+
282
+ search_input = gr.Textbox(
283
+ label="🔍 کلی��واژه جستجو",
284
+ placeholder="کلیدواژه مورد نظر را وارد کنید..."
285
+ )
286
+
287
+ search_btn = gr.Button("🔍 جستجو", variant="primary")
288
+
289
+ search_results = gr.Textbox(
290
+ label="📋 نتایج جستجو",
291
+ interactive=False,
292
+ lines=15
293
+ )
294
+
295
+ search_btn.click(
296
+ fn=interface.search_documents,
297
+ inputs=[search_input],
298
+ outputs=[search_results]
299
+ )
300
+
301
+ with gr.Tab("📊 آمار"):
302
+ gr.Markdown("## آمار و تحلیل داده‌ها")
303
+
304
+ stats_btn = gr.Button("📊 بروزرسانی آمار", variant="secondary")
305
+
306
+ with gr.Row():
307
+ stats_text = gr.Textbox(
308
+ label="📈 آمار متنی",
309
  interactive=False,
310
+ lines=12
311
  )
312
+
313
+ stats_plot = gr.HTML(
314
+ label="📊 نمودارها"
 
315
  )
 
316
 
317
+ stats_btn.click(
318
+ fn=interface.get_database_stats,
319
+ outputs=[stats_text, stats_plot]
320
+ )
321
+
322
+ with gr.Tab("💾 خروجی"):
323
+ gr.Markdown("## ذخیره‌سازی داده‌ها")
324
+
325
+ export_btn = gr.Button("💾 خروجی CSV", variant="primary")
326
+
327
+ export_status = gr.Textbox(
328
+ label="📝 وضعیت",
329
+ interactive=False
330
+ )
331
+
332
+ export_file = gr.File(
333
+ label="📁 فایل خروجی",
334
+ visible=False
335
+ )
336
+
337
+ export_btn.click(
338
+ fn=interface.export_data,
339
+ outputs=[export_status, export_file]
340
+ )
341
+
342
+ with gr.Tab("📚 راهنما"):
343
+ gr.Markdown("""
344
+ # 🕷️ راهنمای اسکراپر اسناد حقوقی
345
+
346
+ ## ویژگی‌ها
347
+
348
+ - جمع‌آوری هوشمند اسناد از منابع وب
349
+ - پردازش زبان طبیعی برای متون فارسی
350
+ - جستجوی پیشرفته با تحلیل معنایی
351
+ - آمار و نمودارهای تحلیلی
352
+ - خروجی در قالب CSV
353
+
354
+ ## نحوه استفاده
355
+
356
+ 1. در تب "اسکراپینگ" URL منابع را وارد کنید
357
+ 2. دکمه "شروع اسکراپینگ" را بزنید
358
+ 3. از تب "جستجو" برای یافتن اسناد استفاده کنید
359
+ 4. آمار را در تب مربوطه مشاهده کنید
360
+
361
+ ## منابع پیشنهادی
362
+
363
+ - مجلس شورای اسلامی
364
+ - پورتال دولت
365
+ - خبرگزاری‌های معتبر
366
+
367
+ ⚠️ **تذکر**: این ابزار برای مقاصد آموزشی و پژوهشی ارائه شده است.
368
+ """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
 
370
  return app
371
 
372
+ def main():
373
+ """Main entry point for Hugging Face Spaces"""
374
+ print("🚀 راه اندازی اسکراپر اسناد حقوقی...")
375
+
376
+ # Create required directories
377
+ os.makedirs("/app/data", exist_ok=True)
378
+ os.makedirs("/app/logs", exist_ok=True)
379
+ os.makedirs("/app/cache", exist_ok=True)
380
+
381
+ # Create and launch interface
382
+ interface = create_interface()
383
+ interface.launch(
384
  server_name="0.0.0.0",
385
  server_port=7860,
386
+ share=False,
387
  show_error=True,
388
+ debug=False
 
389
  )
390
+
391
+ if __name__ == "__main__":
392
+ main()