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 a 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 oserons 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 se 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()
Cet article, publié dans Application, est tagué , . Ajoutez ce permalien à vos favoris.

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s