首页 > 编程语言 >开发一个自动爬取禅道bug信息的脚本程序

开发一个自动爬取禅道bug信息的脚本程序

时间:2022-08-17 22:46:00浏览次数:50  
标签:xpath index keys 爬取 td content bug 禅道

这个想法源于在公司工作时,需要每天关注bug的情况,每次在页面上做条件删选比较麻烦,而且不够直观,无法保存过滤的数据。

接下来要明确这个程序需要实现的功能:

  1. 最基本的,禅道的数据爬取
  2. 爬取的数据进行存储,并且生成直观的图表
  3. 可以将爬取的数据发送到钉钉的工作群中
  4. 可以定时执行,比如每天上班前,或者下班后,自动执行

首先是数据的爬取:

禅道有多个bug分类页面,我的程序就是将每个页面的bug爬取,并对这些数据进行分析。这里我以所有为例

 

 

 爬虫所用到的内容很简单,就是通过selenium将页面的HTML代码爬取,然后通过对html代码进行解析,得到我们所需要的数据:

 1 import time
 2 from lxml import etree
 3 from selenium import webdriver
 4 from datetime import datetime
 5 from selenium.webdriver.common.by import By
 6 
 7 import weekday
 8 import csv
 9 from selenium.webdriver.support.ui import WebDriverWait
10 from selenium.webdriver.support import expected_conditions as EC
11 from ImageCreate import Project_Status_Image, Daily_Bugs

用到的包如上,etree为解析HTML所需,WebDriverWait为显示等待所需。

然后,使用执行selenium代码可以分为显式运行和静默执行,这里我为了让程序执行在Linux服务器上,而Linux服务器是没有图形化界面的,所以我这里使用了静默执行的方式,当然在程序还没有完善之前是需要图像界面来调试的:

 1 # 设置selenium静默执行
 2 chrome_options = webdriver.ChromeOptions()
 3 chrome_options.headless = True
 4 
 5 # 在Linux中运行selenium中进行必要的设置
 6 chrome_options.add_argument('--no-sandbox')
 7 chrome_options.add_argument('--disable-gpu')
 8 chrome_options.add_argument('--disable-dev-shm-usage')
 9 
10 
11 driver = webdriver.Chrome(options=chrome_options)
12 
13 # driver = webdriver.Chrome()
14 driver.maximize_window()

headless就是设置静默执行,此外还有一些options是运行在Linux上必须设置的属性,然后为了元素定位方便,需要让浏览器全屏。

接下来就是selenium的元素操作,登录禅道,进入bug的所有页面。

在爬取页面数据时,如果一个页面的数据爬取完了,那我们要点击下一页来进入下一页,但是我们并不是要获取所有的bug信息,我们应该只是要获取某一部分,比如前20页,或者最近一周的数据,所以,在进行翻页或者数据爬取的过程我们需要做一个判断,看当前的数据是不是我们想要的。这里我想获取的当前周的bug情况,这里我根据bug的创建日期属性判断是否进行爬取:

 1 ext = True
 2 while next:
 3     response = etree.HTML(driver.page_source)
 4 
 5     ths = response.xpath("//table[@id='bugList']/thead/tr/th")  # 这里是标题行
 6     keys = []
 7     for th in ths[:-1]:
 8         title = th.xpath("@title")[0]
 9         print(title)
10         keys.append(title)
11     # print('keys:', keys)
12 
13     trs = response.xpath("//table[@id='bugList']/tbody/tr")  # 这里是bug记录行
14     print('tr存在%s' % len(trs))
15 
16     nums = len(keys)
17     print('nums', nums)
18 
19     for tr in trs:
20         tds = tr.xpath("./td")
21         values = []  # 保存一条bug记录值
22         for td in tds[:nums]:
23             if tds.index(td) == keys.index('ID'):
24                 content = td.xpath(".//a/text()")[0]
25             if tds.index(td) == keys.index('级别'):
26                 content = td.xpath(".//span/@data-severity")[0]
27             if tds.index(td) == keys.index('P'):
28                 content = td.xpath("./span/text()")[0]
29             if tds.index(td) == keys.index('确认'):
30                 content = td.xpath("./span/text()")[0]
31             if tds.index(td) == keys.index('Bug标题'):
32                 content = td.xpath(".//a/text()")[0]
33             if tds.index(td) == keys.index('所属项目'):
34                 try:
35                     content = td.xpath("./text()")[0]
36                 except:
37                     content = ''
38             if tds.index(td) == keys.index('状态'):
39                 content = td.xpath(".//span/text()")[0]
40             # if tds.index(td) == keys.index('相关需求'):
41             #     try:
42             #         content = td.xpath(".//a/text()")[0]  # 这个字段有可能为空的情况
43             #     except:
44             #         content = ''
45             if tds.index(td) == keys.index('创建者'):
46                 content = td.xpath("./text()")[0]
47             if tds.index(td) == keys.index('创建日期'):
48                 content = td.xpath("./text()")[0]
49             if tds.index(td) == keys.index('指派给'):
50                 content = td.xpath(".//span/text()")[0]
51             if tds.index(td) == keys.index('解决日期'):
52                 content = td.xpath("./text()")[0]
53 
54             values.append(content)
55         bug_dict = dict(zip(keys, values))
56         print(bug_dict)
57         if bug_dict['创建日期'].split(' ')[0] in weekday.get_week_day()['list']:  # 获取当前周的日期
58             bug_list.append(bug_dict)
59         else:
60             next = False
61             break

