Mein erster Code: ein Twitter Scraper - quick n dirty

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
Benutzeravatar
bitbum
User
Beiträge: 9
Registriert: Mittwoch 5. September 2012, 10:40

Hallo liebe Python Community.

Ich habe vor ein paar Wochen angefangen Python zu lernen. Zuerst habe ich mich halb durch das wirklich empfehlenswerte Buch "Learning Python the hard way 2.0" geknabbert und bin dann aus ungeduld mit Googles Hilfe und Tutorials und Codebeispielen an mein erstes Projekt gegangen: ein Scraper.

Ich bin von Datenextraktion aus dem großen Blob des Netzes sehr fasziniert und es war auch der Hauptgrund warum ich mit Python angefangen habe.

Zuerst wollte ein Script schreiben, dass mir alle Statusposts von einer beliebigen Person von Facebook beschafft, aber ich hab dann schnell gemerkt, dass Facebook das wohl nicht so gerne hat und alles tut um Scraper fernzuhalten und da hab ich mich dann erstmal an die Twipper API rangemacht :) Sobald ich fähiger bin werde ich hier auch das Facebookprojekt veröffentlichen. Meinen ersten dödeligen Versuch mit BeautifulSoup poste ich mal lieber nicht :B

Was tut der Code: Er checkt für einen zuvor spezifizierten Twitteraccount alle 7 Sekunden auf neue Tweets und speichert diese zusammen mit ein paar Metadaten in einer tmp.txt, wo auf Doubletten gecheckt wird, um dann alle einzigartigen Tweeps in der tweep.txt zu speichern.

Hab das nur zum lernen gecodet und hatte viel Hilfe von Google und ähnlichen Scripten von anderen Leuten.

Hier ist der Code:


Code: Alles auswählen

#	Twitter Scraper Tool 3000

import sys
import os
import urllib2
import json
import re
import time
 
# input
twittername = raw_input("twittername please_> ")
num_tweets = 10

apiresp = urllib2.urlopen("https://api.twitter.com/1/statuses/user_timeline.json?screen_name=%s&count="%twittername+str(num_tweets))
data = json.loads(apiresp.read())

i=1
while i == 1:
	# Show me whats going on. 
	print data

	# prep the temporary file to store tweeps for uniqueness
	tweepcontainer = open("tmp.txt", "a")

	# select the input from the twitter API
	for tweet in data:
		datentime = tweet['created_at']
		tweep_text = tweet['text']
		source = tweet['source']
		coords = tweet['place']
		citeh = coords['name']
		country = coords['country']
		
		# compose the line to store
		one_tweet = "DATE: " + datentime  +" || CONTENT: "+tweep_text+" || DEVICE: " + source +" || LOCATION: %s, %s" "\n" % (citeh, country)

		# write it
		tweepcontainer.write(one_tweet.encode('ascii', 'ignore'))
	
	tweepcontainer.close()
	
	#kill doubles routine	
	notyetcleaned = open("tmp.txt")
	uniquetweeps = open("tweeps.txt", "w")
	uniquelines = set(notyetcleaned.read().split("\n"))
	uniquetweeps.write("".join([line + "\n" for line in uniquelines]))
	uniquetweeps.close()
	notyetcleaned.close()
	os.remove("tmp.txt")
	print "\nSo if you want this endlessloop of checking for new messages to stop hit Ctrl-C.\n"
	
	time.sleep(7)
Der Code scheint mir noch sehr schmutzig zu sein, da ich zum teil nicht wirklich verstehe was nun exakt vor sich geht, lief beim erstellen viel über trial and error.

Für alle Schönheits-Tipps bin ich sehr dankbar. z.B. umschrieb in defs oder so. Vor allem bin ich aber für Inhaltliche Hinweise dankbar. z.B. wo der Code schlecht und falsch oder uneffektiv ist.

danke für alle Tipps im vorraus.
Zuletzt geändert von Hyperion am Montag 17. September 2012, 17:12, insgesamt 1-mal geändert.
Grund: Code in Python-Code Tags gesetzt.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

bitbum hat geschrieben: Der Code scheint mir noch sehr schmutzig zu sein, da ich zum teil nicht wirklich verstehe was nun exakt vor sich geht, lief beim erstellen viel über trial and error.
Das ist schlecht! Du musst schon die Geduld aufbringen, dir Grundlagen zu verstehen und zu verinnerlichen. Du kannst das ja auch gerne an so einem Beispiel machen, aber dann darfst Du eben *nicht* Code einfach so übernehmen, sondern musst Dir die fehlenden Wissenslücken aneignen.

Ich gebe Dir dennoch mal ein wenig inhaltliches Feedback:

- Beachte PEP8! Insbesondere die Einrückungen von vier Spaces statt eines Tabs! (Du kannst jeden guten Editor so konfigurieren, dass er beim Druck auf Tab vier Spaces setzt)

- Es fehlt ein Shebang und eine Kodierungsangabe.

- Benutze lieber das Requests-Modul für HTTP.

- Folgendes ist ziemlich schräg:

Code: Alles auswählen

i=1
while i == 1:
Python kennt doch einen Typen für Wahrheitswerte, nämlich `bool`. Also kannst Du eine Endlosschleife einfach so realisieren:

Code: Alles auswählen

while True:
- Benutze immer `with` mit `open`:

Code: Alles auswählen

