目的:基于办公与互联网隔离,自带的office软件没有带本地帮助工具,因此在写vba程序时比较不方便(后来发现07有自带,心中吐血,瞎折腾些什么)。所以想到通过爬虫在官方摘录下来作为参考。

目标网站:https://docs.microsoft.com/zh-cn/office/vba/api/overview/

所使工具:

    python3.7,requests、selenium库

    前端方面:使用了jquery、jstree(用于方便的制作无限层级菜单:https://blog.csdn.net/jason_renyu/article/details/79168940)

设计思路:

1、分析目标页面,可分出两部分,左边时导航,右边是内容显示。

2、通过selenium对导航条进行深度遍历,取得导航条所有节点以及对应的链接,并以jstree的数据格式存储。

# 导航层级为
<ul>
   <li>
       <a>...
       <span>....

3、使用requests遍历所有链接取得相应主体页面。

实现:

#
#  parent 上级节点
#  wait_text 上级节点对应的xpath路径的文本项
#  level,limit 仅方便测试使用
#
def GetMenuDick_jstree(parent,level,wait_text,limit=2):
    if level >= limit: return []
    parent.click()
    l = []
    num = 1
    new_wati_text =  wait_text + '/following-sibling::ul' # 只需要等待ul出来就可以了/li[' + str(ele_num) + ']'
    try:
        wait.until(EC.presence_of_element_located((By.XPATH,new_wati_text)))
        # 查询子节点所有的 a节点和span节点(子菜单)
        childs = parent.find_elements_by_xpath('following-sibling::ul/li/span | following-sibling::ul/li/a')
        for i in childs:
            k = {}
            if i.get_attribute('role') == None:
                k['text'] = i.text
                # 如果是子菜单,进行深度遍历
                k['children'] = GetMenuDick_jstree(i,level+1,new_wati_text + '/li[' + str(num) + ']/span',limit)
            else:
                # 网页访问的Url无Html后缀,需要加上。去除无相关地址,形成相对路径。
                url_text = str(i.get_attribute('href')).replace('https://docs.microsoft.com/zh-cn/office/', '',1)  + '.html'
                k['text'] = i.text
                k['a_attr'] = {"href":url_text,"target":"showframe"}
                lhref.append(str(i.get_attribute('href')))

            num = num + 1
            l.append(k)
        parent.click()    # 最后收起来
    except Exception as e:
        print('error message:',str(e),'error parent:' ,parent.text,' new_wati_text:',new_wati_text,'num:',str(num))
        lerror.append(parent.text)
    finally:
        return l

#
data菜单,lhref为后续需要访问的地址。 # 找到第一个excel节点,从excel开始 data = [] lhref = [] lerror = [] k = {} browser.get(start_url) browser.set_page_load_timeout(10) #超时设置 xpath_text = '//li[contains(@class,"tree")]/span[text()="Excel"][1]' cl = browser.find_element_by_xpath(xpath_text) k = {'text':'Excel'} k['children'] = GetMenuDick_jstree(cl,1,xpath_text,20) data.append(k) # Writing JSON data with open(r'templete\data.json', 'w', encoding='utf-8') as f: json.dump(data, f)

进行到这里,已经拥有了excel vba下所有的菜单信息以及对应的url。下来需要得到页面主体。

实现思路:

1、遍历所有url

2、通过url得到相应的文件名

#
#   根据网页地址,得到文件名,并创建相应文件夹
#
def create_file(url):
    t = 'https://docs.microsoft.com/zh-cn/office/'
    # 替换掉字眼,然后根据路径生成相应文件夹
    url = url.replace(t,"",1)
    lname = url.split('/')
    # 先判断有没有第一个文件夹
    path = lname[0]
    if not os.path.isdir(path):
        os.mkdir(path)
    for l in lname[1:-1]:
        path = path + '\\' + str(l)
        if not os.path.isdir(path):
            os.mkdir(path)
    if len(lname) > 1:
        path = path + '\\' + lname[-1] + '.html'
    return path

3、访问url得到主体信息储存。

# requests模式
# 循环遍历,如果错误,记录下来,以后再执行
had_lhref = []
error_lhref = []
num = 1
for url in lhref:
    try:
        had_lhref.append(url)
        path = create_file(url)
        resp = requests.get(url,timeout=5,headers = headers)  # 设置访问超时,以及http头
        resp.encoding = 'utf-8'
        html = etree.HTML(resp.text)
        c = html.xpath('//main[@>)
        # tostring获取标签所有html内容,是字节类型,要decode为字符串
        content = html_head + etree.tostring(c[0], method='html').decode('utf-8')
        with open(path,'w', encoding='utf-8') as f:
            f.write(content)
    except Exception as e:
        print('error message:',str(e),'error url:',url)
        error_lhref.append(url)
    if num % 10 == 0 :
        print('done:',str(num) + '/' + str(len(lhref)),'error num:' + str(len(error_lhref)))
    #time.sleep(1)  # 睡眠一下,防止被反
    num = num + 1

现在,菜单信息与内容都有了,需要构建自己的主页,这里使用了jstree;2个html,index.html,menu.html。

index.html:使用frame页面框架,相对隔离。

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <title>参考文档</title>
    <script src="js/jquery.min.js"> </script>
</head>
<frameset rows="93%,7%">
    <frameset cols="20%,80%" frameborder="yes" framespacing="1">
        <frame src="menu.html" name="menuframe"/>
        <frame id="showframe" name="showframe" />
    </frameset>
    <frameset frameborder="no" framespacing="1">
        <frame src="a.html" />
    </frameset>
</frameset>

</html>

menu.html:

1、引入了data.json,这样在可以进行离线调用,使用ajax.get读取json的话,会提示跨域失败;

2、jstree会禁止<a>跳转事件,所有需要通过监听"change.tree"事件来进行跳转。

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="js/jquery.min.js"></script>
    <link rel="stylesheet" href="themes/default/style.min.css" />
    <script src="js/jstree.min.js"></script>
    <script type="text/javascript" src="data.json"></script>
</head>

<body>
    <div>

        <form id="s">
            <input type="search" id="q" />
            <button type="submit">Search</button>
        </form>
        <div id="container">

        </div>

        <div id="container"></div>
        <script>
            $(function () {
                $('#container').jstree({
                    "plugins": ["search", "changed"],
                    'core': {
                        'data': data,
                    }

                });
            });
            $('#container').on("changed.jstree", function (e, data) {
                //console.log(data.changed.selected.length); // newly selected
                //console.log(data.changed.deselected); // newly deselected
                if (data.changed.selected.length > 0){
                    // 说明转换了,获取url
                    var url = data.node.a_attr.href
                    // console.log(url)
                    if (url == "#"){

                    }else{
                        parent[data.node.a_attr.target].location.href = url
                    }
                }else{

                }
            })

            $("#s").submit(function (e) {
                e.preventDefault();
                $("#container").jstree(true).search($("#q").val());
            });
        </script>
    </div>
</body>

</html>

以上,得到最后的本地版网页excel vba参考工具。最后,部分office自带本地版的vba参考工具,有点白干一场。