El Blog de Trespams

Blog personal sobre tecnologia, gestió de projectes i coses que se me passen pel cap

De la web al model

L'scrapping

Ahir ja vàrem veure quin era el model de dades i el bé que va sorl per a manipular imatges, així com la utilització de la llibreria requests ens quedava veure una altra part important: com agafar el contingut de la web, parsejar-lo i obtenir-ne la informació que necessitam.

Per fer això hi ha diverses utilitats, algunes molt especialitzades com scrappy, i amb més solera és BeautifulSoup. Aquesta llibreria té la qualitat de ser molt permisiva amb l'HTML i hi ha poques planes que no pugui tractar d'una manera o altra.

La plana de Meneame té las notícies de portada dins un div anomenat news-summari, així que el primer que farem serà carregar la plana dins una instància de BeautifulSoup i cercar aquestes notícies.

page = requests.get('http://www.meneame.net')
if page.status_code != 200:
print "Ups! pareix que hi ha un petit problema"
return page.status_code

soup = BeautifulSoup(page.content)
noticies = soup.findAll('div', 'news-summary')

amb això BS ens haurà donat tots els divs que tenen la classes 'news-summary' amb la qual cosa ja és sols cosa d'aplicar un tractament semblant per a obtenir la informació de cada notícia

for noticia in noticies:
titular = noticia.find('h1').text
texto = noticia.find('p').text
img = noticia.find('img', 'thumbnail')
if img:
src = img['src']
match_obj = compile_obj.search(src)
id = match_obj.group('id')
try:
NoticiasPortada.objects.get(identificador=id)
except NoticiasPortada.DoesNotExist:
print
u"Tractant la noticia: %s" % id
noticia = NoticiasPortada(identificador=id, texto=texto, titular=titular,
thumbnail=download_image(src, 'thumb-%s.jpg' % id))
noticia.save()

El mètode find ens permet a accedir al primer tag que compleix la condició i text ens en dona el contingut. Si com a segon paràmetre hi possam una classe ens retornarà el primer element d'aquell tipus que tengui la classe que li hem donat.

El problema ve quan volem identificar d'alguna manera les notícies. Volem guardar sols les que tenen una imatge. Podem veure que Meneame genera el thumbnail de la notícia amb l'identificador de la mateixa, així que podem fer us d'una expressió regular:

rawstr = r"""(?P\d+).jpg$"""
compile_obj = re.compile(rawstr)

així

src = img['src']
match_obj = compile_obj.search(src)
id = match_obj.group('id')

ens donarà l'identificar de la notícia a partir de la informació de la url de la imatge. Hi ha una utilitat fantàstica per a la depuració i testeig d'expressions regulars anomenada Kodos, no us la podeu perdre.

El toc final

En una aplicació com aquesta la CPU està molt de temps sense fer res, esperant que li arribi la informació. És un bon candidat per a que l'aplicació faci ús dels Threads o del multiprocés. Una de le maneres més senzilles de fer-ho és fent servir la llibreria Queue, d'aquesta manera sols hem de definir quants Threads farem servir en el processament i que aquests consumeixin el contingut (cada notícia) de la cua.

Així el codi final quedaría si fa no fa:

import re
import cStringIO
import requests
from Queue import Queue
from threading import Thread

from BeautifulSoup import BeautifulSoup
from django.core.management.base import BaseCommand
from django.core.files.uploadedfile import SimpleUploadedFile
from t1.models import NoticiasPortada

rawstr = r"""(?P\d+).jpg$"""
compile_obj = re.compile(rawstr)


class Command(BaseCommand):
"""Divertimento. Permet posar les notícies amb foto de meneame dins una BD. """

def handle(self, *args, **options):
page = requests.get('http://www.meneame.net')
if page.status_code != 200:
print
"Ups! Pareix que tenim un problema"
return page.status_code

self.q = Queue()
for i in range(5):
t = Thread(target=self._importar_portada)
t.daemon = True
t.start()
soup = BeautifulSoup(page.content)
noticies = soup.findAll('div', 'news-summary')
for noticia in noticies:
self.q.put(noticia)
self.q.join()

def download_image(self, img_url, filename):
r = requests.get(img_url)
if r.status_code == 200:
return SimpleUploadedFile(content=r.content, name=filename, content_type=r.headers.get('content-type'))
else:
return None

def _importar_portada(self):
while True:
noticia = self.q.get()
titular = noticia.find('h1').text
texto = noticia.find('p').text
img = noticia.find('img', 'thumbnail')
if img:
src = img['src']
match_obj = compile_obj.search(src)
id = match_obj.group('id')
try:
NoticiasPortada.objects.get(identificador=id)
except NoticiasPortada.DoesNotExist:
print
u"Processant la notícia: %s" % id
noticia = NoticiasPortada(identificador=id, texto=texto, titular=titular,
thumbnail=self.download_image(src, 'thumb-%s.jpg' % id))

noticia.save()

self.q.task_done()

I això és tot, com sempre amb Python l'explicació sol ser molt més llarga que el codi a executar, fins i tot amb els fils.

Esperant que us hagi agradat el divertimento.

blog comments powered by Disqus