repo init
This commit is contained in:
commit
d3818059da
5 changed files with 317 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
gpx/*
|
||||
venv
|
||||
venv/*
|
||||
.env
|
||||
0
README.md
Normal file
0
README.md
Normal file
286
get_bookmarks.py
Normal file
286
get_bookmarks.py
Normal file
|
|
@ -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 = '''<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gpx version="1.1" creator="Park4Night Extractor" xmlns="http://www.topografix.com/GPX/1/1">
|
||||
<metadata>
|
||||
<name>{folder_name}</name>
|
||||
<time>{timestamp}</time>
|
||||
</metadata>
|
||||
{waypoints}
|
||||
</gpx>'''
|
||||
|
||||
waypoint_template = ''' <wpt lat="{lat}" lon="{lon}">
|
||||
<name>{name}</name>
|
||||
<desc>{desc}</desc>
|
||||
</wpt>'''
|
||||
|
||||
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()
|
||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
nrequests
|
||||
python-dotenv
|
||||
beautifulsoup4
|
||||
24
run.sh
Executable file
24
run.sh
Executable file
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue