Web Scraping mit BeautifulSoup

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Jackson
User
Beiträge: 3
Registriert: Samstag 10. April 2021, 15:46

Hallo,
ich möchte gerne Aktienkurse mit Python auslesen und im nächsten Schritt auch auswerten. Allerdings stecke ich noch ganz am Anfang und möchte erst einmal Beautifulsoup besser verstehen und kennen lernen.
Dabei bin ich auf folgenden Blog Post gestoßen:
https://ingo-janssen.de/web-scraping-in ... n-sammeln/
In dem angeleiteten Beispiel werden allerdings nur die ISIN und der Aktienname gescraped.
Hier mal der Code aus dem Beispiel:

Code: Alles auswählen

import requests, bs4, json

url = 'https://www.boerse.de/realtime-kurse/Dax-Aktien/DE0008469008'
seite = requests.get(url)
bs4_seite = None
if seite.status_code == 200:
    bs4_seite = bs4.BeautifulSoup(seite.content, 'html.parser')
else:
    print('Seite konnte nicht geladen werden.', url)
    
pushList = bs4_seite.find('table', {'id': 'pushList'})
body = pushList.find('tbody')
aktien_liste = body.find_all('tr')
daten = {}
for aktie in aktien_liste:
    isin = aktie['id']
    div = aktie.find('div', {'class': 'tablesorter N'})
    link = div.find('a')
    daten[isin] = {
        'name': link.text, 
        'url': link['href']
    }
print(json.dumps(daten, indent=4)) 
Ich möchte gerne als nächsten Schritt die Spalte +/- auslesen.
Mein Problem dabei ist allerdings das es die Bezeichnung hinter div class= "tablesorter green BW_PUSH" zweimal gibt. Einmal für die Spalte +/- und einmal für +/-%. Wie kann ich diese Spalten mit BeautifulSoup unterscheiden.
Als nächstes tue ich mich schwer damit das der Wert nicht wie in dem Beispiel des Blogbeitrages direkt in der <div Zeile vorkommt sondern eine Zeile darunter hinter dem <span.
Wie muss ich damit weiter umgehen um den Wert auszulesen?

Vielen dank schon mal an jeden der sich die Mühe macht mir zu helfen.
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

Die Fehlerbehandlung ist kaputt. Wenn die Seite nicht geladen werden kann, also status_code != 200, dann hat bs4_seite den Wert None, fliegt einem also ein paar Zeilen weiter unten mit einem AttributeError um die Ohren.
`tablesorter` ist keine gute Kennung für eine Spalte, denn jede Spalte hat diese Klasse. Dass dann eine Zelle die Klasse GREEN hat, liegt nur daran, dass der Wert gerade steigt, bei fallenden Werten wäre es RED. Also auch keine sinnvolle Kennung.
Dir bleibt also nichts anderes übrig, als die td-Elemente zu zählen.
Vielleicht kannst Du auch eine passende ID zusammensetzen.
Jackson
User
Beiträge: 3
Registriert: Samstag 10. April 2021, 15:46

Sirius3 hat geschrieben: Samstag 10. April 2021, 19:20 Die Fehlerbehandlung ist kaputt. Wenn die Seite nicht geladen werden kann, also status_code != 200, dann hat bs4_seite den Wert None, fliegt einem also ein paar Zeilen weiter unten mit einem AttributeError um die Ohren.
`tablesorter` ist keine gute Kennung für eine Spalte, denn jede Spalte hat diese Klasse. Dass dann eine Zelle die Klasse GREEN hat, liegt nur daran, dass der Wert gerade steigt, bei fallenden Werten wäre es RED. Also auch keine sinnvolle Kennung.
Dir bleibt also nichts anderes übrig, als die td-Elemente zu zählen.
Vielleicht kannst Du auch eine passende ID zusammensetzen.
Danke habe es mit deinem Hinweis hinbekommen. Vom Code her wahrscheinlich noch zu viel aber ich habe ein Ergebnis und das fühlt sich schon mal richtig gut an :)

Hier mein Zusammengebasteltes:

Code: Alles auswählen

import requests, bs4, json

url = 'https://www.boerse.de/realtime-kurse/Dax-Aktien/DE0008469008'
seite = requests.get(url)

pushList = bs4_seite.find('table', {'id': 'pushList'})
body = pushList.find('tbody')
aktien_liste = body.find_all('tr')
aktien_eigenschaft = body.find_all('td')
aktien_name_spalte = aktien_eigenschaft[1]
aktien_name_wert = aktien_name_spalte.find_all('div')
aktien_name = aktien_name_wert[0].text
aktien_veraenderung = aktien_eigenschaft[5].text

print(aktien_name, aktien_veraenderung)
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