思路就是在大循环外设置一个标志变量,如果为True,就继续爬,如果不是想要的数据,就设置为False,程序就不会继续爬取了。

 

这里我在静默模式下运行时,本来没问题的程序报错了,大致意思就是元素定位不到。但是在图形界面运行是没问题的,后来排查发现,通过排除法,断定这是因为在静默模式下,selenium在爬取数据时不会自动滚动到数据的位置(这点在图形界面是可以的),导致下一页按钮虽然获取到了,但是无法点击。

解决办法就是我们通过代码让它滚动到页面底部:

driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")  # 操作页面滚动到底部
 1     next_click = WebDriverWait(driver, 10, 0.5).until(
 2         EC.presence_of_element_located((By.XPATH, "//*[@id='bugForm']/div[3]/ul/li[6]/a")))  # 显示等待
 3     print('next:', response.xpath("//*[@id='bugForm']/div[3]/ul/li[6]/a/@title"))
 4 
 5     # 如果没有这个滚动到底部的操作,selenium静默执行时就会报这个元素不是可操作的
 6     driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")  # 操作页面滚动到底部
 7     try:
 8         # webdriver.ActionChains(driver).move_to_element(next_click).click(next_click).perform()  # 静默模式使用这种方式点击元素
 9 
10         next_click.click()
11         print('点击了下一页')
12     except Exception as e:
13         print('没有下一页了')
14         print(e)
15         break

在进行数据处理时,为了方便管理,我将之前爬取的所有bug的数据都存储到了一个列表中,列表的每个元素都是一个bug信息的字典,[{},{},{}]这种数据类型。

这样,我在后面进行数据分类时只需要将整个列表过一遍,将符合某一要求的数据添加到某一个列表中,就可以只遍历一遍这个列表完成多种分类。

 1 project = {}  # 项目bug集合
 2 status = {}  # bug状态集合
 3 project_stauts = {}  # 项目bug状态集合
 4 project_bugs = {}
 5 daliy_bugs = {}
 6 
 7 today_weekday_num = weekday.get_week_day()['today_weekday_num']
 8 until_today_weekday = [i for x in weekday.get_week_day()['list'][:today_weekday_num + 1]]
 9 weekdays = weekday.get_week_day()['list']
10 for i in weekdays:
11     daliy_bugs[i] = 0
12 
13 today_bug_num = 0
14 for bug in bug_list:
15     if bug_s in bug['创建日期']:
16         today_bug_num += 1
17     if bug['所属项目'] in project:
18         project[bug['所属项目']].append(bug)
19     else:
20         project[bug['所属项目']] = []
21         project[bug['所属项目']].append(bug)
22 
23     # if bug['创建日期'].split(' ')[0] in daliy_bugs:
24     #     daliy_bugs[bug['创建日期'].split(' ')[0]] += 1
25     # else:
26     #     daliy_bugs[bug['创建日期'].split(' ')[0]] = 1
27 
28     daliy_bugs[bug['创建日期'].split(' ')[0]] += 1
29 
30     # if ('%s-%s' % (bug['所属项目'], bug['状态'])) in project_stauts:
31     #     project_stauts['%s-%s' % (bug['所属项目'], bug['状态'])] += 1
32     # else:
33     #     project_stauts['%s-%s' % (bug['所属项目'], bug['状态'])] = 1
34 
35 ps = project.keys()
36 for p in ps:
37     bs = project[p]
38     status = {'激活': 0, '已解决': 0, '已关闭': 0}  # 设置每个状态初始值为0
39     for b in bs:
40         # if b['状态'] in status:
41         #     status[b['状态']] += 1
42         # else:
43         #     status[b['状态']] = 1
44         # if b['状态'] in status:
45         status[b['状态']] += 1
46     # else:
47     #     status[b['状态']] = 1
48 
49     print('%sbug状态分布' % p, status)
50     Project_Status_Image(p, status)

