爬取年报数据、解析PDF提取数据、分析代码(巨潮 Python)

利用Python爬虫技术从巨潮资讯网自动下载年报PDF,解析并提取审计咨询费等关键财务数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文章已经生成可运行项目,
利用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 中可以看到我们向服务器请求的时候我们发送的都有哪些字符,和字符所代表的含义,这些都可以和程序中我们是设置的关键字对应上。
image1
当点击公告搜索的时候会出现以下界面:
image2
此时再次双击对应的请求,对应的Fiddler界面变为:可以看到body变了,body就相当于我们的请求参数,其中设置pagenum为2,就可以获得第二页的数据了,实现翻页的功能。下面返回的json数据也变了,并且下面还有多个文件的地址,地址可以取出来放在列表中,下面的地址再加上原始网址就是最终该PDF文档所在的地址。
image3
最终的PDF地址:http://static.cninfo.com.cn/finalpage/2019-04-30/1206164079.PDF ,得到了最后的文件地址,我们就可以进行下载了,对应的代码就好理解了。
image4
对应的代码为:文件已经上传到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进行访问,这样就抓不到我们了。

本文章已经生成可运行项目
### Python爬取巨潮资讯网数据的方法 要实现对巨潮资讯网的数据爬取,可以按照以下方法构建程序逻辑: #### 1. 准备工作 在开始之前,需安装必要的库来支持网络请求和JSON解析等功能。常用的库包括`requests`用于发送HTTP请求以及`json`模块处理返回的结构化数据。 ```bash pip install requests ``` #### 2. 获取页面响应 定义函数 `get_response()` 来获取特定页数的目标网页内容。此部分代码通过传递参数指定所需访问的具体年份或者公司列表等内容,并可选择是否返回总数统计信息。 ```python import requests def get_response(page_num, return_total_count=False): url = 'https://www.cninfo.com.cn/new/hisAnnouncement/query' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)', 'Referer': 'https://www.cninfo.com.cn/' } params = { 'pageNum': page_num, 'pageSize': 30, 'tabName': 'fulltext', 'column': 'szse', 'searchkey': '', 'secid': '', 'plate': '', 'stock': '', 'sortName': '', 'sortType': '', 'limit': '', 'showTitle': '', 'seDate': 'CUSTOM', 'startTime': '2022-01-01', # 起始时间可以根据需求调整 'endTime': '2022-12-31' # 结束时间也可以根据需求调整 } try: response = requests.get(url=url, headers=headers, params=params) if response.status_code == 200: data_json = response.json() if return_total_count: total_records = data_json['totalRecords'] return total_records announcements = data_json['announcements'] return announcements else: print(f"Failed to retrieve data: {response.status_code}") return None except Exception as e: print(e) return None ``` 上述代码片段展示了如何设置URL、头部信息及查询字符串以发起GET请求并提取公告信息[^1]。 #### 3. 解析JSON中的年报信息 从API接口获得的结果通常是以JSON格式呈现,在这里我们关注的是其中存储有年度报告详情的部分字段如标题链接等。 ```python announcements = get_response(1) if announcements is not None and isinstance(announcements,list): for ann in announcements[:5]: title = ann['title'] adjunct_url = f"https://disclosure.szse.cn/{ann['adjunctUrl']}" if ann.get('adjunctUrl') else '' print({"Title":title,"Download Link":adjunct_url}) else: print("No valid announcement found.") ``` 这段脚本遍历前几条记录打印它们的名字连同下载地址以便进一步操作比如保存成PDF文档[^2]。 #### 4. 下载并保存PDF文件 最后一步就是依据前面收集好的url去抓取对应的pdf版本财务报表存放到本地目录下供后续分析使用。 ```python def download_pdf(file_url,title): save_path = r'.\save' import os if not os.path.exists(save_path): os.makedirs(save_path) pdf_file_name = os.path.join(save_path,f"{title}.pdf") resp = requests.get(file_url) if resp.status_code==200: with open(pdf_file_name,'wb')as file_handler: file_handler.write(resp.content) return True else: return False for item in namelist.json()['announcements']: success_flag=download_pdf(item['fileUrl'],item['announcement']['announcementTitle']) if(success_flag): print("{} downloaded successfully!".format(item['announcement']['announcementTitle'])) else: print("Error occurred while downloading {}".format(item['announcement']['announcementTitle'])) ``` 以上示例演示了怎样利用循环逐一调用辅助功能完成批量下载任务的同时还加入了简单的错误检测机制确保流程顺畅运行[^3]。
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值