[python] DONE : script de tracking UPS multithreadé

DONE : script de tracking UPS multithreadé [python] - Python - Programmation

Marsh Posté le 01-04-2007 à 18:56:29    

Oï !
 
Pour ceux qui on suivi l'activité de la section achat vente, vous avez surement vu un topic de 200 pages pour al dernière commande groupée d'ecrans Dell.
 
Comme on ne pouvais pas avoir les numeros de tracking UPS, j'ai fait un script assez bourrin pour alles scanner une plage de numeros de tracking sur le site d'UPS. Voilà ce que ça donne :
 

Code :
  1. #!/usr/bin/python
  2. from urllib import urlopen
  3. import subprocess
  4. # chopper le html :
  5. def get_html(url):
  6.     a=""
  7.     handler = urlopen(url)
  8.     try:
  9.         for line in handler:
  10.             a+=line.strip()
  11.     finally:
  12.         handler.close()
  13.        
  14.     return a
  15.    
  16. # isoler la partie qui nous interesse :
  17. def cut_html(html):
  18.     beginTag = "<!-- Scheduled Delivery Date logic -->"
  19.     endTag   = "<!-- Begin SII Link -->"
  20.     out = ""
  21.     startPos = html.find(beginTag)
  22.     while startPos > -1:
  23.         endPos = html.find(endTag,startPos+1)
  24.         if endTag == -1:
  25.             break
  26.         else:
  27.             out+=html[startPos+len(beginTag):endPos]
  28.             startPos = html.find(beginTag,endPos+1)
  29.     return out
  30.    
  31. # parser ce HTML (et l'afficher)
  32. def parse_html(html):
  33.     proc=subprocess.Popen("lynx -dump -stdin", stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=True)
  34.     proc.stdin.write(html)
  35.     print(proc.communicate()[0])
  36. url_start="http://wwwapps.ups.com/WebTracking/processInputRequest?HTMLVersion=5.0&loc=en_FR&Requester=UPSHome&tracknum=1Z+1WX+989+68+855"
  37. url_end="+&AgreeToTermsAndConditions=yes&ignore=&track.x=13&track.y=11"
  38. for a in range(10):
  39.     for b in range(1000):
  40.         for c in range(10):
  41.             full_url=url_start+str(a)+'+'+str(b).zfill(3)+'+'+str(c)+url_end
  42.             print("1Z 1WX 989 68 855"+str(a)+" "+str(b).zfill(3)+" "+str(c)+"\n" )
  43.             parse_html(cut_html(get_html(full_url)))


 
Comme il a mis 10h pour scanner à peu près 60k pages de tracking, et qu'il a planté avant d'avoir fini (session ssh qui a deconnecté, il tournait sur un serveur dedié), je voudrais le refaire de façon optimisée, parceque c'est vraiment pas le cas actuellement.
 
Alors si je repartais de la structure actuelle, il faudrait commencer par remplacer les concatennations par des .join(). Eviter de parser avec lynx via subprocess, mais faire un tidy+re (voir re seul). Et surtout multithreader pour ne pas avoir des ouvertures d'URL bloquantes vu la latence moyenne du serveur de tracking.
 
Je sui vraiment pas doué en code, donc j'en apelle a votre aide au moins pour la partie tidy/re . Par la suite je voudrais exporter les resultats en xml voir csv et faire un petit script qui actualise un compteur (sous forme d'image) pour le nombre de colis en attente de livraison, avec actualisation dans la crontab.
 
Si ça interesse quelqu'un...
 
:hello:

Message cité 1 fois
Message édité par nicolbolas le 02-04-2007 à 01:35:32
Reply

Marsh Posté le 01-04-2007 à 18:56:29   

Reply

Marsh Posté le 01-04-2007 à 19:42:50    

nicolbolas a écrit :

il a planté avant d'avoir fini (session ssh qui a deconnecté, il tournait sur un serveur dedié)


 :o un tit screen et ça serait pas arrivé...
