Spaces:
Runtime error
Runtime error
| """ | |
| تطبيق Gradio يبحث في DuckDuckGo مباشرة (سحب HTML) بدون أي مفتاح API | |
| مصمم ليعمل كواجهة + API في نفس الوقت على Hugging Face Spaces | |
| """ | |
| import requests | |
| from urllib.parse import unquote, urlparse, parse_qs | |
| from bs4 import BeautifulSoup | |
| import gradio as gr | |
| DDG_URL = "https://html.duckduckgo.com/html/" | |
| HEADERS = { | |
| "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " | |
| "(KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", | |
| "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", | |
| "Accept-Language": "ar,en-US;q=0.9,en;q=0.8", | |
| "Referer": "https://html.duckduckgo.com/", | |
| "Origin": "https://html.duckduckgo.com", | |
| "Content-Type": "application/x-www-form-urlencoded", | |
| } | |
| def clean_ddg_link(raw_href: str) -> str: | |
| """يطلع الرابط الحقيقي من رابط DuckDuckGo الوسيط""" | |
| if not raw_href: | |
| return "" | |
| if raw_href.startswith("//"): | |
| raw_href = "https:" + raw_href | |
| parsed = urlparse(raw_href) | |
| qs = parse_qs(parsed.query) | |
| if "uddg" in qs: | |
| return unquote(qs["uddg"][0]) | |
| return raw_href | |
| def search_duckduckgo(query: str): | |
| """ | |
| يبحث في DuckDuckGo ويرجع قائمة نتائج كـ list of dicts | |
| تُستخدم من الواجهة ومن الـ API معاً | |
| """ | |
| if not query or not query.strip(): | |
| return {"query": query, "results": [], "error": "كلمة البحث فارغة"} | |
| last_error = None | |
| response = None | |
| for attempt in range(3): | |
| try: | |
| response = requests.post( | |
| DDG_URL, | |
| data={"q": query}, | |
| headers=HEADERS, | |
| timeout=12, | |
| ) | |
| response.raise_for_status() | |
| last_error = None | |
| break | |
| except requests.exceptions.RequestException as e: | |
| last_error = e | |
| continue | |
| if last_error is not None or response is None: | |
| return { | |
| "query": query, | |
| "results": [], | |
| "error": f"فشل الاتصال بمحرك البحث بعد عدة محاولات: {str(last_error)}", | |
| } | |
| soup = BeautifulSoup(response.text, "html.parser") | |
| results = [] | |
| for result_div in soup.select(".result"): | |
| title_tag = result_div.select_one(".result__title a") or result_div.select_one("a.result__a") | |
| snippet_tag = result_div.select_one(".result__snippet") | |
| if not title_tag: | |
| continue | |
| title = title_tag.get_text(strip=True) | |
| raw_link = title_tag.get("href", "") | |
| link = clean_ddg_link(raw_link) | |
| snippet = snippet_tag.get_text(strip=True) if snippet_tag else "" | |
| if not link or not title: | |
| continue | |
| domain = urlparse(link).netloc.replace("www.", "") | |
| results.append({ | |
| "title": title, | |
| "link": link, | |
| "snippet": snippet, | |
| "displayed_link": domain, | |
| }) | |
| return {"query": query, "results": results, "error": None} | |
| def format_results_html(query: str): | |
| """يبني HTML منسق لعرض النتائج داخل واجهة Gradio نفسها (للاختبار المباشر)""" | |
| data = search_duckduckgo(query) | |
| if data["error"]: | |
| return f"<div style='color:#ff6b6b;padding:16px'>⚠️ {data['error']}</div>" | |
| if not data["results"]: | |
| return "<div style='padding:16px;color:#888'>ما لقينا نتائج لهالبحث.</div>" | |
| html = "<div style='display:flex;flex-direction:column;gap:14px;padding:8px'>" | |
| for r in data["results"]: | |
| html += f""" | |
| <div style='border-bottom:1px solid #333;padding-bottom:12px'> | |
| <div style='font-size:12px;color:#28e0c2;direction:ltr;text-align:right'>{r['displayed_link']}</div> | |
| <a href="{r['link']}" target="_blank" style='font-size:16px;font-weight:700;color:#eef3f5;text-decoration:none'>{r['title']}</a> | |
| <div style='font-size:13px;color:#999;margin-top:4px'>{r['snippet']}</div> | |
| </div> | |
| """ | |
| html += "</div>" | |
| return html | |
| # ============ واجهة Gradio (للعرض المباشر داخل الـ Space) ============ | |
| with gr.Blocks(title="غوّاص - بحث DuckDuckGo") as demo: | |
| gr.Markdown("## 🤿 غوّاص — بحث مباشر عبر DuckDuckGo") | |
| with gr.Row(): | |
| query_input = gr.Textbox(label="ابحث عن أي شيء", placeholder="اكتب كلمة البحث هنا...") | |
| search_btn = gr.Button("بحث", variant="primary") | |
| output_html = gr.HTML() | |
| search_btn.click(fn=format_results_html, inputs=query_input, outputs=output_html) | |
| query_input.submit(fn=format_results_html, inputs=query_input, outputs=output_html) | |
| # ============ API endpoint يستخدمه index.html من الخارج ============ | |
| api_output = gr.JSON(visible=False) | |
| api_trigger = gr.Textbox(visible=False) | |
| api_trigger.change(fn=search_duckduckgo, inputs=api_trigger, outputs=api_output, api_name="search") | |
| if __name__ == "__main__": | |
| demo.launch(server_name="0.0.0.0", server_port=7860) | |