Last active
March 1, 2019 02:08
-
-
Save d3d9/fc3ab7a8c6af6212a25f9456c866cb0b to your computer and use it in GitHub Desktop.
radbox.py: lädt Daten von https://www.bikeandridebox.de/ bzw. https://www.dein-radschloss.de/ und weiteren (Kienzler/SinusQuadrat)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3.7 | |
# -*- coding: utf-8 -*- | |
from radbox import * | |
if __name__ == "__main__": | |
baseurls = [URL("https://www.dein-radschloss.de/"), URL("https://www.bikeandridebox.de/")] | |
print("\n".join(f"[{bi}] {bu}" for bi, bu in enumerate(baseurls))) | |
try: | |
baseurl = baseurls[int(input("Basis-URL aussuchen: "))] | |
except: | |
print("Eingabe unklar, erste URL wird verwendet") | |
baseurl = baseurls[0] | |
loadprices = input("Sollen auch Boxinformationen (aktuell nur Preise) geladen werden (*/n)? ") != "n" | |
buchenurl = URL(baseurl+"boxbuchen/") | |
print("Stationsliste wird geladen") | |
stations, references = getstations(buchenurl) | |
while True: | |
try: | |
print("\nStationen:") | |
print("\n".join(station.id+" "+station.name for station in sorted(stations.values(), key=lambda _: _.id))) | |
if references: | |
print("\nVerweise (Basis-URL wird gewechselt)") | |
print("\n".join(f"{id} {name} ({url})" for id, (name, url) in sorted(references.items(), key=lambda _: _[0]))) | |
in_id = input("\nStations-"+("/Verweis-" if references else "")+"ID eingeben: ") | |
if in_id in stations: | |
station = stations[in_id] | |
if station.getinfo(baseurl=baseurl, loadprices=loadprices): | |
print("\n"+str(station).replace("Box(", "\nBox(").replace("Gap(", "\nGap(")) | |
else: | |
print("loading current data for selected station was unsuccessful") | |
elif in_id in references: | |
baseurl = references[in_id][1] | |
buchenurl = URL(baseurl+"boxbuchen/") | |
print("Stationsliste wird geladen") | |
stations, references = getstations(buchenurl) | |
else: | |
print("ID", in_id, "unbekannt") | |
except KeyboardInterrupt: | |
break |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3.7 | |
# -*- coding: utf-8 -*- | |
"""Helps with loading bicycle box data from services like https://www.bikeandridebox.de/""" | |
from __future__ import annotations | |
from bs4 import BeautifulSoup, element | |
from requests import get, post | |
from typing import NewType, List, Dict, Union | |
from dataclasses import dataclass | |
from datetime import datetime, timedelta | |
URL = NewType("URL", str) | |
HTML = NewType("HTML", str) | |
@dataclass | |
class Station: | |
"""Bicycle "Box" station""" | |
id: str | |
name: str | |
buchenurl: str | |
currency: str | |
description: HTML = None | |
design: HTML = None | |
accepttext: str = None | |
boxword: str = None | |
boxnrlength: int = None | |
boxcount: int = None | |
boxrows: int = None | |
images: List[Dict[str, URL]] = None | |
boxes: List[Union[Box, Gap]] = None | |
def getinfo(self: Station, baseurl: URL, loadprices: bool = False) -> bool: | |
"""Adds current data to this Station object, returns success""" | |
stationdata = post(baseurl, | |
params={'eID': "bookboxUpdateData"}, | |
data={'action': "getData", | |
'controller': "General", | |
'L': "0", | |
'unitId': self.id}) | |
j = stationdata.json() | |
if j['success']: | |
self.description = HTML(j['description']) | |
self.design = HTML(j['design']) | |
self.accepttext = j['accepttext'] | |
self.boxword = j['boxword'] | |
self.boxnrlength = j['boxnrlength'] | |
self.boxcount = j['availableBox'] | |
self.images = j['images'] | |
for image in self.images: | |
for imgtype, path in image.items(): | |
image[imgtype] = URL(baseurl+path) | |
self.boxes = [] | |
if j['design'].find('svg') < 0: | |
uls = BeautifulSoup(j['design'], "lxml").find_all("ul", {'class': "clearfix"}) | |
self.boxrows = len(uls) | |
for row, designelements in enumerate(uls): | |
for e in filter(lambda _: type(_) == element.Tag, designelements): | |
if ("gap" in (e.attrs.get('class') or [])) or not (e.text.strip() or e.attrs.get('data-value')): | |
self.boxes.append(Gap(stationid=self.id, | |
row=row, | |
style=e.attrs.get('style'))) | |
else: | |
self.boxes.append(Box(stationid=self.id, | |
id=None, | |
number=e.attrs['data-value'], | |
displayas=e.text, | |
state=None, | |
usedinreminder=None, | |
row=row, | |
style=e.attrs.get('style'), | |
prices=None, | |
currency=self.currency, | |
boxword=self.boxword, | |
boxtypes=e.attrs.get('class') or [])) | |
else: | |
for box in (j.get('boxes') or []): | |
self.boxes.append(Box(stationid=self.id, | |
id=None, | |
number=box['number'], | |
displayas=None, | |
state=None, | |
usedinreminder=None, | |
row=None, | |
style=None, | |
prices=None, | |
currency=self.currency, | |
boxword=self.boxword, | |
boxtypes=None)) | |
for box in self.boxes: | |
if type(box) == Box: | |
if 'boxes' in j: | |
jsonbox = list(filter(lambda jb: jb['number'] == box.number, j['boxes'])) | |
if len(jsonbox) == 1: | |
box.id = jsonbox[0]['id'] | |
box.state = jsonbox[0]['state'] | |
box.usedinreminder = jsonbox[0].get('usedinreminder') | |
# ^ mittlerweile scheinbar anders, todo | |
if loadprices: | |
box.getinfo(baseurl) | |
elif not jsonbox: | |
box.inactive = True | |
else: | |
print("error: unexpected amount of boxes found in json:", len(jsonbox), "for box", box.number) | |
break | |
else: | |
box.inactive = True | |
actualboxcount = len(list(filter(lambda _: not _.inactive, filter(lambda __: type(__) == Box, self.boxes)))) | |
if self.boxcount != actualboxcount: | |
print("warning: unexpected amount of boxes (", actualboxcount, " instead of ", self.boxcount, ")", sep="") | |
else: | |
print("error: (station info) success is not true. ?", self.id, self.name) | |
return j['success'] | |
@dataclass | |
class Box: | |
"""Box or other type of space that can contain a vehicle""" | |
stationid: str | |
id: int | |
number: str | |
displayas: str | |
state: str | |
usedinreminder: bool | |
row: int | |
style: str | |
prices: List[BoxPrice] | |
currency: str | |
boxword: str | |
boxtypes: List[str] | |
inactive: bool = False | |
def getinfo(self: Box, baseurl: URL) -> bool: | |
"""Loads available information (currently only pricing?) for this "Box", returns success""" | |
boxdata = post(baseurl, | |
params={'eID': "bookboxUpdateData"}, | |
data={'action': "getData", | |
'controller': "General", | |
'L': "0", | |
'boxId': self.id}) | |
j = boxdata.json() | |
self.prices = [] | |
if j['success']: | |
for id, priceinfo in j['priceinfo'].items(): | |
bstr = priceinfo['bookTo'] | |
self.prices.append(BoxPrice(boxid=self.id, | |
id=id, | |
duration=priceinfo['duration'], | |
price=priceinfo['price'], | |
displayas=priceinfo['formated_price'], | |
currency=self.currency, | |
bookto=datetime.strptime(bstr.replace("24:", "23:"), "%d.%m.%Y %H:%M")+timedelta(hours="24:" in bstr), | |
booktostr=bstr)) | |
else: | |
print("error: (box info) success is not true. ?", self.id) | |
return j['success'] | |
@dataclass | |
class BoxPrice: | |
"""An available "Box" price""" | |
boxid: int | |
id: str | |
duration: str | |
price: int # ? | |
displayas: str | |
currency: str | |
bookto: datetime | |
booktostr: str | |
@dataclass | |
class Gap: | |
"""Structural gap between "Box"es""" | |
stationid: str | |
row: int | |
style: str | |
def textfromto(html: HTML, findstr: str, beginafter: bool, until: str) -> str: | |
"""""" | |
pos = html.index(findstr) + len(findstr)*beginafter | |
return html[pos:pos+html[pos:].index(until)] | |
def getstations(buchenurl: URL) -> Tuple[Dict[str, Station], Dict[str, Tuple[str, URL]]]: | |
"""Returns dict containing selectable bike box stations""" | |
buchenpage = get(buchenurl) | |
currency = textfromto(HTML(buchenpage.text), "var paymentCurrency = \"", True, "\"") | |
references = {} | |
if "redirectUnits[" in buchenpage.text: | |
rline = textfromto(HTML(buchenpage.text), "redirectUnits[", False, "\n") | |
for ruline in filter(lambda _: _.strip(), rline.split(';')): | |
url = URL(textfromto(ruline, "] = \"", True, "\"")) | |
url = URL((url+("/" if not url.endswith("/") else ""))[:(-10 if "boxbuchen/" in url else None)]) | |
references[textfromto(ruline, "redirectUnits[", True, "]")] = url | |
stationlist = BeautifulSoup(buchenpage.text, "lxml").find("select", {'name': "location"}).find_all("option") | |
stations = {} | |
for station in stationlist: | |
id = station['value'] | |
name = station.text | |
if(id == '0'): | |
continue | |
if(id in stations): | |
print('warning: duplicate station selectable:', id, name) | |
if(id in references): | |
# print(f'warning: station {id} references other baseurl {references[id]}, not adding to stations dict') | |
references[id] = name, references[id] | |
continue | |
stations[id] = Station(id=id, name=name, buchenurl=buchenurl, currency=currency) | |
return stations, references |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment