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()


Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.