需求内容
根据表格中给出的信息(提供了机构名称、机构代码、用户信息、具体操作等),选择系统管理 → 用户信息管理 → 用户维护,点击用户所在的机构,在机构中选择需调整的用户,进行相应的增删改操作。
关键步骤截图如下:
例如,表格给出的信息是[01001]分行营业部的某个员工需要进行修改操作,那么需要在目录中依次点击
[00000]银行 → [FR001总行] → [01]常州分行 → [010010]常州分行营业部 → [01001]分行营业部
然后在右侧的用户信息表中选中相应员工进行修改操作
难点说明
通过F12查看页面元素,可以看到下图中红框部分只有[00000银行]这一个节点源码
点击[00000银行]前面的'+'号,再次查看源码,发现多了下图红框中的内容,也就是说,这棵银行路径树的源码是根据鼠标点击动态加载的,我们无法提前知道所有的源码。
深入观察
继续点击并不断观察页面元素的变化,我们得到一些规律:
-
每次点击后都会给当前节点下的所有子节点分配id,id号码是在父节点以及父节点的兄弟节点都依次标完号以后按顺序进行标号。下图详细解释了这种分配id的模式,[00000]银行的id为1号,我们先点击它,得到的三个子节点[FR001]总行、[FR002]大丰村镇银行、[FR003]上海村镇银行分别分配为2、3、4号;然后我们点击[FR003]上海村镇银行,得到的一个子节点[03]上海村镇银行分配为5号;我们点击[03]上海村镇银行,会得到四个子节点(注意:[03888]上海清算中心和[03999]上海管理机构也是子节点),分别分配6、7、8、9号,以此类推。如果每次点击的顺序不一样,那么最终的节点id号也会完全不同。
- 带有文件夹标记的节点,需要对前面的'+'号执行点击操作,如果点击的是a标签对应的元素,即上图中的第一个红框,将不会弹出下面的子节点。且'+'号对应的元素比较好找,只要找到a标签对应的元素节点,然后顺藤摸瓜找到它所对应的父节点li下的第一个span标签的元素,就是我们要找的'+'号。
- 每一个节点元素的class属性很容易理解,在根节点的元素([00000]银行)为level0,下面的子节点([FR001]总行、[FR002]大丰村镇银行、[FR003]上海村镇银行)都为level1,以此类推。
解决思路
根据第一个观察,我们可以设计一个动态标号算法,假设我们已经提前知道具体的点击顺序,那么就可以计算出按照这个点击顺序得到的每个节点的id所分配的号码是多少,然后得到每一次点击的xpath代码,通过xpath查找到元素后依次执行click()方法即可。但这种算法的难度较大,可以作为思考题。结合第二和第三个观察,我们可以设计一个更为简便的算法,同样假设已经提前知道了具体的点击顺序,并且依次存储在一个列表中,由于点击顺序一定是按照一层一层的节点往下的结构,所以这个列表中的每一个元素的class属性也一定是按照level0、level1的顺序排列,再结合一开始给定的机构名称、机构代码信息,我们就可以定位到所有的a标签对应的元素,再结合第二个观察,就能找到需要点击的'+'号。需要注意的是,最后用户所在的机构一定是不带有文件夹标记的节点,直接对定位到的a标签对应的元素执行点击操作即可。
以上两种思路都假设我们已经提前知道了具体的点击顺序,下面我们来编写这个函数,接受的输入为用户所在的机构信息,输出具体的点击顺序。
这个函数要能正常运行,必须要提前给定完整的银行路径树,然后从中进行查找。前面的难点说明中已经明确了路径树的源码是动态加载,只有将每个父节点都点开才能获取所有源码,之后我们可以通过BeautifulSoup等工具将其解析为树形结构,但是由于该银行的特殊性,为了便于后期维护以及由测试向生产环境迁移,我们尽量将这棵路径树保存成简单易懂的形式,这样对不懂代码的业务人员也比较友好。编写完成的路径树如下图所示:
其中,前面的switch和span分别表示该节点带有文件夹标记和不带文件夹标记,为了减少不必要的编码错误,我们通过正则匹配所有中文并将其删除,得到如下图的简化路径树:
每一个节点与其父节点缩进四个空格(向Python语法致敬)
下面是这个函数:
import fileinput
def get_path(name):
li = [] # 初始化路径保存列表
for line in fileinput.input('tree.txt'): # 迭代输入流中的行
li = li[:line.count(' ') // 4] + [line] # 每次列表新增元素时将其按对应层级放入列表相应位置,并覆盖后面元素
end_item = li[-1].split('_')
if end_item[0].endswith('span'): # 判断列表最后一个元素是否为不带有文件夹标记的节点
if end_item[1] == name: # 如果列表最后一个元素是我们要找的节点就返回这个列表
return [_.strip() for _ in li]
else: # 否则继续下一次for循环
continue
else:
continue
假设给定的用户所在机构信息为[02031],运行这个函数并查看结果:
>>> get_path('[02031]\n') # 加\n是因为fileinput按行读取txt,除了最后一行外每行的结尾都是\n
['switch_[00000]', 'switch_[FR002]', 'switch_[02]', 'switch_[020310]', 'span_[02031]']
与下图显示的结果一致。
有了这个函数,我们就可以获取具体点击顺序的xpath代码了,下面我们编写获取xpath的函数,该函数接受路径保存列表作为入参,返回每一次点击的xpath路径。
def get_xpath(li):
return [
'//*[@class="level{}" and contains(@title, "{}")]/../span'.format(
num,
item.split('_')[1]) for num, item in enumerate(li[:-1])
] + [
'//*[@class="level{}" and contains(@title, "{}")]'.format(
len(li) - 1, li[-1].split('_')[1])
]
其中contains为xpath中的模糊匹配,由于实际网站中除了机构号还有机构名称,而我们前面为了减少不必要的编码错误,将路径树中的机构名称都删除了,因此这边不能直接用@title=
的形式。需要返回两个列表相加是因为前面指出最后用户所在的机构一定是不带有文件夹标记的节点,直接对定位到的a标签对应的元素执行点击操作即可,而带有文件夹标记的节点需要找到a标签对应的元素节点,然后顺藤摸瓜找到它所对应的父节点li下的第一个span标签的元素。
以[01011]为例,运行这个函数并查看结果:
>>> get_xpath(get_path('[01011]\n'))
['//*[@class="level0" and contains(@title, "[00000]")]/../span', '//*[@class="level1" and contains(@title, "[FR001]")]/../span', '//*[@class="level2" and contains(@title, "[01]")]/../span', '//*[@class="level3" and contains(@title, "[010110]")]/../span', '//*[@class="level4" and contains(@title, "[01011]")]']
然后迭代这个列表里的路径并依次执行点击操作即可,下面是代码运行结果的片段: