RkBlog

Hardware, programming and astronomy tutorials and reviews.

Wykorzystanie WebKit/PyQt4 do zbierania danych, część 2

Kolejny etap to napisanie parserów wydobywających linki reklam. W przypadku wielu flashowych reklam link, na który kieruje reklama przekazuje się w parametrze animacji, który zazwyczaj nazywany jest clickTag (lub podobna nazwa). W przypadku innych systemów - trzeba rozpoznać wzór i napisać wyrażenie regularne. Pomocne w tym celu będzie przejrzenie kodu HTML zwracanego przez webkita. Przykładowo widżet AdTaily wstawia linki postaci:
<a style="position: relative; font-weight: normal; text-align: left; background-image: none; background-repeat: initial; background-attachment: initial; -webkit-background-clip: initial; -webkit-background-origin: initial; background-color: initial; padding-left: 0px; padding-right: 0px; padding-top: 0px; padding-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; margin-bottom: 10px; display: block; width: 125px; height: 125px; background-position: initial initial; " href="http://www.megiteam.pl/" title="Hosting nowych technologii" rel="nofollow" target="_blank">
Dość charakterystyczne style można wykorzystać do napisania wyrażenia regularnego wyciągającego adresy URL.

Parser AdTaily

  • W katalogu z aplikacją stworzyłem plik parsers.py, który zawierał będzie wszystkie parsery.
  • Poniżej pierwszy "parser" - wyciągający linki z widżetu AdTaily:
    # -*- coding: utf-8 -*-
    from re import findall
    
    def get_adtaily(html):
    	"""
    	Extract data from adtaily widgets
    	"""
    	links = findall(r'background-position: initial initial; " href="(.*?)"', unicode(html))
    	ret = []
    	for i in links:
    		if not i.startswith('http://www.adtaily'):
    			ret.append(i)
    	return ret
    
  • Importujemy parsers w run.py i modyfikujemy w tym pliku metodę loadFinished do postaci:
    def loadFinished(self):
    		"""
    		A page was loaded - get the data and load next page
    		"""
    		page = self.ui.webView.page()
    		frame = page.currentFrame()
    		content = frame.toHtml()
    		print u'Page content, got %s bytes' % len(content)
    		
    		links = get_adtaily(content)
    		
    		print links
    		if self.currentRefresh < self.refreshSite:
    			print 'Refresh +1'
    			self.currentRefresh += 1
    		else:
    			print 'Index +1'
    			self.currentRefresh = 0
    			self.currentIndex += 1
    		
    		nexturl = self.__getNextUrl()
    		if nexturl:
    			self.ui.webView.load(nexturl)
    
  • W powyższym kroku zmodyfikowaliśmy metodę tak by przekazywała kod HTML pobranej strony do metody get_adtaily i wyświetlała zwróconą listę. Teraz jeżeli odpalisz aplikację na jakiejś stronie z widżetem AdTaily (np. www.python.rk.edu.pl) to powinieneś zobaczyć wyszukane URLe.

Reklamy Flashowe - clickTag

  • W przypadku reklam flashowych powszechnie stosuje się link pod zmienną clickTag - gdzie umieszcza się link przekierowujący (liczący kliknięcia przez serwis wyświetlający reklamę) na "licznik" i z licznika na docelową stronę. Może to wyglądać nawet tak:
    <embed height="200" width="750" name="td_flash" id="td_flash" wmode="opaque" swliveconnect="true" pluginspage="http://www.macromedia.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash" type="application/x-shockwave-flash" quality="best" menu="false" flashvars="clickTAG=http%3A%2F%2Fclk.tradedoubler.com%2Fclick%3Fp%3D74000%26a%3D1545102%26g%3D17957364%26pools%3D305907%2C282487&amp;CLICKTAG=http%3A%2F%2Fclk.tradedoubler.com%2Fclick%3Fp%3D74000%26a%3D1545102%26g%3D17957364%26pools%3D305907%2C282487&amp;clicktag=http%3A%2F%2Fclk.tradedoubler.com%2Fclick%3Fp%3D74000%26a%3D1545102%26g%3D17957364%26pools%3D305907%2C282487&amp;clickTag=http%3A%2F%2Fclk.tradedoubler.com%2Fclick%3Fp%3D74000%26a%3D1545102%26g%3D17957364%26pools%3D305907%2C282487&amp;ClickTag=http%3A%2F%2Fclk.tradedoubler.com%2Fclick%3Fp%3D74000%26a%3D1545102%26g%3D17957364%26pools%3D305907%2C282487" src="http://ads.open.pl/kreacje/2009/05_09/doplata/google_750x200_z_doplata.swf"/>
    
  • Parsers dla "clickTag" będzie wyglądał tak:
    def get_clickTag(html):
    	"""
    	Extract data from flash ads
    	"""
    	links = findall(r'clicktag=(.*?)"', unicode(html).lower())
    	ret = []
    	for i in links:
    		ret.append(unquote(i))
    	return ret
    
  • Jeżeli potrzebujesz pobierać reklamy z określonych serwisów to musisz sprawdzić w jaki sposób wstawiają reklamy i napisać pod to parsery. Powyższy parser clickTag będzie działał dla większości reklam flashowych wyświetlanych na popularnych serwisach. (unquote importujemy z urllib)

Zapis danych do bazy

