From: Vincent Le Gallic Date: Fri, 14 Feb 2014 19:31:06 +0000 (+0100) Subject: début de modularisation X-Git-Url: http://gitweb.pimeys.fr/?a=commitdiff_plain;h=dabb5eeb6a9c41bd3c415f2a73278d2fcd58dd80;p=today.git début de modularisation * channels youtube dans un fichier à part (gestion et parsing des channels dans youtube.py) * récupération des chaînes youtube en parallèle (module gather.py, ultimement, tous les get_last devaient être dedans) * on arrête de parser le site foireux du visiteur mais on utilise une chaîne youtube --- diff --git a/.gitignore b/.gitignore index ecab017..87aeb2b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,4 @@ timers.txt saints.txt .lasttime .something - -lasts_sync +youtube_channels diff --git a/gather.py b/gather.py new file mode 100755 index 0000000..6a952fd --- /dev/null +++ b/gather.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- + +""" Module chargé de récupérer les nouvelles choses là où il faut et de fournir + le dico des derniers numéros """ + +import threading +import traceback +import inspect +import pprint +#: Gestion des chaînes youtube +import youtube + + +def generate_errmsg(id): + errmsg = "Erreur à la récupération de %s :\n" % id + errmsg += traceback.format_exc() + # On dumpe le contenu local de la mémoire au moment de l'exception + fobj = inspect.trace()[-1][0] + # On fait un peu de ménage + d = {k:v for (k,v) in fobj.f_locals.iteritems() if not k.startswith("_")} + # On évite d'envoyer truckLoadsOfShit + d = {k: (v if len(str(v)) < 800 + else str(v)[:400] + "*" * 40 + "TRUNCATED OBJECT" + "*" * 40 + str(v)[-400:]) + for (k,v) in d.iteritems()} + errmsg += "\nContexte : %s\n\n" % (pprint.pformat(d)) + return errmsg + +def get_storer(key_id, parser): + global results + """ Encapsuleur. Renvoie une fonction qui, une fois appelée, stockera le résultat + de ``parser()`` dans ``results[key_id]``, ``results`` étant global. """ + def local_storer(): + # On prend soin de ne pas crasher + try: + results[key_id] = parser() + except Exception as e: + # et d'afficher du debugging en cas de problème + print generate_errmsg(key_id) + return local_storer + +def gather(): + global results + functions = {} + functions.update(youtube.functions) + + # On convertit les fonctions en storers + functions = {id : get_storer(id, parser) for (id, parser) in functions.items()} + + results = {} + threads = [] + for (id, f) in functions.items(): + newthread = threading.Thread(name="retrieving-%s" % id, target = f) + newthread.start() + threads.append(newthread) + + # On attend que tout le monde ait renvoyé son résultat + [t.join() for t in threads] + + return results + +if __name__ == "__main__": + print gather() diff --git a/serverconfig.py b/serverconfig.py index 86aff45..518ad86 100644 --- a/serverconfig.py +++ b/serverconfig.py @@ -9,3 +9,6 @@ store_seen_file = "lasts_sync" #: Afficher du garbage pour débuguer DEBUG = False + +#: La liste des chaînes Youtube à surveiller +youtube_channels_file = "youtube_channels" diff --git a/today_server.py b/today_server.py index 37615d5..ef73747 100755 --- a/today_server.py +++ b/today_server.py @@ -11,22 +11,21 @@ et répondre à un check. """ import re -import BeautifulSoup from lxml import etree import os import sys import urllib import json -import traceback -import inspect -import pprint os.chdir('/home/vincent/scripts/today/') sys.path.append("/home/vincent/scripts/dtc/") import dtc -# Config serveur +#: Config serveur import serverconfig +#: Récupération de toutes les nouveautés +import gather + def last_dtc(): """Vérifie si il y a des quotes DTC non lues""" return dtc.last_inserted() @@ -44,98 +43,6 @@ def last_xantah(): ids = [int(i) for i in ids] return max(ids) -def last_visiteur(): - p = urllib.urlopen("http://www.levisiteurdufutur.com/episodes.html") - t = p.read() - # On parse - soup = BeautifulSoup.BeautifulSoup(t) - # On récupère les différentes saisons - saisons = soup.findAll("div", attrs={"id" : "episodes_list"}) - nsaisons = len(saisons) - # La saison en cours est la première dans la liste - episodes = saisons[0].findAll("div", attrs={"class" : "thumbCaption"}) - nepisodes = len(episodes) - return nsaisons * 100 + nepisodes - -def parse_youtube(username): - """Récupère les vidéos d'une chaîne Youtube""" - link = "https://gdata.youtube.com/feeds/api/users/%s/uploads?start-index=1&max-results=50" % (username,) - entries = [] - while link: - p = urllib.urlopen(link) - t = p.read() - x = etree.fromstring(t) - # lxml ne supporte pas les namespaces vides dans les requêtes XPath - ns = x.nsmap - ns["default"] = ns[None] - ns.pop(None) - # Il y a potentiellement une suite - nextlinks = x.xpath("//default:link[@rel='next']", namespaces=ns) - if nextlinks: - link = nextlinks[0].attrib["href"] - else: - link = False - localentries = x.xpath("//default:entry", namespaces=ns) - entries.extend(localentries) - titles = [e.xpath(".//default:title", namespaces=ns)[0].text for e in entries] - return titles - -def get_season_episode(title): - """Récupère les numéros de la saison et de l'épisode. Crash si ne trouve pas.""" - ep = int(re.findall("ep([0-9]*)", title)[0]) - saison = int(re.findall("s([0-9]*)", title)[0]) - return saison, ep - -def last_noob_warpzone(): - global last_nw - # GRUIK - if "last_nw" in globals().keys(): - return last_nw - titles = parse_youtube("Funglisoft") - noobs = [t.lower().strip() for t in titles if t.lower().strip().startswith("noob")] - warpzones = [t.lower().strip() for t in titles if t.lower().strip().startswith("warpzone project")] - lasts = [] - for serie in [noobs, warpzones]: - # Les titres sont dans l'ordre antichronologique, on s'arrête donc au premier qu'on comprend - for titre in serie: - if "noob le film" in titre or "making of" in titre or "noob versus rct" == titre or "extraits ost" in titre: - continue - try: - if DEBUG: - print titre - saison, ep = get_season_episode(titre) - except (ValueError, IndexError) as e: - print "%s sur un season_episode warpzone : %s\n" % (e, titre) - continue - lasts.append([saison, ep]) - del saison, ep - break - last_noob = lasts[0][0]*100 + lasts[0][1] - last_warp = lasts[1][0]*100 + lasts[1][1] - last_nw = [last_noob, last_warp] - return last_nw - -def last_noob(): - return last_noob_warpzone()[0] -def last_warpzone(): - return last_noob_warpzone()[1] - -def last_hugo(): - titles = parse_youtube("HugoToutSeul") - return len(titles) - -def last_norman(): - titles = parse_youtube("NormanFaitDesVideos") - return len(titles) - -def last_cyprien(): - titles = parse_youtube("MonsieurDream") - return len(titles) - -def last_grenier(): - titles = parse_youtube("joueurdugrenier") - return len(titles) - def last_jl8(): rss = urllib.urlopen("http://limbero.org/jl8/rss/") t = rss.read() @@ -162,13 +69,6 @@ FETCHS = { "xkcd" : last_xkcd, "dtc" : last_dtc, "xantah" : last_xantah, - "visiteur" : last_visiteur, - "noob" : last_noob, - "warpzone" : last_warpzone, - "hugo" : last_hugo, - "norman" : last_norman, - "cyprien" : last_cyprien, - "grenier" : last_grenier, "dc" : last_jl8, } @@ -179,18 +79,8 @@ def fetch_all(): try: news[k] = f() except Exception as e: - errmsg = "Erreur à la récupération de %s :\n" % k - errmsg += traceback.format_exc() - # On dumpe le contenu local de la mémoire au moment de l'exception - fobj = inspect.trace()[-1][0] - # On fait un peu de ménage - d = {k:v for (k,v) in fobj.f_locals.iteritems() if not k.startswith("_")} - # On évite d'envoyer truckLoadsOfShit - d = {k: (v if len(str(v)) < 800 - else str(v)[:400] + "*" * 40 + "TRUNCATED OBJECT" + "*" * 40 + str(v)[-400:]) - for (k,v) in d.iteritems()} - errmsg += "\nContexte : %s\n\n" % (pprint.pformat(d)) - print errmsg + raise + news.update(gather.gather()) return news def sync(): diff --git a/youtube.py b/youtube.py new file mode 100755 index 0000000..1f30801 --- /dev/null +++ b/youtube.py @@ -0,0 +1,60 @@ +#!/usr/bin/python +# -*- encoding: utf-8 -*- + +""" Gestion des chaînes Youtube """ + +import re +from lxml import etree +import urllib + +# Config serveur +import serverconfig + +def parse_youtube(username, regexp=None, length=False): + """Récupère les vidéos d'une chaîne Youtube""" + link = "https://gdata.youtube.com/feeds/api/users/%s/uploads?start-index=1&max-results=50" % (username,) + entries = [] + while link: + p = urllib.urlopen(link) + t = p.read() + x = etree.fromstring(t) + # lxml ne supporte pas les namespaces vides dans les requêtes XPath + ns = x.nsmap + ns["default"] = ns[None] + ns.pop(None) + # Il y a potentiellement une suite + nextlinks = x.xpath("//default:link[@rel='next']", namespaces=ns) + if nextlinks: + link = nextlinks[0].attrib["href"] + else: + link = False + localentries = x.xpath("//default:entry", namespaces=ns) + entries.extend(localentries) + titles = [e.xpath(".//default:title", namespaces=ns)[0].text for e in entries] + if not regexp is None: # On ne garde que les titres qui matchent la regexp + titles = [t for t in titles if re.match(regexp, t)] + if length: # On n'est intéressés que par le nombre + titles = len(titles) + return titles + +def load_channels(): + """Récupère les chaînes à surveiller à partir du fichier de conf.""" + with open(serverconfig.youtube_channels_file) as f: + channels = [l.strip("\n") for l in f.readlines()] + channels = [l for l in channels if not (l.startswith("#") or l.strip() == "")] + channels = [l.split("\t") for l in channels] + # Il peut y avoir plusieurs tabulations de suite, donc on enlève les chaînes vides + channels = [[i for i in l if not i == ''] for l in channels] + # [id, username, regexp] + # channels = [{"id" : l[0], "username" : l[1], "regexp" : l[2]} for l in channels] + return channels + + +def get_parser(username, regexp=None): + """ Renvoie un parseur de chaîne youtube prêt à être appelé """ + def local_parser(): + return parse_youtube(username, regexp, length=True) + return local_parser + +channels = load_channels() +functions = {id : get_parser(username, regexp) for (id, username, regexp) in channels}