Mazenbs commited on
Commit
1aa9765
·
verified ·
1 Parent(s): 4bb3b2b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +165 -82
app.py CHANGED
@@ -1,85 +1,168 @@
1
- # app.py
2
-
3
- from fastapi import FastAPI, HTTPException
4
- from pydantic import BaseModel
5
- from services.fetcher import fetch_html
6
- from parser.assembler import parse_law_from_html
7
- from helpers.text_blocks import extract_all_text_blocks
8
  from bs4 import BeautifulSoup
9
- from starlette.concurrency import run_in_threadpool
10
- from supabase_utils import save_law_to_supabase
11
-
12
- app = FastAPI(title="Law Extractor API", version="1.0")
13
-
14
-
15
- # -----------------------------
16
- # نماذج البيانات
17
- # -----------------------------
18
- class ParseRequest(BaseModel):
19
- url: str
20
- save_to_supabase: bool = False
21
- response_format: str = "law_json" # "law_json" أو "text_blocks"
22
-
23
-
24
- class LawJsonResponse(BaseModel):
25
- title: str
26
- preamble: str
27
- sections: list
28
-
29
-
30
- class TextBlocksResponse(BaseModel):
31
- text_blocks: list
32
-
33
-
34
- # -----------------------------
35
- # نقطة النهاية
36
- # -----------------------------
37
- @app.post("/parse")
38
- async def parse_law(request: ParseRequest):
39
- url = request.url
40
- save_flag = request.save_to_supabase
41
- response_format = request.response_format.lower()
42
-
43
- # 1) جلب HTML
44
  try:
45
- html = await fetch_html(url)
46
- except Exception as e:
47
- raise HTTPException(status_code=400, detail=f"فشل تحميل الصفحة: {e}")
48
-
49
- # 2) إنشاء BeautifulSoup
50
- soup = BeautifulSoup(html, "html.parser")
51
-
52
- # 3) إذا اختار المستخدم text_blocks
53
- if response_format == "text_blocks":
54
- blocks = extract_all_text_blocks(soup)
55
-
56
- # إزالة التكرارات فقط
57
- seen = set()
58
- clean_blocks = []
59
- for blk in blocks:
60
- if "text" in blk:
61
- if blk["text"] not in seen:
62
- clean_blocks.append(blk)
63
- seen.add(blk["text"])
64
- else:
65
- clean_blocks.append(blk)
66
-
67
- return {"text_blocks": clean_blocks}
68
-
69
- # 4) خلاف ذلك: تحليل كامل إلى law_json
70
  try:
71
- law_json = await run_in_threadpool(parse_law_from_html, html)
72
- except Exception as e:
73
- raise HTTPException(status_code=500, detail=f"خطأ أثناء التحليل: {e}")
74
-
75
- # 5) الحفظ في Supabase عند الطلب
76
- if save_flag:
77
- try:
78
- await run_in_threadpool(save_law_to_supabase, law_json)
79
- except Exception as e:
80
- raise HTTPException(
81
- status_code=500,
82
- detail=f"تم التحليل بنجاح ولكن فشل الحفظ في Supabase: {e}"
83
- )
84
-
85
- return law_json
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
 
 
 
 
 
 
2
  from bs4 import BeautifulSoup
3
+ from fastapi import FastAPI, HTTPException
4
+ from fastapi.responses import JSONResponse
5
+ from pydantic import BaseModel, HttpUrl
6
+ from typing import List, Dict, Optional
7
+ import urllib.parse
8
+ from fastapi.middleware.cors import CORSMiddleware
9
+
10
+ app = FastAPI(
11
+ title="Web Text Extractor API",
12
+ description="API لاستخراج النصوص من صفحات الويب",
13
+ version="1.0.0"
14
+ )
15
+
16
+ # إضافة CORS للسماح بالوصول من أي مصدر
17
+ app.add_middleware(
18
+ CORSMiddleware,
19
+ allow_origins=["*"],
20
+ allow_credentials=True,
21
+ allow_methods=["*"],
22
+ allow_headers=["*"],
23
+ )
24
+
25
+ # نموذج بيانات للإدخال
26
+ class URLRequest(BaseModel):
27
+ url: HttpUrl
28
+ include_empty_text: Optional[bool] = False
29
+ specific_tags: Optional[List[str]] = None
30
+ exclude_tags: Optional[List[str]] = None
31
+
32
+ def is_valid_url(url: str) -> bool:
33
+ """التحقق من صحة الرابط"""
 
 
 
 
34
  try:
