![ac132ddc-2a14-eb11-8da9-e4434bdf6706.png](http://p01.5ceimg.com/content/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](http://p04.5ceimg.com/content/ae132ddc-2a14-eb11-8da9-e4434bdf6706.png)
我们如果要下载某一份研究报告,需要点开报告的详情页,然后在报告的详情页里点击下图中框起来的下载按钮才行。
![af132ddc-2a14-eb11-8da9-e4434bdf6706.png](http://p03.5ceimg.com/content/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
,它的下载地址我已经放在下面了↓。
打开软件以后,你晃动你的鼠标,会发现软件里面的显示的内容会不断地发生变化,如果没有啥变化就点击Action -> Refresh来刷新一下。把鼠标挪到Wind上面就能看到inspect
已经成功地定位到Wind了, 定位成功了以后有什么用呢?
![b1132ddc-2a14-eb11-8da9-e4434bdf6706.png](http://p01.5ceimg.com/content/b1132ddc-2a14-eb11-8da9-e4434bdf6706.png)
从上图中能够看出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')
,表示在桌面的直接子元素当中去查找一个Name
为Wind金融终端..Everest
的WindowControl
元素。 - 指定序号。假如我们打开了两个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](http://p02.5ceimg.com/content/b4132ddc-2a14-eb11-8da9-e4434bdf6706.png)
事实上,即便是你们的界面和我的界面完全一致了,我也无法保证你们能够顺利地定位到相应的元素,所以最好是看懂我的源代码,然后照着inspect
给出的结果去修一修脚本。
然后就是我的源代码当中设置了一些数值并不相同的time.sleep()
,设置这些延时操作主要是为了防止因为网速或者是电脑性能的原因,导致页面元素还没有载入而脚本就急忙忙地去寻找这些元素,那么脚本显然就无法正常工作了,建议老铁们按照自己的实际情况适当增减这些时间,以在脚本准确率和运行时间之间取得较好的平衡。
运行示例
知乎视频www.zhihu.com参考资料
Wind数据个性化定制抓取_RadiumTang的博客-CSDN博客blog.csdn.net![b5132ddc-2a14-eb11-8da9-e4434bdf6706.png](http://p05.5ceimg.com/content/b5132ddc-2a14-eb11-8da9-e4434bdf6706.png)