uiautomation下载_下载Wind金融终端中的行业研究报告

ac132ddc-2a14-eb11-8da9-e4434bdf6706.png

更新【2020.06.14】

最终,我还是找到了使用爬虫的方法来下载行业研究报告,不过我的账号再次到达上限了,没有办法截图了,不过可以先把源代码挂出来,回头解封的时候再补一个视频教程吧。

# -*- coding: utf-8 -*-
# @Time : 2020/6/11 23:11
# @Author : DouHua
# @Email : feng@dongfa.pro
# @File : wind_requests.py.py
# @Project : wind_auto

import requests
from urllib.parse import urlparse
import json


class Wind:
    def __init__(self, wind_session: str, cookie: str, user_id: str, internal_user_id: str):
        self.base_url = 'http://114.80.154.45'
        self.full_url = self.base_url + '/WindRCHost/ashx/ReportListService.ashx?lan=cn'
        self.user_id = user_id
        self.internal_user_id = internal_user_id

        self.session = requests.Session()

        self.headers = {
            'Host': urlparse(self.base_url).netloc,
            'Connection': 'keep-alive',
            'Accept': 'application/json, text/javascript, */*; q=0.01',
            'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
            'Origin': self.base_url,
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36',
            'X-Requested-With': 'XMLHttpRequest',
            'wind-language': 'zh-CN',
            'wind.sessionid': wind_session,
            'windsessionid': wind_session,
            'Referer': 'http://114.80.154.45/WindRCHost/RPP/queryIndex.aspx?col=10101&tabid=1&lang=cn',
            'Accept-Encoding': 'gzip, deflate',
            'Accept-Language': 'en-US,en;q=0.9',
            'Cookie': cookie,
        }

        self.industry_list = {
            '石油石化': 'b101000000000000',
            '煤炭': 'b102000000000000',
            '有色金属': 'b103000000000000',
            '电力及公用事业': 'b104000000000000',
            '钢铁': 'b105000000000000',
            '基础化工': 'b106000000000000',
            '建筑': 'b107000000000000',
            '建材': 'b108000000000000',
            '轻功制造': 'b109000000000000',
            '机械': 'b10a000000000000',
            '电力设备': 'b10b000000000000',
            '国防军工': 'b10c000000000000',
            '汽车': 'b10d000000000000',
            '商贸零售': 'b10e000000000000',
            '餐饮旅游': 'b10f000000000000',
            '家电': 'b10g000000000000',
            '纺织服装': 'b10h000000000000',
            '医药': 'b10i000000000000',
            '食品饮料': 'b10j000000000000',
            '农林牧渔': 'b10k000000000000',
            '银行': 'b10l000000000000',
            '非银行金融': 'b10m000000000000',
            '房地产': 'b10n000000000000',
            '交通运输': 'b10o000000000000',
            '电子元器件': 'b10p000000000000',
            '通信': 'b10q000000000000',
            '计算机': 'b10r000000000000',
            '传媒': 'b10s000000000000',
        }

    def fetch_pdf_list(self, industry_name: str, page_num: int):
        """
        获取pdf列表页信息
        :param industry_name: 待下载数据的行业名
        :param page_num: 相应的页数
        :return:
        """
        print(f"开始获取 {industry_name} 行业的第 {page_num} 页数据...")
        data = {
            "data": json.dumps({
                "contentType": "RPP",
                "zxIndustry1": self.industry_list[industry_name],
                "pageRange": "g20",
                "beginDate": "20170629",
                "endDate": "20200613",
                "area": "CN",
                "language": "110001000",
                "reportType": "806004003",
                "userid": self.user_id,
                "internalUserId": self.internal_user_id,
                "pageSize": 30,
                "doc_storetime": "Doc_Storetime desc",
                "pageNum": str(page_num),
                "lang": "cn",
                "lan": "cn"
            })
        }
        resp = self.session.post(url=self.full_url, headers=self.headers, data=data).json()
        return [{
            'title': item['Title'].replace('/', ''),
            'url': item['WebSiteUrls']
        } for item in resp['Data']]

    def download_pdf(self, path: str, url: str) -> None:
        """
        将url指向的pdf文件下载到path指定的路径上
        :param path: 文件下载的目标路径
        :param url: 文件的链接
        :return:
        """
        print(f'downloading {path} ...')
        resp = self.session.get(url=url, headers=self.headers).content
        with open(path, 'wb') as f:
            f.write(resp)

代码运行示例

知乎视频​www.zhihu.com

引言

在前一篇专栏文章中,我试图下载一些发现报告当中的行业报告来做文本分析的语料库,但是会有两个问题:第一,发现报告并不是一个权威网站,使用它的数据可能会被质疑数据质量有问题;第二,这个网站限制我的日阅读量,上限逐渐从一百衰减到三十,能够获取到的文本量实在是太少。

所以,我将目光投向了wind金融终端当中的行业研究报告。wind账号的日下载量也是有限制的,但是他们的上限比较高,然后我手头上的账号是我自己的个人账户,哪怕是到达了当日的下载上限,也不会影响别人,所以就动起手来呗。

