2015. március 27., péntek

Google keresés kép alapján Python alól

Elég régóta létező funkció a Google képkeresőjében a Keresés kép alapján, tehát amikor belinkelsz vagy feltöltesz egy képet a Google-nek és az visszahányja hogy milyen weboldalakon található meg az a kép. A kereső sor jobb szélén, a kis fényképező ikonra kattintva lehet előhozni.

Még a múlt hétvégén alakult úgy hogy sok képet akartam visszaellenőrizni. Persze semmi se garantálja hogy attól hogy ha egy kép keresésére nincs eredmény akkor az a kép tényleg egyedi, de azért ahogy az embereket ismerem, nem az internet legsötétebb oldalairól vesznek elő képeket, ha identitást kell hazudni.

Egy rövidebb keresés után kiderült, hogy nincs publikus HTTP API amin keresztül egyszerűen lehetne kereséseket intézni, de semmi probléma, ott a HTML az lesz most az API.

Egy egyszerű kis generátor függvényt raktam össze a feladat elvégzésére, ami egy fájlt és betöltendő találati oldalak maximális számát várja, és visszatér a találatok feliratával és az URL-jével.

Függőségek

TL;DR pip install requests requests_toolbelt lxml cssselect

Sok scrapert írtam már, úgyhogy van egy jól bejáratott hálózati stackem ami úgy pumpálja ki a HTTP kéréseket, ahogy én akarom, de fájl feltöltést nem próbáltam még vele, illetve a képkereső form multipart/form-data formátumban várja az adatokat, tehát kellett volna valami serializálót írnom, amihez épp nem volt kedvem. Megnéztem hogy a nagyon népszerű requests library képes-e out-of-box a fenti formában POST-olni a kérésemet, de sajnos úgy láttam hogy nem, viszont request_toolbelt libraryben van olyan encoder ami képes erre. Ezek türkében úgy döntöttem, hogy akkor most nem szarakodok a saját stackemmel.

A hálózati részen kívül már csak egy HTML parserre van szükség. Én mindig az lxml-t használom. Régebben próbálkoztam a Beautiful Souppal, és azon kívül hogy vagy 7x tovább tart leírni a nevét mint azt hogy lxml, még lassú is volt, meg a CSS selectorokat se ismerte. Évekkel ezelőtt volt ez, azóta rá se néztem, lehet azóta már jobb a helyzet. Az külön felbasz hogy a /r/Python-ban állandóan a Beautiful Soup-os kódokat mutogatnak. Ok, aláírom hogy az lxml-t fordítani kell, dehát annyira nem kurva nehéz ám az. Az lxml-hez kell még a cssselect csomag, és akkor végre boldogan állhatunk neki a programozásnak.

Generátorok

Azért döntöttem úgy hogy generátor függvényt írok, mert több találati oldalt akarok majd végig iterálni. Nem szeretném viszont hogy 100 oldal végignézését végig kelljen várni. Helyette inkább szépen iterálgatom a generátort, és ha mondjuk baromi régóta csinálom már ezt, és semmi extra találat nincs, akkor abbahagyom az egészet.

Két ilyen generátor függvényt írtam. Kezdjük az egyszerűbbel. Ez az amelyik visszaadja a találatokat az oldalról:

def extract(soup):
    for elem in soup.cssselect('.rc h3 a'):
        yield {'text': elem.text_content(), 'url': elem.get('href')}

A függvény már egy lxml fát vár. Note: azért soup megmaradt a Beautiful Soup-ból.
A 2. sorban a .rc h3 a selectort kézzel raktam össze. Ennek a történetét most nem részletezem, külön bejegyzést lehetne írni arról, hogy hogy kell selectorokat írni.
A ciklus törzsében egyszerűen visszatérünk egy dict-el, benne a link szövegével és az URL-jével.

Most veszem csak észre, hogy igazából a visszatérés nem a legjobb kifejezés ebben az esetben, hisz nem return utasítás áll a függvény végén, hanem yield. Meg amúgy nem is igazán van értelme ezt a függvényt generátornak megtenni, elég lenne egy sima list-el visszatérni, viszont ki akartam próbálni egy új nyelvei elemet, amit a Python 3.3 vezetett be, és ez pont egy jó szituáció volt a próbára.