可以看到这里我调用了Project_Status_Image这个类,这个类的作用就是根据数据生成图表,是一个绘图的功能。

绘图我使用的是matplotlib中的pyplot,绘制条形图,绘图的细节这里不在细说:

 1 import matplotlib.pyplot as plt
 2 
 3 images_name = []
 4 
 5 
 6 def Project_Status_Image(p, status):
 7     plt.rcParams['font.sans-serif'] = ['SimHei']
 8     plt.rcParams['axes.unicode_minus'] = False
 9     plt.rcParams['font.size'] = 13
10     # plt.autoscale(enable=True, axis='y', tight=True)  # tight layout
11 
12     plt.figure(figsize=(15, 13))
13     x = list(status.keys())
14     y = list(int(i) for i in status.values())
15 
16     # align = 'center'指定x轴上对齐方式可为center edge
17     plt.bar(x, y, width=0.5, bottom=0, align='center', color='steelblue', edgecolor='w', linewidth=2)
18 
19     # x轴斜体
20     # plt.xticks(rotation=30)
21 
22     # 设置y轴为整数
23     yint = []
24     locs, labels = plt.yticks()
25     for each in locs:
26         yint.append(int(each))
27     plt.yticks(yint)
28 
29     # 绘制标题
30     plt.title(p, size=26)
31 
32     # 设置轴标签
33     plt.xlabel('状态', size=24)
34     plt.ylabel('BUG数', size=24)
35     plt.savefig(r'D:\Python_projects\test\images\%s.png' % p, )
36     name = '%s.png' % p
37     images_name.append(name)
38     plt.show()

将图片绘制好并且保存在本地。

 

此时程序已经实现了爬取并分析数据的功能。

本地调试通过后可以将代码移植到服务器上调试。可看上一篇操作:https://www.cnblogs.com/x991788x/p/16588387.html

其实在服务器上调试程序所出现的问题远不止这些,记录有限,碰到问题的时候能分析解决就行。

 

到这一步,我想的应该是如何将数据发送到钉钉的群里。

通过钉钉机器人那不用说了。可以参考钉钉自定义机器人的文档:https://open.dingtalk.com/document/robots/custom-robot-access

读懂文档后我们依葫芦画瓢,写一个简单的demo,消息发送成功。

普通的text类型是比较简单的,但是我们要发送的图片类型的文件,经过官方认定,钉钉机器人是不能发送文件的,图片也是文件,那不是发送不了?后来我看到钉钉开发文档里的Markdown消息类型是附带图片的,那Markdown为啥能够发送图片呢?原因就是Markdown中的图片并不是一个文件,而是一个图片链接,钉钉机器人通过加载这个链接,将图片显示出来。

那思路就清晰了,我将本地统计生成的图片上传到网上,然后生成链接地址,再将这个地址嵌入Markdown消息中就可以了。

那么怎么将本地的图片上传到网上并获得链接呢?

看网上解决办法说是将图片上传到gitee仓库,这个应该可行,但我之前用Django写项目的时候用到过一个网站七牛云,这个网站可以上传图片并通过链接查看到这个图片。

使用七牛云存储的时候需要注意,我们创建的对象存储空间必须设置为公共空间,这样才可以通过链接加载这个图片。其次就是一大堆的配置了,这个七牛云做的很好,他们官网有方便的python版本的SDK,我们本地只需要pip install qiniu就可以调用SDK中封装的方法,非常简单:https://developer.qiniu.com/kodo/1242/python

因为的程序每次生成的图片每次都是一样的,这样如果上传相同名字的文件是上传不上去的,所以我先调用七牛云的删除方法清除这些名字的图片,然后再上传最新的图片。因为七牛云的上传接口上传成功后没有返回文件的链接,但是我们可以访问空间链接,让后将图片名称拼接到后面就能获得图片的链接了:

 1 # 构建鉴权对象
 2 q = Auth(access_key, secret_key)
 3 
 4 # 要上传的空间
 5 bucket_name = 'bugs-images'
 6 
 7 for i in images_name:
 8     # 上传后保存的文件名
 9     key = i
10 
11     # 生成上传 Token,可以指定过期时间等
12     token = q.upload_token(bucket_name, key)
13 
14     # 要上传文件的本地路径
15     localfile = './images/%s' % i
16 
17     ret, info = put_file(token, key, localfile, version='v2')
18     print(info)
19     assert ret['key'] == key
20     assert ret['hash'] == etag(localfile)
21 
22     img_url = 'http://rgnc4j2kz.sabkt.gdipper.com/' + key
23     image_url_li.append(img_url)