问题描述

wind金融终端开放了许多数据接口,很多的数据都可以使用这些接口批量导出,或者不用接口的话,在软件的面板上也有一些批量导出的按钮,我为什么要写这篇专栏文章呢?这是因为在wind金融终端里面查看行业研究报告的界面是下面这个样子的↓,它没有设置批量导出的按钮,也没有直接可用的接口。

ae132ddc-2a14-eb11-8da9-e4434bdf6706.png
并没有提供批量导出功能的Wind研报平台

我们如果要下载某一份研究报告,需要点开报告的详情页,然后在报告的详情页里点击下图中框起来的下载按钮才行。

af132ddc-2a14-eb11-8da9-e4434bdf6706.png
研究报告详情页当中的下载报告按钮

经过我个人的若干次尝试,我发现我没有办法直接抓取到这些报告的下载链接(个人水平属实有限),所以就想到了一个笨办法(如果老铁们有更好的做法,请一定要告诉我,如果我回头找到更好的办法同样也会更新在这里):使用python模拟鼠标和键盘的操作,让脚本去帮我下载行业报告。

思路介绍

python的uiautomation可以实现如定位元素、鼠标点击、模拟按键等操作,我们手工下载行业报告时大体上也就是这三部分动作的组合,所以这个办法肯定是可行的,但是速度不会特别快,而且在运行脚本的时候最好不要使用键盘和鼠标,局限性比较大。

以下内容仅适用于windows系统,其它系统的同学可以关掉浏览器了。

工具链及使用简介

首先,我们如果要使用uiautomation这个库, 肯定要先把它安装好,pip install uiautomation

我也是昨天才接触到uiautomation这个库,感觉它挺像selenium的,两者之间的差别在于uiautomation是用来控制桌面软件的,而selenium是用来控制浏览器的,本质上是很类似的工具。当然,两个工具之间存在着大量的不同点,我只是做一个简单的类比。

现在来简要介绍uiautomation如何实现定位元素鼠标点击模拟按键等操作。

首先来讲定位元素,在爬网页的时候,我们一般都是使用xpath来定位元素,可以在网页上右键你想要定位的元素,在弹出来的菜单中选择检查,然后在打开的浏览器开发者工具里面看到被选中元素的源代码,右键它然后选择复制Xpath就可以拿到xpath了。

大多数windows桌面应用的页面元素也是用类似DOM树的方式组织起来的,uiautomation也可以使用类似xpath的方法来定位元素,主要的难点在于我们没有办法使用浏览器开发者工具那样便捷的手段来直接查看它的树结构。要素察觉,没有便捷的手段就说明不是没有办法,只是要用到的工具会比较繁琐,这里要用到的软件叫做inspect.exe,它的下载地址我已经放在下面了↓。

inspect.exe
309.4K
·
百度网盘

打开软件以后,你晃动你的鼠标,会发现软件里面的显示的内容会不断地发生变化,如果没有啥变化就点击Action -> Refresh来刷新一下。把鼠标挪到Wind上面就能看到inspect已经成功地定位到Wind了, 定位成功了以后有什么用呢?

b1132ddc-2a14-eb11-8da9-e4434bdf6706.png
通过inspect可以查看窗口的各种属性

从上图中能够看出Wind的Name属性为Wind金融终端..Everest,它的ControlType属性为WindowControl(它的属性值比这个长得多,但我们要就这两个词就够了XyzControl),有这两条就可以使用uiautomation来定位Wind了,代码如下:

import uiautomation as auto

wind = auto.WindowControl(Name='Wind金融终端..Everest')  # 定位到Wind金融终端
print(wind.ClassName)  # uiautomation可以直接访问inspect中列出的所有属性
# 通过打印wind窗口的ClassName来验证是否真的成功地定位了Wind金融终端

# 如果返回的结果是TMasterForm,那就说明我们的代码没有问题

有的元素可能会没有Name属性、Name属性含义模糊或者是Name属性值会动态发生变化,上面的代码就不太好定位这些元素了,uiautomation提供了许多其他的方式来定位元素,这里我仅介绍出现在我的源代码当中的几个语句,更多的信息可以查阅官方文档。

  • 使用其他属性,比如说本文的参考资料是用ClassName这个属性来定位Wind的,代码为:wind = auto.WindowControl(ClassName="TMasterForm")
  • 限定层级。uiautomation将当前节点视为第0层,它的直接子元素就在第1层,直接子元素的直接子元素在第2层,可以使用searchDepth来限定层数,比如上面定位wind金融终端的代码可以写成:wind = auto.WindowControl(searchDepth=1, Name='Wind金融终端..Everest'),表示在桌面的直接子元素当中去查找一个NameWind金融终端..EverestWindowControl元素。
  • 指定序号。假如我们打开了两个Wind金融终端,那么上面的代码找到的是哪一个Wind呢?我们可以通过制定序号来明确地告知uiautomation我们想要定位的元素是哪一个,这在定位列表元素时非常有用。事实上,上面的代码都有一个foundIndex=1的默认参数,表示取第一个元素(序号是从1开始的),如果要取第二个元素,令foundIndex=2即可。
  • 获取所有直接子元素。使用GetChildren()方法,后面的源代码里会有具体的示例。