Ezt a függvényt most hagyjuk így, és lássuk azt, amelyik a tényleges feltöltést végzi:

import requests
from lxml.html import fromstring
from requests_toolbelt.multipart.encoder import MultipartEncoder

def reverse(fp, walk_limit=5):
    walked = 0
    user_agent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.89 Safari/537.36'
    m = MultipartEncoder({'encoded_image': (fp.name, fp, 'image/jpeg'), 'image_url': None, 'image_content': None, 'filename': None, 'btnG': None})

    r = requests.post('http://www.google.hu/searchbyimage/upload', data=m, headers={'Content-Type': m.content_type, 'User-Agent': user_agent})
    soup = fromstring(r.text, base_url='http://www.google.hu')
    soup.make_links_absolute()
    yield from extract(soup)

    next = soup.cssselect('#pnnext')
    while walked < walk_limit and next:
        walked += 1

        url = next[0].get('href')
        r = requests.get(url, headers={'User-Agent': user_agent})
        soup = fromstring(r.text, base_url='http://www.google.hu')
        soup.make_links_absolute()

        next = soup.cssselect('#pnnext')
        yield from extract(soup)

A reverse() két paramért vár: egy fájl-szerű objektumot, és a bejárt találati oldalak limitjét.
Az első izgalmas rész a 4. sorban van. Itt jön létre az encoder, ami kap egy szép dict-et. Az itt megjelenő kulcs-érték párokat úgy kaptam, hogy figyeltem Chrome Development Tools Network fülén hogy mi megy ki a POST-ban.

A legizgalmasabb rész a 9. sorban van, a yield from extract(soup). Itt szépen delegáljuk az extract() generátort.

Delegáció nélkül ez így nézne ki:

for hit in extract(soup):
   yield hit

Vagy ha az extract() list-el térne vissza, és akkor yield extract(soup), de az olyan unalmas.

A 11. sorban megint egy kis selector van. Az oldal alján a ‘Következő’ feliratú link id-je a #pnnext, ezt is választja ki a metódus. Szerencsére a cssselect() üres tömbbel tér vissza ha nincs a selectornak megfelelő elem a levesben. Ezt a 12. sorban ki is használom, mivel a [] logikai értéke False. Nem úgy mint Javascriptben ahol az Isten se tudja hogy mi.
Ebben a 12. sorban még azt ellenőrzöm hogy kevesebb oldalt töltöttem-e be mint mint a walk_limit.
A 13. sorban gyors növelem is a betöltött oldalak számát, még mielőtt elfelejteném, és végignézném mind a 7,800,000,000 találatot. JA VÁRJ az amúgy se következik be olyan könnyen, mert laponként iterálgatunk. Köszi generátorok.

A 15. sortól gyakorlatilag minden úgy megy, mint a függvény elején az első oldal lekérésénél. Ez egy szép iteratív bejárás, ennyi bőven elég is ide.
A 20. sorban gyorsan, még mielőtt a 21. sorban delegálnánk a linkeket megjegyezzük a következő oldalra mutató elemet, ha van ilyen. De mellékesen, mivel a függvény ugye nem tér vissza, ez a két sor akár meg is cserélhetné egymást. úgy is működne.

Az egész kóceráj végén a ciklus szépen megy tovább addig, amíg a feltétel igaz ~

Próba

A Python.org oldaláról letöltöttem a logót onnan az oldal tetejéről (remélem nem haragudnak meg érte). Nézzük hogy megtaláljuk-e a képet.

from google_reverse import reverse
results = reverse(open('/tmp/python-logo.png', 'rb'))
next(results)
#>>> {'url': 'http://hu.wikipedia.org/wiki/Python_%28programoz%C3%A1si_nyelv%29', 'text': 'Python (programozási nyelv) – Wikipédia'}

Ez bizony megtalálta.

