Spam du web

Nuit Blanche event in Toronto, Canada
Image via Wikipedia

Tout le monde sait ce que sont les spam ou pourriels qui à l’instar des prospectus inondent les boîtes aux lettres électroniques. Cette nuisance qui parasite le trafic internet pousse à la consommation dans les cas les plus inoffensifs ou bien va jusqu’à la tentative d’arnaque.

Il existe un autre type de nuisance sur le web. Il s’agit de l’occupation même du web à travers ses pages et ses réseaux sociaux. Cette présence à sans doute plus d’impact que le spam classique. En effet occuper l’espace numérique crée un buzz qui touche de nombreuses personnes… nous oseront même dire touche celles qui comptent car celle-ci peuvent changer le cours de l’histoire. Un président américain l’a d’ailleurs bien compris tandis que d’autres ne l’ont pas compris et ne sont plus au pouvoir.

Internet est aujourd’hui le média qu’il faut investir comme outils de propagande. A cette fin des sociétés se proposent, monnayant finance, de créer du buzz ou de lifter une e-réputation. Nous noterons toutefois que si l’objectif de manipuler l’opinion est le même que celui d’une publicité sur papier ou à la télévision, la forme diffère grandement. Les meilleurs buzz sont ceux qui ne ressemblent pas à de la publicité. En effet l’opinion du web est plus sensible à lui même qu’à des messages extérieurs: une forme de bouche à oreille numérique.

Comme rien n’est plus ressemblant à l’original que l’original lui-même, créer un buzz nécessite beaucoup de moyens. Il faut investir les forum, créer des sites web de propagande, simuler de nombreux fans. Pourtant nous sommes bien dans un univers créé par les ordinateurs, il doit donc être possible d’automatiser le buzz. L’idée est d’automatiser la création de contenu sur de multiple site. Chaque instance devra autant faire se peut être différente (pas de copier/coller) de l’autre afin de simuler des rédacteurs humains différents. L’automatisation doit permettre non seulement de simuler le nombre mais aussi de créer l’historique car de nombreuses références qui n’ont pas d’historique sont moins crédibles qu’une référence ayant pignon sur le web depuis des lustres. Cela est réalisable assez facilement puisque les hébergeurs de blog tels que blogger.com ou wordpress.com exposent une API pour publier des billet de manière programmatique. La modifications des billets sans en altérer le sens pour qu’ils ressemblent à ceux qu’aurait pu faire un humain, peut être obtenue en remplaçant les mots par des synonymes piochés par exemples sur le site de l’université de Caen. Le plus difficile est la créations des comptes initiaux (compte email google@par exemple) qui signent l’acte de naissance de la vraie fausse identité. En effet les fournisseurs utilisent des filtres pour repousser la création de compte par des robots.

Le code suivant montre un POC. Quand à savoir si la méthode est efficace pour manipuler le référencement des moteurs de recherche, le lecteur ce lancera lui-même dans l’expérience.

#!/usr/bin/python
# -*- encoding: UTF-8 -*-
'''
Created on 24 avr. 2011

@author: thierry
'''

import os.path
import random
from optparse import OptionParser

from gdata import service
import gdata
import atom
import urllib
from HTMLParser import HTMLParser
import random

import logging


LEVELS = {'debug': logging.DEBUG,
          'info': logging.INFO,
          'warning': logging.WARNING,
          'error': logging.ERROR,
          'critical': logging.CRITICAL}

class TextCloner():
    '''
    This class is responsible for cloning a text e.g. rewrite the text modifying
    some words to simulate human copying.    
    '''
    def clone(self, original):
        pass

class SynonymeCloner(TextCloner):
    
    
    def insert_synonyme(self, before, synonyme, after):
        url = '//dictionnaire.tv5.org/dictionnaires.asp?%s'
        params = urllib.urlencode({'Action':'1', 'mot': synonyme.split(' ')[-1:][0].encode("utf-8")})
        f = urllib.urlopen(url % params)
        response = f.read().decode('iso-8859-1')
        f.close()
        voyelle = ['a','e','i','o','u','y','é']
        if synonyme[0] in voyelle 
            or ( synonyme[0] == 'h' and synonyme[1] in voyelle) :
            art = "l'" 
        elif response.find("masculin") > -1 :
            art = 'le '
        else:
            art = 'la '
        if before.endswith(". ") or len(before.strip()) == 0:
            art = art.capitalize()
        clone = before + art + synonyme + after            
        return clone
        
    def clone(self, original):
        clone = original
        start = 0;
        while start > -1:
            START = "<%"
            start = clone.find(START)
            if start > -1:
                before, start_tag, after = clone.partition(START)
                END = "%>"
                word, end_tag, after = after.partition(END)
                synonyme = self.get_synonyme(word)
                logging.info("choose synonyme %s" % synonyme)
                clone = self.insert_synonyme(before, synonyme, after)                                                
                
        return clone
    
    
    
    def get_synonyme(self, word):            
        logging.info("looking for synonyme for %s" % word)        
        f = urllib.urlopen('//www.crisco.unicaen.fr/des/synonymes/%s' % urllib.quote(word.encode("utf-8")))
        reponse = f.read().decode('utf-8')
        f.close()
        
        class MyParser (HTMLParser):
            result_found = False
            tag = None
            cdep = None
            cp_found = []
            grab_result = False

            def __init__(self):
                HTMLParser.__init__(self)
                self.tag = None
                self.result_found = False
                self.cp_found = []

            def handle_starttag(self, tag, attrs):
                if tag == "div":
                    for a, v in  attrs:
                        if a == 'id' and  v == 'synonymes':
                            self.result_found = True
                            self.tag = tag
                            self.tag_count = 1
                            return
            
                if self.result_found and tag == "div":
                    self.tag_count += 1
            
                if self.result_found and tag == "a":
                    self.grab_result = True
  
            def handle_endtag(self, tag):
                if self.result_found:
                    if tag == self.tag:
                        self.tag_count -=1
                        if self.tag_count == 0:
                            self.result_found = False
                    if tag == "a":
                        self.grab_result = False
 
            def handle_data(self, data):
                if self.result_found and self.grab_result:
                    self.cp_found.append(data)

        
        p = MyParser()
        p.feed(reponse)
        p.close()
        return random.choice(p.cp_found + [word])

    
