SQLAlchemy / Scrapy - doppelte Einträge | Encoding

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
dfint
User
Beiträge: 9
Registriert: Montag 23. April 2018, 14:06

Hallo,

ich habe einen Scraper mitels Scrapy für ein Forum geschrieben, welcher aus dem jeweiligen Posttext auch bestimmte Begrifflichkeiten filtert und in eine SQL-Tabelle "contacts" mittels einer Pipeline von SQLAlchemy speichern soll. Hierzu habe ich zwei Fragen.

1.
Leider bekomme ich die richtige Syntax für das Filtern nicht hin, sodass doppelte Einträge nicht erfasst werden. Es werden beispielsweise E-Mail oder BTC-Adressen aus dem Text gefiltert und dem jeweiligen Author zugeordnet. Sollte der User die Adressen mehrfach posten oder beispielsweise in seiner Signatur haben, kommt diese ja öfters vor, ich möchte sie aber nur einmal speichern.


pipelines.py:

Code: Alles auswählen

    def storeContacts(self, item, spider):

        session = self.Session()
        
        contactdb = ContactDB()
        
        if not session.query(exists().where(ContactDB.user_id == item['user_id']).where(ContactDB.contact == item['contact'])).scalar():
        
            contactdb.author = item["author"]
            
            contactdb.user_id = item["user_id"]
            
            contactdb.contact = item["contact"]
            
            contactdb.medium = item["medium"]
            
            contactdb.url = item["url"]
            
            contactdb.post_id = item["post_id"]
            
             try:
             
                session.add(contactdb)
                
                session.commit()
                
            except:
            
                session.rollback()
                
                raise
                
            finally:
            
                session.close()
                
            return item
            
Es werden dennoch doppelte Einträge gespeichert. Reicht es nicht wenn ich sage, dass bei Gleichheit von user_id (welche einmalig ist) und dem jeweiligen contact (also des jeweiligen Wertes) dieses Item nicht gespeichert werden soll?


2.
In dem Forum werden häufig innerhalb des Textes, des Titels als auch in manchen Usernamen Icons (Sterne, Flammen u.Ä.) verwendet, welche so nicht in der Datenbank gespeichert werden können. Gibt es eine andere Möglichkeit als im Vorgang alles mittel beispielsweise UTF-8 zu encodieren also z.B.

Code: Alles auswählen

                title = response.xpath('...').extract_first()
                
                title = str(title).encode('utf-8', 'strict')
                
                title = str(title)
Schlussendlich sollen die String in einer Weboberfläche wieder dargestellt werden. Hierzu müsste ich diese zunächst wieder dekodieren wenn ich das richtig sehe. Gibt es hier eine Alternative?

Vielen Dank für eure Unterstützung!
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@dfint: Die erste Frage ist mir zu ungenau. Was willst Du denn wie speichern? Was definiert einen Benutzer? Die E-Mail-Adresse? Eine ID aus dem Forum? Was soll Dein Programm machen wenn ein Benutzer zwar mit der ID vom Forum zwar schon in Deiner Datenbank ist, aber er im Forum die E-Mail-Adresse geändert hat?

Wenn die `user_id` eindeutig ist, braucht man nicht noch ein weiteres Kriterium bei `where` prüfen. Und die Spalte hat dann ja in der DB (und im Model) ein UNIQUE-Constraint‽ Dann *kann* man *keine* doppelten Einträge hinzufügen. Was unter Umständen auch schon reichen könnte das man statt zu testen einfach einträgt und die Ausnahme wegen der Constraint-Verletzung ignoriert.

Da ein Benutzer mehr als eine BTC-Adresse haben kann, ist das ja sowieso eine eigene Tabelle. Da kann man dann ja prüfen ob es die BTC-Adresse (für den Benutzer) schon gibt und sie nur falls nicht, hinzufügen.

Warum erzeugst Du ein `ContactDB`-Objekt auch wenn das durch das ``if`` vielleicht gar nicht gebraucht wird?

Ich werde aus dem Objekt nicht so wirklich schlau. Was bedeutet da denn `post_id`? Willst Du wirklich die erste Beitrags-ID für jeden Kontakt speichern, oder etwa für jeden Beitrag den kompletten Kontakt? Das ist komisch.

Was bedeutet das `DB` in `ContactDB`? Das ist ja keine Datenbank, sondern eine Model-Klasse, also ein Objekt das *einen* Datensatz repräsentiert.

`storeContacts()` scheint nur *einen* Kontakt zu speichern.

Das ``try``/``execp``/``finally`` wirst Du genau so ja öfter haben, das würde ich in einem Kontextmanager ”verschwinden” lassen. Siehe `contextlib.contextmanager()`. (Frage mich warum SQLAlchemy so etwas nicht schon anbietet. :-))

Bei der zweiten Frage verstehe ich nicht was die Kodierung als UTF-8 bringen soll‽ Wenn Du ein HTML-Fragment in der Datenbank speichern möchtest, dann wandle das in eine (Unicode-)Zeichenkette um und speichere die als Text in der Datenbank. Wie man das umwandelt, hängt davon ab was für ein Datentyp das ist. Ein einfaches `str()` kann (in Python 3) das richtige sein, es kann aber auch sein das man etwas anderes machen muss um tatsächlich HTML als Zeichenkette zu bekommen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
dfint
User
Beiträge: 9
Registriert: Montag 23. April 2018, 14:06

Vielen Dank erstmal für die schnelle Antwort. Frage 2 ist für mich abgehakt, ich hatte nicht dran gedacht den Datentyp in der Datenbank auf Text zu setzen. Ich hatte ihn auf String und da gibt es eben eine Fehlermeldung wenn die besagten Zeichen gespeichert werden sollen, daher hatte ich mir damit beholfen alles zu encoden.... was natürlich Schwachsinn ist.

Zur 1. Frage habe ich zu wenig geschrieben. Das oben gezeigte Item ist nur eine von mehreren Tabellen. Es gibt noch eine Tabelle Users, welche nur die Nutzer mit ein paar weiteren Informationen wie Registrierungsdatum, Rang etc. beinhaltet und auch eine Tabelle/Item Posts. Das Item Contacts beinhaltet die Kontaktmöglichkeiten zu diesem Nutzer, also E-Mail, Telefon usw... Die Bitcoins sind nochmal in einer anderen Tabelle... daher ist eben auch die user_id, welche sich in der Tabelle Users befindet bekannt und somit eben eindeutig zuzuordnen.
Dass das Model Contacts"DB" heißt ist vielleicht zweideutig, aber ich weiß, dass das keine Datenbank ist. Das Objekt schiebe ich hinter das if, danke für den Hinweis. Unabhängig von den Werten, wäre die Syntax so nun korrekt oder nicht? Das mit UNIQUE wäre auch noch eine Option, aber grundsätzlich? Es scheint ja so falsch zu sein, da die Kombination dennoch gespeichert wird.
Antworten