commit d3818059dade7959bd941b1b0299c201f6ffd95e Author: praul Date: Sat Mar 15 18:03:50 2025 +0100 repo init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7bd95f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +gpx/* +venv +venv/* +.env \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/get_bookmarks.py b/get_bookmarks.py new file mode 100644 index 0000000..05fcb7f --- /dev/null +++ b/get_bookmarks.py @@ -0,0 +1,286 @@ +import requests +import json +import re +import time +import os +from bs4 import BeautifulSoup +from dotenv import load_dotenv +from datetime import datetime + +def create_gpx(places, folder_name, output_file='places.gpx'): + """Create a GPX file from the collected places.""" + gpx_template = ''' + + + {folder_name} + + + {waypoints} +''' + + waypoint_template = ''' + {name} + {desc} + ''' + + waypoints = [] + for place in places: + if place['coordinates']: + waypoints.append(waypoint_template.format( + lat=place['coordinates']['lat'], + lon=place['coordinates']['lng'], + name=place['name'], + desc=place['description'] or '' + )) + + gpx_content = gpx_template.format( + folder_name=folder_name, + timestamp=datetime.utcnow().isoformat(), + waypoints='\n'.join(waypoints) + ) + + with open(output_file, 'w', encoding='utf-8') as f: + f.write(gpx_content) + + print(f"\nGPX file created: {output_file}") + +def get_session_id(): + """Get session ID from environment or user input.""" + load_dotenv() + saved_session = os.getenv('PARK4NIGHT_SESSION') + + if saved_session: + use_saved = input("Use saved session ID? (y/n): ").lower() == 'y' + if use_saved: + return saved_session + + session_id = input("Please enter your PHPSESSID: ") + save = input("Save this session ID for future use? (y/n): ").lower() == 'y' + + if save: + with open('.env', 'a') as f: + f.write(f'\nPARK4NIGHT_SESSION={session_id}') + + return session_id + +def get_place_details(place_id): + """Extract details from a specific park4night place page.""" + url = f"https://park4night.com/de/place/{place_id}" + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' + } + + try: + response = requests.get(url, headers=headers) + response.raise_for_status() + + soup = BeautifulSoup(response.text, 'html.parser') + + # Extract name + name_element = soup.select_one('h1') + name = name_element.text.strip() if name_element else None + + # Extract coordinates + lat, lng = None, None + try: + location_li = soup.select_one('.place-info-location li') + if location_li: + spans = location_li.find_all('span') + for span in spans: + coord_text = span.text.strip() + coord_match = re.match(r'(-?\d+\.\d+),\s*(-?\d+\.\d+)', coord_text) + if coord_match: + lat, lng = coord_match.groups() + break + except Exception as e: + print(f"Warning: Could not extract coordinates for place {place_id}: {e}") + + # Extract German description and rating + description = None + german_desc = soup.select_one('.place-info-description p[lang="de"]') + if german_desc: + description = german_desc.text.strip() + + # Extract rating + rating_div = soup.select_one('.place-feedback-average') + rating = None + if rating_div: + rating_span = rating_div.select_one('.text-gray') + if rating_span: + rating = rating_span.text.strip() + + # Build URL + url = f"https://park4night.com/de/place/{place_id}" + + # Extract details and format them nicely + details_div = soup.select_one('.place-info-details') + prices = None + if details_div: + # Split by identifiable sections and clean up + details_text = details_div.text.strip() + + # Extract price information + price_info = [] + if 'Preis der Dienstleistungen' in details_text: + price_info.append('Servicegebühren:') + if '/6h electricity' in details_text: + price_info.append('- Strom: 1,00€/6h') + if '/25l water' in details_text: + price_info.append('- Wasser: 1€/25l') + + if 'Parkgebühren' in details_text: + park_fee = re.search(r'Parkgebühren(\d+)€', details_text) + if park_fee: + price_info.append(f'Stellplatzgebühr: {park_fee.group(1)}€') + + prices = '\n'.join(price_info) if price_info else "Keine Preisangaben" + + # Combine description with additional information + full_description = [] + if description: + full_description.append(description) + if prices: + full_description.append("\nPreise:") + full_description.append(prices) + if rating: + full_description.append(f"\nBewertung: {rating}") + full_description.append(f"\nLink: {url}") + + description = '\n'.join(full_description) + + return { + 'id': place_id, + 'name': name, + 'coordinates': {'lat': lat, 'lng': lng} if lat and lng else None, + 'description': description, + 'prices': prices + } + + except requests.exceptions.RequestException as e: + print(f"Error fetching place {place_id}: {e}") + return None + +def get_bookmark_ids(session_id): + """Get bookmarks from API and let user select a folder.""" + headers = { + 'accept': 'application/json, text/plain, */*', + 'accept-language': 'de-DE,de;q=0.7', + 'axios-ajax': 'true', + 'cache-control': 'no-cache', + 'pragma': 'no-cache', + 'priority': 'u=1, i', + 'referer': 'https://park4night.com/de/search?bookmarks=1', + 'sec-ch-ua': '"Chromium";v="134", "Not:A-Brand";v="24", "Brave";v="134"', + 'sec-ch-ua-mobile': '?1', + 'sec-ch-ua-platform': '"Android"', + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'same-origin', + 'sec-gpc': '1', + 'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Mobile Safari/537.36' + } + + cookies = { + 'PHPSESSID': session_id, + 'cookie_consent_v2': '{"status":"accepted","acceptedCategories":["essentials","analytics"]}' + } + + try: + response = requests.get('https://park4night.com/api/user', headers=headers, cookies=cookies) + response.raise_for_status() + + data = response.json() + folders = data.get('bookmarksFolders', []) + + print("\nAvailable folders:") + for i, folder in enumerate(folders, 1): + print(f"{i}. {folder['name']} ({len(folder['bookmarks'])} bookmarks)") + + while True: + try: + choice = int(input("\nEnter folder number (1-{}): ".format(len(folders)))) + if 1 <= choice <= len(folders): + break + print("Invalid choice. Please try again.") + except ValueError: + print("Please enter a valid number.") + + selected_folder = folders[choice - 1] + bookmark_ids = selected_folder['bookmarks'] + print(f"\nFound {len(bookmark_ids)} bookmarks in folder '{selected_folder['name']}':") + print("\nBookmark IDs:") + print(', '.join(map(str, bookmark_ids))) + + return bookmark_ids, selected_folder['name'] + + except requests.exceptions.RequestException as e: + print(f"Error making request: {e}") + return None + +def process_bookmarks(bookmark_ids): + """Process all bookmarks with rate limiting.""" + places = [] + total = len(bookmark_ids) + + print(f"\nProcessing {total} bookmarks...") + for i, bookmark_id in enumerate(bookmark_ids, 1): + print(f"Processing place {i}/{total}: {bookmark_id}") + + place = get_place_details(bookmark_id) + if place: + places.append(place) + + # Rate limiting: wait 1 second between requests + if i < total: # Don't wait after the last request + time.sleep(1) + + return places + +def sanitize_filename(name): + """Convert a string to a valid filename.""" + filename = re.sub(r'[^\w\s-]', '', name) + filename = re.sub(r'[-\s]+', '_', filename.strip()) + return filename.lower() + +def main(): + print("Park4Night Bookmark Extractor") + print("----------------------------") + + # Get session ID + session_id = get_session_id() + + # Get bookmark IDs and folder name + result = get_bookmark_ids(session_id) + + if result and isinstance(result, tuple): + bookmark_ids, folder_name = result + + # Process all bookmarks + places = process_bookmarks(bookmark_ids) + + if places: + # Create gpx directory if it doesn't exist + os.makedirs('gpx', exist_ok=True) + + # Create default filename from folder name + default_filename = f"{sanitize_filename(folder_name)}.gpx" + + # Get output filename + output_file = input(f"\nEnter GPX file name (default: {default_filename}): ").strip() or default_filename + + # Add .gpx extension if not present + if not output_file.lower().endswith('.gpx'): + output_file += '.gpx' + + # Add gpx directory to path + output_file = os.path.join('gpx', output_file) + + create_gpx(places, folder_name, output_file) + print("\nExtraction completed successfully!") + else: + print("\nNo places were successfully processed.") + else: + print("\nFailed to get bookmarks.") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6703c7d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +nrequests +python-dotenv +beautifulsoup4 diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..e75779f --- /dev/null +++ b/run.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Create virtual environment if it doesn't exist +if [ ! -d "venv" ]; then + echo "Creating virtual environment..." + python3 -m venv venv +fi + +# Activate virtual environment +source venv/bin/activate + +# Install requirements +echo "Installing requirements..." +pip install -r requirements.txt + +# Create gpx directory if it doesn't exist +mkdir -p gpx + +# Run the parser script +echo "Starting Park4Night parser..." +python3 get_bookmarks.py + +# Deactivate virtual environment +deactivate \ No newline at end of file