Spaces:
Paused
Paused
| 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 = """ | |
| <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() |