with open(...) as handler:
    # handler ist hier File-Objekt und wird automatische *immer* geschlossen
- das hier finde ich auch unnötig:

Code: Alles auswählen

        for tweet in data:
                datentime = tweet['created_at']
                tweep_text = tweet['text']
                source = tweet['source']
                coords = tweet['place']
                citeh = coords['name']
                country = coords['country']
Wenn man mit den Werten hinter einem Dictionary noch "viel" machen will, mag ich es auch, diese an einen separaten Namen zu binden. Du machst damit aber eigentlich nix, außer einen String (s.u.) zusammen zu bauen. Dazu brauchst Du das nicht wirklich, sondern es bläht nur den Code auf.

- Strings solltest Du nie mittels `+` zusammen setzen:

Code: Alles auswählen

one_tweet = "DATE: " + datentime  +" || CONTENT: "+tweep_text+" || DEVICE: " + source +" || LOCATION: %s, %s" "\n" % (citeh, country)
Hier kommt noch hinzu, dass Du ja `%` hinten benutzt, vorne aber noch auf `+` setzt. Nicht nur, dass `+` ineffizient ist, nein, so zerstückelte Strings sind imho auch schlechter zu lesen, als ein Template-String, bei dem die Platzhalter direkt in den String integriert sind.
Mit der `str.format`-Methode kannst Du sogar prima die Keys des Dictionaries nutzen (ungetestet):

Code: Alles auswählen

tweet_text = "DATE: {created_at} || CONTENT: {text} || DEVICE: {source} ...".format(**tweet)
- Du hast Dich bisher noch nicht mit Encodings befasst. Das sieht man an dieser Stelle:

Code: Alles auswählen

one_tweet.encode('ascii', 'ignore')
Lies Dir mal die Links in meiner Signatur durch! ;-)
Um es mal abzukürzen: Der Aufruf ist sinnlos! Du hast einen ASCII codierten String und willst diesen erneut in ASCII wandeln - wozu? :K Zudem ist es bei Twitter fraglich, ob man mit ASCII hinkommt. Wieso nutzt Du intern kein Unicode und als Ausgabe dann UTF-8? Nach der Lektüre solltest Du das ein wenig besser kapieren.

- Diese ist auch umständlich von der Vorgehensweise:

Code: Alles auswählen

        notyetcleaned = open("tmp.txt")
        uniquetweeps = open("tweeps.txt", "w")
        uniquelines = set(notyetcleaned.read().split("\n"))
        uniquetweeps.write("".join([line + "\n" for line in uniquelines]))
        uniquetweeps.close()
        notyetcleaned.close()
Wieso sortierst Du die doppelten Einträge nicht direkt in der obigen Schleife aus, indem Du das alles in ein `set` packst und *danach* speicherst? Damit sparst Du Dir die temporäre Datei.

Mal davon abesgehen erstellt man temporäre Dateien mittels des `tempfile`-Moduls!

So, das mal auf die Schnelle :-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
bitbum
User
Beiträge: 9
Registriert: Mittwoch 5. September 2012, 10:40

vielen, vielen Dank Hyperion.

Ich werde mir den Code mit deinen Tipps nochmal vornehmen, bevor ich versuche weitere features zu implementieren und eher den code besser machen und alles genau verstehen. ich habe mich vielleich ein bissi doof ausgedrückt.. so quick n dirty war das alles gar nicht, speziell mit der für mich seltsamen formatierung der Twitter API hab ich lang gekämpft.

Deine Hinweise machen alle Sinn und ich werd den neuen Code posten sobald ich es gepackt hab. :D

edit:

Code: Alles auswählen

#!/usr/bin/python
############################################
# Twitter Scraper 3000                     #
############################################
# Sets its eyes on a twitteraccount and    #
# saves all tweets, their creation time,   #
# tweet device and if provided the         #
# geo-location to a .txt file.             #
# It also saves the whole raw data to      #
# another .txt, in case you need that data #
# later.                                   #
############################################

import sys
import os
import urllib2
import json
import re
import time
 
# input
twittername = raw_input("twittername please_> ")
num_tweets = 30 #small number for testing

#output by query
apiresp = urllib2.urlopen("https://api.twitter.com/1/statuses/user_timeline.json?screen_name=%s&count="%twittername+str(num_tweets))
data = json.loads(apiresp.read())


while True:
        # Show me whats going on.
        # print data
       
        #save the raw data to another file, just in case i need that stuff later
        with open('raw_twitter_data.txt', 'w') as rawtweets:
			rawtweets.write(str(data))
			rawtweets.close()

        # prep tweepcontainer for saving
        tweepcontainer = open("tweeps.txt", "w")
       
        # select the input from the twitter API
        for tweet in data:
                # compose one line of tweetness
                one_tweet = "DATE: {created_at} || CONTENT: {text} || DEVICE: {source} || LOCATION: {geo} {coordinates} \n" .format(**tweet)
                one_tweet = one_tweet.encode("utf8")     #gets rid of errors when tweeple use weird chars   
                # write it nicely, kill doubles
                tweepcontainer.write(one_tweet)
       
        tweepcontainer.close()

	print "did you know you can abort by pressing Ctrl-C :O"
	time.sleep(7)
soweit fertig :) alle Verbesserungskommentare Willkommen!
Bin nun wieder am Facebook Scraper, aber das viele Java Script macht mich echt meschugge.
Antworten