这样获取图片链接后,我们再通过钉钉机器人的接口,将图片链接包含其中,发送Markdown类型消息:

 1 image_urls = ''
 2 for i in image_url_li:
 3     image_urls = image_urls + '> ![screenshot](%s)\n' % i
 4 
 5 data = {
 6     "msgtype": "markdown",
 7     "markdown": {
 8         "title": "杭州天气",
 9         # 这里的%需要用%%转义
10         "text": "杭州天气 @150XXXXXXXX \n > 9度,西北风1级,空气良89,相对温度73%%\n > %s >10点20分发布 [天气](https://www.dingtalk.com) \n" % image_urls
11         # "text": "杭州天气 @150XXXXXXXX \n > 9度,西北风1级,空气良89,相对温度73%\n > <img src='D:\图片\pest.jpeg'>\n >10点20分发布 [天气](https://www.dingtalk.com) \n"
12     },
13     'at': {'atMobiles': [], 'atUserIds': [], 'isAtAll': 'false'}
14 }
15 response = requests.post(url=complete_url, data=json.dumps(data), headers=headers)
16 print(response.text)

这样就实现了钉钉机器人发送图片的功能,同样,实现这部后,也可以将代码同步到服务器进行调试。

 

最后就是在服务器定时执行了。其实定时执行有两种方式,一个是在windows本地定时运行,一个是在服务器定时运行。在本地运行的话通过windows自带的定时任务功能就能实现,但是必须保证电脑不关机,不睡眠。所以还是让程序在服务器上跑比较方便。

Linux上定时执行程序使用的是cron,使用起来也非常简单:http://t.zoukankan.com/felixzh-p-4950437.html

需要注意的是我们要运行py文件的时候,因为要执行多个命令,命令之间需要用;隔开

标签:xpath,index,keys,爬取,td,content,bug,禅道
From: https://www.cnblogs.com/x991788x/p/16597043.html

相关文章

  • ARM Debug技术概述
    调试的重要性Debug调试几乎是软件开发中最耗时的过程。当产品交付给客户时,解决问题的成本会显著增加。在很多情况下,当一个产品的销售时间窗口很小时,如果该产品晚了,它可能......
  • Verdi -- Protocol Analyzer Debug
    介绍VerdiProtocolAnalyzer和VerdiTransactionDebug中提及的一样,显示transaction的“波形”与具体信息。但是需要配合SNPSVIP使用,显示更多协议相关的信息:如lat......
  • Qt Debug模式无法运行
    问题:如题,在QtCreator中使用Debug模式启动程序,直接提示“程序异常结束。”,也不报任何错,然后Debug目录直接双击xxx.exe文件,提示缺少Qt5Cored.dll解决方法:从QT的bin目录下......
  • 技术分享 | App常见bug解析
    原文链接功能Bug内容显示错误前端页面展示的内容有误。这种错误的产生有两种可能1、前端代码写的文案错误2、接口返回值错误功能错误功能错误是在测试过程中最常......
  • ABAP开发奇葩BUG记录: SUBMIT AND RETURN 不生效
    项目需要创建大量数据并预处理来进行测试,Team里用的是三哥的一个report去创建单个数据。三哥的程序很简单,就是个选择屏幕,输入一些值,然后做主数据,然后alv显示出来。于是我......
  • Asp.net_解决vs运行报在安装 32 位 Oracle 客户端组件的情况下以 64 位模式运行,将出现
    Asp.net_解决vs运行报在安装32位Oracle客户端组件的情况下以64位模式运行,将出现此问题的bug方法由于工作需要对原来一个项目做修改,结果遇到了麻烦。原来一般是在本......
  • 【StoneDB研发日志】union功能bug记录
    1、问题现象createdatabasesyw_mtr;usesyw_mtr;CREATETABLEt1(f1VARCHAR(255)CHARACTERSETutf8)engine=tianmu;CREATETABLEt2ASSELECTLEFT(f1,171)AS......
  • Qt中使用qDebug()打印中文有时会报错的解决方法
    选择菜单栏的“工具”->“选项”,在新窗口的“文本编辑器”->右侧“UTF-8BOM”选择“如果编码是UTF-8则添加”;之后qDebug打印出的中文不会报错,但是会乱码,这时候要用......
  • python爬取ajax
    importrequestsurl='https://api.bilibili.com/x/v2/reply/main?csrf=056718067a9e03b351569ee0294e4a1e&mode=3&next=2&oid=813963991&plat=1&type=1'header={......
  • Python 的 Requests 和 Httpx 在爬取应用中的一个区别
    HTTPX是功能齐全的Python3的HTTP客户端,支持同步和异步API,支持HTTP/1.1和HTTP/2。一般情况下,在爬取网页内容的时候,httpx与requests的基本使用方法几乎是一模一样的。......