Az összes (5 oldalnyi) találat behúzásához csak iteráljuk végig:

from google_reverse import reverse
results = reverse(open('/tmp/python-logo.png', 'rb'))
for result in results:
    print(result)
#>>> {'url': 'http://hu.wikipedia.org/wiki/Python_%28programoz%C3%A1si_nyelv%29', 'text': 'Python (programozási nyelv) – Wikipédia'}
#>>>{'url': 'http://hu.metapedia.org/wiki/F%C3%A1jl:Python_logo.svg', 'text': 'Fájl:Python logo.svg - Metapedia'}
#>>> ...

És így tovább.

Figyelem: mivel képről (bináris adatról) van szó, ezért bináris olvasásra nyissuk meg a fájlt!

Ennyi mára

Huh, ez a bejegyzés még épp belefért ebbe a hónapba. Persze a havi 1 post még mindig szegényes, de még mindig jobb mint az évi 1.

A teljes kód megtekinthető ezen a Gist-en.

-slp

2015. február 8., vasárnap

Javascript függvény paraméterének kiolvasása PyV8-al

(Ez a bejegyzés egy kicsit módosítva lett 2015. február 9-én)

Rendbe, most hogy ilyen szépen elkészítettük a saját PyV8 példányunkat, ideje lenne kipróbálni valami kóddal, hogy működik-e rendesen.

A saját projektjeim közül hoztam a mostani témát: egy weboldalon egy Javascript Object értékét szeretném megkapni és átalakítani dict-é. A mostani történetben a csavar az, hogy ez az objektum egy függvény paramétere, ami ráadásul a jQuery ready() metódusába van belenyomogatva. Tehát nem holmi pi=3.14 változót kellene kipiszkálni.

(A projektről homályosan: Van egy weboldal, amin egy Google Maps-es térkép látható. Erre a térképre valamilyen saját library-vel markereket tesznek. Én egy adott, sokszög alapú területen levő markereket akarom kigyűjteni. A probléma megoldásának első részével foglalkozik a mai bejegyzés: a weboldal térképén látható összes koordináta összegyűjtésével. Egy későbbi részben majd kitaláljuk, hogy hogyan lehet kiválogatni azokat, amik egy területen belül vannak.)

Lássuk a kódot

Ebben a kis részletben csak a lényeg látszódik. Azzal hogy hogy halásztam ki a DOM-ból most nem foglalkozok (amúgy lxml-el):

$(document).ready(function(){
    new AwesomeLibrary.PutMarkers('#map', {
        data: [{id: '1', lat: '47.160971', lng: '16.4878386'}]
    });
});

Maga a forráskód nagyon egyértelmű, a PutMarkers() második paraméterének az értékére van szükség ({data:[{id: 1, lat: '47.160971', lng: '16.4878386'}]})

Megoldás

A jó multkor egyszerűbbnek találtam, ha először egy fake Javascript kódot eval()-olok a contextben, amivel módosítottam a később ténylegesen meghívott metódusok működését. Ahhoz hogy ez most is működjön kézzel deklarálnom kell a $, document (mivel itt nincs DOM), ready(), AwesomeLibrary, PutMarkers értékeit, hogy ne kapjak sehol se undefined is not a function errort. Így szépen végig fut az egész kóceráj, de a sajnos a PutMarkers visszatérési értékét az eredeti függvény nem teszi ki globális változónak, és maga az anonim függvény se tér vissza semmivel se (főleg nem a koordinátákkal). Jobb ötlet híján a saját PutMarkers implementációmban kiraktam globális változóba a kapott paramétereket, de aztán inkább úgy döntöttem, hogy kipróbálok valami mást, és minden hiányzó függvényt egy Python osztályból rakosgatok a contextbe.

class Globals(PyV8.JSClass):
    def __init__(self):
        super(Globals, self).__init__()
        self.coords = []

    def PutMarkers(self, selector, coords):
        coords = PyV8.convert(coords)

        for coord in coords['data']:
            self.coords.append({
                'lat': float(coord['lat']),
                'lng': float(coord['lng']),
                'id': int(coord['id'])
            })

    def ready(self, function):
        function()

    def __call__(self, *args, **kwargs):
        return self

    def __getattr__(self, *args):
        return self

Az osztály legfontosabb metódusa a __getattr__(). Ez minden olyan esetben meghívódik, ha a Python nem találja az objektum egy property-jét. Mivel ez a metódus a globális névtérbe került, ezért minden olyan esetbe, amikor a V8 a globális névtérből keres egy változót, ez a Python metódus fog lefutni. Ide kerül minden olyan logika ami azokat az eseteket kezeli le, amikor valami deklarálatlan értéket keres a V8, de egyáltalán nem fontos, hogy mi annak az értéke. Másik fontos metódus a __call__(). Ennek segítségével a Globals példányunk függvényekhez hasonlóan tud viselkedni (meg lehet hívni).

A két magic method kombinációjával meg lehet oldani, hogy a számunkra nem érdekes változókat és metódusokat ne kelljen kézzel deklarálgatni. Amikor a V8 egy property-t keres, akkor szépen megkapja a objektumunkat a __getattr__()-ből. Ha abban a property-ben még egy propertyt keres, akkor megint csak megkapja az objektumot, de ha a proprty értékét callable-nek tekinti az is működni fog, mivel az objektum függvényként is tud viselkedni.

Magyarul tehát a Javascript kódban bármikor is egy deklarálatlan változót talál a V8, akkor azt mindig a Globals objektum attribútumai között keresi, és bármikor is talál egy deklarálatlan függvényt, meg fogja tudni hívni, mert a Globals példánya callable. Gyakorlatilag ez egy lánc (method chaining), így mindig minden keresett érték deklarált lesz. (Nem kerülünk rekurzióba, mivel a property-k kibontogatása véges).

Ha ez megvan, akkor jöhetnek az “érdekes metódusok”, szóval azok, amik ténylegesen végeznek valami munkát. A Javascript forráskódban ezek a ready() és PutMarkers().

A $ mindegy hogy mit csinál, csak függvény legyen, és valami olyan objektummal térjen vissza, amiben létezik a ready() metódus. A __getattr__() visszatér egy objektummal, ami függvényként viselkedik, és függvény által visszakapott objektumban létezik a ready() metódus (lásd kicsit lejjebb).
A document, AwesomeLibrary értékei hasonlóan. Egy csomót spóroltunk!

A ready() metódus a Globals osztályban magkapja az anonim függvény, egy JSFunction Python objektumként. Ezt ugyan úgy meg lehet hívni mint bármelyik sima függvényt, de természetesen a kódot a V8 futtatja, a kontextusban.

A történet vége a PutMarkers() metódus. Ez 2 paramétert vár, egy css selectort, amivel nem csinálunk semmit, és magát az értékes adatot, szóval a koordinátákat. Innen már nagyon egyszerű a dolog, a PyV8.convert() átalakítja dict-é az Javascript Object-et (így kikerül a context-ből az adat), majd egy sima iterációval bepakolgatjuk az összes koordinátát a self.coords tömbbe.

script_content = """
$(document).ready(function(){
    new AwesomeLibrary.PutMarkers('#map', {
        data: [{id: '1', lat: '47.160971', lng: '16.4878386'}]
    });
});"""

context_globals = Globals()
with PyV8.JSContext(context_globals) as ctx:
    ctx.eval(script_content)

print(context_globals.coords)

Példányosítjuk a Globals osztályt, majd nyitunk egy JSContext-et. Paraméternek átadjuk a csinos objektumunkat. A contextben pedig futtatjuk a Javascript kódot (script_content).

A kontextuson kívül a context_globals.coords propertyben már ott is vannak a koordináták

>>> print(context_globals.coords)
[{'lat': 47.160971, 'id': 1, 'lng': 16.4878386}]

Zárás

Azt hiszem jól látszódik, hogy kivállóan együtt tudnak működni a Python-os osztályok és a V8. Lényegesen elegánsabb ez a megoldás, mint kézzel kikapuzni egy másik Javascript fájlban az összes előforduló de haszontalan változót és aztán betölteni azt a context elején.