35
+ result = urllib.parse.urlparse(url)
36
+ return all([result.scheme, result.netloc])
37
+ except:
38
+ return False
39
+
40
+ def fetch_webpage(url: str) -> str:
41
+ """جلب محتوى صفحة الويب"""
42
+ headers = {
43
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
44
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  try:
46
+ response = requests.get(url, headers=headers, timeout=10)
47
+ response.raise_for_status()
48
+ return response.text
49
+ except requests.exceptions.RequestException as e:
50
+ raise HTTPException(status_code=400, detail=f"فشل في جلب الصفحة: {str(e)}")
51
+
52
+ def extract_text_from_tags(html_content: str, include_empty: bool = False,
53
+ specific_tags: List[str] = None, exclude_tags: List[str] = None) -> List[Dict[str, str]]:
54
+ """استخراج النصوص من جميع العلامات"""
55
+ soup = BeautifulSoup(html_content, 'html.parser')
56
+
57
+ # تحديد العلامات المراد استخراج النصوص منها
58
+ if specific_tags:
59
+ tags = soup.find_all(specific_tags)
60
+ else:
61
+ # استخراج جميع العلامات التي يمكن أن تحتوي على نص
62
+ tags = soup.find_all(True)
63
+
64
+ # فلترة العلامات المستبعدة
65
+ if exclude_tags:
66
+ tags = [tag for tag in tags if tag.name not in exclude_tags]
67
+
68
+ result = []
69
+
70
+ for tag in tags:
71
+ # الحصول على النص مع تنظيفه
72
+ text = tag.get_text(strip=True, separator=' ')
73
+
74
+ # إذا كان النص فارغاً ولا نريد تضمينه
75
+ if not text and not include_empty:
76
+ continue
77
+
78
+ # إضافة النتيجة إلى القائمة
79
+ result.append({
80
+ 'text': text if text else '',
81
+ 'tag': tag.name,
82
+ 'attrs': dict(tag.attrs) if tag.attrs else {}
83
+ })
84
+
85
+ return result
86
+
87
+ @app.get("/")
88
+ async def root():
89
+ """الصفحة الرئيسية"""
90
+ return {
91
+ "message": "مرحباً بك في API استخراج نصوص صفحات الويب",
92
+ "endpoints": {
93
+ "POST /extract": "استخراج النصوص من رابط URL",
94
+ "GET /health": "فحص حالة الخدمة"
95
+ },
96
+ "usage": {
97
+ "example_request": {
98
+ "url": "https://example.com",
99
+ "include_empty_text": False,
100
+ "specific_tags": ["p", "h1", "h2", "h3"],
101
+ "exclude_tags": ["script", "style"]
102
+ }
103
+ }
104
+ }
105
+
106
+ @app.post("/extract", response_model=List[Dict[str, str]])
107
+ async def extract_text(request: URLRequest):
108
+ """
109
+ استخراج النصوص من صفحة ويب
110
+
111
+ - **url**: رابط الصفحة المراد تحليلها
112
+ - **include_empty_text**: تضمين العلامات الفارغة (افتراضي: False)
113
+ - **specific_tags**: قائمة بعلامات محددة لاستخراج النصوص منها (افتراضي: جميع العلامات)
114
+ - **exclude_tags**: قائمة بعلامات لتجاهلها (مثل: script, style)
115
+ """
116
+
117
+ # جلب محتوى الصفحة
118
+ html_content = fetch_webpage(str(request.url))
119
+
120
+ # استخراج النصوص
121
+ extracted_texts = extract_text_from_tags(
122
+ html_content,
123
+ include_empty=request.include_empty_text,
124
+ specific_tags=request.specific_tags,
125
+ exclude_tags=request.exclude_tags or ["script", "style", "meta", "link", "noscript"]
126
+ )
127
+
128
+ return extracted_texts
129
+
130
+ @app.get("/extract")
131
+ async def extract_text_get(url: str, include_empty: bool = False):
132
+ """نسخة GET من استخراج النصوص (للسهولة)"""
133
+ if not is_valid_url(url):
134
+ raise HTTPException(status_code=400, detail="رابط غير صالح")
135
+
136
+ html_content = fetch_webpage(url)
137
+ extracted_texts = extract_text_from_tags(html_content, include_empty)
138
+
139
+ return extracted_texts
140
+
141
+ @app.get("/health")
142
+ async def health_check():
143
+ """فحص حالة الخدمة"""
144
+ return {"status": "healthy", "service": "web-text-extractor"}
145
+
146
+ @app.get("/tags-info")
147
+ async def tags_info():
148
+ """معلومات عن العلامات الشائعة التي تحتوي على نص"""
149
+ common_tags = {
150
+ "headings": ["h1", "h2", "h3", "h4", "h5", "h6"],
151
+ "paragraphs": ["p"],
152
+ "lists": ["li", "dt", "dd"],
153
+ "text_formatting": ["span", "strong", "em", "b", "i", "mark", "small"],
154
+ "links": ["a"],
155
+ "tables": ["td", "th", "caption"],
156
+ "quotes": ["blockquote", "q"],
157
+ "other": ["div", "section", "article", "header", "footer", "nav", "main", "aside"]
158
+ }
159
+
160
+ return {
161
+ "info": "العلامات الشائعة التي تحتوي على نص في صفحات الويب",
162
+ "common_tags": common_tags,
163
+ "excluded_by_default": ["script", "style", "meta", "link", "noscript", "img", "br", "hr"]
164
+ }
165
+
166
+ if __name__ == "__main__":
167
+ import uvicorn
168
+ uvicorn.run(app, host="0.0.0.0", port=8000)