Scraping con Python e WebKit2

Ho sviluppato una applicazione in Python e GTK3 per Ubuntu Linux per la gestione e l’invio degli ordini dei tabacchi. Nel corso del tempo ho cercato di rendere sempre più automatico e meno ripetitivo l’invio di un ordine e la raccolta delle informazioni sul suo stato.

Ho usato le librerie PyGObject per accedere ai bindings per WebKitGTK+, la mia piattaforma di riferimento è una Ubuntu 16.04.2 LTS.

apt install gir1.2-webkit2-4.0

Ho realizzato un mini-browser dedicato, che si occupa, tramite una sequenza di script, dell’autenticazione e della navigazione fino al punto da me desiderato ed in fase di chiusura, tramite un’altra sequenza di script, di ottenere informazioni sull’invio e lo stato degli ordini.

Invio ordine

Invio ordine

Uno dei problemi principali è stato ottenere risultati dall’esecuzione del codice Javascript usando solo Python, senza ricorrere a codice C.
Come si può vedere dalla documentazione online di WebKit2 4.0, l’esecuzione di codice Javascript tramite la classe Webkit2.Webview avviene tramite il metodo run_javascript che, tramite una callback,  grazie al metodo run_javascript_finish ritorna un oggetto JavascriptResult.
Il problema è che i bindings Python non gestiscono l’oggetto appena descritto e non si riesce ad estrarne informazioni utili.
Una soluzione, non eccessivamente complessa e neanche troppo “sporca” è quella di segnalare a Python l’arrivo di un messaggio proveniente da Javascript connettendo l’UserContentManager della WebView ad uno speciale segnale "script-message-received::XXX" da noi predefinito usando la clipboard come canale di trasferimento.
Javascript si occuperà di porre l’elemento da inviare nella clipboard e quindi di segnalare la sua disponibilità tramite window.webkit.messageHandlers.XXX.postMessage().

Di seguito un esempio per dimostrare questa soluzione usando GTK 3 e WebKit2 versione 4.


#!/usr/bin/python
# coding: UTF-8
#
# Copyright (C) Francesco Guarnieri 2017
# 

import gi
gi.require_version('WebKit2', '4.0')
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, Gio, WebKit2, Pango

start_url = "http://www.google.it"

# Uno stack di script da eseguire al termine del caricamento di ogni pagina
scripts = ["""var range = document.createRange();
          var elemento = document.getElementsByClassName('g')[0];;
          range.selectNode(elemento);
          window.getSelection().removeAllRanges();
          window.getSelection().addRange(range);
          document.execCommand('copy');    
	  window.webkit.messageHandlers.CANALE_JS.postMessage('Test');""",
       
	"""document.getElementById('lst-ib').value = 'Il blog di Francesco Guarnieri'; 
	   document.getElementById('tsf').submit();"""	       
	]
			