A teljes kód megtekinthető ezen a Gist-en.

A következő részben kitaláljuk, hogy hogy lehet kiválogatni az egy területen levő koordinátákat.

-slp

2015. január 25., vasárnap

PyV8 még egyszer (Python 3 + pyenv)

Ma rendesen megszopattam magam, ugyanis nem kisebb probléma megoldásának álltam neki mint hogy lefordítom Python 3 alá a PyV8-at (Python 2 alá ez már egyszer sikerül). Az extra csavar a történetben az, hogy az interpreterem pyenv környezetben létezik.

A jövőben mindenképp írok még egy teljes bejegyzést a pyenvről. Egyelőre elég annyit tudni róla, hogy ez egy nagyszerű shell script, amivel pofonegyszerűen lehet menedzselni és váltogatni ugyan azon az operációs rendszeren a különböző verziójú Python interpretereket. Akár minden könyvtárban, minden shellben más verziót lehet futtatni, sőt, a virtualenv-ek kezelését is rá lehet bízni. Innen lehet beszerezni

Térjünk a tárgyra

Ez a mai menet kicsit trükkösebb mint a múltkori, mivel mindent forrásból kell elkészíteni. A PyV8 wikin kívül hatalmas segítség volt a ez a bejegyzés, ez is, meg ez is. Ezúton is köszönet értük.

Előkészületek

Steril gépen szükség lehet ezekre a csomagokra:

$ sudo apt-get install build-essential subversion

Ha ez megvan akkor rakjuk fel a Python 3-as verzióját (3.4.2 volt a legutolsó amikor ezt a bejegyzést írtam). Ha kész a fordítás akkor készítsünk egy virtualenvet.

$ pyenv install 3.4.2
$ pyenv virtualenv 3.4.2 v8
$ pyenv shell v8

Hozzunk létre egy könyvtárat amibe a forrásokat pakolásszuk majd:

$ cd ~
$ mkdir v8
$ cd v8

Jöhetnek a források

$ svn checkout http://v8.googlecode.com/svn/trunk/ v8
$ svn checkout http://pyv8.googlecode.com/svn/trunk/ pyv8

Állítsuk be a V8_HOME környezeti változót, hogy a setup.py megtalálja majd a V8 forrását:

$ export V8_HOME=/home/slapec/v8/v8

A Boostra is szükség lesz, a nélkül nem fordul a PyV8. Jelenleg az 1.57.0-ás verzió a legújabb.

$ wget http://sourceforge.net/projects/boost/files/boost/1.57.0/boost_1_57_0.tar.gz
$ tar -xvzf boost_1_57_0.tar.gz

A boost_1_57_0.tar.gz-t törölhetjük is akár.

Boost fordítás

A Boosttal kezdünk. A 3.4.2-es verzió ellen kell lefordítani. Mivel a pyenv is minden alkalommal forrásból fordítja a feltelepített Python verziót, ezért a header fájlokat megtaláljuk majd a .pyenv könyvtárban.

$ cd boost_1_57_0
$ ./bootstrap.sh --with-python-version=3.4 
                 --with-python-root=/home/slapec/.pyenv/versions/3.4.2/ 
                 --with-python=/home/slapec/.pyenv/versions/3.4.2/bin/python

Az argumentumokat nem kell új sorba írni, csak az olvashatóság miatt írtam így őket. Fontos: a scriptnek abszolút elérési útvonalakat kell adni.

Ha kész a bootstrap akkor pedig:

$ ./b2 include="/home/slapec/.pyenv/versions/3.4.2/include/python3.4m"

És lehet menni ebédelni amíg ezen gondolkozik a számítógép.

A The Boost C++ Libraries were successfully built! üzenet után installáljuk a libraryt, frissítsük a cache-t és állítsuk be a BOOST_HOME környezeti változót hogy a boost könyvtárára mutasson:

