| from flask import Flask, jsonify, request |
| import requests |
| from bs4 import BeautifulSoup |
| import urllib3 |
|
|
| app = Flask(__name__) |
| urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) |
|
|
| BASE_URL = "https://www.myfxbook.com" |
|
|
| @app.route("/calendar") |
| def calendar(): |
| url = BASE_URL + "/forex-economic-calendar" |
| headers = {"User-Agent": "Mozilla/5.0"} |
| response = requests.get(url, headers=headers, verify=False) |
| soup = BeautifulSoup(response.text, "html.parser") |
| rows = soup.find_all("tr", class_="economicCalendarRow") |
|
|
| events = [] |
|
|
| for row in rows: |
| try: |
| row_id = row.get("data-row-id") |
| date_div = row.find("div", attrs={"data-calendardatetd": True}) |
| date_time = date_div.get("data-calendardatetd", "") if date_div else "" |
| date_text = date_div.get_text(strip=True) if date_div else "" |
|
|
| span = row.find("span", attrs={"name": "calendarLeft"}) |
| time_left = span.get_text(strip=True) if span else "" |
| importance = int(span.get("importance", "0")) if span else 0 |
| timestamp = int(span.get("time", "0")) if span else 0 |
| is_holiday = span.get("data-is-holiday", "false") == "true" if span else False |
| is_passed = span.get("isPassed", "0") == "1" if span else False |
|
|
| tds = row.find_all("td") |
| if len(tds) < 9: |
| continue |
|
|
| country_div = row.find("i", title=True) |
| country = country_div.get("title", "").strip() if country_div else "" |
| currency = tds[3].get_text(strip=True) |
|
|
| event_link_tag = tds[4].select_one("a.calendar-event-link") |
| event_name = event_link_tag.get_text(strip=True) if event_link_tag else "" |
| event_url = event_link_tag.get("href", "") if event_link_tag else "" |
|
|
| impact_div = tds[5].find("div") |
| impact_level = impact_div.get_text(strip=True) if impact_div else "" |
| impact_class = " ".join(impact_div.get("class", [])) if impact_div else "" |
| impact = {"class": impact_class, "level": impact_level} if impact_div else None |
|
|
| previous = tds[6].get_text(strip=True) |
| consensus = tds[7].get_text(strip=True) |
| actual = tds[8].get_text(strip=True) |
|
|
| icon_done = row.select_one(".calendarEventDone") |
| icon_done_visible = icon_done and icon_done.get("style", "") != "display: none" |
|
|
| icon_holiday_divs = row.select("div") |
| icon_holiday_visible = ( |
| len(icon_holiday_divs) > 2 and |
| icon_holiday_divs[2].get("style", "") != "display: none" |
| ) |
|
|
| event_data = { |
| "id": row_id, |
| "date": date_text, |
| "date_time": date_time.replace(" ", "T"), |
| "time_left": time_left, |
| "importance": importance, |
| "timestamp": timestamp, |
| "is_holiday": is_holiday, |
| "is_passed": is_passed, |
| "country": country, |
| "currency": currency, |
| "event_name": event_name, |
| "event_url": event_url, |
| "impact": impact, |
| "previous": previous, |
| "consensus": consensus, |
| "actual": actual, |
| "icons": { |
| "done": icon_done_visible, |
| "holiday": icon_holiday_visible |
| } |
| } |
|
|
| events.append(event_data) |
| except: |
| continue |
|
|
| return jsonify(events) |
|
|
| @app.route("/detail") |
| def detail(): |
| relative_url = request.args.get("url") |
| if not relative_url: |
| return jsonify({"error": "Missing 'url' query parameter"}), 400 |
|
|
| full_url = relative_url if relative_url.startswith("http") else BASE_URL + relative_url |
| headers = {"User-Agent": "Mozilla/5.0"} |
| response = requests.get(full_url, headers=headers, verify=False) |
| if response.status_code != 200: |
| return jsonify({"error": f"Failed to fetch detail from {full_url}"}), 500 |
|
|
| soup = BeautifulSoup(response.text, "html.parser") |
|
|
| paragraphs = soup.select("div.margin-top-15 p") |
| description = [p.get_text(strip=True) for p in paragraphs] |
|
|
| impact_el = soup.find("span", class_="impact_high") |
| impact = impact_el.get_text(strip=True) if impact_el else None |
|
|
| currency_el = soup.find("span", id="eventSymbol") |
| currency = currency_el.get_text(strip=True) if currency_el else None |
|
|
| source_label = soup.find("span", string="Source:") |
| source_el = source_label.find_next("a") if source_label else None |
| source = { |
| "name": source_el.get_text(strip=True) if source_el else None, |
| "url": source_el.get("href") if source_el else None |
| } if source_el else None |
|
|
| category_el = soup.find("a", href=lambda x: x and "category/" in x) |
| category = category_el.get_text(strip=True) if category_el else None |
|
|
| units = None |
| for unit_block in soup.select(".category-units .display-flex"): |
| label = unit_block.find("span") |
| if label and "Units:" in label.text: |
| value = label.find_next_sibling("span") |
| if value: |
| units = value.get_text(strip=True) |
| break |
|
|
| latest_block = soup.select_one(".calendar-info-release-block:nth-of-type(2)") |
| previous = latest_block.find("span", {"data-previous": True}) if latest_block else None |
| actual = latest_block.find("span", {"data-actual": True}) if latest_block else None |
| consensus_latest = latest_block.select_one(".calendar-info-release-line:nth-of-type(2) span:nth-of-type(2)") if latest_block else None |
|
|
| next_block = soup.select_one(".calendar-info-release-block:nth-of-type(3)") |
| next_date = next_block.select_one(".calendar-info-release-line:nth-of-type(1) span:nth-of-type(2)") if next_block else None |
| time_left = next_block.select_one(".calendar-info-release-line:nth-of-type(2) span:nth-of-type(2)") if next_block else None |
| consensus_next = next_block.select_one(".calendar-info-release-line:nth-of-type(3) span:nth-of-type(2)") if next_block else None |
|
|
| return jsonify({ |
| "description": description, |
| "impact_level": impact, |
| "currency": currency, |
| "latest_release": { |
| "previous": previous.get_text(strip=True) if previous else None, |
| "consensus": consensus_latest.get_text(strip=True) if consensus_latest else None, |
| "actual": actual.get_text(strip=True) if actual else None |
| }, |
| "next_release": { |
| "date": next_date.get_text(strip=True) if next_date else None, |
| "time_left": time_left.get_text(strip=True) if time_left else None, |
| "consensus": consensus_next.get_text(strip=True) if consensus_next else None |
| }, |
| "meta": { |
| "source": source, |
| "category": category, |
| "units": units |
| } |
| }) |
|
|
| if __name__ == "__main__": |
| app.run(host="0.0.0.0", port=7860, debug=True) |