class Browser(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.set_position(Gtk.WindowPosition.CENTER)
        self.set_default_size(1000,600)
	self.scripts = None
	contentManager = WebKit2.UserContentManager() 
        contentManager.connect("script-message-received::CANALE_JS", self.__handleScriptMessage)
        if not contentManager.register_script_message_handler("CANALE_JS"):
            print("Error registering script message handler: CANALE_JS")
	
	# Inizializza Webview con il contentManager
        self.web_view = WebKit2.WebView.new_with_user_content_manager(contentManager)
	self.web_view.connect("load-changed", self.__loadFinishedCallback)    
            
        # Peronalizza i settings:
	# Nel nostro caso abilita Javascript ad accedere alla clipboard
        settings = WebKit2.Settings()
        settings.set_property('javascript-can-access-clipboard', True)
        self.web_view.set_settings(settings)

        okButton = Gtk.Button()
        icon = Gio.ThemedIcon(name="emblem-ok-symbolic")
        image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.SMALL_TOOLBAR)
        okButton.add(image)
        okButton.connect("clicked", self.__close)
        cancelButton = Gtk.Button()
        icon = Gio.ThemedIcon(name="window-close-symbolic")
        image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.SMALL_TOOLBAR)
        cancelButton.add(image)
        cancelButton.connect("clicked", self.__close)
        headerBar = Gtk.HeaderBar()
        headerBar.set_show_close_button(False)
	self.set_titlebar(headerBar)
        boxTitle = Gtk.Box(spacing = 6)
        self.spinner = Gtk.Spinner()
        labelTitle = Gtk.Label()
        labelFont = labelTitle.get_pango_context().get_font_description()
        labelFont.set_weight(Pango.Weight.BOLD)
        labelTitle.modify_font(labelFont)
        boxTitle.add(labelTitle)
        boxTitle.add(self.spinner)
        headerBar.set_custom_title(boxTitle)
        self.stopButton = Gtk.Button()      
	icon = Gio.ThemedIcon(name="media-playback-stop-symbolic")
        image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.SMALL_TOOLBAR)
        self.stopButton.add(image)
	self.stopButton.connect("clicked", self.__on_stop_click)
        headerBar.pack_end(self.stopButton)
        box = Gtk.Box()
        Gtk.StyleContext.add_class(box.get_style_context(), "linked")
        box.add(okButton)
        box.add(cancelButton)
        headerBar.pack_start(box)
        browserBox = Gtk.Box()
        browserBox.set_orientation(Gtk.Orientation.VERTICAL)
        browserBox.pack_start(self.web_view, True, True, 0)
        self.add(browserBox)
	
    # Callback richiamata ad ogni cambiamento di stato della fase di load della webview
    # I possibili eventi: WEBKIT_LOAD_STARTED, WEBKIT_LOAD_REDIRECTED, 
    # WEBKIT_LOAD_COMMITTED, WEBKIT_LOAD_FINISHED
    def __loadFinishedCallback(self, web_view, load_event):
        if self.scripts and (len(self.scripts) > 0) and (load_event == WebKit2.LoadEvent.FINISHED):
            self.__waitMode(False)
	    web_view.run_javascript(self.scripts.pop(), None, self.__javascript_finished, None)
	return False
    
    # Callback per gestire la fine dell'esecuzione del codice javascript
    def __javascript_finished(self, webview, task, user_data = None):
        try:
	    # Qui si pone il problema dell'oggetto result di tipo JavascriptResult 
	    # non gestibile
            result = webview.run_javascript_finish(task)
        except Exception as e:
            print("JAVASCRIPT ERROR MSG: %s" % e)

    # Gestisce i messaggi ricevuti da Javascript anche in modo asincrono, non
    # necessariamente al termine dell'esecuzione 
    def __handleScriptMessage(self, contentManager, js_result):
        # Anche qui si pone il problema dell'oggetto js_result di tipo 
	# JavascriptResult non gestibile. Per ottenere risultati senza ricorrere
	# al codice C è possibile usare la clipboard       
        print "[ Ricevuto messaggio da JavaScript ]"
	clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
        resultStr = clipboard.wait_for_text()
        if resultStr:
	     print "%s" % resultStr
	return True
    
    # Imposta la modalita di attesa se attiva o meno    
    def __waitMode(self, toggle):
        self.stopButton.set_sensitive(toggle)
        self.stopButton.set_visible(toggle)
        self.web_view.set_sensitive(not toggle)
        if toggle: self.spinner.start()
        else: self.spinner.stop()
    
    # Gestisce la pressione del pulsante di stop
    def __on_stop_click(self, widget):
        self.__waitMode(False)
        self.web_view.stop_loading()

    # Gestisce la chiusura del mini-browser
    def __close(self, widget=None):
        self.web_view.stop_loading()
        self.destroy()
        Gtk.main_quit()

    # Apre il sito
    def open(self, site, scripts = None):
	self.scripts = scripts
        self.__waitMode(True)
        self.web_view.load_uri(site)

if __name__ == "__main__":
    browser = Browser()
    browser.show_all()
    browser.open(start_url, scripts)
    Gtk.main()

Fibra TIM – Bella solo a metà

Bella la fibra.. Se abiti in città..
Ma oltre i 5/600 mt dall’armadio Telecom le cose degradano molto velocemente, fino ad avvicinarsi alle prestazioni di una normale adsl2.
Nel mio caso, disto circa 1 km dall’armadio dove realmente arriva la fibra ottica (FTTC, “fiber to the cabinet”), poi è il solito doppino in rame, che, grazie alla tecnologia Vdsl2 mi porta nel “magico mondo della fibra”.
In teoria dovrei avere 100mb in download e 20mb in upload (profilo 17a), in realtà queste sono le prestazioni della mia linea:

Fibra Tim

Fibra Tim