$ sudo ./b2 install
$ sudo ldconfig
$ export BOOST_HOME=/home/slapec/v8/boost_1_57_0

V8 fordítás

A V8 fordítása kicsit trükkösebb. Egy patchet kéne alkalmazni a PyV8 setup.py fájljának, ami bekapcsolja a V8 RTTI és Exception támogatását. Ez a setup.py viszont nem Python 3 kompatibilis. Ez még a kisebb probléma.

A nagyobb probléma, hogy a V8 fordításához is szükség van a Pythonra, de ott is csak a 2-es verzió a megfelelő. Lassan 7 éve hogy megjelent az első Python 3 verzió, és még mindig ilyen gyakoriak a problémák.

Jobb ötlet híján úgy döntöttem, hogy a PyV8 setup scriptjét módosítom egy kicsit, hogy elvégezze a patchelést, de a V8 fordítását már nem, de kiírja a parancsot amivel a V8-at fordíthatom, amit aztán kézzel, Python 2 környezetben futtatok is.

Tehát a pyv8/setup.py fájlban két dolgot kell szerkeszteni:

Az 523. sorban ebből:

os.makedirs(build_path, 0755)

Ez lesz:

os.makedirs(build_path, 0o755)

Ugyanis Python 3-ban az octal literal is megváltozott.

Az 513. sorban ebből az egy sorból

exec_cmd(cmdline, "build v8 from SVN")

Ez a három lesz:

#exec_cmd(cmdline, "build v8 from SVN")
print('Now switch to a Python 2 environment and build V8 with the following command')
exit(cmdline)

(Természetesen az első print() elhagyható.)

Ezek után jöhet is a build.

$ python setup.py build

A script a végén kiírja a parancsot amivel a V8-at lefordíthatjuk és kilép.

Ha ez megvan, akkor váltsunk át egy Python 2-es környezetre (pyenv shell system ha az alapértelmezett interpreter ilyen, vagy egyszerűen nyissunk egy új shellt).

$ cd v8
$ make -j 8 disassembler=off vtunejit=off library=shared werror=no strictaliasing=on gdbjit=off debuggersupport=on extrachecks=off visibility=on regexp=native objectprint=off backtrace=on i18nsupport=off liveobjectlist=off snapshot=on verifyheap=off x64.release

(Természetesen mindenki a saját make parancsát használja)

Ameddig ez lefordul, addig megtárgyalhatjuk hogy nem-e gáz hogy a build scriptből félúton kiléptünk.

A buld osztályban a run() metódu a prepare_v8() függvényt hívja meg, itt látszódnak a V8 fordításának lépései. Feljebb a build_v8() függvényt írogattuk át kicsit, ez mellékesen az utolsó előtti lépés. A következő a generate_probes() lenne, ami nálam amúgy is meghal dtrace not found errorral, úgyhogy annak mindegy hogy lefut-e vagy sem (természetesen annyira nem mindegy, de ezzel a hibával egyelőre nem tudtam mit kezdeni).

Mostanra remélhetőleg elkészült a V8. Menjünk vissza a PyV8 könyvtárába, váltsunk vissza Python 3-ra (pyenv shell v8) és jöhet az install:

$ cd pyv8
$ python setup.py install

Ha szerencsénk van akkor Finished processing dependencies for PyV8==1.0-dev üzenet fogad. Már csak ki kell próbálni hogy működik-e a vadiúj librarynk :3

$ cd ~
$ python -m unittest PyV8
...
FAILED (failures=2)

Sajna 2 teszt elhasalt, dehát semmi se tökéletes, nem :) ?

Aftermath

Vannak megoldásra váró problémái a PyV8 projektnek, de én nagyon örülök neki, hogy dolgoznak rajta hogy Javascript kódot lehessen futtatni Python alól. Az a példányom, amit még a régi projektemhez fordítottam a mai napig jól működik, csak én már szeretnék Python 3-ra váltani, ezért voltam kénytelen újra elővenni ezt a problémát.

Mindenkinek kellemes fordítgatást : )

-slp

2015. január 1., csütörtök

Ég a pofám