Za obsługę baz danych w Qt odpowiada komponent QtSql. Jest on dostępny także w PyQt4, lecz jego API nie jest zgodne z DB API dla modułów Pythona (bo nie było projektowane z myślą o Pythonie).
  • Zaczynamy od dodania importu:
    from PyQt4.QtSql import *
  • Połączenie się z bazą jest proste, można umieścić poniższy kod w metodzie __init__:
    self.db = QSqlDatabase.addDatabase("QSQLITE")
    		self.db.setDatabaseName("ads")
    		self.dbstatus = self.db.open()
    		if self.dbstatus:
    			print 'DB ok'
    		else:
    			print 'DB error'
    
    Wybieramy sterownik - SQLite, następnie podajemy nazwę bazy i nawiązujemy "połączenie" (w przypadku SQLite nie trzeba podawać hosta, hasła i loginu użytkownika bazy).
  • Potrzebujemy tabeli, do której będziemy wrzucać dane:
    CREATE TABLE "ads_data" (
        "id" integer NOT NULL PRIMARY KEY,
        "link" varchar(255) NOT NULL,
        "dest_title" varchar(255) NULL,
        "dest_url" varchar(255) NULL,
        "date" datetime NOT NULL,
        "source" varchar(255) NOT NULL,
        "is_parsed" bool NULL
    );
    
    Wykonaj powyższe zapytanie np. z wiersza poleceń:
    sqlite3 ./ads
  • Teraz należy dodawać do bazy wyszukane odnośniki:
    def loadFinished(self):
    		"""
    		A page was loaded - get the data and load next page
    		"""
    		page = self.ui.webView.page()
    		frame = page.currentFrame()
    		content = frame.toHtml()
    		print u'Page content, got %s bytes' % len(content)
    		
    		links = get_adtaily(content)
    		links2 = get_clickTag(content)
    		
    		for i in links2:
    			links.append(i)
    		
    		query = QSqlQuery(self.db)
    		try:
    			source = self.sites[self.currentIndex]['site']
    		except:
    			return
    		
    		# dodajemy poszczególne odnośniki
    		for link in links:
    			qry = "INSERT INTO ads_data ('link', 'date', 'source', 'is_parsed') VALUES ('%s', '%s', '%s', 0);" % (link, date.today(), source)
    			if query.exec_(qry):
    				print u'Poszedł INSERT'
    			else:
    				print 'Insert Error'
    				print qry
    				print query.lastError().text()
    				print
    		
    		if self.currentRefresh < self.refreshSite:
    			print 'Refresh +1'
    			self.currentRefresh += 1
    		else:
    			print 'Index +1'
    			self.currentRefresh = 0
    			self.currentIndex += 1
    		
    		nexturl = self.__getNextUrl()
    		if nexturl:
    			self.ui.webView.load(nexturl)
    
    Zaczynamy od stworzenia obiektu query = QSqlQuery(self.db) i wykonania zapytania SQL za pomocą metody exec_. Dane zostaną dodane, a jeżeli zapytanie nie powiedzie się - wyświetlony zostanie komunikat błędu
  • Powyższy przykład pokazuje gotowe rozwiązanie logowania linków do bazy danych. Warto rozbudować go o np. sprawdzanie czy dany link danego dnia został już dodany by uniknąć zbędnych duplikatów. Zebrane dane należy jeszcze przerobić - napisać drugą aplikację, która otworzy link i pobierze adres i tytuł strony, na której wyląduje (czym zajmiemy się w trzeciej części kursu).
  • Poniżej metoda blokująca dodawanie duplikatów do bazy danych:
    def loadFinished(self):
    		"""
    		A page was loaded - get the data and load next page
    		"""
    		page = self.ui.webView.page()
    		frame = page.currentFrame()
    		content = frame.toHtml()
    		print u'Page content, got %s bytes' % len(content)
    		
    		links = get_adtaily(content)
    		links2 = get_clickTag(content)
    		
    		for i in links2:
    			links.append(i)
    		
    		query = QSqlQuery(self.db)
    		try:
    			source = self.sites[self.currentIndex]['site']
    		except:
    			# no more sites
    			return
    		
    		for link in links:
    			cnt = 0
    			if query.exec_("SELECT COUNT(*) FROM ads_data WHERE date = '%s' AND source = '%s' AND link = '%s'" % (date.today(), source, link)):
    				query.next()
    				cnt = query.value(0).toInt()[0]
    				print cnt
    			
    			if cnt < 1:
    				qry = "INSERT INTO ads_data ('link', 'date', 'source', 'is_parsed') VALUES ('%s', '%s', '%s', 0);" % (link, date.today(), source)
    				if query.exec_(qry):
    					print u'Poszedł INSERT'
    				else:
    					print 'Insert Error'
    					print qry
    					print query.lastError().text()
    					print
    			else:
    				print 'pass'
    		
    		if self.currentRefresh < self.refreshSite:
    			print 'Refresh +1'
    			self.currentRefresh += 1
    		else:
    			print 'Index +1'
    			self.currentRefresh = 0
    			self.currentIndex += 1
    		
    		nexturl = self.__getNextUrl()
    		if nexturl:
    			self.ui.webView.load(nexturl)
    
    Tutaj wykorzystaliśmy zapytanie pobierające dane. Iterowanie przez pobrane wiersze odbywa się za pomocą query.next(), które zwraca True, jeżeli udało się zwrócić kolejny wiersz (stosuj "while query.next()" jeżeli chcesz przeiterować wszystkie wyniki). W naszym przypadku pobieramy jeden wiersz (liczenie) i interesuje nas pierwsza wartość zwrócona przez zapytanie. Metoda query.value(index) zwróci daną w postaci obiektu QVariant, który trzeba znormalizować np. toString() lub toInt().

Kod źródłowy

RkBlog

PyQt, 3 November 2009, Piotr Maliński

Comment article