Da una parte mi accontento: ho quasi triplicato le prestazioni rispetto alla vecchia adsl. Se invece osservo “l’upgrade” da un’altra prospettiva, sono molto (ma molto) lontano da quello che potrei avere da questa tecnologia.
Speriamo in un affinamento nei sistemi di cancellazione del rumore e in nuove evoluzioni della tecnologia Vdsl2 (profilo 35b, magari con vectoring).

Beebox N3000 ed Ubuntu

Il mese scorso mi è stato “commissionato” un mediacenter di buona qualità, silenzioso, non eccessivamente costoso (il budget era massimo 200€), che potesse essere acceso ed usato con un telecomando e dove possibilmente poter riutilizzare un  disco SSD di piccola taglia.

Il Beebox N3000 mi è sembrato il candidato ideale, ad un prezzo di 133€ era imperdibile. Per 45€ ho aggiunto due moduli ram Corsair da 4GB DDR3 (1600 MHz, CL9), identici, per usufruire dei benefici della modalità dual-channel.

Beebox N3000

Beebox N3000

Questo piccolo gioiellino fanless si basa su un SoC Braswell con un Intel Dual-Core Celeron N3000 e dispone di una scheda di rete gigabit, un modulo WIFI 802.11ac e Bluetooth 4.0, 3 porte USB3, una porta USB3 Type C,  2 prese HDMI e 1 Displayport.

Inoltre, supporta dischi in formato mSata e anche Sata da 2,5″, insieme a due slot per memorie DDR3,  una presa ir e telecomando incluso.

Le mie istruzioni per realizzare un mediacenter basato su Ubuntu 16.04LTS con Kodi, si applicano a questo pc con poche modifiche, essenzialmente legate alla scheda video integrata nella CPU e un differente driver per la presa IR.

Senza entrare troppo nello specifico, posso testimoniare che questo mediacenter è in grado di visualizzare senza alcun affanno video full-hd con codifica H.265, per non parlare di H.264. Non ho potuto verificare con risoluzioni superiori (4K), ma forse siamo ai limiti delle potenzialità di questo ottimo sistema.

Macbook 2006 e Ubuntu 16.04 con EFI

In famiglia avevamo un vecchio Macbook 2006 che se ne stava in un cassetto già da molto tempo. Perchè non riportarlo a nuova vita?

Oltre OS X Lion 10.7.5 non era possibile andare e realisticamente con 1gb di ram e un hard-disk da 5400rpm eravamo già ai limiti.

Grazie ad un precedente “upgrade” del mio mediacenter avevo un disco SSD da 64gb che mi avanzava, un vecchio portatile con lo schermo rotto e mai riparato (antieconomico) avrebbe fornito la ram per espandere il Macbook al massimo di 3gb.

Ho deciso di passare ad Ubuntu 16.04.1 LTS.  Ho scelto la versione a 64bit, visto che il portatile ha una cpu Intel Core 2 Duo.

Anche in questo caso (vedi il mio post su Lenovo Ideapad Flex 10) mi sono trovato con un problema di sistemi EFI a 32bit con sistemi potenzialmente a 64bit.
Neanche l’immagine di sistema più recente (con Ubuntu 16.04.1) supporta questa situazione ibrida. Ho dovuto quindi cercare delle soluzioni per generare un’immagine da scrivere sulla mia chiavetta usb che potesse essere eseguita dal bios EFI a 32bit del Macbook.

Questa è la sequenza delle istruzioni (da eseguire con diritti di root su un sistema con una Ubuntu 16.04), che ho lievemente modificato rispetto all’originale dedicato ad un ASUS EeeBook:

apt install p7zip-full

wget http://releases.ubuntu.com/16.04/ubuntu-16.04.1-desktop-amd64.iso

# Suppongo che la chiavetta USB sia sul device  /dev/sdb
# La chiavetta non deve essere montata su nessuna directory
# ATTENZIONE! QUESTO CANCELLERA' TUTTO QUELLO CHE STA SU /dev/sdb !
sgdisk --zap-all /dev/sdb
sgdisk --new=1:0:0 --typecode=1:ef00 /dev/sdb
mkfs.vfat -F32 /dev/sdb1

# Monto la chiavetta sulla directory /mnt e
# ci copio il file ISO:
mount -t vfat /dev/sdb1 /mnt
7z x ubuntu-16.04.1-desktop-amd64.iso -o/mnt/ 

