import gradio as gr import logging import os import sqlite3 import json import time from datetime import datetime from typing import Dict, List, Tuple, Optional from urllib.parse import urljoin, urlparse from urllib.robotparser import RobotFileParser import re from bs4 import BeautifulSoup import requests # 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', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' }) 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=10) response.raise_for_status() 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 = """

📊 نمودارهای تحلیلی

انواع اسناد

توزیع بر اساس نوع

سطح اهمیت

امتیازبندی اسناد

""" 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("""

🕷️ اسکراپر اسناد حقوقی

سیستم جمع‌آوری و تحلیل اسناد حقوقی از منابع وب

""") 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()