Apprende Python – Partie 5 – Création d’un extenseur d’url

Publié le 12 mars 2014 par Psyphi

Logo de Twitonomy

Cher lecteur, tu sais probablement déjà ce qu’est une URL réduite. Nous les utilisons tous les jours pour éviter de s’échanger des adresses complètes qui sont bien souvent trop longues pour tenir dans les tweets. Il existe plusieurs services qui permettent de réduire les urls, les plus connus sont: bit.ly et goo.gl

Dans le cadre de son doctorat, un ami étudie l’utilisation de Twitter faite par les journalistes. Pour cela, il génère des rapports d’utilisateurs de Twitter en utilisant le service Twitonomy. L’application Twitonomy permet de générer des fichiers Excel avec plusieurs Tweets d’une même personne. Bien sûr, les tweets contiennent des URLs qui sont réduites. Mais mon ami a besoin de remplacer les urls réduites par les urls réelles. Sachant qu’il a plus de 80 fichiers Excel à traiter et que chaque fichier peut contenir jusqu’à 5000 tweets, une approche manuelle est exclue. C’est alors que j’interviens.

Après une recherche rapide sur Google DuckDuckGo je trouve un service en ligne libre et gratuit qui permet, à partir d’une URL réduite, de retrouver l’URL originale. Ce dernier se nomme expandurl et met à disposition une API. Il suffit de passer l’url réduite comme paramètre à l’url du service. J’espère que vous arrivez toujours à me suivre :). Concrètement ça donne ça:

http://expandurl.me/expand?url=http://bit.ly/1lBjO6q

Le service répond ensuite ainsi:

{
"status": "OK",
"end_url": "http://blog.bores.fr/",
"redirects": 1,
"urls": [
	"http://bit.ly/1lBjO6q",
	"http://blog.bores.fr/"
],
"start_url": "http://bit.ly/1lBjO6q"
}

Il faut maintenant automatiser le tout. Résumons les librairies qui me sont nécessaires:

  • Premièrement j’ai besoin de lire et d’écrire des fichiers Excel et comme je travaille sous Gnu/Linux je peux déjà oublier PyWin32 et l’utilisation de la couche COM. J’opte donc pour Python-Excel. Par contre, ce dernier me limite à l’utilisation de Python 2.x. (exit les nouveautés de Python3)
  • Deuxièmement, j’ai besoin de me connecter via http au service expandurl. Pour cela j’utilise le module urlllib2 qui est disponible par défaut avec Python 2.x
  • Enfin, il me faut un outil pour analyser rapidement et proprement la réponse. Le AST (Abstract Syntax Tree) est parfait pour ça!

Pour compléter, je vais utiliser le module os, afin de pouvoir lire plusieurs fichiers E dans un même dossier et le module pickle pour enregistrer les urls déjà connues dans un fichier. Ce dernier point est important, il permet de créer un dictionnaire local de paires {url_réduite:url_réelle} et de l’enregistrer dans un fichier à la fermeture du programme. Ceci permet d’accélérer le traitement si l’on doit relancer le programme sur d’autres fichiers qui contiennent des urls réduites qui étaient déjà présentes dans les fichiers Excel précédents.

Il faut lier les briques ensemble et voilà ce que ça donne. Eh oui bien souvent, la programmation s’apparente au Lego!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
 
"""
url_expander.py
 
This program enables user to parse excel files generated from twitony
and to convert shortened urls in expended urls.
It doesn't modify the input files but create new files instead.
 
Update the User configuration below for your usage.
"""
 
from xlrd import open_workbook
from xlutils.copy import copy
import urllib2
import ast
import pickle
import signal
import sys
import os
 
# User configuration, should be updated
INPUT_EXCEL_DIR = './excels/'
OUTPUT_EXCEL_DIR = './expanded_urls_excels/'
 
# Program Configuration
VERSION = 'v2.0'
SOCKET_TIMEOUT = 30
DICT_PATH = 'urls.pkl'
 