再看如何点击鼠标,使用Click()方法就可以模拟点击鼠标左键,具体示例请看源代码。

最后看如何模拟按键,使用SendKeys()方法。如果要发送组合键,写在一起就行了,比如说auto.SendKeys("{Ctrl}w")就表示按下Ctrl+w的组合键。 一些键盘上没有的特殊键就要查下表了。

源代码

挂出我的源代码,大部分代码都写了注释,理解并且改写这些代码的难度应该不大。

# -*- coding: utf-8 -*-
# @Time : 2020/6/11 0:06
# @Author : DouHua
# @Email : feng@dongfa.pro
# @File : wind.py
# @Project : wind_auto

# 使用uiautomation来选中想要的元素
import uiautomation as auto
import time
import re

# 桌面在第0层,以其为基点寻找第1层,名为Wind金融终端的窗口
wind = auto.WindowControl(searchDepth=1, Name='Wind金融终端..Everest')

# 令Wind金融终端窗口为第0层,以其为基点寻找第2层,名为研报平台的窗口
report_platform = wind.WindowControl(searchDepth=2, Name='研报平台')

while True:
    # 当前获取到的总报告数
    total_count = int(report_platform.DataItemControl(foundIndex=1).TextControl(foundIndex=2).Name)

    # 总页数
    total_page = total_count // 30 if total_count % 30 == 0 else total_count // 30 + 1

    # 当前页数
    current_page = re.search(
        r'd+',
        report_platform.ListControl(foundIndex=2).ListItemControl(foundIndex=3).TextControl().Name).group()

    # 寻找研报平台窗口中第3个类型为DataItemControl的元素,该元素是包含当前页所有研报列表的表格,这个3是大佬试出来的
    reports = report_platform.DataItemControl(foundIndex=3).GetChildren()

    # 具体的研报就放在reports的子元素当中,所以要遍历研报
    index = 1
    for report in reports:
        print(f'正在尝试下载第 {current_page} 页的第 {index} 篇研报...')

        # 点击研报链接,打开研报详情页
        report.GetChildren()[0].GetChildren()[2].TextControl(foundIndex=1).Click()
        # 给点时间让电脑加载报告详情页
        time.sleep(0.5)

        # 寻找下载按钮并点击
        wind.WindowControl(searchDepth=2, ClassName="TfrmWebBrowser").ImageControl(foundIndex=3).Click()
        # 给点时间让电脑弹出下载确认框
        time.sleep(0.8)

        # 点击完下载按钮以后,按左将焦点移动到保存按钮上
        auto.SendKeys("{LEFT}")
        time.sleep(0.1)

        # 按下回车,选择保存文档
        auto.SendKeys("{ENTER}")
        time.sleep(0.5)

        # 在打开的保存框内再次回车确认保存
        auto.SendKeys("{ENTER}")
        time.sleep(0.1)

        # 退出当前研报详情页
        index += 1
        auto.SendKeys("{Ctrl}w")
        # 给点时间,让电脑反应一下
        time.sleep(0.2)

    # 判断是否应该结束循环了
    if int(current_page) == total_page:
        print("看来你已经下载完当前行业内所有的行业研究报告了!")
        break

    # 点击下一页按钮
    report_platform.ListControl(foundIndex=2).ListItemControl(foundIndex=4).Click()

    # 给点时间加载下一页内容
    time.sleep(1)

重要提示

因为我们的Wind版本可能并不一样,或者是因为其他的各种原因,在我的电脑上能够成功定位到相应的元素,在你的电脑上就是无法成功定位,所以老铁们要确认一下你们运行脚本时的Wind界面是否和我的界面完全一致↓:(wind可以在软件界面中右键 -> 缩小,让所有的列表项都出现在同一页中)

b4132ddc-2a14-eb11-8da9-e4434bdf6706.png
尽量和我保持一致的软件界面

事实上,即便是你们的界面和我的界面完全一致了,我也无法保证你们能够顺利地定位到相应的元素,所以最好是看懂我的源代码,然后照着inspect给出的结果去修一修脚本。

然后就是我的源代码当中设置了一些数值并不相同的time.sleep(),设置这些延时操作主要是为了防止因为网速或者是电脑性能的原因,导致页面元素还没有载入而脚本就急忙忙地去寻找这些元素,那么脚本显然就无法正常工作了,建议老铁们按照自己的实际情况适当增减这些时间,以在脚本准确率和运行时间之间取得较好的平衡。

运行示例

知乎视频​www.zhihu.com

参考资料

Wind数据个性化定制抓取_RadiumTang的博客-CSDN博客​blog.csdn.net
b5132ddc-2a14-eb11-8da9-e4434bdf6706.png
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值