Загрузка данных
import json
import re
import sys
import time
from datetime import datetime
import requests
from bs4 import BeautifulSoup
# User config
BOT_TOKEN = "7423335967:AAE_9_i3AoqwlfpYE_h9qiMs2A44K4V7Qtw"
CHAT_ID = "5408718450"
CHECK_INTERVAL_SECONDS = 10
TARGET_URL = (
"https://atlasbus.by/"
"%D0%9C%D0%B0%D1%80%D1%88%D1%80%D1%83%D1%82%D1%8B/"
"%D0%92%D0%B8%D0%BB%D0%B5%D0%B9%D0%BA%D0%B0/"
"%D0%9C%D0%B8%D0%BD%D1%81%D0%BA"
"?date=2026-08-31&passengers=1&from=c620181&to=c625144"
)
TARGET_API_URL = (
"https://atlasbus.by/api/search"
"?from_id=c620181&to_id=c625144"
"&calendar_width=30&date=2026-08-31&passengers=1&operatorId="
)
TARGET_DEPARTURE_TIMES = ["11:10","10:40"]
def send_telegram_message(text: str) -> None:
url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage"
payload = {
"chat_id": CHAT_ID,
"text": text,
"disable_web_page_preview": True,
}
try:
response = requests.post(url, data=payload, timeout=20)
response.raise_for_status()
except requests.RequestException as error:
print(f"[{datetime.now()}] Telegram error: {error}")
def normalize_text(text: str) -> str:
return " ".join(text.replace("\xa0", " ").split())
def extract_next_data(html: str) -> dict:
soup = BeautifulSoup(html, "html.parser")
script = soup.find("script", id="__NEXT_DATA__")
if not script:
return {}
try:
return json.loads(script.string or script.get_text())
except json.JSONDecodeError:
return {}
def get_search_rides_state(html: str) -> dict:
data = extract_next_data(html)
return (
data.get("props", {})
.get("initialState", {})
.get("searchRides", {})
)
def get_time(value: str) -> str:
return str(value or "")[11:16]
def find_target_trip_statuses_from_rides(rides: list):
statuses = {
dep: {"found": False, "available": False, "arrival": "", "raw": ""}
for dep in TARGET_DEPARTURE_TIMES
}
for ride in rides:
departure = get_time(
ride.get("departure")
or ride.get("departureDate")
or ride.get("departureTime")
)
arrival = get_time(
ride.get("arrival")
or ride.get("arrivalDate")
or ride.get("arrivalTime")
)
if departure in statuses:
free_seats = int(ride.get("freeSeats", 0) or 0)
statuses[departure] = {
"found": True,
"available": free_seats > 0,
"arrival": arrival,
"raw": f"freeSeats={free_seats}",
}
return statuses
def find_target_trip_statuses(html: str):
# Fallback for saved HTML/debug mode. Runtime checks use the API directly.
search_rides = get_search_rides_state(html)
statuses = find_target_trip_statuses_from_rides(search_rides.get("rides", []))
if any(status["found"] for status in statuses.values()):
return statuses
# Fallback in case page structure changes.
soup = BeautifulSoup(html, "html.parser")
for node in soup.find_all(["div", "section", "article", "li"]):
block_text = normalize_text(node.get_text(" ", strip=True))
for dep in TARGET_DEPARTURE_TIMES:
if dep in block_text:
statuses[dep] = {
"found": True,
"available": (
"Свободно" in block_text
or "ÐоÑледнее меÑÑо" in block_text
or "ÐаказаÑÑ" in block_text
),
"arrival": "",
"raw": block_text,
}
return statuses
def check_once():
headers = {
"Accept": "application/json",
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/124.0.0.0 Safari/537.36"
)
}
response = requests.get(TARGET_API_URL, headers=headers, timeout=30)
response.raise_for_status()
data = response.json()
return find_target_trip_statuses_from_rides(data.get("rides", []))
def debug_once() -> None:
headers = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/124.0.0.0 Safari/537.36"
)
}
response = requests.get(TARGET_URL, headers=headers, timeout=30)
response.raise_for_status()
html = response.text
search_rides = get_search_rides_state(html)
rides = search_rides.get("rides", [])
refreshed_rides = search_rides.get("refreshedRides", [])
print("Atlas debug")
print(f"HTTP: {response.status_code}")
print(f"URL: {response.url}")
print(f"HTML bytes: {len(html)}")
print(f"searchRides.status: {search_rides.get('status')}")
print(f"searchRides.statusApi: {search_rides.get('statusApi')}")
print(f"rides in __NEXT_DATA__: {len(rides)}")
print(f"refreshedRides in __NEXT_DATA__: {len(refreshed_rides)}")
all_times = sorted(set(re.findall(r"\b\d{2}:\d{2}\b", html)))
target_times = [time for time in TARGET_DEPARTURE_TIMES if time in all_times]
print(f"target times visible in raw HTML: {', '.join(target_times) or 'none'}")
if rides:
print("Rides from __NEXT_DATA__:")
for ride in rides:
departure = str(
ride.get("departure")
or ride.get("departureDate")
or ride.get("departureTime")
or ""
)[11:16]
arrival = str(
ride.get("arrival")
or ride.get("arrivalDate")
or ride.get("arrivalTime")
or ""
)[11:16]
print(f"- {departure}->{arrival} freeSeats={ride.get('freeSeats')}")
else:
print(
"No rides are present in the server HTML. "
"Atlas loads them later via a browser API request."
)
def main():
if "--debug" in sys.argv:
debug_once()
return
print("Started Atlas monitor")
print("Target departure times:")
for dep in TARGET_DEPARTURE_TIMES:
print(f"- {dep}")
print(f"Check interval: {CHECK_INTERVAL_SECONDS} sec")
while True:
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
try:
statuses = check_once()
for dep in TARGET_DEPARTURE_TIMES:
status = statuses[dep]
arrival = status["arrival"] or "?"
if not status["found"]:
print(f"[{now}] {dep}: Trip not found on page")
elif status["available"]:
print(f"[{now}] {dep}->{arrival}: Seats AVAILABLE")
send_telegram_message(
"Atlasbus: found free seats for your trip\n"
f"{dep} -> {arrival}\n"
f"{TARGET_URL}"
)
else:
print(f"[{now}] {dep}->{arrival}: No seats")
except requests.RequestException as error:
print(f"[{now}] Request error: {error}")
except Exception as error: # noqa: BLE001
print(f"[{now}] Unexpected error: {error}")
time.sleep(CHECK_INTERVAL_SECONDS)
if __name__ == "__main__":
main()