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 = 'http://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('http://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()Tags: Python Weblogs