小爬最近给同事制作一个小爬虫:具体要求:
1、每天自动定时触发;
2、模拟用户自动登陆;
3、自动爬取对应API接口数据;
4、对爬取结果进行逻辑判断,对符合条件的数据进行规则化列示;
5、列示的行项目支持超链接,如果用用户已经通过浏览器登陆过,该超链接需要能支持单击后在浏览器内新建选项卡并直接进入对应的表单,无需再次登陆。
小爬思考了下:整个程序的功能实现中,2、3、4步骤涉及的功能在先前小爬编写的一些自动化工具中已经有实现过,所以与小爬而言,核心问题就是步骤1和5;经过翻阅相关资料,“每天自动定时触发”可以通过Windows标准的“任务计划程序"来实现:
该功能可以实现 按特定频次自动触发特定程序或脚本,且支持传参,配置过程非常简单易用;
唯一的问题,如果经由此功能启动特定脚本,则通过"os.getcwd()"得到的路径是"C:windowssystem32",而不是脚本自身所在的路径,可能会与您的预期不一致;所以如果您的脚本中涉及调用一个同目录下的excel文件,那么如何自动获得该"excel"的路径呢,可以通过下面的代码实现:
1 filepath=os.path.abspath(sys.argv[0]) 2 filepath=os.path.dirname(filepath)
至于tkinter怎么动态追加按钮并附上超链接事件,中间涉及到两个问题:
1.按钮数量不固定,如何动态追加,按钮名怎么动态生成;
2.按钮如何绑定事件,进行超链接到系统默认浏览器.
问题1的python其中一种解决方法是:
1 names=locals() 2 for i in range(len(afFormNumberList)): 3 names["link%s"%i]=tk.Button(window,text="%s %s %s %s "%(afFormNumberList[i],afPersonNameList[i],failDateList[i],afFromUrlList[i]),font=('微软雅黑', 10))
5 names["link%s"%i].pack()
上面的代码中,当我们对这些button进行事件绑定且动态传参时,如果我们绑定的url中动态引用了变量i,则事件不会马上触发event,而是在用户点击该button时才会进行事件响应,此时,i的值永远是循环的最后一个数,那么我们动态生成的所有的button链接的地址或者说事件永远是一样的,这显然不是我们想要的结果,我们可能希望的结果是:
1 if i==0: 2 names["link%s"%i].bind("<Button-1>",lambda event:open_url(event,afFromUrlList[0])) 3 elif i==1: 4 names["link%s"%i].bind("<Button-1>",lambda event:open_url(event,afFromUrlList[1])) 5 elif i==2: 6 names["link%s"%i].bind("<Button-1>",lambda event:open_url(event,afFromUrlList[2])) 7 elif i==3: 8 names["link%s"%i].bind("<Button-1>",lambda event:open_url(event,afFromUrlList[3])) 9 elif i==4: 10 names["link%s"%i].bind("<Button-1>",lambda event:open_url(event,afFromUrlList[4])) 11 elif i==5: 12 names["link%s"%i].bind("<Button-1>",lambda event:open_url(event,afFromUrlList[5])) 13 elif i==6: 14 names["link%s"%i].bind("<Button-1>",lambda event:open_url(event,afFromUrlList[6])) 15 elif i==7: 16 names["link%s"%i].bind("<Button-1>",lambda event:open_url(event,afFromUrlList[7])) 17 elif i==8: 18 names["link%s"%i].bind("<Button-1>",lambda event:open_url(event,afFromUrlList[8])) 19 elif i==9: 20 names["link%s"%i].bind("<Button-1>",lambda event:open_url(event,afFromUrlList[9])) 21 elif i==10: 22 names["link%s"%i].bind("<Button-1>",lambda event:open_url(event,afFromUrlList[10]))
上段代码通过变量指定,能有效解决超链接永远为最后一个的问题,但是这段代码过于冗长,实现方式太笨,且这种写法是假定按钮动态追加的数量小于预设值.缺乏灵活性!
小爬互联网上找了一圈,最终在stackoverflow中找到了想要的答案:https://stackoverflow.com/questions/20596892/disabling-buttons-after-click-in-tkinter
这段代码中的核心在于"n=letters[index]",随着每次的index递增,我们的n值是不一样的.也就是按钮动态增加的过程中,每次command中绑定的n是不一样的,而不是永远的最后一个"i",另外的巧妙之处在于,代码将button对象追加到一个buttons的空列表中,这样,随着index的递增,我们的buttons[index]就可以对应不同的button.那就是不同index跟不同button之间形成了一一映射关系.这才是这个问题解决的关键.
最后生成的gui界面如下:
通过点击该button,可以自动打开对应表单(如果浏览器已完成该门户的登陆,则无需再次验证登陆,而通过office系列软件集成的超链接在面对此问题时,无论之前是否已登录,都会生成新的cookie和session,用户需要再次登陆,这是出于安全考虑,但是操作步骤变得繁琐).
上文中提到自动化工具的示例代码如下,供诸位参考,一起学习进步:
1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 # @Time : 2019/7/09 17:15 4 # @Author : New June 5 # @Desc : 6 # @Software: PyCharm 7 # @修改日期:2019-07-09.内容:支持水泥临时授信业务到期单据的提醒、存档、单进程 8 # @修改日期:2019-07-11.内容:支持对信息展示,单击对应行能超链接至对应表单页 9 # @修改日期:2019-07-17.内容:改进代码,使代码能够对未知数量的button进行事件响应,不需要穷举,且支持button点击后置灰,不可用,与未点击的button区分 10 11 12 import time,re,datetime,csv,requests,json,os,easygui,sys 13 from requests.exceptions import ConnectionError 14 import webbrowser 15 import tkinter as tk 16 17 if __name__ == '__main__': 18 #记得登陆后时间起点,进入爬取过程 19 #print(os.getcwd()) 20 def open_url(index,url): 21 #print(event.x,event.y) 22 #url=buttons[index]["text"].split(" ")[1] 23 buttons[index].config(state="disabled") #禁用事件 24 webbrowser.open("%s"%url, new=0) #打开浏览器 25 26 filepath=os.path.abspath(sys.argv[0]) 27 filepath=os.path.dirname(filepath) 28 filepath=filepath+"\水泥临时授信存档.txt" 29 #print(filepath) 30 with open(filepath,"r",encoding='UTF-8-sig') as t: 31 history=t.read().strip() 32 txt=open(filepath,"a",encoding='UTF-8') 33 username="yourusername" 34 psw="password" 35 36 #today=datetime.date.today() 37 afFormNumberList=[] 38 afPersonNameList=[] 39 afFromUrlList=[] 40 failDateList=[] 41 #business="水泥(临时授信)" 42 43 """登陆,拿到登陆后的session""" 44 loginData={'redirect':'','username':username,'password':psw.lower()} 45 session=requests.sessions.Session() 46 response=session.post('http://someurl.com/portal/u/a/login.do',loginData) 47 text=response.text 48 if "密码不匹配" in text: 49 easygui.msgbox("OA登陆密码不正确!") 50 sys.exit(0) 51 '''事后抽查-事中''' 52 data_search={ 53 'page':1, 54 'rows':100, 55 'condition': 56 """[{"column":"DELETE_STATUS","exp":"=","value":0},{"column":"CREDIT_TYPE","exp":"like","value":"cement-0"},{"column":"AF_STATUS","exp":"=","value":1},{"column":"CREDIT_TYPE","orderType":"default","orderKey":"","direction":"ASC"}]""", 57 'additionalParams':'{}' 58 } 59 try: 60 response=session.post(url="http://someurl.com/mdm/af/credit/list.do",data=data_search) 61 if response.status_code==200: 62 pageContent = response.json() 63 for element in pageContent['rows']: 64 temporaryStatus=element['temporaryStatus'] #SAP状态,1代表成功,0代表未处理 65 failDate=element['failDate'] #授信失效日期 临时授信期限 66 afFormNumber=element['afFormNumber'] 67 #if failDate == str(datetime.date.today()-datetime.timedelta(days=1)) and temporaryStatus=="1": #失效日期在昨天,且SAP状态为成功 68 if temporaryStatus=="1" and failDate < str(datetime.date.today()) and afFormNumber not in history: 69 txt.writelines("%s"%afFormNumber) 70 txt.write(" ") 71 afFormNumberList.append(afFormNumber) #表单号 72 afFormId=element['id'] 73 afPersonNameList.append(element['afPersonName']) 74 failDateList.append(failDate) 75 afFromUrlList.append("http://someurl.com/mdm/af/credit/temporaryForm.do?_hf_data_id=%s"%afFormId) #表单网址 76 if afFormNumberList: 77 # 建立窗口window 78 window = tk.Tk() 79 # 给窗口的可视化起名字 80 window.title('临时授信到期信息') 81 # 设置窗口的居中显示 82 screenwidth = window.winfo_screenwidth() 83 screenheight = window.winfo_screenheight() 84 width = 1080 85 height = 700 86 size = "%dx%d+%d+%d" % (width, height, (screenwidth - width) / 2, (screenheight - height) / 2) 87 # 设定窗口的大小(长 * 宽) 88 window.geometry(size) 89 names=locals() 90 navigation=tk.Label(window,text="表单编号 申请人 临时授信期限 表单url地址 ", justify="left",font=('微软雅黑', 14,"bold"),fg="#8B008B") 91 navigation.pack() 92 #letters=["A", "T", "D", "M", "E", "A", "S", "R", "M"] #https://stackoverflow.com/questions/20596892/disabling-buttons-after-click-in-tkinter 93 buttons=[] 94 for i in range(len(afFormNumberList)): 95 url=afFromUrlList[i] 96 index=i 97 button =tk.Button(window,text="%s %s %s %s "%(afFormNumberList[i],afPersonNameList[i],failDateList[i],afFromUrlList[i]),font=('微软雅黑', 10),command=lambda index=index,url=url:open_url(index,url)) 98 99 #names["link%s"%i].place(x=20,y=100*(i+1)) 100 button.pack() 101 #window.update() 102 buttons.append(button) 103 txt.close() 104 window.mainloop() 105 else: 106 txt.close() 107 easygui.msgbox("今天没有授信到期单据!") 108 except requests.ConnectionError as e: 109 print('Error',e.args)