Te jó Isten. Azért most rendesen ég a pofám: több mint 1 éve nem írtam egyetlen bejegyzést se. Az utóbbi napokban pedig pont azon agyaltam, hogy hogyan tudnék a jövőben több időt tölteni a kódok írásával, és kevesebbet a bejegyzés formázásával.
Mert úgy érzem, hogy nem azért nem írok semmit hónapokig évekig, mert semmit se csinálok a számítógép előtt, csak a macskás videókat bámulom, hanem mert borzalmasan körülményes igényesre megformázni a szöveget. Szerencsére most, hogy egyre többet használom a Markdown és reStructedText leírónyelveket, lehet -nagyon remélem- hogy a jövőben tényleg úgy írhatok majd bejegyzéseket, hogy nem kell állandóan elengednem a billentyűzetet, és akkor több kedvem lesz kis bejegyzéseket írni.

Addig is, BUÉK mindenkinek!

2013. december 26., csütörtök

Javascript futtatása Python kódból, PyV8 fordítása

Kellemes Ünnepeket mindenkinek!
Természetesen azért, mert megint nem írtam már hetek hónapok óta, még nem állt meg az élet. Mostanában web scraping-el foglalkozok, ami nagyon komplex egy feladat, persze csak ha az ember normálisan akarja megcsinálni.

Egy olyan problémába ütköztem, ami manapság egyre hétköznapibb, hogy a weboldal nagy részét Javascript kódból építik fel. Használhatnék amúgy erre egy valódi böngészőt is, ami futtatja a kapott kódot, és Python alól birizgálom, pl Selenium-al, de nekem csak a Javascript kódban tárolt adatokra van szükségem. Párperces keresgélés után nem találtam normális parsert a nyelvhez, így aztán úgy döntöttem, hogy felrakom a Google V8 engine-jét, a hozzá tartozó Python-os bindinggel, a PyV8-al, betápolom neki a kódot, és dolgozok a visszakapott értékekkel.

Szerencsére a kis Odroid-X2-n szépen -lassan- lefordul maga az engine, és a binding is. A dokumentációban nem nagyon van részletes leírás a fordítás menetéről, így ezt magamnak kellett kitalálnom. Szerencsére apt-ből mindent össze lehet szedni. Lássuk a parancsokat!
sudo apt-get install libboost-python-dev libboost-thread-dev libboost-system-dev
Ennyi az összes függőség a fordításhoz. Ha nincs svn kliens feltelepítve, akkor erre még szükség lehet:
sudo apt-get install subversion
Ezek után egyszerűen pip-el telepítjük
sudo pip install pyv8

Rövid példácska

Rendbe, ha már ilyen szépen feltelepült a lib, akkor azt is leírom hogy mire kellett.
Tehát, a feldolgozandó kódban, egyes HTML tag-ek onmouseover eventjén van az alábbihoz hasonló kód:
jQuery("#1337").html("Leet");
Azaz rámutatáskor ad 1337 id-ű tag tartalma legyen a Leet string. Ilyen egyszerű.

Nem akartam az egész jQuery-t betölteni a memóriába, ezért inkább írtam egy kis kamu függvényt, amivel a fenti sor futtatható. Így néz ki:
var jQuery = function (unused){
    html = function(data){
        return data
    }
    return this;
}
A jQuery függvény visszatér a this-el, ami úgy mellékesen a window. Maga a függvény nem csinál semmit se a paraméterével. A függvény törzsében definiálunk egy html nevű függvényt. Ez globális lesz, így a window.html-en keresztül is elérhető.
Tehát amikor a jQuery visszatér a this-el, ott lesz már a window.html is, így működni fog method chaining (igazából nem fog működni, ahhoz megint csak this-el kellett volna visszatérni, de nekem most így is megfelel a kód).

Rakjuk össze az egészet és próbáljuk ki:

import PyV8
ctx = PyV8.JSContext()
ctx.enter()
ctx.eval('var jQuery=function(unused){html=function(data){return data};return this;}')
ctx.eval("jQuery('#1337').html('Leet')")
#>>>'Leet'