# Crea il bootloader a 32bit e lo scrive sulla chiavetta:
apt install git bison libopts25 libselinux1-dev autogen m4 autoconf help2man libopts25-dev 
apt install flex libfont-freetype-perl automake autotools-dev libfreetype6-dev texinfo
git clone git://git.savannah.gnu.org/grub.git
cd grub
./autogen.sh
./configure --with-platform=efi --target=i386 --program-prefix=''
make
cd grub-core
../grub-mkimage -d . -o bootia32.efi -O i386-efi -p /boot/grub \
  ntfs hfs appleldr boot cat efi_gop efi_uga elf fat hfsplus iso9660 linux keylayouts \
  memdisk minicmd part_apple ext2 extcmd xfs xnu part_bsd part_gpt search search_fs_file \
  chain btrfs loadbios loadenv lvm minix minix2 reiserfs memrw mmap msdospart scsi loopback \
  normal configfile gzio all_video efi_gop efi_uga gfxterm gettext echo boot chain eval
cp bootia32.efi /mnt/EFI/BOOT/
umount /mnt

Ricordandosi che è necessario premere il tasto “Option” in fase di boot, altrimenti non potremo scegliere il boot dalla partizione EFI della chiavetta.

Finalmente,  potremo installare una bella Ubuntu sul nostro Macbook, con tabella delle partizioni GPT e pronta per il boot da EFI.

Ma il lavoro, purtroppo, non è ancora finito.. Alla fine dell’installazione, ci accorgeremo che il boot da disco non funziona e che il solo modo per eseguire Ubuntu è affidarsi alla partizione di boot dalla nostra fidata chiavetta.

Qualcosa ancora non va. Finalmente, grazie ad un ottimo articolo ho trovato la spiegazione e la soluzione del mio problema: la partizione EFI deve essere formattata con il filesystem Mac HFS+ (e non Fat32) e devono essere presenti alcuni specifici files.

Nell’articolo appena menzionato, viene spiegato come installare Ubuntu su Mac recenti (dal 2012 in poi) che ovviamente non hanno i problemi che ho affrontato per creare il disco di installazione (sono sistemi full 64bit).

Supponendo che la partizione EFI sia la numero 1, riporto di seguito le istruzioni leggermente modificate per un EFI a 32 bit:

# Repository con utilities per Intel Mac (fan control, HFS bless, bootloader icon, etc..)
add-apt-repository ppa:detly/mactel-utils
apt update
apt install mactel-boot hfsprogs gdisk grub-efi-ia32

umount /dev/sda1 
# Modifica la partizione EFI /dev/sda1 
gdisk /dev/sda

Questi sono i comandi da eseguire con gdisk:

Command (? for help): d
Partition number (1-3): 1

Command (? for help): n
Partition number (1-128, default 1): 1
«accept defaults»

Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300): AF00
Changed type of partition to 'Apple HFS/HFS+'

Command (? for help): w

Formatta la partizione EFI e Aggiorna fstab:

mkfs.hfsplus /dev/sda1 -v Ubuntu
bash -c 'echo $(blkid -o export -s UUID /dev/sda1) /boot/efi auto defaults 0 0 >> /etc/fstab'
mount /boot/efi

Installa Grub:

mkdir -p "/boot/efi/EFI/$(lsb_release -ds)/"
bash -c 'echo "This file is required for booting" > "/boot/efi/EFI/$(lsb_release -ds)/mach_kernel"'
bash -c 'echo "This file is required for booting" > /boot/efi/mach_kernel'
grub-install --target i386-efi --boot-directory=/boot --efi-directory=/boot/efi --bootloader-id="$(lsb_release -ds)"
hfs-bless "/boot/efi/EFI/$(lsb_release -ds)/System/Library/CoreServices/boot.efi"

Kodi ed ubuntu – guida all’installazione

Ho appena pubblicato una pagina per la guida all’installazione del mediacenter Kodi su Ubuntu 16.04 LTS.

La guida è molto dettagliata, ma richiede un minimo di dimestichezza con Linux e la shell bash.
Prevede la configurazione con script Systemd per il boot automatico di Kodi e per sostituire Grub con Extlinux ed avere un dual boot con splash screen full HD.

Spiego come personalizzare il display LCD del mio case Antec Fusion Black ed usare il mediacenter con un telecomando Logitech Harmony usando ir-keytable al posto di Lirc.

Cercherò di tenerla aggiornata con le novità e i miglioramenti che si presenteranno in futuro.

Come difendersi dalle vendite telefoniche

Nonostante mi sia iscritto al registro delle opposizioni, continuo a ricevere telefonate non volute, che mi propongono (spesso con modi truffaldini) le cose più inutili.

Finalmente, credo di aver trovato una soluzione, anche se non a costo zero.

Sulla mia linea telefonica fissa ho attivato l’opzione “Chi è” di Telecom Italia (adesso TIM) alla “modica” cifra di 2,90€ mensili. Quindi ho cambiato il mio vecchio cordless con un nuovo Siemens Gigaset S850.

Siemens Gigaset S850

Siemens Gigaset S850

Tutto questo, per poter avere a disposizione una “blacklist”, funzione che esiste da tempo nel mondo mobile.
Prima di tutto, ho bloccato tutte le chiamate anonime. Successivamente, ogni chiamata non voluta è stata aggiunta alla blacklist, in modalità “blocco”, così da non avere il minimo fastidio, neanche un lampeggio.

Speriamo che i 15 numeri messi a disposizione siano sufficienti, io sarei stato più generoso.

Che clima ci aspetta?

Dopo un autunno inesistente (a Novembre non si sono viste piogge in Toscana) e un inverno ridicolo (temperature miti è dir poco), a fine Gennaio eravamo già a rischio siccità.
Per fortuna Febbraio ci ha dato una mano con le precipitazioni e i nostri media si sono scordati del problema.
Purtroppo la situazione climatica sta cambiando molto rapidamente, ed è talmente evidente che anche il più distratto tra noi non può fare a meno di notarlo.

Gli studi che qualche anno fa facevano quasi sorridere, prevedendo climi mediterranei nelle isole britanniche, e temperature tropicali nel mediterraneo, si stanno velocemente avverando.

Per il NOAA  marzo è l’undicesimo mese consecutivo di aumento record delle temperature a livello globale.

Speriamo che la Cina e gli Stati Uniti, di gran lunga i più grandi inquinatori del pianeta, il prossimo 22 Aprile (la giornata della Terra), con la ratifica degli accordi sul clima COP21 presi lo scorso Dicembre a Parigi, inizino a fare realmente sul serio.

Non c’è più tempo!

Anomalie temperature Marzo 2016 - Scarto dai valori medi

Anomalie temperature Marzo 2016

Black Sails

Grazie a Netflix ho scoperto questa serie TV, che non esagero, è di grande livello!
Sia come qualità della sceneggiatura, sia come interpreti (di tutto rispetto), sia come effetti speciali (niente da invidiare a grandi produzioni internazionali).

È ambientata nel periodo d’oro della pirateria, circa 20 anni prima delle vicende narrate nell’Isola del tesoro di Robert Louis Stevenson. Ci sono tutti, John Silver, Barbanera, il capitano Flint.. E’ appena terminata la terza stagione, ma Netflix si ferma alla seconda.

Se vi piacciono le storie di pirati non vi potete perdere Black Sails!

Black Sails

Black Sails

Restyling

Operazione di “restyling”. Ho cambiato molti aspetti di questo sito, alcuni più visibili, come l’immagine della testata o i link “social”,  altri meno visibili, come le ottimizzazioni SEO o per Google Page Speed. Il tema è rimasto il solito Adventurous by Catch Themes, con cui mi trovo benissimo. Ho rivisto e cambiato alcuni plugin di WordPress, in funzione dei cambiamenti di cui sopra.
L’immagine della testata cambia, ma rimane un’isola tropicale, questa volta è La Digue nell’arcipelago delle Seychelles, che ho avuto la fortuna di visitare.

Manutenzione sito urgente

Il mio nuovo plugin per le statistiche mi informa che ho un bel po’ di fan cinesi, ucraini e dalle ex-repubbliche sovietiche.

Forse un po’ troppi per un piccolo blog italiano  :cry:
Niente di grave, ma temo sia questa l’origine dell’impennata di spam che ho notato recentemente (dopo la relativa quiete legata al cambio di provider).
Ho rinforzato le difese, ma temo sia troppo tardi per lo spam.

Oramai i bot “maligni” generano più traffico degli utenti normali.