class Blog():
    '''
    This class can post an entry to a blog
    '''
    def post(self, title, content):
        pass
    

class Blogger(Blog):
    '''
    This blog poster implement blogger API.
    '''
    def __init__(self, login, password, blog_url):
        self.blogger_service = service.GDataService(login, password)
        self.blogger_service.source = 'publisher'
        self.blogger_service.service = 'blogger'
        self.blogger_service.account_type = 'GOOGLE'
        self.blogger_service.server = 'www.blogger.com'
        self.blogger_service.ProgrammaticLogin()
        
        query = service.Query()
        query.feed = '/feeds/default/blogs'
        feed = self.blogger_service.Get(query.ToUri())

        print feed.title.text
        for entry in feed.entry:
            logging.info("t" + entry.title.text)
            for link in entry.link:
                if link.href.startswith(blog_url):
                    self.blog_id = entry.GetSelfLink().href.split("/")[-1]

                    

    
    def post(self, title, content):
        
        entry = gdata.GDataEntry()
        entry.title = atom.Title('xhtml', title)
        entry.content = atom.Content(content_type='html', text=content)
        return self.blogger_service.Post(entry, '/feeds/%s/posts/default' % self.blog_id)



class AccountParser():
    '''
    This class can read the account file
    '''
    def __init__(self, filename):
        self.filename = filename
    
    def account_iter(self):
        logging.info("parsing %s account file" % self.filename)
        with open(self.filename) as f:
            for line in f:
                splitted = line.split(",")
                url = splitted[0]
                login = splitted[1]
                password = splitted[2]
                if url.find("blogspot.com") > -1:  
                    logging.info("found blospot account %s" % url)                        
                    yield Blogger(login, password, url)
                else:
                    logging.warn("skip unsupported blog %s" % url)
    




def run():
    usage = "%prog -a <account> -p <post> [-l <log level>]"
    str_version = "%prog 0.1"
    parser = OptionParser(usage=usage, version=str_version)
    parser.add_option("-a", "--account", action="store", type="string", dest="account", help="account file")
    parser.add_option("-p", "--post", action="store", type="string", dest="post", help="post file")
    parser.add_option("-l", "--log", action="store", type="string", dest="level_name", help="log level")
    parser.add_option("-t", "--title", action="store", type="string", dest="title", help="post title")    
    options, args = parser.parse_args()

    level = LEVELS.get(options.level_name, logging.NOTSET)
    logging.basicConfig(level=level)
    
    with open(options.post) as post_file:
        post = post_file.read()       
        for blog in AccountParser(options.account).account_iter(): 
            if options.title:
                title= options.title
            else:
                title = ""       
            blog.post(title, SynonymeCloner().clone(post))

if __name__ == '__main__':    
    run()


Un monde en ligne

L’orientation du monde numérique est d’avoir à sa disposition toute sa vie numérique en tout lieu. Le concept de mobilité est devenue la caractéristique principale de l’informatique. Nous y sommes tellement habitué qu’il nous est pas facile d’imaginer un monde où il faudrait rentrer chez soi consulter son calepin pour y retrouver les coordonnées d’une personne ou bien aller à la bibliothèque pour se renseigner sur la biographie de l’interprète d’une chanson qu’on entend à la radio. De nos jours tout se trouve sur internet.

La mise en ligne d’information touche aussi la sphère privée. Non seulement des données personnelles peuvent se retrouver sur la toile de manière plus ou moins volontaire au grès des informations que chacun renseigne sur les sites web mais aussi des données que l’on met volontairement sur l’internet. Il peut s’agir dans ce cas d’information que l’on souhaite partager avec un groupe restreint comme par exemple des photos de vacances que l’on souhaite partager avec sa famille. Il existe pour cela des services comme flickr qui propose un espace limité aux photos et vidéo auquel on préfèrera l’excellent vox qui propose un hébergement complet : blog, photo, vidéo, audio sans limite de taille totale.

L’exemple de l’album photo en ligne n’est pas le seul à illustrer la virtualisation de notre monde. Les agendas en ligne avec google en est un autre et il est loin de fermer la liste: les services de messagerie instantanée, les webradio, les services bancaires en ligne…