Először létrehozunk egy új contextet és bele is lépünk. Ez után betöltjük a függvényt, aztán kipróbáljuk hogy visszakapjuk-e a html-nek adott stringet, természetesen a Python interpreterben.
Az utolsó sorban látszik hogy ez megtörtént, úgyhogy nagy az öröm!

-slp

2013. július 14., vasárnap

zvbi-0.2.34 fordítása Ubuntu 13.04 alatt

(Ez a bejegyzés -már megint- nem Pythonnal foglalkozik)
A zvbi egy olyan linuxos lib amiben mindenféle tool található amivel a VBI alatt kiküldött adatokat lehet dekódolni. Szerintem igazából minden csomagkezelőben megtalálható, viszont a forráskódjában van egy test mappa, és az abban található eszközöket nem rakja fel az apt-get. Az egyik ilyen program egy segédeszköz, ami megjeleníti a VBI device kimenetét képként, így nagyon sokat segít a debuggolásban. Mivel leírást nem találtam róla hogy hogyan kell lefordítani a zvbi-t forrásból ezért most leírom ide.

Patchelés

Okulva a rengeteg rossz tapasztalatból úgy álltam neki a fordításnak, hogy ez se fog elsőre menni. Igazam lett. A gcc meghal majd a következő errorral:
date.c:96:3: error: incompatible type for argument 2 of ‘clock_settime’

Szerencsére sikerült kézzel kijavítanom. Így utólag visszagondolva lehet hogy csak a kódot tettem fordíthatóvá de a program maga szar maradt, de ameddig én próbálgattam semmi se dobott segfaultot. Csináltam hozzá gyönyörűséges patchet, amit innen le lehet tölteni.

Patcheljük meg a kódot. Aki nem csinált még ilyet (pl én) annak:

Töltsük le a patchet a forrás gyökerébe, aztán alkalmazzuk
wget https://dl.dropboxusercontent.com/u/58194064/zvbi_date_clock_settime.patch
patch -p1 < zvbi-ftbfs.patch

Függőségek

Épp ebben a pillanatban raktam fel egy teljesen szűz Ubuntut, és ezen végzem a fordítást. A lib maga lefordul minden extra nélkül, de az osc-hez szükség van a libx11-dev-re
sudo apt-get install libx11-dev

Fordítás & telepítés

Innentől már nem is annyira izgalmas:
./configure
make
sudo make install

osc

A make install nem rakja jól látható helyre az osc programot (vagy csak én nem találom). A zvbi forrásának könyvtárában a test mappában van mindenféle csoda, ebből az osc kell. A README fájlban van részletes leírás az összes toolhoz. Az osc röviden:

./osc -d /dev/vbi0

Egy kép

Mindenki szereti a képeket

Igen, a terminálban 0.2.33 van. Amikor elkezdtem írni a bejegyzért akkor a 0.2.33-at szedtem le, de eszembe jutott hogy már van 0.2.34 is. Vicces mód mivel az se fordult le egyből, így nem kellett a 0.2.33-hoz íródott bejegyzésemet kidobnom, csak át kellett írnom a verziószámot a szövegemben és a hibaüzenetet. Azért ez igazából kicsit égő, mármint hogy az új verzió se fordul hiba nélkül.

2013. július 9., kedd

Hol az új post?

Meglehesőgen régóta nem volt már megint frissítés, pedig csak a meglevő szövegeimet kéne áthúznom ide. Ami azt illeti, most kivételesen jó alibim van, mert hát vizsgáztam, dolgozok, demopartyn jártam és közben masszívan 1 projektre fókuszálok aminek eléggé gyér a szakirodalma pláne magyarul. Ezért is fogok majd beszámolni róla pontosan, illetve forráskóddal is jövök.
Már nagyon szeretnék beszámolni róla, de a vicc hogy több hónapos munka van már benne, és még mindig nem működik.
A lényeg hogy élek, de egy nagyon fontos probléma megoldásán dolgozok, ezért nincs most frissítés.