vlambda博客
学习文章列表

openpyxl, webdriver批量查询运单,并更新表格数据

应朋友要求,帮他写一个程序,帮助他自动更新物流信息,并更新在excel表里。

零. 简要说明

朋友做电子商务,需要发很多快递到国外,每天有很多订单产生,并要更新物流信息,以随时掌握订单的情况。他们主要是在17track和trackdog批量查询物流信息,并把物流数据复制到表格中,前者可以每次查询40个后者可以50个,如果每天的数据都同通过手动输入必将是一个繁琐的事情,所以我想可以帮他解决这个问题。

我开始想通过网页数据请求的方式,通过爬虫得到物流信息,但是同我预感到的一样,每个网站都有反爬机制,我立刻就放弃了,要破解反爬机制的加密方式是一个非常艰难而又超出我能力范围的工程, 我立马放弃的这个方法。转而投向webdriver, 这个简单实用的方法,虽然有点不智能的感觉,但是朋友要求的不高,可以解决问题就可以了。

另外17track设置了很多防止机器操作的验证码程序,而trackdog就简单一些,于是选择使用trackdog. 代码如下:由于时间有限就不一步一步分析过程了。

一. 源代码

 #encoding = UTF-8
 
 import openpyxl
 import chardet
 from selenium import webdriver
 from selenium.webdriver.common.action_chains import ActionChains
 from selenium.webdriver.support.wait import WebDriverWait
 from selenium.webdriver.support import expected_conditions as EC
 from selenium.webdriver.common.by import By
 from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
 import sys
 import pyperclip
 import time
 import pickle
 import os
 
 #由于需要把autoit的EXE文件一起打包,所以使用了本函数
 def resource_path(relative_path): #这段函数是为了把这个程序用pyintaller打包成exe。参数是相对路径,即在程序所在文件夹的路径
     if getattr(sys, 'frozen', False): #这是python的内置函数,用于判断第一个参数(对象),是否含有名为第二个参数的属性,如果没有返回第三个参数的值。根据资料,如果sys里面有'frozen'属性,说明运行的是被打包成exe的文件。如果正常运行,py文件,就没有'frozen'属性,则返回False,返回打包前的文件地址。
         base_path = sys._MEIPASS #系统会在运行exe文件时在临时文件夹中新建一个_MEIxxxx,xxxx是随机数字的文件夹,并把所有需要的文件资源存放在里面。当关闭运行exe后,系统自动删除_MEIxxxx文件夹。那么这句代码就把临时文件夹的地址赋值给base_path,再传入主函数中,那么运行文件时,程序就会在临时文件去寻找所需要的文件。
     else:
         base_path = os.path.abspath(".")#当前py文件的地址,当前目录的绝对路径
     return os.path.join(base_path, relative_path) #返回绝对地址
 
 #读取主表格的运单号,写入跟踪号等信息
 def write_item():
     wb = openpyxl.load_workbook('data.xlsx')
     sh = wb.active
     maxrow = sh.max_row #最大行
     maxcolumn = sh.max_column #最大列
   
     global tra_column #全局变量,定义为'运单号'的列数
     for r in range(1, maxcolumn+1):
         if sh.cell(1, r).value == '运单号': #找到'运单号'这一列的列数
             tra_column = r
             break
     #然后在后面写入'跟踪号','详细状态''投递状态'
     sh.cell(1, r+1).value = '跟踪号'
     sh.cell(1, r+2).value = '详细状态'
     sh.cell(1, r+3).value = '投递状态'
 
     #把主表格的运单号写入一个列表并保存在文件中
     active_data = []
     for r1 in range(2, maxrow+1):
         active_data.append(sh.cell(r1, tra_column).value)
     with open('active_data.txt', 'wb') as f:#注意这里要用'wb'直接写入,而不能用'ab’追加,追加会导致后面追加的数据不能读取出来
         pickle.dump(active_data, f)
           
     wb.save('data.xlsx')
     
     
 #利用容器导出所有的地址,由于网站每次最多可以查询50个单号
 def get_address():
     wb = openpyxl.load_workbook('data.xlsx')
     sh = wb.active    
     maxrow = sh.max_row
     tr_n = ['https://www.trackdog.com/track.htm?tn=']
     n = 1 #记录写入网址后面的订单个数
     m = 0 #记录查询次数
     k = (maxrow-1)%50 #记录每次50个运单号查询后剩下的运单号
     for r in range(2, maxrow+1):
         v_row = sh.cell(r, tra_column).value #读取单号
         if m < (maxrow-1)//50: #地板除,及需要查询的总次数,每次最多查询50个单号。如果还没有到最后一次,每次查询50个      
             if n == 1:#第一个订单号直接追加到网址后面
                 tr_n.append(v_row)
                 n += 1
                 continue #结束本轮循环,继续下轮循环            
             elif n%50 != 0:#不能被50整除的时候,需要在前面加上一个逗号
                 tr_n.append(',')
                 tr_n.append(v_row)
                 n += 1
                 continue              
             else:#整除的时候,跳出循环,生成地址
                 tr_n.append(',')
                 tr_n.append(v_row)
                 n += 1
         else:#m等于地板除的结果,及最后一次查询。最后一次查询运单个数小于或者等于50
             if k > 1 and n == 1:#最后剩下的个数大于1,又是第一个单号
                 tr_n.append(v_row)
                 n += 1
                 k -= 1
                 continue
             if k > 1 and n > 1:#最后剩下的个数大于1,不是第一个单号
                 tr_n.append(',')
                 tr_n.append(v_row)
                 n += 1
                 k -= 1
                 continue
             if k == 1:#最后只剩下一个单号
                 tr_n.append(',')
                 tr_n.append(v_row)
                 n += 1
                 k -= 1          
         global no_num #全局变量,定义为每次查询的订单数
         no_num = int(len(tr_n)/2)#根据列表中元素个数计算运单的个数,在地板除的值以内每次都是50,最后一次等于开始K的值。
         add = ''.join(tr_n)
         tr_n = ['https://www.trackdog.com/track.htm?tn='] #清除所有订单号,用于下一轮的查询
         n = 1 #返回原始值,用于下一轮查询
         m += 1 #查询次数叠加
         yield add #生成器导出每次的地址  
                             
 #定义显性等待,参数为需要等待出现的具体元素的xpath值
 def waitok(location):
     while True:
         locator = (By.XPATH, location)
         try:#等待时间60秒
             WebDriverWait(browser, 60, 0.5).until(EC.presence_of_element_located(locator))
             #print('Get the target.%s' % location)
             break
             
         except:
             print('网络出现问题,需要刷新页面。')
             browser.refresh()
 
 #用于通过ID查询
 def waitOK(location):
     while True:
         locator = (By.ID, location)
         try:#等待时间60秒
             WebDriverWait(browser, 60, 0.5).until(EC.presence_of_element_located(locator))
             #print('Get the target.%s' % location)
             break
             
         except:
             print('网络出现问题,需要刷新页面。')
             browser.refresh()
 
 
 #查询新订单,并把相关信息写入主表格里
 def get_new_track_data():
     wb = openpyxl.load_workbook('发货数据.xlsx')
     sh = wb.active
    (maxrow, maxcolumn) = (sh.max_row, sh.max_column)
     #同样找到运单号的列
     for col in range(1, maxcolumn+1):
         if sh.cell(1, col).value == '运单号':
             tra_column_new = col
             break    
 
     #导出前面存在文件里主表格中的运单号
     with open('active_data.txt', 'rb') as f1:
         activedata = pickle.load(f1)
 
     #找到第一个新运单的行,因为所有数据都是以时间为顺序,只要找到第一个新运单,这之后的都将是新运单  
     for ro in range (2, maxrow+1):        
         if sh.cell(ro, tra_column_new).value not in activedata:
             copy_data_start_row = ro
             break  
 
     #开始把新订单号的相关数据写入主表格
     wb_data = openpyxl.load_workbook('data.xlsx')
     sh_data = wb_data.active
     maxrow_data = sh_data.max_row
     maxcolumn_data = sh_data.max_column
     x = 0
     #从原主表格最大行下一行开始写入,(maxrow-copy_data_start_row)+1为新数据的行数
     for copy_data_row in range(maxrow_data+1, maxrow_data+1+(maxrow-copy_data_start_row)+1):
         new_copy_data_row = copy_data_start_row+x #新数据的读取行
         for copy_data_col in range(1, tra_column_new+1):#写入的列
             #把新运单号的数据写入主表格里
             sh_data.cell(copy_data_row, copy_data_col).value = sh.cell(new_copy_data_row, copy_data_col).value
         x += 1      
     wb_data.save('data.xlsx')
     os.remove('发货数据.xlsx')
     os.remove('active_data.txt')
     
           
 
 #为了避免browser.get()的长时间加载
 desired_capabilities = DesiredCapabilities.CHROME
 desired_capabilities["pageLoadStrategy"] = "none"
 
 #读取主表格的运单号,写入跟踪号等信息
 write_item()
 #查询新订单,并把相关信息写入主表格里,如果没有新数据就会显示无新订单,并删除文件中的运单号
 try:
     get_new_track_data()
 except:
     print('没有新订单,更新其他数据。。')
     os.remove('active_data.txt')
     pass
 
 #导出生成器
 address = get_address()
 #browser = webdriver.Chrome()
 browser = webdriver.Chrome(resource_path('chromedriver.exe'))
 m = 2
 for each in address:#一次循环生成一个地址
     browser.get(each)
     browser.minimize_window()
     while True:
         waitok('/html/body/main/section[1]/ul/li[1]/span[3]')
         waitOK('allNum')
         allnum = browser.find_element_by_id('allNum')
         num = int(allnum.text)#通过ID得到每次查询的运单个数,要与每次的查询的运单个数一样,如果网页加载完后就会相等
         if num == no_num:
             time.sleep(5)#给点时间页面加载其他信息,然后跳出循环
             break
         else: #数据没加载完,继续加载
             pass
     #复制所有运单查询结果
     while True:    
         waitok('/html/body/main/section[1]/div/a[2]')#等待copy data的按钮出现
         copy_data = browser.find_element_by_xpath('/html/body/main/section[1]/div/a[2]')                                
         copy_data.click()
         data = pyperclip.paste()#点击复制数据并赋值给变量
         if '加载中' in data:#如果没有加载完则等3秒,加载完了则跳出循环
             print('网页未加载完,重新读取页面。')
             time.sleep(3)
             continue
         else:
             break
     #得到的数据进行拆分,切片得到我们需要的物流信息,然后把需要的信息写入主表格中
     b = data.split('\r\n')
     c = b[:-3]
     wb = openpyxl.load_workbook('data.xlsx')
     sh = wb.active
     maxrow = sh.max_row
     for i in c:
        (tra_n,fro,to,suma,situ) = i.split('\t')
         #print((tra_n,fro,to,suma,situ))
         if sh.cell(m, tra_column).value == tra_n:
             
             sh.cell(m, tra_column+1).value = tra_n
             sh.cell(m, tra_column+2).value = suma
             sh.cell(m, tra_column+3).value = situ
         m += 1          
     wb.save('data.xlsx')
     
 browser.quit()
 sys.exit()        

二.遇到的问题

如图,两次用append在文件中写入不同的表格,但是导出的数据没有变。到目前为止还不知道什么原因,但是可以通过改为write解决这个问题。在此记录一下。