爬取年报数据、解析PDF提取数据、分析代码(巨潮 Python)
利用Python爬取巨潮网页上的年报等数据,通过解析下载的PDF文件,提取其中有用的数据,并写入到本地文件中。
主要分为几个模块:
- 1、从Excel或者列表或者TXT读取股票代码
- 2、根据股票代码和年份等信息爬取特定网页中的信息,获得年报数据所在的网络地址
- 3、根据年报数据的网络地址,下载对应的到本地
- 4、解析本地的PDF文件,通过关键词检索到对应的value
- 5、添加多线程,一边对股票代码进行解析,一边进行文件的下载,一边进行文件的解析,三者同时进行,节省时间
GitHub:项目代码地址
最终的效果:
(1)Excel文件中部分的股票代码,读取后返回一个股票代码的列表
(2)下载的部分年报PDF文件,这里设定为2002年到2007年的中文年报数据(后续会边解析边删除文件,不用担心文件过多)
(3)从PDF文件解析得到的value,通过设置关键词为想要咨询或者中介费用,最终得到对应的值,并写入文件保存,最后一列为该条数据所在的页数。
开发过程说明
1、打开Fiddler软件,通过该软件可以更为清晰的捕捉并看到客户端与服务器之间的响应,功能和网页中的network一样。(可以下载在:Fiddler地址)
2、首先在浏览器界面打开网址 http://www.cninfo.com.cn/new/disclosure/stock?orgId=gssz0000004&stockCode=000004#
3、当打开网址时,Fiddler就会捕捉到这一事件,此时在Fiddler界面会看到一个对应的链接如图,其中 1 表示从服务器读取数据,双击可以看到,服务器返回的数据可以被解析为 json 数据如 3 所示,从json数据可以很容易的对数据进行字符字典操作,并且 2 中可以看到我们向服务器请求的时候我们发送的都有哪些字符,和字符所代表的含义,这些都可以和程序中我们是设置的关键字对应上。
当点击公告搜索的时候会出现以下界面:
此时再次双击对应的请求,对应的Fiddler界面变为:可以看到body变了,body就相当于我们的请求参数,其中设置pagenum为2,就可以获得第二页的数据了,实现翻页的功能。下面返回的json数据也变了,并且下面还有多个文件的地址,地址可以取出来放在列表中,下面的地址再加上原始网址就是最终该PDF文档所在的地址。
最终的PDF地址:http://static.cninfo.com.cn/finalpage/2019-04-30/1206164079.PDF ,得到了最后的文件地址,我们就可以进行下载了,对应的代码就好理解了。
对应的代码为:文件已经上传到GitHub上了
# 前面的一些参数PLATE CATEGORY等是向服务器请求时要发送过去的参数。
OUTPUT_FILENAME = 'report'
# 板块类型:沪市:shmb;深市:szse;深主板:szmb;中小板:szzx;创业板:szcy;
PLATE = 'szzx;'
# 公告类型:category_scgkfx_szsh(首次公开发行及上市)、category_ndbg_szsh(年度报告)、category_bndbg_szsh(半年度报告)
CATEGORY = 'category_ndbg_szsh;'
URL = 'http://www.cninfo.com.cn/new/hisAnnouncement/query'
# 使用浏览器代理,否则网站检测到是Python爬虫时会自动屏蔽
HEADER = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
'X-Requested-With': 'XMLHttpRequest'
}
MAX_PAGESIZE = 50
MAX_RELOAD_TIMES = 5
RESPONSE_TIMEOUT = 10
def standardize_dir(dir_str):
assert (os.path.exists(dir_str)), 'Such directory \"' + str(dir_str) + '\" does not exists!'
if dir_str[len(dir_str) - 1] != '/':
return dir_str + '/'
else:
return dir_str
# 参数:页面id(每页条目个数由MAX_PAGESIZE控制),是否返回总条目数(bool)
def get_response(page_num,stack_code,return_total_count=False,START_DATE = '2013-01-01',END_DATE = '2018-01-01'):
# 这里就是 body 信息
query = {
'stock': stack_code,
'searchkey': '',
'plate': '',
'category': CATEGORY,
'trade': '',
'column': '', #注意沪市为sse
# 'columnTitle': '历史公告查询',
'pageNum': page_num,
'pageSize': MAX_PAGESIZE,
'tabName': 'fulltext',
'sortName': '',
'sortType': '',
'limit': '',
'showTitle': '',
'seDate': START_DATE + '~' + END_DATE,
}
result_list = []
reloading = 0
while True:
try:
r = requests.post(URL, query, HEADER, timeout=RESPONSE_TIMEOUT)
except Exception as e:
print(e)
continue
if r.status_code == requests.codes.ok and r.text != '':
break
# 以下就是开始解析 json 数据,和解析字典类似
my_query = r.json()
try:
r.close()
except Exception as e:
print(e)
if return_total_count:
return my_query['totalRecordNum']
else:
for each in my_query['announcements']:
file_link = 'http://static.cninfo.com.cn/' + str(each['adjunctUrl'])
file_name = __filter_illegal_filename(
str(each['secCode']) + str(each['secName']) + str(each['announcementTitle']) + '.' + '(' + str(each['adjunctSize']) + 'k)' +
file_link[-file_link[::-1].find('.') - 1:] # 最后一项是获取文件类型后缀名
)
if file_name.endswith('.PDF') or file_name.endswith('.pdf'):
if '取消' not in file_name and '摘要' not in file_name and '年度' in file_name:
result_list.append([file_name, file_link])
return result_list
最终的result_list:
eg:
000002万科A2012年年度报告.(1848k).PDF http://static.cninfo.com.cn/finalpage/2013-02-28/62162993.PDF
接下来就是通过该网址进行文件的下载
然后通过Python的pdfplunber模块进行PDF的解析,提取其中我们想要的数据(这里提取的是审计、咨询费用一项)
代码见项目代码地址
调试经验:
1、try-excep如果发生异常,可在except中捕捉,但是一定要处理该异常,不能简单的pass,可能会造成后续没有参数被赋值等问题,导致后续还会有bug。比如try里面放了一个
pdf_file=open(xxxx.xx),如果发生异常,except中必须对该异常进行处理,否则后续中出现的pdf_file就是一个没有被赋值的参数,调用pdf_file.readlines()等方法肯定会再次报错。可以在except之后至一个标志位(if 语句),表示此时不再继续向下运行代码。
2、当使用多线程进行编程时,一定要考虑到加锁后,锁是否能正常的被释放
3、当频繁的访问某一个网站时,有可能会被网站当成网络攻击而屏蔽掉,发生以下的error:
ConnectionResetError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。这时候这个error也是可以处理的,可以再次新建一个线程或者回调函数进行再次的网络访问,或者在每次访问之间设置一个sleep 1 s,不访问的太频繁,或者可以建立一个IP池,每次我们使用不同的IP进行访问,这样就抓不到我们了。
更多推荐
所有评论(0)