# Functions
def load_already_known_urls():
    """
Load urls that were saved during previous passes.
 
Return:
a dict of urls with key is the shortened url and value
the expanded url
"""
    urls = dict()
    print 'Try to load list of already known URL from '+DICT_PATH+'.'
    try:
        urls_file = open(DICT_PATH, 'rb')
        urls = pickle.load(urls_file)
        urls_file.close()
        print 'Found '+str(len(urls.keys()))+' urls.'
    except IOError, error:
        print error
        print 'The '+DICT_PATH+' file doen\'t exists?'
        print 'It is normal the first start.'
    return urls
 
def save_url_list(urls):
    """
Save the found urls in a pickle file
"""
    print 'Save urls in '+DICT_PATH+'.'
    try:
        urls_file = open(DICT_PATH, 'wb')
        pickle.dump(urls, urls_file)
        urls_file.close()
    except IOError, error:
        print error
        print 'Couldn\'t save url list for future usage!'
 
def signal_handler():
    """
Catch the + keyboard keys for saving results before quitting
"""
    print 'You pressed Ctrl+C!'
    save_url_list(urls_dict)
    sys.exit(0)
 
# -------------------------
# ----- Program Start -----
# -------------------------
print 'Welcome in url_expander.py '+VERSION
print 'Coded by Thomas Bores'
 
# Load already known urls
urls_dict = load_already_known_urls()
 
# Catch CTRL+C
signal.signal(signal.SIGINT, signal_handler)
 
# Check if INPUT_EXCEL_DIR exists
if os.path.exists(INPUT_EXCEL_DIR) == False:
    os.makedirs(INPUT_EXCEL_DIR)
# Check if OUTPUT_EXCEL_DIR exists
if os.path.exists(OUTPUT_EXCEL_DIR) == False:
    os.makedirs(OUTPUT_EXCEL_DIR)
 
# Open all exels files in INPUT_EXCEL_DIR and parse them
for f_xls in os.listdir(INPUT_EXCEL_DIR):
    if f_xls.endswith('.xls'):
        try:
            rb = open_workbook(INPUT_EXCEL_DIR+f_xls,
                               formatting_info=True)
            r_sheet = rb.sheet_by_index(0)
 
            wb = copy(rb)
            ws = wb.get_sheet(0)
 
            for c_row in range(r_sheet.nrows):
                content = r_sheet.cell(c_row, 3).value
                if 'http://' in content:
                    try:
                        print '--------------------'
                        print 'Find short url at row '+str(c_row)
                        pos1 = content.find('http://')
                        sub1 = content[pos1:]
                        pos2 = len(sub1)
 
                        if ' ' in sub1:
                            pos2 = sub1.find(' ')
 
                        url = sub1[:pos2]
                        print 'short url = \"'+url+'\"'
 
                        # Get real url
                        real_url = url
                        if url in urls_dict:
                            # Check if we already have it
                            print 'URL already in dict :)'
                            real_url = urls_dict[url]
                        else:
                            # Otherwise expand it with online expandurl service
                            print """URL is unknown :(,
we connect to expandurl service"""
                            try:
                                res = urllib2.urlopen(
                                            'http://expandurl.me/expand?url='+\
                                            url,
                                            timeout=SOCKET_TIMEOUT).read()
                                res_as_dict = ast.literal_eval(res)
                                real_url = res_as_dict['end_url']
                                urls_dict[url] = real_url
                            except IOError, error:
                                print error
                                print "Timeout error"
                        print 'real url = \"'+real_url+'\"'
                        new_content = content.replace(url, real_url)
                        ws.write(c_row, 3, new_content)
 
                    except UnicodeEncodeError, error:
                        print error
                        print 'Cannot read content of row '+str(c_row)+\
                              ' in file '+f_xls
 
            wb.save(OUTPUT_EXCEL_DIR+'expanded_url_'+f_xls)
        except IOError, error:
            print error
            print "Cannot read excel file "+f_xls
 
save_url_list(urls_dict)