【SAP】四种方式筛选特别总帐标志既含 A 又含空的科目

在进行月末客户清账的流程设计时,其中一个步骤是导出电子表格或者网页并从中筛选数据。

页面如下:
【SAP】四种方式筛选特别总帐标志既含 A 又含空的科目

另外,如果是网页格式的文件,用浏览器打开可能是这样的(后缀名为 mhtml):
【SAP】四种方式筛选特别总帐标志既含 A 又含空的科目

也可能是这样的(后缀名为 htm):
【SAP】四种方式筛选特别总帐标志既含 A 又含空的科目

对于红框中的每一个单元格,我们需要判断其中的值,如果红框(上一个黄色行与下一个黄色行中间的那些行)中既有 A 又有空单元格(比如第一个红框中最后一项是空单元格,前面都是 A),那么我们就取箭头指向的科目,如果红框中全是 A 或者全是空则排除那个科目(比如第二个红框中全是 A,就排除箭头所指的科目)

思路

首先读取 "凭证编号" 这一列中的空值,获取所有黄色行所在的索引。然后依次遍历黄色行中间的每一块白色行,将 "特别总帐标志" 中的每一个值放入集合中,对于每一块白色行都判断一下集合的长度,如果为 2 则说明那个科目是我们需要的。

方法一(针对导出的是 xlsx 格式)

import pandas as pd

def parse_excel(path):
    df = pd.read_excel(path)
    index = df[df[df.columns[3]].isnull()].index[:-1].tolist()
    subjects = []
    try:
        set_index = set()
        for x in range(index[0]):
            set_index.add(str(df[df.columns[6]].ix[x]))
        if len(set_index) == 2:
            subjects.append(df[df.columns[0]].ix[index[0] + 1][-8:])  # 这里的-8是因为科目一共为8位数,前面的科目两个字我们不需要
            
        for j in range(1, len(index)//2):
            set_index = set()
            for x in range(index[j*2-1] + 1, index[j*2]):
                set_index.add(str(df[df.columns[6]].ix[x]))
            if len(set_index) == 2:
                subjects.append(df[df.columns[0]].ix[index[j*2] + 1][-8:])
    except:
        pass
    return subjects

方法二(针对导出的是第一种 mhtml 格式)

import pandas as pd

def parse_html1(path):
    df = pd.read_html(path, header=0)[0]
    index = df[df['凭证编号'].isnull()].index[:-1].tolist()
    subjects = []
    try:
        set_index = set()
        for x in range(index[0]):
            set_index.add(str(df['特别总帐标志'].ix[x]))
        if len(set_index) == 2:
            subjects.append(df['已清项目/未清项目符号'].ix[index[0] + 1][-8:])
            
        for j in range(1, len(index)//2):
            set_index = set()
            for x in range(index[j*2-1] + 1, index[j*2]):
                set_index.add(str(df['特别总帐标志'].ix[x]))
            if len(set_index) == 2:
                subjects.append(df['已清项目/未清项目符号'].ix[index[j*2] + 1][-8:])
    except:
        pass
    return subjects

方法三(针对导出的是第二种 htm 格式)

前面两种都使用了 pandas 这个第三方库,但是对于第二种有很多个表的网页格式,pandas 就无能为力了,我们只能换一种思路,通过直接获取其 html 源码并将其转换为树结构,这里我们使用 BeautifulSoup 这个库

from bs4 import BeautifulSoup

def parse_html2(path):
    with open(path, 'r', encoding = 'utf-8') as html:
        soup = BeautifulSoup(html, 'lxml')
        tables = soup.find_all('table')
        tem_list = []
        for table in tables[:-1]:
            tbodys = table.find_all('tbody')
            tem_set = set([])
            for tr in tbodys[1].find_all('tr'):
                string = tr.find_all('td')[6].string
                tem_set.add(string)
            if len(tem_set) == 2:
                subject = tbodys[3].find('tr').find_all('td')[0].find_all('font')[2].string[3:11]
                tem_list.append(subject)
    return tem_list

方法四(针对导出的是第一种 mhtml 格式)

from bs4 import BeautifulSoup

def parse_html3(path):
    with open(path, 'r', encoding = 'utf-8') as html:
        soup = BeautifulSoup(html, 'lxml')
        trs = soup.find_all('tr')[1:-1]
        index = []
        subjects = []
        for i in range(len(trs)):
            td = trs[i].find_all('td')[3].string
            if td is None:
                index.append(i)
        # print(index)
        set_index = set()
        try:
            for x in range(index[0]):
                set_index.add(trs[x].find_all('td')[6].string)
            if len(set_index) == 2:
                subjects.append(trs[index[0] + 1].find_all('td')[0].string[-8:])
                
            for j in range(1, len(index)//2):
                set_index = set()
                for x in range(index[j*2-1] + 1, index[j*2]):
                    set_index.add(trs[x].find_all('td')[6].string)
                if len(set_index) == 2:
                    subjects.append(trs[index[j*2] + 1].find_all('td')[0].string[-8:])
        except:
            pass
    return subjects

处理网页时 BeautifulSoup 和 pandas 速度对比 (方法二和方法四)

import time
from bs4 import BeautifulSoup
import pandas as pd
import numpy as np

path = r'C:\Users\lenovo\Documents\SAP\SAP GUI\4152 70.MHTML' # 总共八千多行数据

def parse_html3(path):  # 方法四
    time_start = time.time()
    with open(path, 'r', encoding = 'utf-8') as html:
        soup = BeautifulSoup(html, 'lxml')
        trs = soup.find_all('tr')[1:-1]
        index = []
        subjects = []
        for i in range(len(trs)):
            td = trs[i].find_all('td')[3].string
            if td is None:
                index.append(i)
        # print(index)
        set_index = set()
        try:
            for x in range(index[0]):
                set_index.add(trs[x].find_all('td')[6].string)
            if len(set_index) == 2:
                subjects.append(trs[index[0] + 1].find_all('td')[0].string[-8:])
                
            for j in range(1, len(index)//2):
                set_index = set()
                for x in range(index[j*2-1] + 1, index[j*2]):
                    set_index.add(trs[x].find_all('td')[6].string)
                if len(set_index) == 2:
                    subjects.append(trs[index[j*2] + 1].find_all('td')[0].string[-8:])
        except:
            pass
    time_end = time.time()
    print('BeautifulSoup用时{}秒'.format(time_end - time_start))
    return subjects

def parse_html1(path):  # 方法二
    time_start = time.time()
    df = pd.read_html(path, header=0)[0]
    index = df[df['凭证编号'].isnull()].index[:-1].tolist()
    subjects = []
    try:
        set_index = set()
        for x in range(index[0]):
            set_index.add(str(df['特别总帐标志'].ix[x]))
        if len(set_index) == 2:
            subjects.append(df['已清项目/未清项目符号'].ix[index[0] + 1][-8:])
            
        for j in range(1, len(index)//2):
            set_index = set()
            for x in range(index[j*2-1] + 1, index[j*2]):
                set_index.add(str(df['特别总帐标志'].ix[x]))
            if len(set_index) == 2:
                subjects.append(df['已清项目/未清项目符号'].ix[index[j*2] + 1][-8:])
    except:
        pass
    time_end = time.time()
    print('pandas用时{}秒'.format(time_end - time_start))
    return subjects

parse_html3(path)
parse_html1(path)

运行结果如下:
【SAP】四种方式筛选特别总帐标志既含 A 又含空的科目

经过测试发现 pandas 慢的主要原因是 pd.read_html() 这个函数花了太多时间,因此对于 html 格式的数据尽量还是使用 BeautifulSoup 来处理。