Python Abspeicherung von Blog Beiträgen einer Wordpress Seite

MAxM1

Newbie
Registriert
Juli 2024
Beiträge
4
Hi ich versuche auf Linux gerade ein Script zu basteln welche mir Links aus einer Textdatei ausliest und für diese Links dann mit BeautifulSoup als markdown inkl. Bilder speichert. Die Seite die ich dazu verwenden möchte blockiert das normale BeautifulSoup somit greife ich auf Selenium zurück der einen normalen Browser Zugriff simuliert. Mir ist Geschwindigkeit nicht wichtig - ich will ja auch den Zielserver schonen, da das nur für meine privaten Zwecke ist.

Das ist mein momentaner Code:
Python:
import os
import time
import requests
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from bs4 import BeautifulSoup
from urllib.parse import urljoin, urlparse

# Function to create a valid folder name from the blog title
def create_folder_name(blog_title):
    return ''.join(c if c.isalnum() else '_' for c in blog_title)

# Function to download an image and save it locally
def download_image(img_url, folder_path):
    response = requests.get(img_url)
    if response.status_code == 200:
        img_name = os.path.basename(urlparse(img_url).path)
        img_dir = os.path.join(folder_path, 'img')
        os.makedirs(img_dir, exist_ok=True)
        img_path = os.path.join(img_dir, img_name)
        with open(img_path, 'wb') as f:
            f.write(response.content)
        return img_path
    return None

# Function to fetch and parse a blog post
def fetch_blog_post(post_url, base_dir, chrome_binary, chrome_driver_path):
    options = Options()
    options.binary_location = chrome_binary
    options.add_argument('--headless')
    options.add_argument('--no-sandbox')
    options.add_argument('--disable-dev-shm-usage')
    
    service = Service(chrome_driver_path)
    driver = webdriver.Chrome(service=service, options=options)
    driver.get(post_url)
    
    try:
        # Wait until the article content is present on the page
        WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.TAG_NAME, 'article'))
        )
        
        # Get the page source after waiting for the page to load
        page_source = driver.page_source
        
        # Use BeautifulSoup to parse the page source
        soup = BeautifulSoup(page_source, 'html.parser')
        
        # Extract the blog title
        title_element = soup.find('title')
        if title_element:
            blog_title = title_element.text.strip().split('|')[0].strip()
        else:
            raise Exception("Title element not found")
        
    except Exception as e:
        print(f"Error fetching blog title from {post_url}: {e}")
        driver.quit()
        return
    
    folder_name = create_folder_name(blog_title)
    post_dir = os.path.join(base_dir, folder_name)
    os.makedirs(post_dir, exist_ok=True)
    
    md_content = f"# {blog_title}\n\n"
    
    try:
        article = soup.find('article')
        
        if article:
            for element in article.find_all(['p', 'figure']):
                if element.name == 'p':
                    md_content += element.get_text() + "\n\n"
                elif element.name == 'figure':
                    img_tag = element.find('img')
                    if img_tag and img_tag.get('srcset'):
                        # Select the best quality image from srcset
                        srcset = img_tag['srcset'].split(',')
                        best_img_url = srcset[-1].split()[0]  # Choose the last one assuming it's the best quality
                        best_img_url = urljoin(post_url, best_img_url)
                        img_path = download_image(best_img_url, post_dir)
                        if img_path:
                            md_content += f"![{os.path.basename(img_path)}](img/{os.path.basename(img_path)})\n\n"
        else:
            print(f"No article found at {post_url}")
    
    except Exception as e:
        print(f"Error parsing content from {post_url}: {e}")
    
    with open(os.path.join(post_dir, 'post.md'), 'w') as f:
        f.write(md_content)
    
    print(f"Saved post: {blog_title}")
    driver.quit()

# Main function to fetch multiple blog posts from a file
def main():
    base_dir = "permanentstyle"
    os.makedirs(base_dir, exist_ok=True)
    
    with open('input.txt', 'r') as file:
        post_urls = [line.strip() for line in file if line.strip()]
    
    chrome_binary = '/usr/bin/chromium'  # Path to Chromium executable
    chrome_driver_path = '/home/p/chromedriver'  # Download from https://sites.google.com/chromium.org/driver/
    
    for post_url in post_urls:
        fetch_blog_post(post_url, base_dir, chrome_binary, chrome_driver_path)

if __name__ == "__main__":
    main()

Starte ich mein Skript bekomme ich aktuell einen Stacktrace zurück. Und um ehrlich zu sein kenne ich mich garnicht mehr aus, bin leider ein kompletter Python Noob und weiß nichtmal wie ich das debugge...
Kann mir jemand einen Hinweis geben wo ich einen groben Schnitzer habe? Es soll schon eine Lernerfahrung für mich sein das Projekt aber ich dachte nicht, dass es so zeitintensiv wird...
 
Naja wie wäre es denn den stracktrace anzuschauen? Der steht da ja nicht zum spaß, da steht dann ja eine Fehelrmeldung drin und auch an welcher Stelle im Code sie aufgetreten ist.
 
P.S. Der Zugriff/Ladezugriff allein ist mit Playwright und Firefox um ein Vielfaches schneller. Also auch zum Debuggen etc. Ohne dass der Server jetzt mehr arbeiten müsste.
 
Ich würde die Infos einfach über die WordPress eigene Rest API die in der Regel bei allen WordPress Seiten an ist, auslesen. Dann musst du kein HTML parsen, sondern bekommst alles direkt als JSON.

Du kannst an eine Domain einfach folgendes anhängen um die Posts auszulesen: "/wp-json/wp/v2/posts"

Als Beispiel mein eigener Blog: https://rueegger.me/wp-json/wp/v2/posts <-- zeigt die Posts an:

Oder hier ein kompletter Beitrag (mit der ID 340) mit allen Informationen als JSON: https://rueegger.me/wp-json/wp/v2/posts/340
 
  • Gefällt mir
Reaktionen: M4ttX, MAxM1, Krik und eine weitere Person
+1 für die Rest API!
Im Grunde lässt sich WP so als "Headless" CMS betreiben.

Ansonsten würd ich das eher mit PHP im WP Plugins-Verzeichnis machen und direkt die WP-PHP API nutzen, statt mit Python da ziemlich lang herumzufummeln um die Daten zu erhalten.
 
  • Gefällt mir
Reaktionen: MAxM1
kim88 schrieb:
Ich würde die Infos einfach über die WordPress eigene Rest API die in der Regel bei allen WordPress Seiten an ist, auslesen. Dann musst du kein HTML parsen, sondern bekommst alles direkt als JSON.

Du kannst an eine Domain einfach folgendes anhängen um die Posts auszulesen: "/wp-json/wp/v2/posts"

Als Beispiel mein eigener Blog: https://rueegger.me/wp-json/wp/v2/posts <-- zeigt die Posts an:

Oder hier ein kompletter Beitrag (mit der ID 340) mit allen Informationen als JSON: https://rueegger.me/wp-json/wp/v2/posts/340
Das ist interessant! Aber wenn ich auf deinen Link klicke zeigt er mir nur 9 Posts an, auf deinem Blog sind aber mehr Posts als 9. Mache ich was falsch? Wie kann ich mir alle anzeigen lassen?

netzgestaltung schrieb:
+1 für die Rest API!
Im Grunde lässt sich WP so als "Headless" CMS betreiben.

Ansonsten würd ich das eher mit PHP im WP Plugins-Verzeichnis machen und direkt die WP-PHP API nutzen, statt mit Python da ziemlich lang herumzufummeln um die Daten zu erhalten.
Es ist ja nicht meine Seite also ich habe keinen Zugriff darauf

M4ttX schrieb:
P.S. Der Zugriff/Ladezugriff allein ist mit Playwright und Firefox um ein Vielfaches schneller. Also auch zum Debuggen etc. Ohne dass der Server jetzt mehr arbeiten müsste.
Danke das sehe ich mir an! Du meinst also Playwright anstatt Selenium?
 
  • Gefällt mir
Reaktionen: M4ttX und netzgestaltung
wenn deine wordpress-seite nicht super geheim ist, kannst du sie ja auch mal posten. vermutlich geht zugriff mit requests+beautifulsoup nicht weil der user-agent von requests blockiert wird (was sich ändern lassen würde).
 
  • Gefällt mir
Reaktionen: M4ttX
@0x8100 Nein, die Seite steht im Code. permanentstyle.com
Ich habe aber gerade vorhin geschnallt wie ich mehr als 9 Posts auslese - also ich werde so wie es jetzt aussieht auf die Rest API setzen, da ich da das für mich noch einfacher aussieht :)
 
  • Gefällt mir
Reaktionen: MAxM1 und netzgestaltung
Trotzdem im Fehlerfall einfach mal den Stacktrace und Fehlermeldungen posten würde jegliche Hilfe sehr viel einfacher machen.
 
  • Gefällt mir
Reaktionen: netzgestaltung und kim88
der 403 fehler scheint übrigens an cloudflare zu liegen und kann auch auftreten wenn man die api verwendet. irgendwann kommt die prüfung, ob man ein bot ist, und dann steht man da...
 
0x8100 schrieb:
irgendwann kommt die prüfung, ob man ein bot ist, und dann steht man da...
Hierfür gibts wiederum für Selenium einen stealth patch.
 
Also so einfach ist das ganze wohl doch nicht. Die Rest Api kann ich im Browser abrufen aber wenn ich dazu ein Skript erstelle stellt er sich quer. Naja ich könnte die Posts "per Hand" im Browser abrufen und dann verarbeiten aber da ja ein Limit von 100 ist, ists etwas zäh aber wohl machbar...
 
Ohne hier Code zu sehen, oder zu wissen was mit „stellt sich quer“ gemeint ist geht das nicht.

Aber so kurz als Gedankengang:
Bei Post- Seiten durchloopen bis es keine Seiten mehr gibt und jeweils die Post-IDs in ein aaray speichern.

Dann das Array durchloopen, und jeweils restapi mit entsprechender post is öffnen und dann Daten auslesen.
 
MAxM1 schrieb:
Browser abrufen aber wenn ich dazu ein Skript erstelle stellt er sich quer
Was stellt sich quer und wie? Du musst ja eine Fehlermeldung haben?
 
Zurück
Oben