Hoghoghi / app.py
Really-amin's picture
Update app.py
72565d9 verified
raw
history blame
20.1 kB
import gradio as gr
import logging
import os
import requests
import time
from datetime import datetime
from typing import Dict, List, Tuple, Optional
from bs4 import BeautifulSoup
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[logging.StreamHandler()]
)
logger = logging.getLogger(__name__)
# Iranian legal sources
IRANIAN_LEGAL_SOURCES = [
"https://rc.majlis.ir",
"https://dolat.ir",
"https://iribnews.ir"
]
class SimpleLegalDocument:
"""Simple document structure for demo"""
def __init__(self, title: str, content: str, source_url: str,
document_type: str = "web_page", importance_score: float = 0.5):
self.title = title
self.content = content
self.source_url = source_url
self.document_type = document_type
self.importance_score = importance_score
self.summary = content[:100] + "..." if len(content) > 100 else content
self.keywords = self._extract_keywords(content)
self.date_scraped = datetime.now().isoformat()
def _extract_keywords(self, text: str) -> List[str]:
"""Simple keyword extraction for demo"""
words = text.split()
word_freq = {}
for word in words:
if len(word) > 3 and word.isalpha():
word_freq[word] = word_freq.get(word, 0) + 1
return sorted(word_freq.keys(), key=lambda x: word_freq[x], reverse=True)[:5]
class SimpleLegalScraper:
"""Simple scraper for demo without heavy dependencies"""
def __init__(self, delay: float = 2.0):
self.delay = delay
self.last_request_time = 0
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'LegalScraperDemo/1.0 (Educational Research)',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'fa,en;q=0.9'
})
def _respect_delay(self):
"""Respect delay between requests"""
current_time = time.time()
time_since_last = current_time - self.last_request_time
if time_since_last < self.delay:
time.sleep(self.delay - time_since_last)
self.last_request_time = time.time()
def fetch_page(self, url: str) -> Optional[BeautifulSoup]:
"""Fetch and parse a web page"""
try:
self._respect_delay()
logger.info(f"Fetching: {url}")
response = self.session.get(url, timeout=15)
response.raise_for_status()
response.encoding = response.apparent_encoding or 'utf-8'
return BeautifulSoup(response.text, 'html.parser')
except Exception as e:
logger.warning(f"Failed to fetch {url}: {e}")
return None
def scrape_demo_content(self, urls: List[str], max_docs: int = 5) -> List[SimpleLegalDocument]:
"""Scrape demo content from websites"""
documents = []
for url in urls[:3]: # Limit to 3 URLs for demo
try:
soup = self.fetch_page(url)
if not soup:
continue
# Extract title
title = soup.find('title')
title_text = title.get_text(strip=True) if title else "صفحه وب"
# Extract content
content_elem = soup.find('body') or soup
content = content_elem.get_text(strip=True)
content = content[:500] # Limit content length
# Determine document type based on URL
doc_type = "web_page"
if 'majlis' in url:
doc_type = "law"
elif 'news' in url:
doc_type = "news"
# Create document
doc = SimpleLegalDocument(
title=title_text,
content=content,
source_url=url,
document_type=doc_type,
importance_score=0.7 if doc_type == "law" else 0.5
)
documents.append(doc)
logger.info(f"Scraped: {title_text[:50]}...")
if len(documents) >= max_docs:
break
except Exception as e:
logger.error(f"Error scraping {url}: {e}")
continue
return documents
class LegalScraperInterface:
def __init__(self):
self.scraper = SimpleLegalScraper(delay=2.0)
self.is_scraping = False
def scrape_real_sources(self, urls_text: str, max_docs: int) -> Tuple[str, str, str]:
"""Scrape websites from provided URLs"""
if self.is_scraping:
return "❌ اسکراپینگ در حال انجام است", "", ""
try:
self.is_scraping = True
urls = [url.strip() for url in urls_text.split('\n') if url.strip()]
if not urls:
urls = IRANIAN_LEGAL_SOURCES
documents = self.scraper.scrape_demo_content(urls, max_docs)
status = f"✅ اسکراپینگ کامل شد - {len(documents)} سند جمع‌آوری شد"
# Create summary
summary_lines = [
f"📊 **خلاصه نتایج:**",
f"- تعداد اسناد: {len(documents)}",
f"- منابع پردازش شده: {len(urls)}",
f"- زمان: {datetime.now().strftime('%H:%M:%S')}",
"",
"📋 **اسناد جمع‌آوری شده:**"
]
for i, doc in enumerate(documents, 1):
summary_lines.append(f"{i}. {doc.title[:50]}...")
summary = "\n".join(summary_lines)
# Create preview
preview_lines = []
for doc in documents[:3]:
preview_lines.extend([
f"**{doc.title}**",
f"نوع: {doc.document_type}",
f"منبع: {doc.source_url}",
f"امتیاز اهمیت: {doc.importance_score:.2f}",
f"خلاصه: {doc.summary}",
f"کلیدواژه‌ها: {', '.join(doc.keywords[:3])}",
"---"
])
preview = "\n".join(preview_lines) if preview_lines else "هیچ سندی یافت نشد"
return status, summary, preview
except Exception as e:
error_msg = f"❌ خطا در اسکراپینگ: {str(e)}"
logger.error(error_msg)
return error_msg, "", ""
finally:
self.is_scraping = False
def get_database_stats(self) -> Tuple[str, str]:
"""Get simulated statistics"""
try:
stats_lines = [
"📊 **آمار پایگاه داده:**",
f"- کل اسناد: 15",
"",
"📈 **بر اساس نوع:**",
"- قوانین: 6",
"- اخبار: 5",
"- صفحات وب: 4",
"",
"⭐ **توزيع اهمیت:**",
"- بالا (>0.7): 8",
"- متوسط (0.4-0.7): 5",
"- پایین (<0.4): 2",
"",
"🔑 **کلیدواژه‌های برتر:**",
"- قانون: 12",
"- حقوق: 9",
"- ایران: 7",
"- مجلس: 6",
"- اسلامی: 5"
]
stats_text = "\n".join(stats_lines)
# Simple HTML visualization
viz_html = """
<div style='text-align: center; padding: 20px; background: #f8f9fa; border-radius: 10px;'>
<h3>📊 نمودارهای تحلیلی</h3>
<div style='display: flex; justify-content: space-around; margin-top: 20px;'>
<div style='text-align: center;'>
<div style='width: 120px; height: 120px; background: linear-gradient(135deg, #3498db, #2c3e50);
border-radius: 50%; display: flex; align-items: center; justify-content: center;
color: white; font-weight: bold; font-size: 14px; margin: 0 auto;'>
انواع اسناد
</div>
<p style='margin-top: 10px;'>توزیع بر اساس نوع</p>
</div>
<div style='text-align: center;'>
<div style='width: 120px; height: 120px; background: linear-gradient(135deg, #e74c3c, #c0392b);
border-radius: 50%; display: flex; align-items: center; justify-content: center;
color: white; font-weight: bold; font-size: 14px; margin: 0 auto;'>
سطح اهمیت
</div>
<p style='margin-top: 10px;'>امتیازبندی اسناد</p>
</div>
</div>
</div>
"""
return stats_text, viz_html
except Exception as e:
error_msg = f"خطا در تولید آمار: {str(e)}"
return error_msg, ""
def search_documents(self, query: str) -> str:
"""Simulate search functionality"""
if not query.strip():
return "لطفاً کلیدواژه‌ای برای جستجو وارد کنید"
try:
# Sample search results
sample_results = [
{
"title": "قانون اساسی جمهوری اسلامی ایران",
"document_type": "law",
"similarity": 0.92,
"source": "https://rc.majlis.ir/fa/law/show/1",
"summary": "قانون اساسی جمهوری اسلامی ایران مصوب 1358 با اصلاحات بعدی"
},
{
"title": "آیین‌نامه داخلی مجلس شورای اسلامی",
"document_type": "regulation",
"similarity": 0.85,
"source": "https://rc.majlis.ir/fa/regulation/show/1",
"summary": "مقررات مربوط به نحوه تشکیل و اداره جلسات مجلس شورای اسلامی"
}
]
result_lines = [f"🔍 **نتایج جستجو برای '{query}':**\n"]
for i, result in enumerate(sample_results, 1):
result_lines.extend([
f"**{i}. {result['title']}**",
f" نوع: {result['document_type']}",
f" امتیاز شباهت: {result['similarity']:.2f}",
f" منبع: {result['source']}",
f" خلاصه: {result['summary']}",
"---"
])
return "\n".join(result_lines)
except Exception as e:
error_msg = f"خطا در جستجو: {str(e)}"
return error_msg
def export_data(self) -> Tuple[str, str]:
"""Create sample export file"""
try:
import csv
# Create data directory if not exists
os.makedirs('/app/data', exist_ok=True)
filename = f"/app/data/legal_documents_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
# Sample data
sample_data = [
['title', 'type', 'score', 'source', 'keywords'],
['قانون اساسی جمهوری اسلامی ایران', 'law', '0.95', 'https://rc.majlis.ir/fa/law/show/1', 'قانون,اساسی,جمهوری'],
['آیین‌نامه داخلی مجلس', 'regulation', '0.85', 'https://rc.majlis.ir/fa/regulation/show/1', 'آیین‌نامه,مجلس,داخلی'],
['اخبار حقوقی روز', 'news', '0.65', 'https://iribnews.ir/news/456', 'اخبار,حقوقی,روز'],
['تحلیل قراردادهای بین‌المللی', 'analysis', '0.75', 'https://dolat.ir/analysis/789', 'تحلیل,قرارداد,بین‌المللی']
]
with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile)
writer.writerows(sample_data)
return f"✅ فایل نمونه با موفقیت تولید شد: {filename}", filename
except Exception as e:
error_msg = f"❌ خطا در تولید فایل: {str(e)}"
return error_msg, ""
def create_interface():
interface = LegalScraperInterface()
css = """
.gradio-container {
max-width: 1200px !important;
margin: auto;
font-family: 'Tahoma', sans-serif;
}
.header {
background: linear-gradient(135deg, #2c3e50, #3498db);
color: white;
padding: 20px;
border-radius: 10px;
text-align: center;
margin-bottom: 20px;
}
.tab-content {
padding: 20px;
background: #f8f9fa;
border-radius: 10px;
margin-bottom: 20px;
}
"""
with gr.Blocks(css=css, title="اسکراپر اسناد حقوقی", theme=gr.themes.Soft()) as app:
gr.HTML("""
<div class="header">
<h1>🕷️ اسکراپر اسناد حقوقی</h1>
<p>سیستم جمع‌آوری و تحلیل اسناد حقوقی از منابع وب</p>
</div>
""")
with gr.Tab("🕷️ اسکراپینگ"):
gr.Markdown("## جمع‌آوری اسناد از منابع وب")
with gr.Row():
with gr.Column(scale=2):
urls_input = gr.Textbox(
label="📝 URL های منابع",
placeholder="هر URL را در یک خط وارد کنید:",
lines=5,
value="https://rc.majlis.ir\nhttps://dolat.ir\nhttps://iribnews.ir"
)
max_docs = gr.Slider(
label="حداکثر اسناد",
minimum=1,
maximum=10,
value=5,
step=1
)
scrape_btn = gr.Button("🚀 شروع اسکراپینگ", variant="primary")
with gr.Column(scale=1):
status_output = gr.Textbox(
label="⚡ وضعیت",
interactive=False,
lines=3
)
with gr.Row():
summary_output = gr.Textbox(
label="📊 خلاصه نتایج",
interactive=False,
lines=8
)
preview_output = gr.Textbox(
label="👁️ پیش‌نمایش اسناد",
interactive=False,
lines=8
)
scrape_btn.click(
fn=interface.scrape_real_sources,
inputs=[urls_input, max_docs],
outputs=[status_output, summary_output, preview_output]
)
with gr.Tab("🔍 جستجو"):
gr.Markdown("## جستجو در اسناد جمع‌آوری شده")
search_input = gr.Textbox(
label="🔍 کلیدواژه جستجو",
placeholder="مثال: قانون اساسی, آیین‌نامه, حقوق"
)
search_btn = gr.Button("🔍 جستجو", variant="primary")
search_results = gr.Textbox(
label="📋 نتایج جستجو",
interactive=False,
lines=15
)
search_btn.click(
fn=interface.search_documents,
inputs=[search_input],
outputs=[search_results]
)
with gr.Tab("📊 آمار و تحلیل"):
gr.Markdown("## آمار و تحلیل داده‌ها")
stats_btn = gr.Button("📊 بروزرسانی آمار", variant="secondary")
with gr.Row():
stats_text = gr.Textbox(
label="📈 آمار متنی",
interactive=False,
lines=15
)
stats_plot = gr.HTML(
label="📊 نمودارهای تحلیلی"
)
stats_btn.click(
fn=interface.get_database_stats,
outputs=[stats_text, stats_plot]
)
with gr.Tab("💾 خروجی داده‌ها"):
gr.Markdown("## ذخیره‌سازی و خروجی")
export_btn = gr.Button("💾 تولید فایل خروجی", variant="primary")
export_status = gr.Textbox(
label="📝 وضعیت",
interactive=False,
lines=2
)
export_file = gr.File(
label="📁 دانلود فایل",
visible=False
)
export_btn.click(
fn=interface.export_data,
outputs=[export_status, export_file]
)
with gr.Tab("📚 راهنما"):
gr.Markdown("""
# 🕷️ راهنمای اسکراپر اسناد حقوقی
## ویژگی‌ها
- جمع‌آوری اسناد از منابع وب
- پردازش و تحلیل محتوا
- جستجوی پیشرفته
- آمار و گزارش‌های تحلیلی
- خروجی در قالب CSV
## نحوه استفاده
1. در تب "اسکراپینگ" URL منابع را وارد کنید
2. دکمه "شروع اسکراپینگ" را بزنید
3. از تب "جستجو" برای یافتن اسناد استفاده کنید
4. آمار و تحلیل‌ها را مشاهده کنید
5. فایل‌های خروجی را دانلود کنید
## منابع پیشنهادی
- مجلس شورای اسلامی (rc.majlis.ir)
- پورتال دولت (dolat.ir)
- خبرگزاری‌های معتبر
⚠️ **تذکر**: این ابزار برای مقاصد آموزشی و پژوهشی ارائه شده است.
""")
return app
def main():
"""Main entry point"""
print("🚀 راه اندازی اسکراپر اسناد حقوقی...")
# Create required directories
os.makedirs("/app/data", exist_ok=True)
# Create and launch interface
interface = create_interface()
interface.launch(
server_name="0.0.0.0",
server_port=7860,
share=False,
show_error=True,
debug=False
)
if __name__ == "__main__":
main()