Dein Code ist unvollständig, weil bs4_seite gar nicht definiert wird.
Das Problem an find oder find_all ist, dass es standardmäßig alle Elemente in beliebiger Tiefe sucht. body.find_all('td') sucht alle td-Elemente in allen Zeilen und, falls es noch verschachtelte Tabellen gibt, diese auch noch in den Untertabellen. Damit kann man kein stabiles Scraping machen.
Das macht Sinn, wenn man ein Element irgendwo per eindeutiger ID sucht, aber innerhalb dieser Struktur, muß man sehr vorsichtig sein, wenn man mit sehr allgemeinen Suchfiltern sucht.
Wenn man verschachtelte Strukturen abbilden will, dann sind CSS-Selectoren meist einfacher zu lesen:

Code: Alles auswählen

import requests, bs4

url = 'https://www.boerse.de/realtime-kurse/Dax-Aktien/DE0008469008'
response = requests.get(url)
response.raise_for_status()
html_tree = bs4.BeautifulSoup(response.content, 'html.parser')
rows = html_tree.select('table#pushList > tbody > tr')
Jetzt haben wir schonmal alle Zeilen mit den Aktien, und die können wir uns nun näher ansehen:
Jede Zeile hat viele td-Elemente, die den eigentlich interessanten Text enthalten.

Code: Alles auswählen

for row in rows:
    cells = [td.text.strip() for td in row.select('> td')]
    print(cells)
Jackson
User
Beiträge: 3
Registriert: Samstag 10. April 2021, 15:46

Vielen dank.
Das sieht doch nach richtig schönem sauberem Code aus :)
TenchiMuyo1984
User
Beiträge: 18
Registriert: Donnerstag 17. Januar 2019, 21:17

Hallo zusammen,
ich habe ein Problem mit dem Scraping von JavaScript-generierten HTML-Elementen.

Wenn ich den HTML-Quelltext anschaue, steht das darin:
(ein Auszug)

Code: Alles auswählen

<div class="other_item"></div>

<div class="recomend">
    <h4>Recommend items</h4>
    <div class="recommend_edi"></div>
    <div class="recommend_aix"></div>
</div>

<div class="recomend">
    <h4>Other items from this category</h4>
    <div class="recommend_same_category thumsmall"></div>
</div>
Dann habe ich mir das mal über die "Untersuchen"-Funktion von Google-Chrome angeschaut, und da steht dies darin:
Bild
Wie man sehen kann, bedeutend mehr.
Jetzt meine Frage. Wie kann ich die Inhalte auslesen?

Habe bereits folgendes probiert:
1. selenium:

Code: Alles auswählen

from selenium import webdriver
from selenium.webdriver.chrome.service import Service

service = Service('C:\Webdrivers\chromedriver.exe')
service.start()
driver = webdriver.Remote(service.service_url)
driver.get('url zensiert')
Klappt manchmal, aber Inhalte werden partiell beim Generieren gelesen in driver gespeichert. Darum kann ich nicht darauf vertrauen.
Ich weiß auch nicht, wie ich die Wartezeit verlängern kann. Der Browser sendet "fertig" zurück, obwohl die JS-Elemente noch nicht fertig generiert wurden.

2. requests_html:

Code: Alles auswählen

from bs4 import BeautifulSoup
from requests_html import HTMLSession

session = HTMLSession()
resp = session.get('url zensiert')
resp.html.render()
soup = BeautifulSoup(resp.html.html, 'lxml')
Klappt nicht. Ich bekomme nur den normalen Quelltext, ohne die JS-Ergänzungen zurück.

3. PyQt5

Code: Alles auswählen

from bs4 import BeautifulSoup
from requests_html import HTMLSession
import requests
import time

import sys
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import QUrl
from PyQt5.QtWebEngineWidgets import QWebEnginePage


class Page(QWebEnginePage):

    def __init__(self, url):
        self.app = QApplication(sys.argv)
        QWebEnginePage.__init__(self)
        self.loadFinished.connect(self.on_load_finished)
        self.load(QUrl(url))
        self.app.exec_()

    def on_load_finished(self):
        self.html = self.toHtml(self.Callable)
        print('Load Finished')

    def Callable(self, html_str):
        self.html = html_str
        self.app.quit()
        
page = Page('url zensiert')
soup = BeautifulSoup(page.html, 'lxml')
Klappt nicht. Ich bekomme nur den normalen Quelltext, ohne die JS-Ergänzungen zurück.

Ich hoffe mir kann jemand dabei helfen :(

Grüße
TenchiMuyo1984
TenchiMuyo1984
User
Beiträge: 18
Registriert: Donnerstag 17. Januar 2019, 21:17

*Push*
Antworten