(bon heuu c'est tout ce que j'avais à dire, la prog c'est pas (plus) mon truc :whistle: )
 
... ah si un truc, j'oubliais, euuh, selon les termes et conditions du machin d'UPS, t'as le droit d'automatiser des requêtes sur leur interface de suivi ? :??: (J'ai pas lu leur machin mais je pense que ça se résume à "Utilisation personelle uniquement" ou un truc dans le genre)


---------------
No one should take themselves so seriously, With many years ahead to fall in line, Why would you wish that on me? I never want to act my age!
Reply

Marsh Posté le 01-04-2007 à 19:49:51    

on va dire que c'est pas ma préoccupation première...

Reply

Marsh Posté le 01-04-2007 à 20:30:18    

bon alors pour que ce soit un poil plus propre, j'ai deja mis à jour la construction de la requette HTTP :
 

Code :
  1. def simple_request(tracking_number):
  2.    """does a simple request to the tracking server"""
  3.    
  4.    tracking_url = 'http://wwwapps.ups.com/WebTracking/processInputRequest'
  5.    user_agent =  'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
  6.    headers =  { 'User-Agent'  : user_agent  }
  7.    values = {'HTMLVersion'  : '5.0',
  8.              'loc' : 'en_FR',
  9.              'Requester' : 'UPSHome',
  10.              'tracknum' : tracking_number,
  11.              'AgreeToTermsAndConditions' : 'yes',
  12.              'ignore' : '',
  13.              'track.x' : '13',
  14.              'track.y' : '11' }
  15.    data =  urllib.urlencode(values)
  16.    req =  urllib2.Request(tracking_url, data, headers)
  17.    try:
  18.        handle = urllib2.urlopen(req)
  19.    except IOError, e:
  20.        if hasattr(e, 'reason'):
  21.            ret = [ 'Server unreachable.', 'Raison: ', e.reason ]
  22.        elif hasattr(e, 'code'):
  23.            ret = [ 'Server Error.', 'Code d\' erreur : ', e.code ]
  24.        return ret
  25.    else:
  26.        return handle.read()


 
edit : petit update


Message édité par nicolbolas le 01-04-2007 à 20:47:53
Reply

Marsh Posté le 01-04-2007 à 22:22:05    

pour le parsing, j'ai trouvé une base sympa :
 

Code :
  1. #!/usr/bin/python
  2. # -*- coding: iso-8859-1 -*-
  3. # Hello, this program is written in Python - http://python.org
  4. programname = 'html2csv - version 2002-09-20 - http://sebsauvage.net'
  5.  
  6. import HTMLParser, re
  7.  
  8. try:    import psyco ; psyco.jit()  # If present, use psyco to accelerate the program
  9. except: pass
  10.  
  11. class html2csv(HTMLParser.HTMLParser):
  12.    ''' A basic parser which converts HTML tables into CSV.
  13.        Feed HTML with feed(). Get CSV with getCSV(). (See example below.)
  14.        All tables in HTML will be converted to CSV (in the order they occur
  15.        in the HTML file).
  16.        You can process very large HTML files by feeding this class with chunks
  17.        of html while getting chunks of CSV by calling getCSV().
  18.        Should handle badly formated html (missing <tr>, </tr>, </td>,
  19.        extraneous </td>, </tr>...).
  20.        This parser uses HTMLParser from the HTMLParser module,
  21.        not HTMLParser from the htmllib module.
  22.        Example: parser = html2csv()
  23.                 parser.feed( open('mypage.html','rb').read() )
  24.                 open('mytables.csv','w+b').write( parser.getCSV() )
  25.        This class is public domain.
  26.        Author: Sébastien SAUVAGE <sebsauvage at sebsauvage dot net>
  27.                http://sebsauvage.net
  28.        Versions:
  29.           2002-09-19 : - First version
  30.           2002-09-20 : - now uses HTMLParser.HTMLParser instead of htmllib.HTMLParser.
  31.                        - now parses command-line.
  32.        To do:
  33.            - handle <PRE> tags
  34.            - convert html entities (&name; and &#ref;) to Ascii.
  35.            '''
  36.    def __init__(self):
  37.        HTMLParser.HTMLParser.__init__(self)
  38.        self.CSV = ''      # The CSV data
  39.        self.CSVrow = ''   # The current CSV row beeing constructed from HTML
  40.        self.inTD = 0      # Used to track if we are inside or outside a <TD>...</TD> tag.
  41.        self.inTR = 0      # Used to track if we are inside or outside a <TR>...</TR> tag.
  42.        self.re_multiplespaces = re.compile('\s+')  # regular expression used to remove spaces in excess
  43.        self.rowCount = 0  # CSV output line counter.
  44.    def handle_starttag(self, tag, attrs):
  45.        if   tag == 'tr': self.start_tr()
  46.        elif tag == 'td': self.start_td()
  47.    def handle_endtag(self, tag):
  48.        if   tag == 'tr': self.end_tr()
  49.        elif tag == 'td': self.end_td()        
  50.    def start_tr(self):
  51.        if self.inTR: self.end_tr()  # <TR> implies </TR>
  52.        self.inTR = 1
  53.    def end_tr(self):
  54.        if self.inTD: self.end_td()  # </TR> implies </TD>
  55.        self.inTR = 0            
  56.        if len(self.CSVrow) > 0:
  57.            self.CSV += self.CSVrow[:-1]
  58.            self.CSVrow = ''
  59.        self.CSV += '\n'
  60.        self.rowCount += 1
  61.    def start_td(self):
  62.        if not self.inTR: self.start_tr() # <TD> implies <TR>
  63.        self.CSVrow += '"'
  64.        self.inTD = 1
  65.    def end_td(self):
  66.        if self.inTD:
  67.            self.CSVrow += '",'  
  68.            self.inTD = 0
  69.    def handle_data(self, data):
  70.        if self.inTD:
  71.            self.CSVrow += self.re_multiplespaces.sub(' ',data.replace('\t',' ').replace('\n','').replace('\r','').replace('"','""'))
  72.    def getCSV(self,purge=False):
  73.        ''' Get output CSV.
  74.            If purge is true, getCSV() will return all remaining data,
  75.            even if <td> or <tr> are not properly closed.
  76.            (You would typically call getCSV with purge=True when you do not have
  77.            any more HTML to feed and you suspect dirty HTML (unclosed tags). '''
  78.        if purge and self.inTR: self.end_tr()  # This will also end_td and append last CSV row to output CSV.
  79.        dataout = self.CSV[:]
  80.        self.CSV = ''
  81.        return dataout


 
ça converti des tables HTML en CSV, je devrais pouvoir l'adapter à peu de frais :jap:


Message édité par nicolbolas le 01-04-2007 à 22:23:38
Reply

Marsh Posté le 01-04-2007 à 22:54:50    

alors voilà un code clean, pas encore threadé, mais je suis dessus :

 
Code :
  1. #!/usr/bin/python
  2. # -*- coding: ISO-8859-1 -*-
  3.  
  4.  
  5. import urllib
  6. import urllib2
  7. import re
  8. import HTMLParser
  9.  
  10. try:    import psyco ; psyco.jit()  # If present, use psyco to accelerate the program
  11. except: pass
  12.  
  13.  
  14.  
  15. class html2txt(HTMLParser.HTMLParser):
  16.    ''' A basic parser which converts HTML tables into plain text.
  17.        Feed HTML with feed(). Get txt with getTXT(). (See example below.)
  18.        '''
  19.    def __init__(self):
  20.        HTMLParser.HTMLParser.__init__(self)
  21.        self.TXT = ''      # The TXT data
  22.        self.TXTrow = ''   # The current TXT row beeing constructed from HTML
  23.        self.inTD = 0      # Used to track if we are inside or outside a <TD>...</TD> tag.
  24.        self.inTR = 0      # Used to track if we are inside or outside a <TR>...</TR> tag.
  25.        self.re_multiplespaces = re.compile('\s+')  # regular expression used to remove spaces in excess
  26.        self.rowCount = 0  # CSV output line counter.
  27.    def handle_starttag(self, tag, attrs):
  28.        if   tag == 'tr': self.start_tr()
  29.        elif tag == 'td': self.start_td()
  30.    def handle_endtag(self, tag):
  31.        if   tag == 'tr': self.end_tr()
  32.        elif tag == 'td': self.end_td()        
  33.    def start_tr(self):
  34.        if self.inTR: self.end_tr()  # <TR> implies </TR>
  35.        self.inTR = 1
  36.    def end_tr(self):
  37.        if self.inTD: self.end_td()  # </TR> implies </TD>
  38.        self.inTR = 0            
  39.        if len(self.TXTrow) > 0:
  40.            self.TXT += self.TXTrow[:-1]
  41.            self.TXTrow = ''
  42.        self.TXT += '\n'
  43.        self.rowCount += 1
  44.    def start_td(self):
  45.        if not self.inTR: self.start_tr() # <TD> implies <TR>
  46.        self.inTD = 1
  47.    def end_td(self):
  48.        if self.inTD:
  49.            self.inTD = 0
  50.    def handle_data(self, data):
  51.        if self.inTD:
  52.            self.TXTrow += self.re_multiplespaces.sub(' ',data.replace('\t',' ').replace('\n','').replace('\r','').replace('"','""'))
  53.    def getTXT(self,purge=False):
  54.        ''' Get output text.
  55.            If purge is true, getTXT() will return all remaining data,
  56.            even if <td> or <tr> are not properly closed.
  57.            (You would typically call getTXT with purge=True when you do not have
  58.            any more HTML to feed and you suspect dirty HTML (unclosed tags). '''
  59.        if purge and self.inTR: self.end_tr()  # This will also end_td and append last TXT row to output text.
  60.        dataout = self.TXT[:]
  61.        self.TXT = ''
  62.        return dataout
  63.  
  64.  
  65.  
  66. def simple_request(tracking_number):
  67.    """does a simple request to the tracking server"""
  68.    
  69.    tracking_url = 'http://wwwapps.ups.com/WebTracking/processInputRequest'
  70.    user_agent =  'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
  71.    headers =  { 'User-Agent'  : user_agent  }
  72.    values = {'HTMLVersion'  : '5.0',
  73.              'loc' : 'en_FR',
  74.              'Requester' : 'UPSHome',
  75.              'tracknum' : tracking_number,
  76.              'AgreeToTermsAndConditions' : 'yes',
  77.              'ignore' : '',
  78.              'track.x' : '13',
  79.              'track.y' : '11' }
  80.    data =  urllib.urlencode(values)
  81.    req =  urllib2.Request(tracking_url, data, headers)
  82.    try:
  83.        handle = urllib2.urlopen(req)
  84.    except IOError, e:
  85.        if hasattr(e, 'reason'):
  86.            ret = [ 'Server unreachable.', 'Raison: ', e.reason ]
  87.        elif hasattr(e, 'code'):
  88.            ret = [ 'Server Error.', 'Code d\' erreur : ', e.code ]
  89.            print e.code
  90.        return ret
  91.    else:
  92.        return handle.read()
  93.  
  94. def cut_html(html):
  95.    beginTag = "<!-- Begin: Exception Error Message -->"
  96.    endTag   = "<!-- End Package Progress -->"
  97.    out = ""
  98.  
  99.    startPos = html.find(beginTag)
  100.  
  101.    while startPos > -1:
  102.        endPos = html.find(endTag,startPos+1)
  103.        if endTag == -1:
  104.            break
  105.        else:
  106.            out+=html[startPos+len(beginTag):endPos]
  107.            startPos = html.find(beginTag,endPos+1)
  108.    return out
  109.  
  110.  
  111.  
  112.  
  113. if __name__ == '__main__':
  114.    tracking_number = '1Z 1WX 989 68 8551 540 2'
  115.    parser=html2txt()
  116.    parser.feed(cut_html(simple_request(tracking_number)))
  117.    print parser.getTXT()


Message édité par nicolbolas le 01-04-2007 à 22:55:31
Reply

Marsh Posté le 02-04-2007 à 00:16:10    

bon ben ça, ça m'a pas l'air mal :

 
Code :
  1. #!/usr/bin/python
  2. # -*- coding: ISO-8859-1 -*-
  3.  
  4.  
  5. import urllib
  6. import urllib2
  7. import re
  8. import HTMLParser
  9. import threading
  10. import Queue
  11. import sys
  12.  
  13. try:    import psyco ; psyco.jit()  # If present, use psyco to accelerate the program
  14. except: pass
  15.  
  16.  
  17. THREAD_LIMIT = 50
  18. packages_to_track = Queue.Queue(0)
  19. parsed_results = Queue.Queue(THREAD_LIMIT)
  20.  
  21. class html2txt(HTMLParser.HTMLParser):
  22.    ''' A basic parser which converts HTML tables into plain text.
  23.        Feed HTML with feed(). Get txt with getTXT(). (See example below.)
  24.        '''
  25.    def __init__(self):
  26.        HTMLParser.HTMLParser.__init__(self)
  27.        self.TXT = ''      # The CSV data
  28.        self.TXTrow = ''   # The current CSV row beeing constructed from HTML
  29.        self.inTD = 0      # Used to track if we are inside or outside a <TD>...</TD> tag.
  30.        self.inTR = 0      # Used to track if we are inside or outside a <TR>...</TR> tag.
  31.        self.re_multiplespaces = re.compile('\s+')  # regular expression used to remove spaces in excess
  32.        self.rowCount = 0  # CSV output line counter.
  33.    def handle_starttag(self, tag, attrs):
  34.        if   tag == 'tr': self.start_tr()
  35.        elif tag == 'td': self.start_td()
  36.    def handle_endtag(self, tag):
  37.        if   tag == 'tr': self.end_tr()
  38.        elif tag == 'td': self.end_td()        
  39.    def start_tr(self):
  40.        if self.inTR: self.end_tr()  # <TR> implies </TR>
  41.        self.inTR = 1
  42.    def end_tr(self):
  43.        if self.inTD: self.end_td()  # </TR> implies </TD>
  44.        self.inTR = 0            
  45.        if len(self.TXTrow) > 0:
  46.            self.TXT += self.TXTrow[:-1]
  47.            self.TXTrow = ''
  48.        self.TXT += '\n'
  49.        self.rowCount += 1
  50.    def start_td(self):
  51.        if not self.inTR: self.start_tr() # <TD> implies <TR>
  52.        self.inTD = 1
  53.    def end_td(self):
  54.        if self.inTD:
  55.            self.inTD = 0
  56.    def handle_data(self, data):
  57.        if self.inTD:
  58.            self.TXTrow += self.re_multiplespaces.sub(' ',data.replace('\t',' ').replace('\n','').replace('\r','').replace('"','""'))
  59.    def getTXT(self,purge=False):
  60.        ''' Get output text.
  61.            If purge is true, getTXT() will return all remaining data,
  62.            even if <td> or <tr> are not properly closed.
  63.            (You would typically call getTXT with purge=True when you do not have
  64.            any more HTML to feed and you suspect dirty HTML (unclosed tags). '''
  65.        if purge and self.inTR: self.end_tr()  # This will also end_td and append last TXT row to output text.
  66.        dataout = self.TXT[:]
  67.        self.TXT = ''
  68.        return dataout
  69.  
  70.  
  71. def simple_request(tracking_number):
  72.    """does a simple request to the tracking server"""
  73.    
  74.    tracking_url = 'http://wwwapps.ups.com/WebTracking/processInputRequest'
  75.    user_agent =  'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
  76.    headers =  { 'User-Agent'  : user_agent  }
  77.    values = {'HTMLVersion'  : '5.0',
  78.              'loc' : 'en_FR',
  79.              'Requester' : 'UPSHome',
  80.              'tracknum' : tracking_number,
  81.              'AgreeToTermsAndConditions' : 'yes',
  82.              'ignore' : '',
  83.              'track.x' : '13',
  84.              'track.y' : '11' }
  85.    data =  urllib.urlencode(values)
  86.    req =  urllib2.Request(tracking_url, data, headers)
  87.    try:
  88.        handle = urllib2.urlopen(req)
  89.    except IOError, e:
  90.        if hasattr(e, 'reason'):
  91.            ret = [ 'Server unreachable.', 'Raison: ', e.reason ]
  92.        elif hasattr(e, 'code'):
  93.            ret = [ 'Server Error.', 'Code d\' erreur : ', e.code ]
  94.            print e.code
  95.        return ret
  96.    else:
  97.        return handle.read()
  98.  
  99.  
  100. def cut_html(html):
  101.    beginTag = "<!-- Begin: Exception Error Message -->"
  102.    endTag   = "<!-- End Package Progress -->"
  103.    out = ""
  104.    startPos = html.find(beginTag)
  105.    while startPos > -1:
  106.        endPos = html.find(endTag,startPos+1)
  107.        if endTag == -1:
  108.            break
  109.        else:
  110.            out+=html[startPos+len(beginTag):endPos]
  111.            startPos = html.find(beginTag,endPos+1)
  112.    return out
  113.  
  114.  
  115. def thread():
  116.    """Get, cut and parse tracking information"""
  117.    while True:
  118.        try:
  119.            tracking_number = packages_to_track.get(False)
  120.        except Queue.Empty:
  121.            return
  122.        parser = html2txt()
  123.        parser.feed(cut_html(simple_request(tracking_number)))
  124.        parsed_results.put((tracking_number, parser.getTXT()), True)
  125.  
  126.  
  127. def run():
  128.    jobs_done = 0
  129.    sys.stderr
  130.    sys.stderr.write("Starting...\n" )
  131.    for a in range(10):
  132.        for b in range(1000):
  133.            for c in range(10):
  134.                packages_to_track.put('1Z 1WX 989 68 855'+str(a)+' '+str(b).zfill(3)+' '+str(c))
  135.    sys.stderr.write("Created 100 000 job entries\n" )
  136.    
  137.    
  138.        
  139.    for n in xrange(THREAD_LIMIT): # Unleash the hounds
  140.        t = threading.Thread(target=thread)
  141.        t.start()
  142.    sys.stderr.write("Unleashed the hounds...\n" )
  143.        
  144.    while threading.activeCount()> 1 or not packages_to_track.empty():
  145.        # That condition means we want to do this loop if there are threads
  146.        # running OR there's stuff to process
  147.        try:
  148.            tracking_number, tracking_info = parsed_results.get(False, 10) # Wait for up to a second for a
  149.                                                   # result
  150.        except Queue.Empty:
  151.            continue
  152.        jobs_done += 1
  153.        sys.stderr.write("Processed package #%s - Got %s bytes data - %s processed so far\n" % ( str(tracking_number), len(tracking_info), str(jobs_done) ) )
  154.        print tracking_number, '\n', tracking_info
  155.        
  156.  
  157. if __name__ == '__main__':
  158.    run()
 

edit : rajout de code pour un peu de debug sur stderr


Message édité par nicolbolas le 02-04-2007 à 02:00:17
Reply

Marsh Posté le 02-04-2007 à 02:32:34    

apparament j'ai un probleme de synchro quelque part : certains numeros connus n'aparaissent pas dans la sortie. Je relance un test pour comparer le nombre de retour par rapport au nombre de requettes, mais ça me parait vraiment etrange...

Reply

Marsh Posté le 02-04-2007 à 04:30:33    

mouarf, c'etait juste trop de threads, le serveur d'UPS etait débordé et a refusé quelques connexions... Ca va mieux avec 10 threads et ça va  meme un poil plus vite avec 20 mais avec un peu de perte

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed