import os import sys import urllib.request import ssl import re import time import json from datetime import datetime # Ensure terminal output supports UTF-8 on Windows if sys.platform == 'win32': try: sys.stdout.reconfigure(encoding='utf-8') except AttributeError: pass # Persian/Arabic to English digit mapping FA_TO_EN = { '۰': '0', '۱': '1', '۲': '2', '۳': '3', '۴': '4', '۵': '5', '۶': '6', '۷': '7', '۸': '8', '۹': '9', '٠': '0', '١': '1', '٢': '2', '٣': '3', '٤': '4', '٥': '5', '٦': '6', '٧': '7', '٨': '8', '٩': '9' } def clean_persian_text(text): """Clean HTML tags and standardize spacing.""" if not text: return "" text = re.sub(r'<[^>]+>', '', text) text = re.sub(r'\s+', ' ', text).strip() return text def to_english_digits(text): """Convert Persian/Arabic digits in a string to English digits.""" if not text: return "" return "".join(FA_TO_EN.get(char, char) for char in text) def parse_numeric(text): """Convert a price string with commas and Persian digits into an integer.""" if not text: return 0 clean_text = to_english_digits(text) digits_only = re.sub(r'[^\d]', '', clean_text) try: return int(digits_only) if digits_only else 0 except ValueError: return 0 def fetch_html_with_retry(url, retries=3, delay=2): """Fetch URL with retries, custom headers, and disabled SSL checks.""" ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE 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/avif,image/webp,image/apng,*/*;q=0.8', 'Accept-Language': 'fa,en-US;q=0.9,en;q=0.8', 'Cache-Control': 'no-cache', 'Pragma': 'no-cache' } # Make it a POST request by passing empty data (b"") to completely bypass CDN/ArvanCloud HTML caching req = urllib.request.Request(url, data=b"", headers=headers) for attempt in range(1, retries + 1): try: with urllib.request.urlopen(req, context=ctx, timeout=10) as response: if response.status == 200: html = response.read().decode('utf-8') if "sekee" in html: return html else: raise ValueError("Incorrect page content returned (missing expected coin tags).") except Exception as e: if attempt < retries: time.sleep(delay) delay *= 1.5 else: print(f"⚠️ Connection error on attempt {attempt}: {e}") return None HISTORY_LOG = [] HISTORY_INITIALIZED = False def initialize_history_from_excel(): global HISTORY_LOG, HISTORY_INITIALIZED excel_filename = "coin_prices.xlsx" if not HISTORY_INITIALIZED: if os.path.exists(excel_filename): try: import pandas as pd df = pd.read_excel(excel_filename, sheet_name="تاریخچه تغییرات") HISTORY_LOG = df.to_dict(orient="records") print(f"📥 Loaded {len(HISTORY_LOG)} history logs from existing '{excel_filename}'.") except Exception as e: print(f"💡 Initialized new history log in memory (failed to load: {e})") HISTORY_INITIALIZED = True def save_live_js_and_json(scraped_data): """Save scraped data to JSON and JS files for real-time HTML dashboard.""" current_time_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") live_records = [] for row in scraped_data: live_records.append({ "name": row["Name"], "price_rial": row["Price_Rial"], "price_toman": row["Price_Toman"], "change": row["Change"], "min_toman": row["Min_Price_Toman"], "max_toman": row["Max_Price_Toman"], "last_update": row["Last_Update"] }) data_to_save = { "last_fetch": current_time_str, "prices": live_records } # Save as JSON try: with open("live_prices.json", "w", encoding="utf-8") as f: json.dump(data_to_save, f, ensure_ascii=False, indent=2) except Exception as e: print(f"⚠️ Error saving live_prices.json: {e}") # Save as JS (for file:// protocol CORS bypass) try: js_content = f"window.LIVE_COIN_PRICES = {json.dumps(data_to_save, ensure_ascii=False, indent=2)};" with open("live_prices.js", "w", encoding="utf-8") as f: f.write(js_content) except Exception as e: print(f"⚠️ Error saving live_prices.js: {e}") def save_to_excel(scraped_data): """Save scraped data to Excel file with Latest Prices and History sheets.""" global HISTORY_LOG excel_filename = "coin_prices.xlsx" # Initialize history from Excel if we haven't already initialize_history_from_excel() # Create list for current scraped data new_rows = [] current_time_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S") for row in scraped_data: new_rows.append({ "زمان ثبت": current_time_str, "نام سکه": row["Name"], "قیمت فعلی (ریال)": row["Price_Rial"], "قیمت فعلی (تومان)": row["Price_Toman"], "میزان و درصد تغییر": row["Change"], "کمترین قیمت روز (تومان)": row["Min_Price_Toman"], "بیشترین قیمت روز (تومان)": row["Max_Price_Toman"], "زمان بهروزرسانی سایت": row["Last_Update"] }) import pandas as pd df_new = pd.DataFrame(new_rows) df_latest = df_new.drop(columns=["زمان ثبت"]) # Accumulate history in memory list temp_history_log = HISTORY_LOG + new_rows df_history = pd.DataFrame(temp_history_log) # Write both sheets to Excel try: with pd.ExcelWriter(excel_filename, engine='openpyxl') as writer: df_latest.to_excel(writer, sheet_name="قیمتهای لحظهای", index=False) df_history.to_excel(writer, sheet_name="تاریخچه تغییرات", index=False) # Write succeeded! Commit the temp log to our persistent memory HISTORY_LOG HISTORY_LOG = temp_history_log print(f"📊 Excel file updated: '{excel_filename}' (Latest & History sheets)") return True except Exception as e: print(f"⚠️ Warning: Excel file is locked (likely open in Microsoft Excel).") print(f"⚠️ {len(new_rows)} rows kept in memory and will auto-save on the next minute cycle when you close Excel. (Error: {e})") return False def run_scraper_cycle(): """Runs a single cycle of the scraper.""" url = "https://www.tgju.org/coin" html = fetch_html_with_retry(url) if not html: print("📡 Connection to TGJU failed. Retrying in next cycle...") return False market_rows = re.findall(r'