import gradio as gr
import logging
import requests
from datetime import datetime
from typing import Dict, List, Optional, Tuple
from enhanced_legal_scraper import EnhancedLegalScraper, LegalDocument, IRANIAN_LEGAL_SOURCES
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('/app/logs/legal_scraper.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
class LegalScraperInterface:
def __init__(self):
self.scraper = EnhancedLegalScraper(delay=1.5)
self.is_scraping = False
self.api_base_url = os.getenv("API_BASE_URL", "http://localhost:8000")
def scrape_real_sources(self, urls_text: str, max_docs: int, doc_type: str) -> Tuple[str, str, str]:
if self.is_scraping:
return "❌ اسکراپینگ در حال انجام است", "", ""
try:
self.is_scraping = True
urls = [url.strip() for url in urls_text.split('\n') if url.strip()] or IRANIAN_LEGAL_SOURCES
status_msg = f"🔄 شروع اسکراپینگ {len(urls)} منبع..."
max_docs = min(max_docs, 20)
response = requests.post(
f"{self.api_base_url}/api/scrape",
json={"max_docs": max_docs}
)
response.raise_for_status()
result = response.json()
documents = [LegalDocument(**doc) for doc in result["documents"]]
status = f"✅ {len(documents)} سند با موفقیت جمعآوری شد"
summary_lines = [
"📊 **خلاصه نتایج:**",
f"- تعداد اسناد: {len(documents)}",
f"- منابع پردازش شده: {len(urls)}",
f"- زمان: {datetime.now().strftime('%H:%M:%S')}",
""
]
if documents:
doc_types = {}
for doc in documents:
doc_types[doc.document_type] = doc_types.get(doc.document_type, 0) + 1
summary_lines.append("📈 **توزیع بر اساس نوع:**")
type_names = {
'law': 'قوانین', 'news': 'اخبار',
'ruling': 'آرا', 'regulation': 'آییننامه', 'general': 'عمومی'
}
for doc_type, count in doc_types.items():
summary_lines.append(f"- {type_names.get(doc_type, doc_type)}: {count}")
top_docs = sorted(documents, key=lambda x: x.importance_score, reverse=True)[:3]
summary_lines.extend(["", "⭐ **مهمترین اسناد:**"])
for doc in top_docs:
summary_lines.append(f"- {doc.title[:50]}... (امتیاز: {doc.importance_score:.2f})")
summary = "\n".join(summary_lines)
preview_lines = []
for i, doc in enumerate(documents[:3], 1):
preview_lines.extend([
f"**{i}. {doc.title}**",
f"🏷️ نوع: {type_names.get(doc.document_type, doc.document_type)} | ⭐ امتیاز: {doc.importance_score:.2f}",
f"🔗 منبع: {doc.source_url}",
f"📝 خلاصه: {doc.summary or 'خلاصه در دسترس نیست'}",
f"📌 کلیدواژهها: {', '.join(doc.keywords) if doc.keywords else 'ندارد'}",
""
])
preview = "\n".join(preview_lines)
return status, summary, preview
except Exception as e:
logger.error(f"Scrape failed: {e}")
return f"❌ خطا در اسکراپینگ: {str(e)}", "", ""
finally:
self.is_scraping = False
def search_documents(self, query: str, search_type: str, doc_filter: str) -> str:
try:
if not query.strip():
return "❌ لطفاً عبارت جستجو را وارد کنید"
filter_map = {
'همه': None,
'قوانین': 'law',
'اخبار': 'news',
'آرا': 'ruling',
'آییننامه': 'regulation',
'عمومی': 'general'
}
response = requests.post(
f"{self.api_base_url}/api/search",
json={"query": query, "search_type": search_type, "doc_filter": doc_filter}
)
response.raise_for_status()
results = response.json()
if not results:
return "📭 هیچ نتیجهای یافت نشد"
output_lines = ["📋 **نتایج جستجو:**"]
for i, result in enumerate(results[:10], 1):
output_lines.extend([
f"**{i}. {result['title']}**",
f"🏷️ نوع: {filter_map.get(result['document_type'], result['document_type'])}",
f"⭐ امتیاز اهمیت: {result['importance_score']:.2f}",
f"🔍 امتیاز شباهت: {result.get('similarity_score', 0.0):.2f}",
f"🔗 منبع: {result['source_url']}",
f"📝 خلاصه: {result['summary'] or 'خلاصه در دسترس نیست'}",
f"📄 محتوا: {result['content'][:200]}...",
""
])
return "\n".join(output_lines)
except Exception as e:
logger.error(f"Search failed: {e}")
return f"❌ خطا در جستجو: {str(e)}"
def get_analytics(self) -> Tuple[str, str]:
try:
response = requests.get(f"{self.api_base_url}/api/statistics")
response.raise_for_status()
stats = response.json()
text_lines = [
"📊 **تحلیل آماری اسناد حقوقی:**",
f"- تعداد کل اسناد: {stats['total_documents']}",
"",
"🏷️ **توزیع بر اساس نوع:**"
]
type_names = {
'law': 'قوانین', 'news': 'اخبار',
'ruling': 'آرا', 'regulation': 'آییننامه', 'general': 'عمومی'
}
for doc_type, count in stats['by_type'].items():
text_lines.append(f"- {type_names.get(doc_type, doc_type)}: {count}")
text_lines.extend(["", "📈 **توزيع بر اساس دستهبندی:**"])
for category, count in stats['by_category'].items():
text_lines.append(f"- {category}: {count}")
text_lines.extend([
"",
"⭐ **توزيع اهمیت:**",
f"- بالا (>0.7): {stats['importance_distribution']['high']}",
f"- متوسط (0.3-0.7): {stats['importance_distribution']['medium']}",
f"- پایین (<0.3): {stats['importance_distribution']['low']}"
])
text_lines.extend(["", "🔑 **کلیدواژههای برتر:**"])
for kw, count in list(stats['top_keywords'].items())[:10]:
text_lines.append(f"- {kw}: {count}")
text_lines.extend(["", "📅 **فعالیت اخیر (هفت روز گذشته):**"])
for day, count in stats['recent_activity'].items():
text_lines.append(f"- {day}: {count} سند")
text_summary = "\n".join(text_lines)
import plotly.express as px
import pandas as pd
type_df = pd.DataFrame([
{'نوع': type_names.get(k, k), 'تعداد': v}
for k, v in stats['by_type'].items()
])
type_fig = px.pie(
type_df,
names='نوع',
values='تعداد',
title='توزيع اسناد بر اساس نوع',
color_discrete_sequence=px.colors.qualitative.Plotly
)
type_fig.update_layout(
title_x=0.5,
margin=dict(l=20, r=20, t=50, b=20),
height=400
)
importance_df = pd.DataFrame([
{'سطح': 'بالا (>0.7)', 'تعداد': stats['importance_distribution']['high']},
{'سطح': 'متوسط (0.3-0.7)', 'تعداد': stats['importance_distribution']['medium']},
{'سطح': 'پایین (<0.3)', 'تعداد': stats['importance_distribution']['low']}
])
importance_fig = px.bar(
importance_df,
x='سطح',
y='تعداد',
title='توزيع اسناد بر اساس اهمیت',
color='سطح',
color_discrete_sequence=px.colors.qualitative.Plotly
)
importance_fig.update_layout(
title_x=0.5,
margin=dict(l=20, r=20, t=50, b=20),
height=400
)
viz_html = f"""
{type_fig.to_html(full_html=False, include_plotlyjs='cdn')}
{importance_fig.to_html(full_html=False, include_plotlyjs='cdn')}
"""
return text_summary, viz_html
except Exception as e:
logger.error(f"Analytics failed: {e}")
return f"❌ خطا در تحلیل: {str(e)}", "خطا در تولید نمودارها
"
def export_data(self, export_format: str) -> Tuple[str, Optional[gr.File]]:
try:
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
filename = f"legal_documents_{timestamp}.{export_format.lower()}"
if export_format == "CSV":
result = self.scraper.export_to_csv(filename)
if result:
return f"✅ فایل CSV با موفقیت تولید شد: {filename}", gr.File(value=filename, visible=True)
else:
return "❌ خطا در تولید فایل CSV", None
else:
import sqlite3
import pandas as pd
import json
conn = sqlite3.connect(self.scraper.db_path)
query = '''
SELECT * FROM legal_documents
ORDER BY importance_score DESC, date_scraped DESC
'''
df = pd.read_sql_query(query, conn)
conn.close()
for col in ['tags', 'keywords', 'legal_entities', 'embedding']:
if col in df.columns:
df[col] = df[col].apply(lambda x: json.loads(x) if x else [])
df.to_json(filename, orient='records', lines=True, force_ascii=False)
return f"✅ فایل JSON با موفقیت تولید شد: {filename}", gr.File(value=filename, visible=True)
except Exception as e:
logger.error(f"Export failed: {e}")
return f"❌ خطا در تولید فایل: {str(e)}", None
def process_ocr(self, file: Optional[gr.File]) -> str:
if not file:
return "❌ لطفاً یک فایل PDF یا تصویر آپلود کنید"
try:
file_extension = os.path.splitext(file.name)[1].lower()
endpoint = "/api/ocr/extract-pdf" if file_extension == '.pdf' else "/api/ocr/extract-image"
with open(file.name, "rb") as f:
files = {"file": (os.path.basename(file.name), f, "multipart/form-data")}
response = requests.post(f"{self.api_base_url}{endpoint}", files=files)
response.raise_for_status()
result = response.json()
if result["success"]:
return f"""
✅ **نتایج OCR:**
- **روش**: {result['method']}
- **متن استخراج شده**: {result['text'][:500]}{'...' if len(result['text']) > 500 else ''}
- **متادیتا**: {json.dumps(result['metadata'], ensure_ascii=False, indent=2)}
"""
else:
return f"❌ خطا در OCR: {result['metadata']['error']}"
except Exception as e:
logger.error(f"OCR failed: {e}")
return f"❌ خطا در پردازش OCR: {str(e)}"
def create_interface():
interface = LegalScraperInterface()
css = """
.tab-content { padding: 20px; background: #f8f9fa; border-radius: 10px; }
.gradio-container { direction: rtl; font-family: 'Vazir', Arial, sans-serif; }
h1, h2, h3 { text-align: center; color: #2c3e50; }
.btn-primary { background-color: #3498db !important; }
.btn-secondary { background-color: #7f8c8d !important; }
"""
with gr.Blocks(css=css, theme=gr.themes.Soft()) as app:
gr.Markdown("""
# 🕷️ سامانه هوشمند جمعآوری و تحلیل اسناد حقوقی ایران
**نسخه پیشرفته با هوش مصنوعی، OCR و پردازش زبان پارسی** | 🚀 مناسب برای حقوقدانان و پژوهشگران
""")
with gr.Tabs():
with gr.Tab("📥 جمعآوری اسناد"):
gr.HTML('')
gr.Markdown("### 🌐 جمعآوری هوشمند اسناد حقوقی")
with gr.Row():
urls_input = gr.Textbox(
label="🔗 آدرسهای منابع (اختیاری)",
placeholder="هر URL در یک خط\nمثال:\nhttps://www.irna.ir\nhttps://www.tasnimnews.com",
lines=5
)
max_docs = gr.Slider(
label="📚 حداکثر تعداد اسناد",
minimum=1,
maximum=20,
value=5,
step=1,
info="برای عملکرد بهتر، حداکثر ۲۰ سند"
)
doc_type = gr.Dropdown(
label="🏷️ نوع محتوا",
choices=["همه", "قوانین", "اخبار", "آرا", "آییننامه", "عمومی"],
value="همه"
)
scrape_btn = gr.Button("🚀 شروع جمعآوری واقعی", variant="primary")
with gr.Row():
status_output = gr.Textbox(
label="📡 وضعیت لحظهای",
placeholder="آماده برای شروع جمعآوری...",
lines=5, interactive=False
)
with gr.Row():
summary_output = gr.Textbox(
label="📋 خلاصه تفصیلی نتایج",
lines=15, interactive=False,
show_copy_button=True
)
preview_output = gr.Textbox(
label="👁️ پیشنمایش اسناد پردازش شده",
lines=15, interactive=False,
show_copy_button=True
)
scrape_btn.click(
fn=interface.scrape_real_sources,
inputs=[urls_input, max_docs, doc_type],
outputs=[status_output, summary_output, preview_output]
)
gr.HTML('
')
with gr.Tab("🔍 جستجوی پیشرفته"):
gr.HTML('')
gr.Markdown("### 🧠 جستجوی هوشمند با پردازش زبان طبیعی")
with gr.Row():
search_input = gr.Textbox(
label="🔍 عبارت جستجو",
placeholder="مثال: قانون اساسی، حقوق شهروندی، آیین دادرسی مدنی، مقررات کار",
scale=3,
info="جستجوی هوشمند معنا و مفهوم را درنظر میگیرد"
)
search_type = gr.Dropdown(
label="🎯 نوع جستجو",
choices=["هوشمند", "متنی"],
value="هوشمند",
info="هوشمند: بر اساس معنا | متنی: تطبیق کلمات",
scale=1
)
doc_filter = gr.Dropdown(
label="🏷️ فیلتر نوع سند",
choices=["همه", "قوانین", "اخبار", "آرا", "آییننامه", "عمومی"],
value="همه",
scale=1
)
search_btn = gr.Button("🔍 جستجوی پیشرفته", variant="primary")
search_results = gr.Textbox(
label="📋 نتایج جستجوی هوشمند",
lines=20, interactive=False,
show_copy_button=True,
placeholder="نتایج جستجو با امتیاز شباهت و اهمیت اینجا نمایش داده میشود..."
)
search_btn.click(
fn=interface.search_documents,
inputs=[search_input, search_type, doc_filter],
outputs=[search_results]
)
gr.HTML('
')
with gr.Tab("📊 آمار و تحلیل هوشمند"):
gr.HTML('')
gr.Markdown("### 📈 تحلیل جامع دادههای جمعآوری شده")
analytics_btn = gr.Button("🔄 بروزرسانی تحلیلهای هوشمند", variant="secondary")
with gr.Row():
stats_text = gr.Textbox(
label="📈 گزارش آماری تفصیلی",
lines=25, interactive=False,
show_copy_button=True
)
stats_viz = gr.HTML(
label="📊 نمودارهای تعاملی پیشرفته",
value="
📊 برای مشاهده نمودارهای تعاملی، دکمه بروزرسانی را بزنید
"
)
analytics_btn.click(
fn=interface.get_analytics,
outputs=[stats_text, stats_viz]
)
app.load(
fn=interface.get_analytics,
outputs=[stats_text, stats_viz]
)
gr.HTML('
')
with gr.Tab("💾 خروجی دادهها"):
gr.HTML('')
gr.Markdown("### 📁 دانلود و خروجی دادههای پردازش شده")
with gr.Row():
export_format = gr.Dropdown(
label="📋 فرمت خروجی",
choices=["CSV", "JSON"],
value="JSON",
info="JSON شامل اطلاعات کامل NLP است"
)
export_btn = gr.Button("💾 تولید فایل خروجی", variant="primary")
export_status = gr.Textbox(
label="📄 وضعیت خروجی",
placeholder="وضعیت تولید فایل اینجا نمایش داده میشود...",
interactive=False,
lines=3
)
export_file = gr.File(
label="📁 دانلود فایل",
visible=True
)
export_btn.click(
fn=interface.export_data,
inputs=[export_format],
outputs=[export_status, export_file]
)
gr.HTML('
')
with gr.Tab("📄 پردازش OCR"):
gr.HTML('')
gr.Markdown("### 📄 استخراج متن از PDF و تصاویر")
file_input = gr.File(
label="📤 آپلود فایل (PDF یا تصویر)",
file_types=[".pdf", ".jpg", ".jpeg", ".png", ".bmp", ".tiff"]
)
ocr_btn = gr.Button("🚀 پردازش فایل", variant="primary")
ocr_output = gr.Textbox(
label="📋 نتایج OCR",
lines=15,
interactive=False,
show_copy_button=True
)
ocr_btn.click(
fn=interface.process_ocr,
inputs=[file_input],
outputs=[ocr_output]
)
gr.HTML('
')
with gr.Tab("📚 راهنما و اطلاعات"):
gr.HTML('')
gr.Markdown("""
# 🕷️ راهنمای کاربری سیستم هوشمند اسناد حقوقی ایران
## 🌟 ویژگیهای منحصر به فرد
### 🤖 هوش مصنوعی پیشرفته
- **مدل ParsBERT**: پردازش حرفهای متنهای فارسی
- **امتیازدهی هوشمند**: ارزیابی خودکار اهمیت اسناد (0-1)
- **دستهبندی خودکار**: تشخیص نوع سند (قانون، رای، اخبار، آییننامه)
- **استخراج کلیدواژه**: شناسایی مفاهیم و اصطلاحات کلیدی
- **تولید خلاصه**: خلاصهسازی هوشمند محتوا
- **تحلیل نهادهای حقوقی**: شناسایی قوانین، مواد، دادگاهها
- **OCR پیشرفته**: استخراج متن از PDF و تصاویر
- **رعایت robots.txt**: اسکراپینگ اخلاقی و قانونی
### 🌐 منابع واقعی ایرانی
- **مرکز پژوهشهای مجلس شورای اسلامی** (rc.majlis.ir)
- **پورتال ملی دولت الکترونیک** (dolat.ir)
- **خبرگزاری صدا و سیما** (iribnews.ir)
- **خبرگزاری جمهوری اسلامی** (irna.ir)
- **خبرگزاری تسنیم** (tasnimnews.com)
- **خبرگزاری مهر** (mehrnews.com)
- **خبرگزاری فارس** (farsnews.ir)
### 🔍 جستجوی معنایی
- **درک معنا**: جستجو بر اساس مفهوم، نه فقط کلمات
- **امتیاز شباهت**: نمایش میزان مرتبط بودن نتایج (0-1)
- **رتبهبندی هوشمند**: ترکیب امتیاز شباهت و اهمیت
- **فیلترهای پیشرفته**: جستجو بر اساس نوع، تاریخ، منبع
## 🚀 نحوه استفاده
### مرحله ۱: جمعآوری اسناد 📥
1. **انتخاب منابع**: منابع سفارشی وارد کنید یا از پیشفرض استفاده کنید
2. **تنظیم پارامترها**: نوع محتوا و تعداد مطلوب را مشخص کنید
3. **شروع فرآیند**: دکمه "شروع جمعآوری واقعی" را بزنید
4. **پیگیری پیشرفت**: وضعیت لحظهای را دنبال کنید
### مرحله ۲: جستجوی هوشمند 🔍
1. **وارد کردن کلیدواژه**: عبارت مورد نظر را تایپ کنید
2. **انتخاب نوع جستجو**: هوشمند (پیشنهادی) یا متنی
3. **اعمال فیلتر**: نوع سند مورد نظر را انتخاب کنید
4. **بررسی نتایج**: امتیازها و خلاصهها را مطالعه کنید
### مرحله ۳: تحلیل دادهها 📊
1. **مشاهده آمار**: اطلاعات کلی مجموعه دادهها
2. **بررسی نمودارها**: توزیعها و روندهای تعاملی
3. **تحلیل کلیدواژهها**: مفاهیم پرتکرار و مهم
4. **پیگیری فعالیت**: روند جمعآوری در زمان
### مرحله ۴: خروجی دادهها 💾
1. **انتخاب فرمت**: CSV (ساده) یا JSON (کامل با NLP)
2. **تولید فایل**: شامل تمام اطلاعات پردازش شده
3. **دانلود**: فایل آماده برای استفاده خارجی
### مرحله ۵: پردازش OCR 📄
1. **آپلود فایل**: PDF یا تصویر (JPG, PNG, BMP, TIFF)
2. **پردازش**: دکمه "پردازش فایل" را بزنید
3. **مشاهده نتایج**: متن استخراج شده و متادیتا
## 📊 شاخصهای کیفی
### امتیاز اهمیت (Importance Score)
- **0.8-1.0**: 🔥 اهمیت بسیار بالا (قوانین اساسی، آرای مهم)
- **0.6-0.8**: ⭐ اهمیت بالا (قوانین عادی، اخبار مهم)
- **0.4-0.6**: 🟡 اهمیت متوسط (آییننامهها، اخبار عادی)
- **0.2-0.4**: ⚪ اهمیت پایین (مطالب عمومی)
- **0.0-0.2**: 🔸 اهمیت خیلی پایین
### امتیاز شباهت (Similarity Score)
- **0.9-1.0**: تطابق کامل با جستجو
- **0.7-0.9**: شباهت بسیار بالا
- **0.5-0.7**: شباهت قابل قبول
- **0.3-0.5**: شباهت ضعیف
- **0.0-0.3**: عدم مرتبط بودن
## ⚙️ تنظیمات پیشرفته
### بهینهسازی عملکرد
- **حداکثر ۲۰ سند**: برای عملکرد بهتر در هاگینگ فیس
- **تأخیر ۱.۵ ثانیه**: بین درخواستها برای احترام به سرورها
- **رعایت robots.txt**: جلوگیری از نقض قوانین سایتها
- **CPU Mode**: پردازش بر روی CPU برای سازگاری
- **Memory Management**: مدیریت بهینه حافظه
### امنیت و اخلاق
- **Rate Limiting**: محدودیت سرعت درخواستها
- **منابع عمومی**: فقط از محتوای عمومی استفاده
- **حفظ حریم خصوصی**: عدم ذخیره اطلاعات شخصی
## 🔧 عیبیابی
### مشکلات رایج
**❌ "سیستم آماده نیست"**
- صفحه را بروزرسانی کنید
- چند دقیقه صبر کنید (بارگذاری مدل)
**❌ "هیچ سندی یافت نشد"**
- منابع دیگری امتحان کنید
- کلیدواژههای سادهتر استفاده کنید
- نوع محتوای مختلف انتخاب کنید
**⚠️ "خطا در اسکراپینگ یا OCR"**
- اتصال اینترنت را بررسی کنید
- منابع معتبر وارد کنید
- تعداد کمتری درخواست کنید
**🐌 "عملکرد آهسته"**
- طبیعی است (پردازش هوش مصنوعی)
- تعداد اسناد را کم کنید
- از جستجوی متنی استفاده کنید
## 🆘 پشتیبانی
### راههای ارتباط
- **GitHub Issues**: گزارش باگ و درخواست ویژگی
- **Documentation**: راهنمای کامل فنی
- **Community**: انجمن کاربران
### منابع یادگیری
- **Tutorial Videos**: آموزش تصویری
- **Best Practices**: بهترین روشهای استفاده
- **API Documentation**: راهنمای برنامهنویسی
---
## 📜 اطلاعات قانونی
**⚖️ تذکر مهم**: این ابزار صرفاً برای مقاصد آموزشی، پژوهشی و اطلاعرسانی طراحی شده است.
**📋 مسئولیت**: کاربران مسئول رعایت قوانین کپیرایت و حریم خصوصی هستند.
**🔒 حریم خصوصی**: هیچ اطلاعات شخصی ذخیره یا منتقل نمیشود.
---
**💡 نسخه**: 2.1 Enhanced | **📅 تاریخ**: مهر ۱۴۰۴ | **🏛️ Made for Iranian Legal Community**
""")
gr.HTML('
')
return app
if __name__ == "__main__":
print("🚀 Starting Enhanced Iranian Legal Scraper...")
print("🌟 Features: Persian NLP, Real Web Scraping, OCR, Smart Analytics")
app = create_interface()
app.launch(
server_name="0.0.0.0",
server_port=7860,
share=True,
show_error=True,
show_tips=True,
enable_queue=True
)